diff --git a/aleksis/apps/resint/menus.py b/aleksis/apps/resint/menus.py index 983f95d1346704e1184ffdfbb1419a2e72aa1008..ba45ab2760529763fa873180732a5d5ebfc85103 100644 --- a/aleksis/apps/resint/menus.py +++ b/aleksis/apps/resint/menus.py @@ -34,7 +34,7 @@ get_menu_entries_lazy = lazy(_get_menu_entries, list) MENUS = { "NAV_MENU_CORE": [ { - "name": _("Poster"), + "name": _("Documents"), "url": "#", "icon": "open_in_browser", "root": True, @@ -64,6 +64,17 @@ MENUS = { ), ], }, + { + "name": _("Live documents"), + "url": "live_documents", + "icon": "update", + "validators": [ + ( + "aleksis.core.util.predicates.permission_validator", + "resint.view_livedocuments_rule", + ), + ], + }, ], } ] diff --git a/aleksis/apps/resint/rules.py b/aleksis/apps/resint/rules.py index 326dff271ef4136a92c0debe03552d94fddecc6d..f1d9f72db0f2e38dfc1b9f20f4c74876a2e10432 100644 --- a/aleksis/apps/resint/rules.py +++ b/aleksis/apps/resint/rules.py @@ -109,3 +109,32 @@ add_perm("resint.view_poster_pdf_menu", view_poster_pdf_menu_predicate) # Show the poster manage menu view_poster_menu_predicate = view_posters_predicate | view_poster_groups_predicate add_perm("resint.view_poster_menu", view_poster_menu_predicate) + + +# View live document list +view_live_documents_predicate = has_person & has_global_perm("resint.view_livedocument") +add_perm("resint.view_livedocuments_rule", view_live_documents_predicate) + +# View live document +view_live_document_predicate = has_person & ( + has_global_perm("resint.view_livedocument") | has_object_perm("resint.view_livedocument") +) +add_perm("resint.view_livedocument_rule", view_live_document_predicate) + +# Add live document +add_live_document_predicate = view_live_documents_predicate & has_global_perm( + "resint.add_livedocument" +) +add_perm("resint.add_livedocument_rule", add_live_document_predicate) + +# Edit live document +edit_live_document_predicate = view_live_documents_predicate & has_global_perm( + "resint.change_livedocument" +) +add_perm("resint.edit_livedocument_rule", edit_live_document_predicate) + +# Delete live document +delete_live_document_predicate = view_live_documents_predicate & has_global_perm( + "resint.delete_livedocument" +) +add_perm("resint.delete_livedocument_rule", delete_live_document_predicate) diff --git a/aleksis/apps/resint/tables.py b/aleksis/apps/resint/tables.py new file mode 100644 index 0000000000000000000000000000000000000000..a769e50729e28893481369929477ae2755f059ab --- /dev/null +++ b/aleksis/apps/resint/tables.py @@ -0,0 +1,30 @@ +from django.utils.translation import gettext as _ + +from django_tables2 import A, Column, LinkColumn, Table + + +class LiveDocumentTable(Table): + """Table to list live documents.""" + + class Meta: + attrs = {"class": "responsive-table highlight"} + + document_name = Column(accessor="pk") + name = LinkColumn("edit_live_document", args=[A("id")]) + edit = LinkColumn( + "edit_live_document", + args=[A("id")], + text=_("Edit"), + attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}}, + verbose_name=_("Actions"), + ) + delete = LinkColumn( + "delete_live_document", + args=[A("id")], + text=_("Delete"), + attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}}, + verbose_name=_("Actions"), + ) + + def render_document_name(self, value, record): + return record._meta.verbose_name diff --git a/aleksis/apps/resint/templates/resint/live_document/create.html b/aleksis/apps/resint/templates/resint/live_document/create.html new file mode 100644 index 0000000000000000000000000000000000000000..9e30f4e329aa1f6447e32ea74c51a3dbfe8b025b --- /dev/null +++ b/aleksis/apps/resint/templates/resint/live_document/create.html @@ -0,0 +1,21 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n data_helpers %} + +{% block browser_title %} + {% verbose_name_object model as document_title %} + {% blocktrans with document=document_title %}Create {{ document }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% verbose_name_object model as document_title %} + {% blocktrans with document=document_title %}Create {{ document }}{% endblocktrans %} +{% endblock %} + +{% block content %} + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> +{% endblock %} diff --git a/aleksis/apps/resint/templates/resint/live_document/edit.html b/aleksis/apps/resint/templates/resint/live_document/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..c47d5382977026a4c8a6f0f1f827d6007e4d0f93 --- /dev/null +++ b/aleksis/apps/resint/templates/resint/live_document/edit.html @@ -0,0 +1,21 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n data_helpers %} + +{% block browser_title %} + {% verbose_name_object object as document_title %} + {% blocktrans with document=document_title %}Edit {{ document }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% verbose_name_object object as document_title %} + {% blocktrans with document=document_title %}Edit {{ document }}{% endblocktrans %} +{% endblock %} + +{% block content %} + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> +{% endblock %} diff --git a/aleksis/apps/resint/templates/resint/live_document/list.html b/aleksis/apps/resint/templates/resint/live_document/list.html new file mode 100644 index 0000000000000000000000000000000000000000..fcebf71fc88d2c0e39538ebce725f7c60cd3e70f --- /dev/null +++ b/aleksis/apps/resint/templates/resint/live_document/list.html @@ -0,0 +1,34 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n data_helpers rules %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Live documents{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Live documents{% endblocktrans %}{% endblock %} + +{% block content %} + <a class="btn green waves-effect waves-light dropdown-trigger" href="#" data-target="widget-dropdown"> + <i class="material-icons left">add</i> + {% trans "Create live document" %} + </a> + + <ul id="widget-dropdown" class="dropdown-content"> + <li> + <a href="{% url 'create_live_document' "resint" "livedocument" %}"> + {% trans "Live document" %} + </a> + </li> + {% for ct, model in widget_types %} + <li> + <a href="{% url 'create_live_document' ct.app_label ct.model %}"> + {% verbose_name_object model as widget_name %} + {% blocktrans with name=widget_name %}Create {{ name }}{% endblocktrans %} + </a> + </li> + {% endfor %} + </ul> + + {% render_table table %} +{% endblock %} diff --git a/aleksis/apps/resint/urls.py b/aleksis/apps/resint/urls.py index aea1b58abb91513c0f8a8b06d94c81fa22f67b71..f72aee866a7583501be114253243807f8e742ce6 100644 --- a/aleksis/apps/resint/urls.py +++ b/aleksis/apps/resint/urls.py @@ -1,6 +1,11 @@ from django.urls import path from .views import ( + LiveDocumentCreateView, + LiveDocumentDeleteView, + LiveDocumentEditView, + LiveDocumentListView, + LiveDocumentShowView, PosterCurrentView, PosterDeleteView, PosterEditView, @@ -22,4 +27,19 @@ urlpatterns = [ path("groups/create/", PosterGroupCreateView.as_view(), name="create_poster_group"), path("groups/<int:pk>/edit/", PosterGroupEditView.as_view(), name="edit_poster_group"), path("groups/<int:pk>/delete/", PosterGroupDeleteView.as_view(), name="delete_poster_group"), + path("live/", LiveDocumentListView.as_view(), name="live_documents"), + path( + "live/<str:app>/<str:model>/create/", + LiveDocumentCreateView.as_view(), + name="create_live_document", + ), + path("live/<int:pk>/edit/", LiveDocumentEditView.as_view(), name="edit_live_document",), + path( + "live_documents/<int:pk>/delete/", + LiveDocumentDeleteView.as_view(), + name="delete_live_document", + ), + path( + "live_documents/<str:slug>.pdf", LiveDocumentShowView.as_view(), name="show_live_document", + ), ] diff --git a/aleksis/apps/resint/views.py b/aleksis/apps/resint/views.py index d2e16ee48c0e3f7f2dc7985e7d8fc9a9dbf71538..f987f381cc211fd6d11802dfe788a107c2d63bdc 100644 --- a/aleksis/apps/resint/views.py +++ b/aleksis/apps/resint/views.py @@ -1,20 +1,28 @@ -from typing import Any, Dict +from typing import Any, Dict, Type +from django.contrib.contenttypes.models import ContentType from django.db.models import QuerySet +from django.forms import BaseModelForm, modelform_factory from django.http import FileResponse, HttpRequest +from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy +from django.utils.decorators import method_decorator from django.utils.translation import gettext as _ from django.views import View +from django.views.decorators.cache import never_cache from django.views.generic.detail import SingleObjectMixin from django.views.generic.list import ListView +from django_tables2 import SingleTableView from guardian.shortcuts import get_objects_for_user +from reversion.views import RevisionMixin from rules.contrib.views import PermissionRequiredMixin from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView from .forms import PosterGroupForm, PosterUploadForm -from .models import Poster, PosterGroup +from .models import LiveDocument, Poster, PosterGroup +from .tables import LiveDocumentTable class PosterGroupListView(PermissionRequiredMixin, ListView): @@ -138,3 +146,87 @@ class PosterCurrentView(PermissionRequiredMixin, SingleObjectMixin, View): current_poster = group.current_poster file = current_poster.pdf if current_poster else group.default_pdf return FileResponse(file, content_type="application/pdf") + + +class LiveDocumentListView(PermissionRequiredMixin, SingleTableView): + """Table of all live documents.""" + + model = LiveDocument + table_class = LiveDocumentTable + permission_required = "resint.view_livedocuments_rule" + template_name = "resint/live_document/list.html" + + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: + context = super().get_context_data(**kwargs) + context["widget_types"] = [ + (ContentType.objects.get_for_model(m, False), m) for m in LiveDocument.__subclasses__() + ] + return context + + +@method_decorator(never_cache, name="dispatch") +class LiveDocumentCreateView(PermissionRequiredMixin, AdvancedCreateView): + """Create view for live documents.""" + + def get_model(self, request, *args, **kwargs): + app_label = kwargs.get("app") + model = kwargs.get("model") + ct = get_object_or_404(ContentType, app_label=app_label, model=model) + return ct.model_class() + + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: + context = super().get_context_data(**kwargs) + context["model"] = self.model + return context + + def get(self, request, *args, **kwargs): + self.model = self.get_model(request, *args, **kwargs) + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.model = self.get_model(request, *args, **kwargs) + return super().post(request, *args, **kwargs) + + fields = "__all__" + model = LiveDocument + permission_required = "resint.add_livedocument_rule" + template_name = "resint/live_document/create.html" + success_url = reverse_lazy("live_documents") + success_message = _("The live document has been created.") + + +@method_decorator(never_cache, name="dispatch") +class LiveDocumentEditView(PermissionRequiredMixin, AdvancedEditView): + """Edit view for live documents.""" + + def get_form_class(self) -> Type[BaseModelForm]: + return modelform_factory(self.object.__class__, fields=self.fields) + + model = LiveDocument + fields = "__all__" + permission_required = "resint.edit_livedocument_rule" + template_name = "resint/live_document/edit.html" + success_url = reverse_lazy("live_documents") + success_message = _("The live document has been saved.") + + +@method_decorator(never_cache, name="dispatch") +class LiveDocumentDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView): + """Delete view for live documents.""" + + model = LiveDocument + permission_required = "resint.delete_livedocument_rule" + template_name = "core/pages/delete.html" + success_url = reverse_lazy("live_documents") + success_message = _("The live document has been deleted.") + + +class LiveDocumentShowView(PermissionRequiredMixin, SingleObjectMixin, View): + """Show the current version of the live document.""" + + model = LiveDocument + permission_required = "resint.view_livedocument_rule" + + def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> FileResponse: + live_document = self.get_object() + return FileResponse(live_document.get_current_file(), content_type="application/pdf")