diff --git a/src/backoffice/forms.py b/src/backoffice/forms.py index a9440699e2d959f6bf3d010ea0c68fa47c04a7ad..c4a6863bd43740701eb2e3c6233fcc6375c8e6ae 100644 --- a/src/backoffice/forms.py +++ b/src/backoffice/forms.py @@ -11,9 +11,10 @@ from django.core.validators import validate_slug from core.base_forms import TranslatedFieldsForm from core.integrations import Hangar, IntegrationError, WorkAdventure from core.models import Application, Assembly, AssemblyMember, Conference, Event, PlatformUser, Room, RoomLink, \ - ScheduleSource, WorkadventureTexture, StaticPageRevision + ScheduleSource, WorkadventureTexture, StaticPageRevision, StaticPage from core.utils import str2timedelta +from modeltranslation.settings import AVAILABLE_LANGUAGES logger = logging.getLogger(__name__) @@ -505,17 +506,20 @@ class CreateAssemblyRoomLinkForm(forms.ModelForm): fields = ['name', 'link_type', 'link'] -class StaticPageForm(TranslatedFieldsForm): +class StaticPageEditForm(forms.ModelForm): class Meta: - model = StaticPageRevision - fields = ['title', 'body', 'is_draft'] + model = StaticPage + fields = ['protection', 'privacy'] - publish = forms.BooleanField(required=False) - def clean_publish(self): - if self.cleaned_data['is_draft'] and self.cleaned_data['publish']: - raise ValidationError(_('StaticPage__cannot_publish_draft')) - return self.cleaned_data['publish'] +class StaticPageCreateForm(forms.ModelForm): + class Meta: + model = StaticPage + fields = ['slug', 'language'] + + language = forms.CharField(widget=forms.Select(choices=[(lang, lang) for lang in AVAILABLE_LANGUAGES]), required=True) + title = forms.CharField(required=True) + body = forms.CharField(required=True, widget=forms.Textarea()) class AssignBadgeForm(forms.Form): diff --git a/src/backoffice/locale/de/LC_MESSAGES/django.po b/src/backoffice/locale/de/LC_MESSAGES/django.po index f3737040c761a1b61e6193fb634899d294e6c763..7c1d5087d2802f9c9654be3f381915e961c86987 100644 --- a/src/backoffice/locale/de/LC_MESSAGES/django.po +++ b/src/backoffice/locale/de/LC_MESSAGES/django.po @@ -104,9 +104,6 @@ msgstr "Hangar-Zugriff" msgid "Room-hangar_backend_link__help" msgstr "Private FTP-Zugang um Dateien im Hangar abzulegen" -msgid "StaticPage__cannot_publish_draft" -msgstr "eine als Entwurf markierte Revision kann nicht veröffentlicht werden" - msgid "Event--schedule_end--after_conference_end" msgstr "Muss mit der Konferenz enden!" @@ -845,9 +842,40 @@ msgstr "Hiermit wird diese Selforganized Session gelöscht. Wenn du sie nur unsi msgid "Event--sos-delete-confirm" msgstr "Diese Selforganized Session wirklich löschen?" +msgid "StaticPage--delete-confirm" +msgstr "Diese Seite und zugehörige Historie wirklich löschen?" + msgid "StaticPage" msgstr "statische Seite" +msgid "StaticPage--delete-page" +msgstr "Seite löschen" + +msgid "StaticPageRevision--delete-confirm" +msgstr "Diesen Historieneintrag wirklich löschen?" + +msgid "StaticPage--revisions" +msgstr "Versionen" + +msgid "StaticPageRevision--revision" +msgstr "Version" + +msgid "StaticPageRevision--title" +msgstr "Titel" + +msgid "StaticPageRevision--author" +msgstr "Author" + +msgid "StaticPageRevision--date" +msgstr "Datum" + +msgid "StaticPageRevision--delete" +msgstr "Historieneintrag löschen" + +#, python-format +msgid "StaticPage--preview %(object.public_revision)s" +msgstr "Aktuell veröffentlichte Version: %(object.public_revision)s" + msgid "StaticPages" msgstr "statische Seiten" @@ -857,6 +885,24 @@ msgstr "Kurzname" msgid "StaticPage__title" msgstr "Überschrift" +msgid "StaticPage__language" +msgstr "Sprache" + +msgid "StaticPage__last_edited_by" +msgstr "Zuletzt Von" + +msgid "StaticPage__last_edited_at" +msgstr "Am" + +msgid "Configured Static Page Namespaces" +msgstr "Konfigurierte Geschützte Namespaces" + +msgid "StaticPage__namespace_prefix" +msgstr "Namespace-Prefix" + +msgid "StaticPage__required_group" +msgstr "Benötigt Pages-Gruppe" + msgid "PlatformUser__show_name" msgstr "" @@ -1239,6 +1285,18 @@ msgstr "Projekte" msgid "StaticPage__slug__duplicate" msgstr "Dieser Kurzname wird bereits verwendet." +msgid "StaticPage--changedmetadata" +msgstr "Parameter der Wiki-Seite geändert" + +msgid "StaticPage--notchangedmetadata" +msgstr "Änderung der Parameter der Wiki-Seite fehlgeschlagen" + +msgid "StaticPage--deleted" +msgstr "Statische Seite und zugehörige Historie gelöscht." + +msgid "StaticPageRevision--deleted" +msgstr "Historieneintrag gelöscht." + msgid "userprofile_updated" msgstr "Benutzerprofil aktualisiert" diff --git a/src/backoffice/locale/en/LC_MESSAGES/django.po b/src/backoffice/locale/en/LC_MESSAGES/django.po index 0433208cf503a3a38ca97fe98eb09727a68142f9..a999bf1b5544d6cd58f2fedf776b9aec27499bb2 100644 --- a/src/backoffice/locale/en/LC_MESSAGES/django.po +++ b/src/backoffice/locale/en/LC_MESSAGES/django.po @@ -104,9 +104,6 @@ msgstr "Hangar Access" msgid "Room-hangar_backend_link__help" msgstr "your private ftp access to put files on your hangar" -msgid "StaticPage__cannot_publish_draft" -msgstr "Cannot publish a revision marked as draft." - msgid "Event--schedule_end--after_conference_end" msgstr "Must end with the conference!" @@ -844,17 +841,66 @@ msgstr "This removes this selforganized session. If you only want to hide it for msgid "Event--sos-delete-confirm" msgstr "Are you sure you want to remove this selforganized session?" +msgid "StaticPage--delete-confirm" +msgstr "Are you sure you want to remove this Static Page? All revisions and all stored history will be removed as well!" + msgid "StaticPage" msgstr "static page" +msgid "StaticPage--delete-page" +msgstr "Delete Page" + +msgid "StaticPageRevision--delete-confirm" +msgstr "Are you sure you want to remove this Static Page Revision?" + +msgid "StaticPage--revisions" +msgstr "Versions" + +msgid "StaticPageRevision--revision" +msgstr "Version" + +msgid "StaticPageRevision--title" +msgstr "Title" + +msgid "StaticPageRevision--author" +msgstr "Author" + +msgid "StaticPageRevision--date" +msgstr "Date" + +msgid "StaticPageRevision--delete" +msgstr "Delete Version" + +#, python-format +msgid "StaticPage--preview %(object.public_revision)s" +msgstr "Preview of currently published version %(object.public_revision)s" + msgid "StaticPages" -msgstr "static pages" +msgstr "Static Pages" msgid "StaticPage__slug" -msgstr "slug" +msgstr "Slug" msgid "StaticPage__title" -msgstr "title" +msgstr "Title" + +msgid "StaticPage__language" +msgstr "Language" + +msgid "StaticPage__last_edited_by" +msgstr "Last modified by" + +msgid "StaticPage__last_edited_at" +msgstr "At" + +msgid "Configured Static Page Namespaces" +msgstr "Configured Protected Namespaces" + +msgid "StaticPage__namespace_prefix" +msgstr "Prefix" + +msgid "StaticPage__required_group" +msgstr "Required Group" msgid "PlatformUser__show_name" msgstr "" @@ -1237,6 +1283,18 @@ msgstr "Projects" msgid "StaticPage__slug__duplicate" msgstr "This slug is already in use on another page." +msgid "StaticPage--changedmetadata" +msgstr "changed wiki page's metadata" + +msgid "StaticPage--notchangedmetadata" +msgstr "failed to change metadata of wiki page" + +msgid "StaticPage--deleted" +msgstr "StaticPage and all corresponding revisions deleted." + +msgid "StaticPageRevision--deleted" +msgstr "StaticPageRevision deleted." + msgid "userprofile_updated" msgstr "user profile updated" diff --git a/src/backoffice/templates/backoffice/staticpage_detail.html b/src/backoffice/templates/backoffice/staticpage_detail.html index 10d3db147ab00c349d9e71818976710827d1339f..20ac291cd22f98816983da5afca8c513b3090570 100644 --- a/src/backoffice/templates/backoffice/staticpage_detail.html +++ b/src/backoffice/templates/backoffice/staticpage_detail.html @@ -4,13 +4,17 @@ {% block content %} +<form action="{% url 'backoffice:page-delete' pk=object.pk %}" method="POST" id="delete_page" onsubmit="return confirm('{% trans 'StaticPage--delete-confirm' %}')"> + {% csrf_token %} +</form> + + <form action="{% url 'backoffice:page-detail' pk=object.pk %}" method="POST"> {% csrf_token %} <div class="card"> - <div class="card-header">{% trans 'StaticPage' %} {{ object.pk }}</div> + <div class="card-header">{% trans 'StaticPage' %} "{{ object.slug }}"</div> <div class="card-body"> - <p>Slug: <strong>{{ object.slug }}</strong> (<a href="{% url 'backoffice:page-edit' pk=object.pk %}">edit</a>)</p> <p>Revision: <strong>{{ object.newest_revision.revision }}</strong>{% if object.newest_revision.revision != object.public_version %} (public: {{ object.public_revision }}){% endif %}</p> {% if form.errors %} <div class="row"><div class="col-md-12"> @@ -22,15 +26,46 @@ {% bootstrap_form form %} </div> <div class="card-footer text-right"> + <button type="submit" class="btn btn-danger" form="delete_page">{% trans "StaticPage--delete-page" %}</button> <button type="submit" class="btn btn-primary">{% trans "assemblyedit_submitbtn" %}</button> </div> </div> +</form> + +<form method="POST" action="{% url 'backoffice:page-revision-delete' pk=object.pk %}" onsubmit="return confirm('{% trans 'StaticPageRevision--delete-confirm' %}')"> +{% csrf_token %} +<div class="card mt-2"> + <div class="card-header">{% trans 'StaticPage--revisions' %}</div> + <div class="card-body"> + <table class="table table-sm"> + <thead><tr> + <th>{% trans "StaticPageRevision--revision" %}</th> + <th>{% trans "StaticPageRevision--title" %}</th> + <th>{% trans "StaticPageRevision--author" %}</th> + <th>{% trans "StaticPageRevision--date" %}</th> + <th> </th> + </tr></thead> + <tbody> + {% for revision in revisions %} + <tr> + <td>{{ revision.revision }}</td> + <td>{{ revision.title }}</td> + <td>{{ revision.author | default:"-" }}</td> + <td>{{ revision.timestamp | date:"DATETIME_FORMAT" }}</td> + <td><button class="btn btn-danger btn-sm" type="submit" name="revision" value="{{ revision.revision }}">{% trans "StaticPageRevision--delete" %}</button></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> </form> + {% if object.public_revision > 0 %} <div class="card mt-3"> - <div class="card-header">preview: published rev. {{ object.public_revision }}</div> + <div class="card-header">{% blocktranslate %}StaticPage--preview {{ object.public_revision }}{% endblocktranslate%}</div> <div class="card-body"> {{ object.body_html|safe }} </div> diff --git a/src/backoffice/templates/backoffice/staticpage_form.html b/src/backoffice/templates/backoffice/staticpage_form.html deleted file mode 100644 index b3f29e68e6bee2aaef0d55df2356579802b3a6bf..0000000000000000000000000000000000000000 --- a/src/backoffice/templates/backoffice/staticpage_form.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'backoffice/base.html' %} -{% load bootstrap4 %} -{% load i18n %} - -{% block content %} - -<form action="#" method="POST"> -{% csrf_token %} - -<div class="card"> - <div class="card-header">{% trans 'StaticPage' %} {{ object.pk }}</div> - <div class="card-body"> - {% if form.errors %} - <div class="row"><div class="col-md-12"> - <div class="alert alert-danger"> - {{ form.errors }} - </div> - </div></div> - {% endif %} - {% bootstrap_form form %} - </div> -</div> - -<div class="row"><div class="col-md-12 text-right mb-5"> - <button type="submit" class="btn btn-primary">{% trans "assemblyedit_submitbtn" %}</button> -</div> -</form> - -{% endblock %} \ No newline at end of file diff --git a/src/backoffice/templates/backoffice/staticpage_list.html b/src/backoffice/templates/backoffice/staticpage_list.html index 173cdc529063e578045b2ad8338a0be67805c571..21bc6efe5b2db801bc20383546d2736d23f7eb70 100644 --- a/src/backoffice/templates/backoffice/staticpage_list.html +++ b/src/backoffice/templates/backoffice/staticpage_list.html @@ -1,5 +1,6 @@ {% extends 'backoffice/base.html' %} {% load i18n %} +{% load humanize %} {% block content %} @@ -13,6 +14,11 @@ <tr> <th>{% trans "StaticPage__slug" %}</th> <th>{% trans "StaticPage__title" %}</th> + <th>{% trans "StaticPage__language" %}</th> + <th>{% trans "StaticPage__privacy" %}</th> + <th>{% trans "StaticPage__protection" %}</th> + <th>{% trans "StaticPage__last_edited_by" %}</th> + <th>{% trans "StaticPage__last_edited_at" %}</th> </tr> </thead> <tbody> @@ -20,13 +26,41 @@ <tr class="{% if page.public_revision == 0 %}text-dim{% endif %}"> <td><a href="{% url 'backoffice:page-detail' pk=page.pk %}">{{ page.slug }}</a></td> <td>{{ page.title }}</td> + <td>{{ page.language }}</td> + <td title="{{ page.get_privacy_display }}">{% if page.privacy == page.Privacy.NONE %}👁{% elif page.privacy == page.Privacy.CONFERENCE %}🎪{% elif page.privacy == page.Privacy.PERM %}⚙{% else %}?{% endif %}</td> + <td title="{{ page.get_protection_display }}">{% if page.protection == page.Protection.NONE %}👁{% elif page.protection == page.Protection.PERM %}⚙{% else %}?{% endif %}</td> + <td>{{ page.last_revision_details.author | default:"-" }}</td> + <td>{{ page.last_revision_details.timestamp | date:"DATETIME_FORMAT" }}</td> </tr> {% endfor %} </tbody> </table> </div> - <div class="card-footer"> - <a href="{% url 'backoffice:page-new' %}">add a new page</a> +</div> + + +<div class="card mt-2"> + <div class="card-header"> + {% trans "Configured Static Page Namespaces" %} + </div> + <div class="card-body"> + <table class="table table-sm"> + <thead> + <tr> + <th>{% trans "StaticPage__namespace_prefix" %}</th> + <th>{% trans "StaticPage__required_group" %}</th> + </tr> + </thead> + <tbody> + {% for namespace in configured_namespaces %} + <tr> + <td>{{ namespace.namespace }}</td> + <td>{{ namespace.required_group }}</td> + </tr> + {% endfor %} + </tbody> + </table> </div> </div> + {% endblock %} diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py index 70738b5c46a1332f60e16ed5e0915ca015e0b12b..615cb4cd1b78cd4ceda917e5d3b2503d6ed3f2d4 100644 --- a/src/backoffice/urls.py +++ b/src/backoffice/urls.py @@ -43,9 +43,9 @@ urlpatterns = [ path('conferences', misc.ConferenceSelectionView.as_view(), name='conference_selection'), path('pages', pages.PagesView.as_view(), name='pages'), - path('page/new', pages.NewPageView.as_view(), name='page-new'), - path('page/<uuid:pk>/edit', pages.EditPageView.as_view(), name='page-edit'), path('page/<uuid:pk>', pages.PageView.as_view(), name='page-detail'), + path('page/<uuid:pk>/delete', pages.PageDeleteView.as_view(), name='page-delete'), + path('page/<uuid:pk>/delete-revision', pages.PageRevisionDeleteView.as_view(), name='page-revision-delete'), path('assemblies', assemblyteam.AssembliesView.as_view(), name='assemblies'), path('assemblies/list/<str:variant>', assemblyteam.AssembliesListsView.as_view(), name='assemblieslist'), diff --git a/src/backoffice/views/pages.py b/src/backoffice/views/pages.py index bfe6e9dcece3652723da7b0c4dd44f21c7295e4b..1a44e4d76aa3d7a2cea63c1d77964b7cb6acdc7d 100644 --- a/src/backoffice/views/pages.py +++ b/src/backoffice/views/pages.py @@ -1,117 +1,120 @@ +from datetime import datetime +import json +from operator import itemgetter + +from django.conf import settings from django.contrib import messages -from django.db import IntegrityError +from django.db import IntegrityError, models +from django.db.models import OuterRef +from django.db.models.expressions import F +from django.db.models.functions import JSONObject from django.shortcuts import redirect from django.views.generic import FormView, ListView -from django.views.generic.edit import CreateView, UpdateView -from django.urls import reverse +from django.views.generic.edit import UpdateView, DeleteView +from django.urls import reverse, reverse_lazy from django.utils.translation import gettext_lazy as _, get_language -from core.models import StaticPage +from core.models import StaticPage, StaticPageRevision -from ..forms import StaticPageForm +from ..forms import StaticPageCreateForm, StaticPageEditForm from .mixins import ConferenceMixin +class LastRevisionDetailsDecoder(json.JSONDecoder): + def decode(self, s: str, *args, **kwargs): + res = super().decode(s, *args, **kwargs) + + # parse timestamp. Will parse microseconds wrongly (psql returns a fraction of a second, not the total microseconds, so for example .6 is 600000 ms) + # -> they'll be incorrect, but we don't need this permission so we will cut them off. + ts = datetime.strptime(res['timestamp'], r'%Y-%m-%dT%H:%M:%S.%f%z') + ts = ts.replace(microsecond=0) + + res['timestamp'] = ts + return res + + +class LastRevisionJSONObject(JSONObject): + output_field = models.JSONField(decoder=LastRevisionDetailsDecoder) + + class PagesView(ConferenceMixin, ListView): permission_required = ('core.static_pages') template_name = 'backoffice/staticpage_list.html' require_conference = True def get_queryset(self, *args, **kwargs): - return StaticPage.objects.accessible_by_user(conference=self.conference, user=self.request.user, language=get_language()) + last_revision_details = StaticPageRevision.objects.filter(page=OuterRef('pk'), pk=OuterRef('public_revision'))\ + .values(data=LastRevisionJSONObject(author=F('author__username'), timestamp=F('timestamp')))[0:1] + return StaticPage.objects.accessible_by_user(conference=self.conference, user=self.request.user, language=None)\ + .order_by('slug', 'language')\ + .annotate(last_revision_details=last_revision_details) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['active_page'] = 'pages' + namespaces = [{'namespace': namespace[0], 'required_group': namespace[1]} for namespace in settings.STATIC_PAGE_NAMESPACES] + namespaces.sort(key=itemgetter('namespace')) + context['configured_namespaces'] = namespaces return context -class NewPageView(ConferenceMixin, CreateView): +class PageView(ConferenceMixin, UpdateView): permission_required = ('core.static_pages') - model = StaticPage - fields = ['slug'] - template_name = 'backoffice/staticpage_form.html' - - def post(self, request, *args, **kwargs): - try: - response = super().post(request, *args, **kwargs) - - except IntegrityError: - messages.error(request, _('StaticPage__slug__duplicate')) - response = self.get(request, *args, **kwargs) - - return response + form_class = StaticPageEditForm + template_name = 'backoffice/staticpage_detail.html' + require_conference = True - def form_valid(self, form): - form.instance.conference = self.conference - return super().form_valid(form) + def get_object(self): + return StaticPage.objects.accessible_by_user(self.request.user, self.conference, language=None).get(pk=self.kwargs.get('pk')) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['active_page'] = 'pages' + context['object'] = self.object + context['revisions'] = self.object.revisions.filter(is_draft=False).order_by('-timestamp', '-pk') return context + def form_valid(self, form): + messages.success(self.request, _('StaticPage--changedmetadata') + ': ' + self.object.slug) + return super().form_valid(form) + + def form_invalid(self, form): + messages.error(self.request, _('StaticPage--notchangedmetadata') + ': ' + self.object.slug) + return super().form_invalid(form) + def get_success_url(self, *args, **kwargs): return reverse('backoffice:page-detail', kwargs={'pk': self.object.pk}) -class PageView(ConferenceMixin, FormView): +class PageDeleteView(ConferenceMixin, DeleteView): permission_required = ('core.static_pages') - form_class = StaticPageForm - template_name = 'backoffice/staticpage_detail.html' + model = StaticPage + success_url = reverse_lazy('backoffice:pages') require_conference = True - def dispatch(self, *args, **kwargs): - self.object = StaticPage.objects.accessible_by_user(self.request.user, self.conference, language=get_language()).get(pk=kwargs.get('pk')) - return super().dispatch(*args, **kwargs) - - def get_initial(self): - if (rev := self.object.newest_revision) is None: - return None - - initial = {'title': rev.title, 'is_draft': rev.is_draft, 'public': False, 'body': rev.body} - return initial + def get_object(self): + return StaticPage.objects.accessible_by_user(self.request.user, self.conference, language=None).get(pk=self.kwargs.get('pk')) def form_valid(self, form): - if not form.has_changed(): - return redirect(self.get_success_url()) - - rev = form.save(commit=False) - rev.page = self.object - rev.save() - messages.success(self.request, f'Saved revision {rev.revision} of page {self.conference.slug}/{self.object.slug}') + res = super().form_valid(form) + messages.success(self.request, _("StaticPage--deleted")) + return res - if form.cleaned_data['publish']: - rev.set_public() - messages.success(self.request, f'Made revision {rev.revision} of page {self.conference.slug}/{self.object.slug} public.') - self.object.refresh_from_db() - return redirect(self.get_success_url()) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['active_page'] = 'pages' - context['object'] = self.object - return context - - def get_success_url(self, *args, **kwargs): - return reverse('backoffice:page-detail', kwargs={'pk': self.object.pk}) - - -class EditPageView(ConferenceMixin, UpdateView): +class PageRevisionDeleteView(ConferenceMixin, DeleteView): permission_required = ('core.static_pages') model = StaticPage - fields = ['slug', 'public_revision'] - template_name = 'backoffice/staticpage_form.html' + success_url = reverse_lazy('backoffice:pages') require_conference = True - def get_queryset(self, *args, **kwargs): - return StaticPage.objects.accessible_by_user(conference=self.conference, user=self.request.user, language=get_language()) + def get_object(self): + page = StaticPage.objects.accessible_by_user(self.request.user, self.conference, language=None).get(pk=self.kwargs.get('pk')) + return page.revisions.filter(revision=self.request.POST.get('revision')) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['active_page'] = 'pages' - return context + def get_success_url(self) -> str: + return reverse('backoffice:page-detail', kwargs={'pk': self.kwargs.get('pk')}) - def get_success_url(self, *args, **kwargs): - return reverse('backoffice:page-detail', kwargs={'pk': self.kwargs['pk']}) + def form_valid(self, form): + res = super().form_valid(form) + messages.success(self.request, _("StaticPageRevision--deleted")) + return res diff --git a/src/core/models/pages.py b/src/core/models/pages.py index e1662f90bbeda680afa18e095e5df4a9bea54d51..5140b32eafb100444e2fe5601005b635ce0cc21f 100644 --- a/src/core/models/pages.py +++ b/src/core/models/pages.py @@ -43,8 +43,11 @@ for (prefix, group) in settings.STATIC_PAGE_NAMESPACES: class StaticPageManager(models.Manager): - def accessible_by_user(self, user: PlatformUser, conference: Conference, language: str = ''): - qs = self.get_queryset().filter(language=language, conference=conference) + def accessible_by_user(self, user: PlatformUser, conference: Conference, language: Optional[str] = ''): + if language is not None: + qs = self.get_queryset().filter(language=language, conference=conference) + else: + qs = self.get_queryset().filter(conference=conference) # for global staff apply no more limits if user.is_superuser or user.is_staff: