diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2024-10-22 12:45:25 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2025-02-19 14:43:48 +0100 |
commit | be063a7032971db7c00a160595e69e1e67dd2c9f (patch) | |
tree | 2c18bedabd6a1afaafead68cf71b92dd2c439388 | |
parent | 0b8933306e071f626af0ff2250bca962c1a03090 (diff) | |
download | Ishtar-be063a7032971db7c00a160595e69e1e67dd2c9f.tar.bz2 Ishtar-be063a7032971db7c00a160595e69e1e67dd2c9f.zip |
✨ permissions refactoring: adapt admin pages
-rw-r--r-- | ishtar_common/admin.py | 110 | ||||
-rw-r--r-- | ishtar_common/models.py | 14 | ||||
-rw-r--r-- | ishtar_common/models_common.py | 21 |
3 files changed, 126 insertions, 19 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index ce66e535c..f67a99e01 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -42,7 +42,7 @@ from django.contrib.auth.models import Group, User from django.contrib.contenttypes.models import ContentType from django.contrib.sites.admin import SiteAdmin from django.contrib.sites.models import Site -from django.contrib.gis.forms import PointField, OSMWidget, MultiPolygonField +from django.contrib.gis.forms import PointField, OSMWidget from django.contrib.gis.geos import GEOSGeometry, MultiPolygon from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.geos.error import GEOSException @@ -58,7 +58,7 @@ from django.db.models.fields import ( ) from django.db.models.fields.related import ForeignKey from django.forms import BaseInlineFormSet -from django.http import HttpResponseRedirect, HttpResponse +from django.http import HttpResponseRedirect, HttpResponse, Http404 from django.shortcuts import render from django.urls import reverse from django.utils.decorators import method_decorator @@ -470,8 +470,8 @@ class ImportedObjectAdmin(admin.ModelAdmin): return fields + ("imports", "imports_updated") return fields - END_FIELDS = ["data", "last_modified", "created", "need_update", "locked", "lock_user", - "main_geodata", "geodata", "qrcode"] + END_FIELDS = ["data", "last_modified", "created", "need_update", "locked", + "lock_user", "main_geodata", "geodata", "qrcode"] def get_fields(self, request, obj=None, **kwargs): """ @@ -520,7 +520,19 @@ class MyGroupAdmin(GroupAdmin): css = {"all": ("media/admin.css",)} -admin_site.register(User, UserAdmin) +class IshtarUserAdmin(UserAdmin): + fieldsets = ( + (None, {'fields': ('username', 'password')}), + (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}), + (_('Permissions'), { + 'fields': ('is_active', 'is_staff', 'is_superuser') + }), + (_('Important dates'), {'fields': ('last_login', 'date_joined')}), + ) + pass + + +admin_site.register(User, IshtarUserAdmin) admin_site.register(Group, MyGroupAdmin) admin_site.register(Site, SiteAdmin) @@ -770,6 +782,46 @@ class PersonAdmin(HistorizedObjectAdmin): admin_site.register(models.Person, PersonAdmin) +class PermissionRequestAdminForm(forms.ModelForm): + class Meta: + model = models_common.PermissionRequest + exclude = [] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + choices = [] + for ct in ContentType.objects.all(): + klass = ct.model_class() + if not klass: + continue + if getattr(klass, "SHOW_URL", None) and hasattr(klass, "history_creator_id"): + choices.append((ct.pk, ct.name)) + self.fields["model"].choices = [("", "-" * 9)] + list( + sorted(choices, key=lambda x: x[1]) + ) + + def clean(self): + limit_to_attached_areas = self.cleaned_data.get("limit_to_attached_areas", "") + if not limit_to_attached_areas: + return self.cleaned_data + model = self.cleaned_data["model"] + if model.model not in ("operation", "contextrecord", "find", + "archaeologicalsite", "file"): + raise forms.ValidationError(_("This model do not accept area limitation.")) + return self.cleaned_data + + +@admin.register(models_common.PermissionRequest, site=admin_site) +class PermissionRequestAdmin(admin.ModelAdmin): + prepopulated_fields = {"slug": ("name",)} + search_fields = ("model__model__unaccent", "name") + list_display = ( + "model", "name", "active", "include_associated_items", + "include_upstream_items", "limit_to_attached_areas" + ) + form = PermissionRequestAdminForm + + MAIN_ITEM_READONLY_FIELDS = [ "history_creator", "history_modifier", @@ -1593,6 +1645,54 @@ admin_site.register(models.Area, AreaAdmin) class ProfileTypeAdmin(GeneralTypeAdmin): model = models.ProfileType filter_vertical = ("groups",) + autocomplete_fields = ("permission_requests",) + + def check_permission(self, request, object_id): + # check that all "own" permission has a request associated + try: + obj = models.ProfileType.objects.get(pk=int(object_id)) + except models.ProfileType.DoesNotExist: + return Http404() + permissions_needed = set() + permissions_not_needed = set() + for group in obj.groups.all(): + for perm in group.permissions.all(): + sp = perm.codename.split("_") + perm_type = (sp[0], sp[-1]) + if sp[1] == "own": + permissions_needed.add(perm_type) + else: + permissions_not_needed.add(perm_type) + for permission_request in obj.permission_requests.all(): + model = permission_request.model.model + for perm_type, perm_model in list(permissions_needed): + if model == perm_model: + permissions_needed.remove((perm_type, perm_model)) + for permission in list(permissions_needed): + if permission in permissions_not_needed: + permissions_needed.remove(permission) + if permissions_needed: + permission_needed = ", ".join( + sorted(set([model for __, model in permissions_needed])) + ) + messages.add_message( + request, + messages.ERROR, + mark_safe(str(_( + """Permission requests are needed for theses models: +<strong>{permission_needed}.</strong><br> +Associate the profile type \"<strong>{obj}</strong>\" with correct permission request +otherwise default request will be used (Ishtar v4.4) or no permission will be +granted (Ishtar >= v5).""")).format(permission_needed=permission_needed, obj=obj)) + ) + + def change_view(self, request, object_id, form_url="", extra_context=None): + returned = super().change_view( + request, object_id, form_url=form_url, extra_context=extra_context + ) + if not request.POST or not request.POST.get("_continue", False): + self.check_permission(request, object_id) + return returned admin_site.register(models.ProfileType, ProfileTypeAdmin) diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 148843fd4..8fcb9edbb 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -3429,7 +3429,9 @@ class GDPRLog(models.Model): values = values[start:end] for pk in values: if pk: - gdpr_persons.append(cls.persons.through(gdprperson_id=pk, gdprlog_id=log.pk)) + gdpr_persons.append( + cls.persons.through(gdprperson_id=pk, gdprlog_id=log.pk) + ) if gdpr_persons: cls.persons.through.objects.bulk_create(gdpr_persons) @@ -3437,7 +3439,7 @@ class GDPRLog(models.Model): class ProfileType(GeneralType): groups = models.ManyToManyField(Group, verbose_name=_("Groups"), blank=True) permission_requests = models.ManyToManyField( - PermissionRequest, verbose_name=_("Permission request"), blank=True, + PermissionRequest, verbose_name=_("Permissions requests"), blank=True, related_name="profile_types" ) @@ -3445,7 +3447,7 @@ class ProfileType(GeneralType): verbose_name = _("Profile type") verbose_name_plural = _("Profile types") ordering = ("label",) - ADMIN_SECTION = _("Directory") + ADMIN_SECTION = _("Account") post_save.connect(post_save_cache, sender=ProfileType) @@ -3457,7 +3459,7 @@ class ProfileTypeSummary(ProfileType): proxy = True verbose_name = _("Profile type summary") verbose_name_plural = _("Profile types summary") - ADMIN_SECTION = _("Directory") + ADMIN_SECTION = _("Account") class UserProfile(models.Model): @@ -3494,7 +3496,7 @@ class UserProfile(models.Model): verbose_name = _("User profile") verbose_name_plural = _("User profiles") unique_together = (("name", "profile_type", "person"),) - ADMIN_SECTION = _("Directory") + ADMIN_SECTION = _("Account") def __str__(self): lbl = self.name or str(self.profile_type) @@ -3699,7 +3701,7 @@ class IshtarUser(FullSearch): verbose_name = _("Ishtar user") verbose_name_plural = _("Ishtar users") ordering = ("person",) - ADMIN_SECTION = _("Directory") + ADMIN_SECTION = _("Account") def __str__(self): return str(self.person) diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index cd6c266ef..e92d7d55d 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -2626,7 +2626,7 @@ class GeoVectorData(Imported, OwnPerms): if not precision and rounded: precision = 6 - r = re.compile(r"(\d+)\.(\d{6})(\d*)") + # AFAC: r = re.compile(r"(\d+)\.(\d{6})(\d*)") new_collection = [] for feat in collection: geom_type = feat["geometry"].get("type", None) @@ -3068,18 +3068,15 @@ class GeographicItem(models.Model): class PermissionRequest(models.Model): - name = models.CharField(_("Name"), max_length=200) - slug = models.SlugField(_("Slug"), unique=True) model = models.ForeignKey(ContentType, related_name="permissions", verbose_name=_("Model"), on_delete=models.CASCADE) + name = models.CharField(_("Name"), max_length=200) + slug = models.SlugField(_("Slug"), unique=True) request = models.TextField( _("Request"), blank=True, null=False, default="", help_text=_("Use 'text' request used in Ishtar search input") ) - limit_to_attached_areas = models.BooleanField( - _("Limit request to attached areas"), default=False, - help_text=_("Request is limited to areas attached to the ishtar user") - ) + active = models.BooleanField(_("Active"), default=True) include_associated_items = models.BooleanField( _("Include associated items"), default=True, help_text=_("All items associated items match the request") @@ -3091,12 +3088,20 @@ class PermissionRequest(models.Model): "For instance, match is done for all finds associated with own " "context records") ) - active = models.BooleanField(_("Active"), default=True) + limit_to_attached_areas = models.BooleanField( + _("Limit request to attached areas"), default=False, + help_text=_("Request is limited to areas attached to the ishtar user") + ) + + ADMIN_SECTION = _("Account") class Meta: verbose_name = _("Permission request") verbose_name_plural = _("Permissions requests") + def __str__(self): + return f"{self.model} - {self.name}" + class SerializeItem: SERIALIZE_EXCLUDE = ["search_vector"] |