feat: add suffix approval workflow in admin UI

Introduced a suffix approval workflow, allowing admins to manage pending suffixes via a new approval form and pending suffix list view. Added the ability to mark suffixes as approved, updating multiple templates and views accordingly. Ensured only the relevant suffixes appear in lists based on user permissions and approval status.

- Added SuffixApprovalForm for approval actions.
- Created migration to add description field to the Suffix model.
- Updated navigation links and templates to reflect user-specific data.
- Implemented views for pending suffixes and suffix approval.

This enhancement improves the admin experience by providing streamlined management of suffix approvals.
This commit is contained in:
Kumi 2024-06-23 10:06:47 +02:00
parent fd2962cbd5
commit da929654c0
Signed by: kumi
GPG key ID: ECBCC9082395383F
9 changed files with 118 additions and 22 deletions

View file

@ -33,6 +33,12 @@ class SuffixForm(forms.ModelForm):
self.fields["prefix"].widget.attrs["readonly"] = True self.fields["prefix"].widget.attrs["readonly"] = True
class SuffixApprovalForm(forms.ModelForm):
class Meta:
model = Suffix
fields = ["name", "suffix", "description"]
class IdentifierForm(forms.ModelForm): class IdentifierForm(forms.ModelForm):
class Meta: class Meta:
model = Identifier model = Identifier

View file

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-06-23 08:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("resolver", "0006_alter_suffix_options_alter_prefix_owner_and_more"),
]
operations = [
migrations.AddField(
model_name="suffix",
name="description",
field=models.TextField(blank=True),
),
]

View file

@ -34,16 +34,12 @@
{% if user.is_authenticated %} {% if user.is_authenticated %}
{% if user.is_superuser or user.prefixes.exists %} {% if user.is_superuser or user.prefixes.exists %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'prefix_list' %}">Prefixes</a> <a class="nav-link" href="{% url 'prefix_list' %}">Your Prefixes</a>
</li> </li>
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'suffix_list' %}">Suffixes</a> <a class="nav-link" href="{% url 'suffix_list' %}">Your Suffixes</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'identifier_list' %}"
>Identifiers</a
>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'logout' %}">Logout</a> <a class="nav-link" href="{% url 'logout' %}">Logout</a>

View file

@ -0,0 +1,13 @@
{% extends "base_generic.html" %} {% block title %}Pending Suffixes
{% endblock %} {% block content %}
<div class="container mt-5">
<h1>Pending Suffixes</h1>
<ul class="list-group">
{% for suffix in pending_suffixes %}
<li class="list-group-item">
<a href="{% url 'suffix_approval' suffix.pk %}">{{ suffix.prefix.prefix }}.{{ suffix.suffix }} &dash; {{ suffix.name }}</a>
</li>
{% endfor %}
</ul>
</div>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends "base_generic.html" %} {% block title %}Approve Suffix{% endblock %}
{% load crispy_forms_tags %}{% block content %}
<div class="container mt-5">
<h1>Approve Suffix</h1>
<form method="post">
{% csrf_token %} {{ form | crispy }}
<button type="submit" class="btn btn-primary">Approve</button>
<a class="btn btn-link" href="{% url 'pending_suffixes' %}"
>Back to pending suffixes</a
>
</form>
</div>
{% endblock %}

View file

@ -5,7 +5,10 @@
<p><strong>Suffix:</strong> {{ object.suffix }}</p> <p><strong>Suffix:</strong> {{ object.suffix }}</p>
<p><strong>Prefix:</strong> {{ object.prefix }}</p> <p><strong>Prefix:</strong> {{ object.prefix }}</p>
<p><strong>Type:</strong> {{ object.get_type_display }}</p> <p><strong>Type:</strong> {{ object.get_type_display }}</p>
<p><strong>Remote Resolver:</strong> {{ object.remote_resolver }}</p> {% if object.type == "remote" %}
<p><strong>Remote Resolver:</strong> {{ object.remote_resolver }}</p>
{% endif %}
<p><strong>Approval:</strong> {% if object.approved %}Approved{% else %}Pending{% endif %}</p>
<a class="btn btn-secondary" href="{% url 'suffix_update' object.pk %}" <a class="btn btn-secondary" href="{% url 'suffix_update' object.pk %}"
>Edit</a >Edit</a
> >

View file

@ -5,7 +5,7 @@
<ul class="list-group"> <ul class="list-group">
{% for suffix in object_list %} {% for suffix in object_list %}
<li class="list-group-item"> <li class="list-group-item">
<a href="{% url 'suffix_detail' suffix.pk %}">{{ suffix.name }}</a> <a href="{% url 'suffix_detail' suffix.pk %}">{{ suffix.suffix.prefix }}.{{ suffix.suffix }} &dash; {{ suffix.name }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View file

@ -12,17 +12,19 @@ from .views import (
SuffixCreateView, SuffixCreateView,
SuffixUpdateView, SuffixUpdateView,
SuffixDeleteView, SuffixDeleteView,
IdentifierListView, SuffixIdentifierListView,
IdentifierDetailView, IdentifierDetailView,
IdentifierCreateView, IdentifierCreateView,
IdentifierUpdateView, IdentifierUpdateView,
IdentifierDeleteView, IdentifierDeleteView,
SuffixIdentifierListView, PermissionCreateView,
PendingSuffixesListView,
SuffixApprovalView,
) )
urlpatterns = [ urlpatterns = [
path("", HomeView.as_view(), name="home"), path("", HomeView.as_view(), name="home"),
path("<prefix>.<suffix>/<path:identifier>/", resolve_doi, name="resolve_doi"), path("<prefix>.<suffix>/<identifier>/", resolve_doi, name="resolve_doi"),
path("prefixes/", PrefixListView.as_view(), name="prefix_list"), path("prefixes/", PrefixListView.as_view(), name="prefix_list"),
path("prefixes/<int:pk>/", PrefixDetailView.as_view(), name="prefix_detail"), path("prefixes/<int:pk>/", PrefixDetailView.as_view(), name="prefix_detail"),
path("prefixes/create/", PrefixCreateView.as_view(), name="prefix_create"), path("prefixes/create/", PrefixCreateView.as_view(), name="prefix_create"),
@ -33,28 +35,42 @@ urlpatterns = [
path("suffixes/create/", SuffixCreateView.as_view(), name="suffix_create"), path("suffixes/create/", SuffixCreateView.as_view(), name="suffix_create"),
path("suffixes/<int:pk>/update/", SuffixUpdateView.as_view(), name="suffix_update"), path("suffixes/<int:pk>/update/", SuffixUpdateView.as_view(), name="suffix_update"),
path("suffixes/<int:pk>/delete/", SuffixDeleteView.as_view(), name="suffix_delete"), path("suffixes/<int:pk>/delete/", SuffixDeleteView.as_view(), name="suffix_delete"),
path("identifiers/", IdentifierListView.as_view(), name="identifier_list"),
path( path(
"identifiers/<int:pk>/", "suffixes/<int:suffix_pk>/identifiers/",
SuffixIdentifierListView.as_view(),
name="identifier_list",
),
path(
"suffixes/<int:suffix_pk>/identifiers/<int:pk>/",
IdentifierDetailView.as_view(), IdentifierDetailView.as_view(),
name="identifier_detail", name="identifier_detail",
), ),
path( path(
"identifiers/create/", IdentifierCreateView.as_view(), name="identifier_create" "suffixes/<int:suffix_pk>/identifiers/create/",
IdentifierCreateView.as_view(),
name="identifier_create",
), ),
path( path(
"identifiers/<int:pk>/update/", "suffixes/<int:suffix_pk>/identifiers/<int:pk>/update/",
IdentifierUpdateView.as_view(), IdentifierUpdateView.as_view(),
name="identifier_update", name="identifier_update",
), ),
path( path(
"identifiers/<int:pk>/delete/", "suffixes/<int:suffix_pk>/identifiers/<int:pk>/delete/",
IdentifierDeleteView.as_view(), IdentifierDeleteView.as_view(),
name="identifier_delete", name="identifier_delete",
), ),
path('suffixes/<int:suffix_pk>/identifiers/', SuffixIdentifierListView.as_view(), name='identifier_list'), path(
path('suffixes/<int:suffix_pk>/identifiers/<int:pk>/', IdentifierDetailView.as_view(), name='identifier_detail'), "permissions/create/",
path('suffixes/<int:suffix_pk>/identifiers/create/', IdentifierCreateView.as_view(), name='identifier_create'), PermissionCreateView.as_view(),
path('suffixes/<int:suffix_pk>/identifiers/<int:pk>/update/', IdentifierUpdateView.as_view(), name='identifier_update'), name="permission_create",
path('suffixes/<int:suffix_pk>/identifiers/<int:pk>/delete/', IdentifierDeleteView.as_view(), name='identifier_delete'), ),
path(
"pending-suffixes/", PendingSuffixesListView.as_view(), name="pending_suffixes"
),
path(
"suffixes/<int:pk>/approve/",
SuffixApprovalView.as_view(),
name="suffix_approval",
),
] ]

View file

@ -11,7 +11,13 @@ from django.views.generic import (
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.http import HttpResponseBadRequest, Http404 from django.http import HttpResponseBadRequest, Http404
from .models import Prefix, Suffix, Identifier, Permission from .models import Prefix, Suffix, Identifier, Permission
from .forms import PrefixForm, SuffixForm, IdentifierForm, PermissionForm from .forms import (
PrefixForm,
SuffixForm,
IdentifierForm,
PermissionForm,
SuffixApprovalForm,
)
def resolve_doi(request, prefix, suffix, identifier): def resolve_doi(request, prefix, suffix, identifier):
@ -154,6 +160,7 @@ class SuffixCreateView(LoginRequiredMixin, CreateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.suffix_set.count() >= request.user.maximum_suffixes: if request.user.suffix_set.count() >= request.user.maximum_suffixes:
return self.handle_no_permission() return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self): def get_form_kwargs(self):
@ -185,6 +192,30 @@ class SuffixUpdateView(LoginRequiredMixin, CustomPermissionMixin, UpdateView):
return kwargs return kwargs
class PendingSuffixesListView(LoginRequiredMixin, ListView):
model = Suffix
template_name = "resolver/pending_suffixes_list.html"
context_object_name = "pending_suffixes"
def get_queryset(self):
return Suffix.objects.filter(prefix__owner=self.request.user, approved=False)
class SuffixApprovalView(LoginRequiredMixin, UpdateView):
model = Suffix
form_class = SuffixApprovalForm
template_name = "resolver/suffix_approval_form.html"
context_object_name = "suffix"
success_url = "/pending-suffixes/"
def get_queryset(self):
return Suffix.objects.filter(prefix__owner=self.request.user, approved=False)
def form_valid(self, form):
form.instance.approved = True
return super().form_valid(form)
class SuffixDeleteView(LoginRequiredMixin, CustomPermissionMixin, DeleteView): class SuffixDeleteView(LoginRequiredMixin, CustomPermissionMixin, DeleteView):
model = Suffix model = Suffix
template_name = "resolver/suffix_confirm_delete.html" template_name = "resolver/suffix_confirm_delete.html"