summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
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
commitbe063a7032971db7c00a160595e69e1e67dd2c9f (patch)
tree2c18bedabd6a1afaafead68cf71b92dd2c439388 /ishtar_common
parent0b8933306e071f626af0ff2250bca962c1a03090 (diff)
downloadIshtar-be063a7032971db7c00a160595e69e1e67dd2c9f.tar.bz2
Ishtar-be063a7032971db7c00a160595e69e1e67dd2c9f.zip
✨ permissions refactoring: adapt admin pages
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/admin.py110
-rw-r--r--ishtar_common/models.py14
-rw-r--r--ishtar_common/models_common.py21
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"]