summaryrefslogtreecommitdiff
path: root/ishtar_common
diff options
context:
space:
mode:
Diffstat (limited to 'ishtar_common')
-rw-r--r--ishtar_common/admin.py8
-rw-r--r--ishtar_common/management/commands/ishtar_update_permissions.py66
-rw-r--r--ishtar_common/migrations/0254_permissionrequests.py32
-rw-r--r--ishtar_common/migrations/0255_migrate_delete_perm_clean_groups.py (renamed from ishtar_common/migrations/0255_migrate_delete_permissions.py)43
-rw-r--r--ishtar_common/models.py113
-rw-r--r--ishtar_common/models_common.py17
6 files changed, 270 insertions, 9 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py
index 369821b45..465a9f152 100644
--- a/ishtar_common/admin.py
+++ b/ishtar_common/admin.py
@@ -1648,6 +1648,11 @@ class ProfileTypeAdmin(GeneralTypeAdmin):
filter_vertical = ("groups",)
autocomplete_fields = ("permission_requests",)
+ def save_related(self, request, form, formsets, change):
+ super().save_related(request, form, formsets, change)
+ # clean "owns" VS "generics" groups
+ form.instance.clean_groups()
+
def check_permission(self, request, object_id):
# check that all "own" permission has a request associated
try:
@@ -1749,6 +1754,9 @@ admin_site.register(models.ProfileTypeSummary, ProfileTypeSummaryAdmin)
class IshtarUserAdmin(admin.ModelAdmin):
model = models.IshtarUser
search_fields = ("user_ptr__username", "person__raw_name")
+ exclude = ("search_vector",)
+ readonly_fields = ("user_ptr", "latest_news_version",)
+ autocomplete_fields = ["person"]
admin_site.register(models.IshtarUser, IshtarUserAdmin)
diff --git a/ishtar_common/management/commands/ishtar_update_permissions.py b/ishtar_common/management/commands/ishtar_update_permissions.py
new file mode 100644
index 000000000..d8d8944bf
--- /dev/null
+++ b/ishtar_common/management/commands/ishtar_update_permissions.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from argparse import RawTextHelpFormatter
+import datetime
+import sys
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+
+from ishtar_common import models
+from ishtar_common.utils import BColors
+
+
+def update_permissions(quiet=False):
+ q = models.IshtarUser.objects.all()
+ nb = q.count()
+ updated = 0
+ for idx, user in enumerate(q.all()):
+ if not quiet:
+ sys.stdout.write(f"\r[{percent(idx, nb)}] {idx + 1}/{nb}")
+ if user.need_permission_refresh():
+ user.generate_permission()
+ updated += 1
+ if not quiet:
+ msg = BColors.format("OKGREEN", f"\r{updated} user permissions updated")
+ sys.stdout.write(msg)
+ sys.stdout.write("\n")
+
+
+def percent(current, total):
+ return f"{(current + 1) / total * 100:.1f}".rjust(4, "0") + "%"
+
+
+def get_time():
+ return datetime.datetime.now().isoformat().split(".")[0]
+
+
+class Command(BaseCommand):
+ help = "Update permissions"
+
+ def parser_error(self, message=""):
+ sys.stderr.write(f"{message}\n")
+ self.parser.print_help()
+ sys.exit(2)
+
+ def create_parser(self, *args, **kwargs):
+ parser = super(Command, self).create_parser(*args, **kwargs)
+ parser.formatter_class = RawTextHelpFormatter
+ self.parser = parser
+ parser.error = self.parser_error
+ return parser
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ "--quiet", dest="quiet", action="store_true", help="Quiet output"
+ )
+
+ def handle(self, *args, **options):
+ settings.USE_BACKGROUND_TASK = False
+ quiet = options["quiet"]
+ if not quiet:
+ msg = BColors.format("HEADER", f"[{get_time()}] Updating permissions\n")
+ sys.stdout.write(msg)
+ update_permissions(quiet)
+ sys.exit(1)
diff --git a/ishtar_common/migrations/0254_permissionrequests.py b/ishtar_common/migrations/0254_permissionrequests.py
index 68c4891b0..6d3435ddb 100644
--- a/ishtar_common/migrations/0254_permissionrequests.py
+++ b/ishtar_common/migrations/0254_permissionrequests.py
@@ -169,5 +169,35 @@ class Migration(migrations.Migration):
model_name='userprofile',
name='profile_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_profiles', to='ishtar_common.ProfileType', verbose_name='Profile type')
- )
+ ),
+ migrations.AddField(
+ model_name='ishtaruser',
+ name='need_permission_update',
+ field=models.BooleanField(default=True, verbose_name='Need permission update'),
+ ),
+ migrations.AlterField(
+ model_name='biographicalnote',
+ name='ishtar_users',
+ field=models.ManyToManyField(blank=True, related_name='biographicalnote_associated', to='ishtar_common.IshtarUser'),
+ ),
+ migrations.AlterField(
+ model_name='document',
+ name='ishtar_users',
+ field=models.ManyToManyField(blank=True, related_name='document_associated', to='ishtar_common.IshtarUser'),
+ ),
+ migrations.AlterField(
+ model_name='organization',
+ name='ishtar_users',
+ field=models.ManyToManyField(blank=True, related_name='organization_associated', to='ishtar_common.IshtarUser'),
+ ),
+ migrations.AlterField(
+ model_name='person',
+ name='ishtar_users',
+ field=models.ManyToManyField(blank=True, related_name='person_associated', to='ishtar_common.IshtarUser'),
+ ),
+ migrations.AlterField(
+ model_name='profiletype',
+ name='groups',
+ field=models.ManyToManyField(blank=True, related_name='profile_types', to='auth.Group', verbose_name='Groups'),
+ ),
]
diff --git a/ishtar_common/migrations/0255_migrate_delete_permissions.py b/ishtar_common/migrations/0255_migrate_delete_perm_clean_groups.py
index 61b63c0df..d9aa4cd32 100644
--- a/ishtar_common/migrations/0255_migrate_delete_permissions.py
+++ b/ishtar_common/migrations/0255_migrate_delete_perm_clean_groups.py
@@ -3,7 +3,46 @@
from django.db import migrations
+def clean_groups(profile_type):
+ # raw copy of the admin code
+ owns, full = {}, []
+ # get all permissions
+ for group in profile_type.groups.all():
+ permissions = []
+ own, gen = False, False
+ q = group.permissions
+ if not q.count():
+ continue
+ for permission in q.all():
+ if "_own_" in permission.codename:
+ own = True
+ else:
+ gen = True
+ parts = permission.codename.split("_")
+ permissions.append(f"{parts[0]}_{parts[-1]}")
+ if own and gen:
+ # group has "own" and "generic" permissions: do nothing
+ continue
+ permissions = tuple(sorted(permissions))
+ if own:
+ owns[permissions] = group
+ else:
+ full.append(permissions)
+ # clean
+ for permissions in owns.keys():
+ if len(permissions) == 1:
+ for full_permissions in full:
+ for full_permission in full_permissions:
+ if full_permission == permissions[0]:
+ profile_type.groups.remove(owns[permissions])
+ break
+ else:
+ if permissions in full:
+ profile_type.groups.remove(owns[permissions])
+
+
def migrate_permission(apps, __):
+ # clean delete permissions
Permission = apps.get_model("auth", "permission")
Group = apps.get_model("auth", "group")
ProfileType = apps.get_model("ishtar_common", "profiletype")
@@ -39,6 +78,10 @@ def migrate_permission(apps, __):
for profile_type in ProfileType.objects.filter(groups__pk=modif_group.pk).all():
profile_type.groups.add(delete_group)
print(f"\t- profile type {profile_type.label} updated")
+ # clean groups
+ ProfileType = apps.get_model("ishtar_common", "ProfileType")
+ for pt in ProfileType.objects.all():
+ clean_groups(pt)
class Migration(migrations.Migration):
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index 1569c97c9..045bab1cc 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -54,7 +54,7 @@ from xml.etree import ElementTree as ET # nosec
from django.apps import apps
from django.conf import settings
-from django.contrib.auth.models import User, Group
+from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.gis.db import models
from django.contrib.gis.db.models.aggregates import Union
@@ -73,7 +73,7 @@ from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db import connection, transaction
from django.db.models import Q, Max, Count
-from django.db.models.signals import post_save, post_delete, m2m_changed
+from django.db.models.signals import post_save, post_delete, pre_delete, m2m_changed
from django.db.utils import DatabaseError
from django.template import Context, Template
from django.template.defaultfilters import slugify
@@ -3453,11 +3453,65 @@ class ProfileType(GeneralType):
ordering = ("label",)
ADMIN_SECTION = _("Account")
+ def clean_groups(self):
+ """
+ Remove "own" groups if generic group is associated
+ """
+ owns, full = {}, []
+ # get all permissions
+ for group in self.groups.all():
+ permissions = []
+ own, gen = False, False
+ q = group.permissions
+ if not q.count():
+ continue
+ for permission in q.all():
+ if "_own_" in permission.codename:
+ own = True
+ else:
+ gen = True
+ parts = permission.codename.split("_")
+ permissions.append(f"{parts[0]}_{parts[-1]}")
+ if own and gen:
+ # group has "own" and "generic" permissions: do nothing
+ continue
+ permissions = tuple(sorted(permissions))
+ if own:
+ owns[permissions] = group
+ else:
+ full.append(permissions)
+ # clean
+ for permissions in owns.keys():
+ if len(permissions) == 1:
+ for full_permissions in full:
+ for full_permission in full_permissions:
+ if full_permission == permissions[0]:
+ self.groups.remove(owns[permissions])
+ break
+ else:
+ if permissions in full:
+ self.groups.remove(owns[permissions])
+
post_save.connect(post_save_cache, sender=ProfileType)
post_delete.connect(post_save_cache, sender=ProfileType)
+def permission_requests_changed(sender, **kwargs):
+ instance = kwargs.get("instance", None)
+ if not instance:
+ return
+ IshtarUser.objects.filter(
+ person__profiles__profile_type_id=instance.id
+ ).update(need_permission_update=True)
+
+
+m2m_changed.connect(permission_requests_changed,
+ sender=ProfileType.permission_requests.through)
+m2m_changed.connect(permission_requests_changed,
+ sender=ProfileType.groups.through)
+
+
class ProfileTypeSummary(ProfileType):
class Meta:
proxy = True
@@ -3567,12 +3621,14 @@ class UserProfile(models.Model):
item_ids += list(
Find.objects.filter(**{k: ishtar_user}).values_list("pk", flat=True)
)
- print("ishtar_common/models.py - 3561", item_ids, ishtar_user, content_type, permission_type)
+ # DEBUG
+ # print("ishtar_common/models.py - 3578", item_ids, ishtar_user, content_type, permission_type)
if permission_request.include_upstream_items:
item_ids += model_class.get_ids_from_upper_permissions(
ishtar_user.user_ptr.pk, permissions
)
- print("ishtar_common/models.py - 3566", item_ids, ishtar_user, content_type, permission_type)
+ # DEBUG
+ # print("ishtar_common/models.py - 3584", item_ids, ishtar_user, content_type, permission_type)
if permission_request.request or permission_request.limit_to_attached_areas:
_get_item = get_item(
content_type.model_class(),
@@ -3606,7 +3662,8 @@ class UserProfile(models.Model):
else:
result = result_limit
item_ids += result
- print("ishtar_common/models.py - 3600", item_ids, ishtar_user, content_type, permission_type)
+ # DEBUG
+ # print("ishtar_common/models.py - 3619", item_ids, ishtar_user, content_type, permission_type)
return item_ids
def generate_permission(self, content_type, permission_type,
@@ -3643,8 +3700,9 @@ class UserProfile(models.Model):
item_ids = []
if not q_req.count():
# TODO v5: delete old behaviour
- print(f"WARNING: no permission request for content {content_type.name} and profile {self}")
- print("Using old behaviour")
+ # DEBUG
+ # print(f"WARNING: no permission request for content {content_type.name} and profile {self}")
+ # print("Using old behaviour")
model_class = content_type.model_class()
query = model_class.get_owns(user=ishtar_user, query=True, no_auth_check=True)
if query:
@@ -3711,6 +3769,9 @@ def post_save_userprofile(sender, **kwargs):
if not kwargs.get("instance"):
return
instance = kwargs.get("instance")
+ IshtarUser.objects.filter(
+ person__profiles__pk=instance.id
+ ).update(need_permission_update=True)
try:
instance.person.ishtaruser.show_field_number(update=True)
except IshtarUser.DoesNotExist:
@@ -3720,6 +3781,18 @@ def post_save_userprofile(sender, **kwargs):
post_save.connect(post_save_userprofile, sender=UserProfile)
+def pre_delete_user_profile(sender, **kwargs):
+ instance = kwargs.get("instance", None)
+ if not instance:
+ return
+ IshtarUser.objects.filter(
+ person__profiles__pk=instance.id
+ ).update(need_permission_update=True)
+
+
+pre_delete.connect(pre_delete_user_profile, sender=UserProfile)
+
+
TASK_STATE = (
("S", _("Scheduled")),
("P", _("In progress")),
@@ -3837,6 +3910,9 @@ class IshtarUser(FullSearch):
blank=True, max_length=20)
display_news = models.BooleanField(_("Display news"), default=True)
display_forum_entries = models.BooleanField(_("Display forum entries"), default=True)
+ # permissions update
+ need_permission_update = models.BooleanField(_("Need permission update"),
+ default=True)
class Meta:
verbose_name = _("Ishtar user")
@@ -3929,6 +4005,26 @@ class IshtarUser(FullSearch):
return self.user_ptr.has_perm(permission, obj)
return self.user_ptr.has_perm(permission)
+ def need_permission_refresh(self):
+ if self.need_permission_update:
+ return True
+ q = UserProfile.objects.filter(
+ person_id=self.person_id,
+ expiration_date__lt=datetime.date.today()
+ )
+ if q.count():
+ self.need_permission_update = True
+ self.save()
+ return True
+ q = Permission.objects.filter(
+ group__profile_types__user_profiles__person__ishtaruser__pk=self.pk,
+ codename__contains="_own_",
+ )
+ if q.count():
+ self.need_permission_update = True
+ self.save()
+ return bool(q.count())
+
def generate_permission(self):
# models to treat first in this order to manage cascade permissions
model_names = [
@@ -3971,6 +4067,9 @@ class IshtarUser(FullSearch):
for permission_type in ("view", "change", "delete"):
profile.generate_permission(ct, permission_type)
+ self.need_permission_update = False
+ self.save()
+
def has_permission_dict(self):
"""
Get permission dict with permission codename as key and True or False as result.
diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py
index ed2d46305..dc48fa9e5 100644
--- a/ishtar_common/models_common.py
+++ b/ishtar_common/models_common.py
@@ -2466,8 +2466,12 @@ class GeoVectorData(Imported, OwnPerms):
sub_q = cls._construct_query_own(
"", model._get_query_owns_dicts(ishtaruser)
)
+ if not sub_q:
+ continue
q2 = Q(
- source_id__in=list(sub_q.values_list("id", flat=True)),
+ source_id__in=list(
+ model.objects.filter(sub_q).values_list("id", flat=True)
+ ),
source_content_type__app_label=app_label,
source_content_type__model=model_name.lower(),
)
@@ -3127,6 +3131,17 @@ class PermissionRequest(models.Model):
return f"{self.model} - {self.name}"
+def post_save_permission_request(sender, **kwargs):
+ permission_request = kwargs["instance"]
+ IshtarUser = apps.get_model("ishtar_common", "IshtarUser")
+ IshtarUser.objects.filter(
+ person__profiles__profile_type__permission_requests__pk=permission_request.pk
+ ).update(need_permission_update=True)
+
+
+post_save.connect(post_save_permission_request, sender=PermissionRequest)
+
+
class SerializeItem:
SERIALIZE_EXCLUDE = ["search_vector"]
SERIALIZE_PROPERTIES = [