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:
parent
fd2962cbd5
commit
da929654c0
9 changed files with 118 additions and 22 deletions
|
@ -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
|
||||||
|
|
18
freedoi/resolver/migrations/0007_suffix_description.py
Normal file
18
freedoi/resolver/migrations/0007_suffix_description.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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>
|
||||||
|
|
|
@ -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 }} ‐ {{ suffix.name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -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 %}
|
|
@ -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
|
||||||
>
|
>
|
||||||
|
|
|
@ -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 }} ‐ {{ suffix.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -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",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue