diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2025-02-14 17:49:37 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2025-02-19 14:43:48 +0100 |
commit | 4f60b4805a7eac04c2a8ec2116a245dbeec3c822 (patch) | |
tree | 561f87e11ae60c96320523c80c6317ff8f1d2f99 /ishtar_common | |
parent | 94f357939957dc8a5de453224913dbecdc4dc9db (diff) | |
download | Ishtar-4f60b4805a7eac04c2a8ec2116a245dbeec3c822.tar.bz2 Ishtar-4f60b4805a7eac04c2a8ec2116a245dbeec3c822.zip |
✨ generate_permissions
manage:
- possession (direct, creation, basket)
- heritage
- areas association
- requests ({USER} special syntax)
Diffstat (limited to 'ishtar_common')
-rw-r--r-- | ishtar_common/admin.py | 5 | ||||
-rw-r--r-- | ishtar_common/menu_base.py | 2 | ||||
-rw-r--r-- | ishtar_common/menus.py | 50 | ||||
-rw-r--r-- | ishtar_common/models.py | 95 | ||||
-rw-r--r-- | ishtar_common/models_common.py | 107 | ||||
-rw-r--r-- | ishtar_common/models_rest.py | 30 | ||||
-rw-r--r-- | ishtar_common/rest.py | 5 | ||||
-rw-r--r-- | ishtar_common/utils.py | 77 | ||||
-rw-r--r-- | ishtar_common/views.py | 18 | ||||
-rw-r--r-- | ishtar_common/views_item.py | 135 |
10 files changed, 331 insertions, 193 deletions
diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index 743d643a3..369821b45 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -72,7 +72,8 @@ from django import forms from ishtar_common import models, models_common, models_rest from ishtar_common.apps import admin_site from ishtar_common.model_merging import merge_model_objects -from ishtar_common.utils import get_cache, create_slug, get_person_gdpr_log +from ishtar_common.utils import API_MAIN_CONTENT_TYPES, get_cache, create_slug,\ + get_person_gdpr_log from ishtar_common import forms as common_forms, forms_common as other_common_forms from ishtar_common.serializers import restore_serialized, IMPORT_MODEL_LIST @@ -2561,7 +2562,7 @@ admin_site.register(models_rest.ApiUser, ApiUserAdmin) def get_api_choices(): pks = [] - for app_label, model_name in models_rest.MAIN_CONTENT_TYPES: + for app_label, model_name in API_MAIN_CONTENT_TYPES: try: ct = ContentType.objects.get(app_label=app_label, model=model_name) pks.append(ct.pk) diff --git a/ishtar_common/menu_base.py b/ishtar_common/menu_base.py index e0f206a57..feb05c6db 100644 --- a/ishtar_common/menu_base.py +++ b/ishtar_common/menu_base.py @@ -17,7 +17,7 @@ # See the file COPYING for details. -from ishtar_common.models import get_current_profile +from ishtar_common.utils import get_current_profile class SectionItem: diff --git a/ishtar_common/menus.py b/ishtar_common/menus.py index aae127a09..c455fa73a 100644 --- a/ishtar_common/menus.py +++ b/ishtar_common/menus.py @@ -30,33 +30,34 @@ from django.urls import reverse from django.contrib.auth.models import User -_extra_menus = [] -# collect menu from INSTALLED_APPS -for app in settings.INSTALLED_APPS: - mod = __import__(app, fromlist=["ishtar_menu"]) - if hasattr(mod, "ishtar_menu"): - menu = getattr(mod, "ishtar_menu") - _extra_menus += menu.MENU_SECTIONS - -# sort -__section_items = [mnu for order, mnu in sorted(_extra_menus, key=lambda x: x[0])] -# regroup menus -_section_items, __keys = [], [] -for section_item in __section_items: - if section_item.idx not in __keys: - __keys.append(section_item.idx) - _section_items.append(section_item) - continue - section_childs = _section_items[__keys.index(section_item.idx)].childs - childs_idx = [child.idx for child in section_childs] - for child in section_item.childs: - if child.idx not in childs_idx: - section_childs.append(child) +def get_section_items(): + _extra_menus = [] + # collect menu from INSTALLED_APPS + for app in settings.INSTALLED_APPS: + mod = __import__(app, fromlist=["ishtar_menu"]) + if hasattr(mod, "ishtar_menu"): + menu = getattr(mod, "ishtar_menu") + _extra_menus += menu.MENU_SECTIONS + + # sort + __section_items = [mnu for order, mnu in sorted(_extra_menus, key=lambda x: x[0])] + # regroup menus + _section_items, __keys = [], [] + for section_item in __section_items: + if section_item.idx not in __keys: + __keys.append(section_item.idx) + _section_items.append(section_item) + continue + section_childs = _section_items[__keys.index(section_item.idx)].childs + childs_idx = [child.idx for child in section_childs] + for child in section_item.childs: + if child.idx not in childs_idx: + section_childs.append(child) + return _section_items -class Menu: - ref_childs = _section_items +class Menu: def __init__(self, user, current_action=None, session=None): self.user = user self.initialized = False @@ -74,6 +75,7 @@ class Menu: self.selected_idx = None self.session = session self.items_by_idx = {} + self.ref_childs = get_section_items() def reinit_menu_for_all_user(self): """ diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 8e5b7f703..3e51f8cb1 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -97,6 +97,7 @@ from ishtar_common.utils import ( InlineClass ) from ishtar_common.utils_secretary import IshtarSecretaryRenderer +from ishtar_common.views_item import get_item from ishtar_common.alternative_configs import ( ALTERNATE_CONFIGS, @@ -140,7 +141,8 @@ from ishtar_common.utils import ( cached_label_changed, generate_relation_graph, max_size_help, - JSON_SERIALIZATION + JSON_SERIALIZATION, + SearchAltName, ) from ishtar_common.models_common import ( @@ -175,7 +177,6 @@ from ishtar_common.models_common import ( PermissionRequest, post_save_cache, QuickAction, - SearchAltName, SearchVectorConfig, SpatialReferenceSystem, TemplateItem, @@ -198,6 +199,7 @@ __all__ = [ "ImporterColumn", "ImporterDuplicateField", "Imported", + "PermissionRequest", "Regexp", "ImportTarget", "ItemKey", @@ -3466,7 +3468,8 @@ class ProfileTypeSummary(ProfileType): class UserProfile(models.Model): name = models.CharField(_("Name"), blank=True, default="", max_length=100) profile_type = models.ForeignKey( - ProfileType, verbose_name=_("Profile type"), on_delete=models.PROTECT + ProfileType, verbose_name=_("Profile type"), on_delete=models.PROTECT, + related_name="user_profiles" ) areas = models.ManyToManyField( "Area", verbose_name=_("Areas"), blank=True, related_name="profiles" @@ -3521,7 +3524,9 @@ class UserProfile(models.Model): def duplicate(self, **kwargs): areas = [area for area in self.areas.all()] - external_sources = [external_source for external_source in self.external_sources.all()] + external_sources = [ + external_source for external_source in self.external_sources.all() + ] new_item = self new_item.pk = None name = self.name @@ -3541,50 +3546,95 @@ class UserProfile(models.Model): new_item.external_sources.add(src) return new_item - def _generate_permission(self, ishtar_user, content_type, permission_request): + def _generate_permission(self, ishtar_user, content_type, permission_request, + permissions, permission_type): item_ids = [] model_class = content_type.model_class() - # TODO: gérer les paniers if permission_request.include_associated_items: - item_ids += model_class.filter( + item_ids += model_class.objects.filter( ishtar_users__pk=ishtar_user.pk ).values_list("pk", flat=True) + item_ids += model_class.objects.filter( + history_creator_id=ishtar_user.pk + ).values_list("pk", flat=True) + if content_type.model == "find" and \ + permission_type in ("view", "change"): + Find = apps.get_model("archaeological_finds", "Find") + k = "basket__shared_write_with" if permission_type == "change" \ + else "basket__shared_with" + 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) if permission_request.include_upstream_items: - # TODO.... - item_ids += model_class.get_ids_from_upper_permissions(ishtar_user.user_ptr.pk) + 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) if permission_request.request or permission_request.limit_to_attached_areas: - # TODO - pass - query = model_class.objects + _get_item = get_item( + content_type.model_class(), + "", "", no_permission_check=True, + ) + result = [] + query = permission_request.request + if query: + if "{USER}" in query: + query = query.replace("{USER}", f"id:{ishtar_user.person_id}") + query = {"search_vector": query} + q = _get_item(None, return_query=True, ishtaruser=ishtar_user, + query=query) + result = list(q.values_list("pk", flat=True)) + if permission_request.limit_to_attached_areas: + profile = ishtar_user.current_profile + if not profile: # no areas attached + return [] + town_ids = list(profile.query_towns.values_list("pk", flat=True)) + result_limit = [] + get_limit_to_area_query = getattr( + model_class, "get_limit_to_area_query", None + ) + q = get_limit_to_area_query(town_ids) if get_limit_to_area_query else None + if q: + result_limit = list( + model_class.objects.filter(q).values_list("pk", flat=True) + ) + if result: + result = [pk for pk in result if pk in result_limit] + else: + result = result_limit + item_ids += result + print("ishtar_common/models.py - 3600", item_ids, ishtar_user, content_type, permission_type) return item_ids - def generate_permission(self, content_type): + def generate_permission(self, content_type, permission_type): ishtar_user = self.person.ishtaruser # add base permissions for group in self.profile_type.groups.all(): - for perm in group.permissions.all(): + for perm in group.permissions.filter( + codename__startswith=permission_type).all(): ishtar_user.user_ptr.user_permissions.add(perm) q_has_perm = self.profile_type.groups.filter( permissions__content_type=content_type, - permissions__codename__contains="_own_" + permissions__codename__startswith=f"{permission_type}_own_", ) if not q_has_perm.count(): # no permission to generate return permissions = [] for group in q_has_perm.all(): - permissions += list(group.permissions.values_list("pk", flat=True)) + permissions += list(group.permissions.filter( + codename__contains=permission_type + ).all()) q_req = self.profile_type.permission_requests.filter( model=content_type, active=True ) 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") - """ model_class = content_type.model_class() query = model_class.get_owns(user=ishtar_user, query=True, no_auth_check=True) if query: @@ -3594,13 +3644,15 @@ class UserProfile(models.Model): else: for perm_request in q_req.all(): item_ids += self._generate_permission( - ishtar_user, content_type, perm_request + ishtar_user, content_type, perm_request, permissions, + permission_type ) user_id = ishtar_user.user_ptr.pk object_permissions = [] item_ids = list(set(item_ids)) permissions = list(set(permissions)) - for permission_id in permissions: + for permission in permissions: + permission_id = permission.pk exclude = list(UserObjectPermission.objects.filter( content_type_id=content_type.pk, permission_id=permission_id, user_id=user_id @@ -3900,7 +3952,8 @@ class IshtarUser(FullSearch): for ct in content_types: for profile in self.person.profiles.all(): - profile.generate_permission(ct) + for permission_type in ("view", "change", "delete"): + profile.generate_permission(ct, permission_type) def full_label(self): return self.person.full_label() diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index 28f5aba00..920b71584 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -17,11 +17,12 @@ import re import shutil import tempfile import time +from unidecode import unidecode from django import forms from django.apps import apps from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth.models import Permission, User from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.gis.db import models @@ -49,6 +50,8 @@ from ishtar_common.utils import ( get_image_path, get_columns_from_class, human_date, + HistoryError, + SearchAltName, SheetItem ) from simple_history.models import HistoricalRecords as BaseHistoricalRecords @@ -56,7 +59,8 @@ from simple_history.signals import ( post_create_historical_record, pre_create_historical_record, ) -from unidecode import unidecode + +from guardian.models import UserObjectPermission from ishtar_common.data_importer import post_importer_action, ImporterError from ishtar_common.model_managers import TypeManager @@ -64,19 +68,20 @@ from ishtar_common.model_merging import merge_model_objects from ishtar_common.models_imports import Import from ishtar_common.templatetags.link_to_window import simple_link_to_window from ishtar_common.utils import ( - get_cache, + cached_label_changed, disable_for_loaddata, + duplicate_item, + external_id_changed, + GENERAL_TYPE_PREFIX, get_all_field_names, + get_cache, + get_current_profile, + get_generated_id, merge_tsvectors, - cached_label_changed, - external_id_changed, + OwnPerms, post_save_geo, post_save_geodata, task, - duplicate_item, - get_generated_id, - get_current_profile, - OwnPerms ) @@ -513,12 +518,6 @@ class GeneralType(Cached, models.Model): res[parent_id].append((item["id"], item["label"])) return res - PREFIX = "│ " - PREFIX_EMPTY = " " - PREFIX_MEDIUM = "├ " - PREFIX_LAST = "└ " - PREFIX_CODES = ["\u2502", "\u251C", "\u2514"] - @classmethod def _get_childs( cls, @@ -559,20 +558,20 @@ class GeneralType(Cached, models.Model): cprefix -= 1 if not cprefix: if (idx + 1) == total: - p += cls.PREFIX_LAST + p += GENERAL_TYPE_PREFIX["prefix_last"] else: - p += cls.PREFIX_MEDIUM + p += GENERAL_TYPE_PREFIX["prefix_medium"] elif is_last: if mylast_of: clast = mylast_of.pop(0) if clast: - p += cls.PREFIX_EMPTY + p += GENERAL_TYPE_PREFIX["prefix_empty"] else: - p += cls.PREFIX + p += GENERAL_TYPE_PREFIX["prefix"] else: - p += cls.PREFIX_EMPTY + p += GENERAL_TYPE_PREFIX["prefix_empty"] else: - p += cls.PREFIX + p += GENERAL_TYPE_PREFIX["prefix"] lst.append((child[0], SafeText(p + child[1]))) clast_of = last_of[:] clast_of.append(idx + 1 == total) @@ -1149,17 +1148,6 @@ class FullSearch(models.Model): return changed -class SearchAltName(object): - def __init__( - self, search_key, search_query, extra_query=None, distinct_query=False, related_name=None - ): - self.search_key = search_key - self.search_query = search_query - self.extra_query = extra_query or {} - self.distinct_query = distinct_query - self.related_name = related_name - - class Imported(models.Model): imports = models.ManyToManyField( Import, blank=True, related_name="imported_%(app_label)s_%(class)s", @@ -1386,14 +1374,6 @@ class FixAssociated: setattr(item, subkey, new_value) -class HistoryError(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) - - class HistoricalRecords(BaseHistoricalRecords): def get_extra_fields(self, model, fields): def get_history_m2m(attr): @@ -1561,6 +1541,7 @@ class BaseHistorizedItem( EXTERNAL_ID_KEY = "" EXTERNAL_ID_DEPENDENCIES = [] HISTORICAL_M2M = [] + UPPER_PERMISSIONS = [] history_modifier = models.ForeignKey( User, @@ -1634,8 +1615,50 @@ class BaseHistorizedItem( return cls._meta.verbose_name @classmethod - def get_ids_from_upper_permissions(cls, user_id): - return [] + def get_ids_from_upper_permissions(cls, user_id, base_permissions): + if not cls.UPPER_PERMISSIONS: + return [] + ProfileType = apps.get_model("ishtar_common", "ProfileType") + item_ids = [] + for model, attr in cls.UPPER_PERMISSIONS: + if isinstance(model, tuple): + app_label, model_name = model + model = apps.get_model(app_label, model_name) + permissions = list(set([ + "_".join(permission.codename.split("_")[:-1]) + + f"_{model._meta.model_name}" + for permission in base_permissions + ])) + q = ProfileType.objects.filter( + user_profiles__person__ishtaruser=user_id, + groups__permissions__codename__in=permissions + ) + lst = [] + if not q.count(): + # no permissions associated for upstream model get direct attachement + lst = model.objects.filter( + ishtar_users__pk=user_id + ).values_list("pk", flat=True) + else: + perms = [] + for codename in permissions: + perms += [ + perm + for perm in Permission.objects.filter( + codename=codename).all() + ] + lst = [] + for permission in perms: + lst += list( + UserObjectPermission.objects.filter( + permission=permission, + user_id=user_id + ).values_list("object_pk", flat=True) + ) + item_ids += cls.objects.filter( + **{f"{attr}__in": lst} + ).values_list("pk", flat=True) + return list(set(item_ids)) def is_locked(self, user=None): if not user: diff --git a/ishtar_common/models_rest.py b/ishtar_common/models_rest.py index 05b1206d1..c47b04168 100644 --- a/ishtar_common/models_rest.py +++ b/ishtar_common/models_rest.py @@ -10,6 +10,12 @@ from django.contrib.postgres.fields import ArrayField from django.core.files import File from django.utils.text import slugify +from django.apps import apps +from django.template import loader + +from ishtar_common.models_common import SheetFilter +from ishtar_common.utils import ugettext_lazy as _ + UnoCalc = None ITALIC = None if settings.USE_LIBREOFFICE: @@ -19,30 +25,6 @@ if settings.USE_LIBREOFFICE: except ImportError: pass -from django.apps import apps -from django.template import loader - -from ishtar_common.models_common import SheetFilter -from ishtar_common.utils import ugettext_lazy as _ - - -APP_CONTENT_TYPES = [ - ("archaeological_operations", "operation"), - ("archaeological_context_records", "contextrecord"), - ("archaeological_finds", "find"), - ("archaeological_warehouse", "warehouse"), - ("archaeological_files", "file"), -] - -MAIN_CONTENT_TYPES = APP_CONTENT_TYPES + [ - ("archaeological_operations", "archaeologicalsite"), - ("archaeological_warehouse", "container"), -] - -MAIN_MODELS = dict( - [(model_name, app_name) for app_name, model_name in MAIN_CONTENT_TYPES] -) - class ApiUser(models.Model): user_ptr = models.OneToOneField( diff --git a/ishtar_common/rest.py b/ishtar_common/rest.py index 0a831634a..699440f15 100644 --- a/ishtar_common/rest.py +++ b/ishtar_common/rest.py @@ -1,10 +1,6 @@ -import datetime -import requests - from django.conf import settings from django.db.models import Q from django.http import HttpResponse -from django.shortcuts import reverse from django.utils.translation import activate, deactivate from rest_framework import authentication, permissions, generics @@ -13,7 +9,6 @@ from rest_framework.views import APIView from ishtar_common import models_rest from ishtar_common.models_common import GeneralType -from ishtar_common.models_imports import Importer from ishtar_common.views_item import get_item diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 709f020a4..11ff45fa7 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -333,6 +333,83 @@ class SheetItem: return +def get_current_item_keys(): + return ( + ("file", apps.get_model("archaeological_files", "File")), + ("operation", apps.get_model("archaeological_operations", "Operation")), + ("site", apps.get_model("archaeological_operations", "ArchaeologicalSite")), + ("contextrecord", + apps.get_model("archaeological_context_records", "ContextRecord")), + ("warehouse", apps.get_model("archaeological_warehouse", "Warehouse")), + ("container", apps.get_model("archaeological_warehouse", "Container")), + ("find", apps.get_model("archaeological_finds", "Find")), + ("findbasket", apps.get_model("archaeological_finds", "FindBasket")), + ("treatmentfile", apps.get_model("archaeological_finds", "TreatmentFile")), + ("treatment", apps.get_model("archaeological_finds", "Treatment")), + ("administrativeact", + apps.get_model("archaeological_operations", "AdministrativeAct")), + ("administrativeactop", + apps.get_model("archaeological_operations", "AdministrativeAct")), + ("administrativeactfile", + apps.get_model("archaeological_operations", "AdministrativeAct")), + ("administrativeacttreatment", + apps.get_model("archaeological_operations", "AdministrativeAct")), + ("administrativeacttreatmentfile", + apps.get_model("archaeological_operations", "AdministrativeAct")), + ) + + +def get_current_item_keys_dict(): + return dict(get_current_item_keys()) + + +API_APP_CONTENT_TYPES = [ + ("archaeological_operations", "operation"), + ("archaeological_context_records", "contextrecord"), + ("archaeological_finds", "find"), + ("archaeological_warehouse", "warehouse"), + ("archaeological_files", "file"), +] + +API_MAIN_CONTENT_TYPES = API_APP_CONTENT_TYPES + [ + ("archaeological_operations", "archaeologicalsite"), + ("archaeological_warehouse", "container"), +] + +API_MAIN_MODELS = dict( + [(model_name, app_name) for app_name, model_name in API_MAIN_CONTENT_TYPES] +) + + +class HistoryError(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +class SearchAltName(object): + def __init__( + self, search_key, search_query, extra_query=None, distinct_query=False, + related_name=None + ): + self.search_key = search_key + self.search_query = search_query + self.extra_query = extra_query or {} + self.distinct_query = distinct_query + self.related_name = related_name + + +GENERAL_TYPE_PREFIX = { + "prefix": "│ ", + "prefix_empty": " ", + "prefix_medium": "├ ", + "prefix_last": "└ ", + "prefix_codes": ["\u2502", "\u251C", "\u2514"] +} + + class OwnPerms: """ Manage special permissions for object's owner diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 6c209a848..f01e848a0 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -79,6 +79,8 @@ from ishtar_common.utils_migrations import HOMEPAGE_DEFAULT, HOMEPAGE_TITLE from ishtar_common.utils import ( clean_session_cache, CSV_OPTIONS, + get_current_item_keys, + get_current_item_keys_dict, get_field_labels_from_path, get_person_gdpr_log, get_random_item_image_link, @@ -92,13 +94,7 @@ from ishtar_common.utils import ( from ishtar_common.widgets import JQueryAutoComplete from ishtar_common import tasks -convert_document = None -if settings.USE_LIBREOFFICE: - from ishtar_common.libreoffice import convert_document - from .views_item import ( - CURRENT_ITEM_KEYS, - CURRENT_ITEM_KEYS_DICT, check_permission, display_item, get_item, @@ -108,6 +104,10 @@ from .views_item import ( get_short_html_detail, ) +convert_document = None +if settings.USE_LIBREOFFICE: + from ishtar_common.libreoffice import convert_document + logger = logging.getLogger(__name__) @@ -700,7 +700,8 @@ def shortcut_menu(request): def get_current_items(request): currents = {} - for key, model in CURRENT_ITEM_KEYS: + current_item_keys = get_current_item_keys() + for key, model in current_item_keys: currents[key] = None if key in request.session and request.session[key]: try: @@ -711,7 +712,8 @@ def get_current_items(request): def unpin(request, item_type, cascade=False): - if item_type not in CURRENT_ITEM_KEYS_DICT.keys(): + current_item_keys_dict = get_current_item_keys_dict() + if item_type not in current_item_keys_dict.keys(): logger.warning("unpin unknow type: {}".format(item_type)) return HttpResponse("nok") if "administrativeact" in item_type: diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py index 23752c8a9..b5c63cc65 100644 --- a/ishtar_common/views_item.py +++ b/ishtar_common/views_item.py @@ -13,6 +13,7 @@ import requests import subprocess # nosec from tempfile import NamedTemporaryFile +from django.apps import apps from django.conf import settings from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType @@ -46,6 +47,7 @@ from django.utils.translation import ( deactivate, pgettext_lazy, ) +from guardian.models import UserObjectPermission from tidylib import tidy_document as tidy from unidecode import unidecode from weasyprint import HTML, CSS @@ -54,50 +56,24 @@ from weasyprint.fonts import FontConfiguration from bootstrap_datepicker.widgets import DateField from ishtar_common.utils import ( + API_MAIN_MODELS, check_model_access_control, CSV_OPTIONS, + GENERAL_TYPE_PREFIX, get_all_field_names, - Round, + get_current_item_keys_dict, + get_current_profile, + HistoryError, PRIVATE_FIELDS, + SearchAltName, + Round, ) -from ishtar_common.models import get_current_profile, GeneralType, SearchAltName -from ishtar_common.models_common import HistoryError from .menus import Menu -from . import models, models_rest -from archaeological_files.models import File -from archaeological_operations.models import ( - Operation, - ArchaeologicalSite, - AdministrativeAct, -) -from archaeological_context_records.models import ContextRecord -from archaeological_finds.models import Find, FindBasket, Treatment, TreatmentFile -from archaeological_warehouse.models import Warehouse, Container - logger = logging.getLogger(__name__) ENCODING = settings.ENCODING or "utf-8" -CURRENT_ITEM_KEYS = ( - ("file", File), - ("operation", Operation), - ("site", ArchaeologicalSite), - ("contextrecord", ContextRecord), - ("warehouse", Warehouse), - ("container", Container), - ("find", Find), - ("findbasket", FindBasket), - ("treatmentfile", TreatmentFile), - ("treatment", Treatment), - ("administrativeact", AdministrativeAct), - ("administrativeactop", AdministrativeAct), - ("administrativeactfile", AdministrativeAct), - ("administrativeacttreatment", AdministrativeAct), - ("administrativeacttreatmentfile", AdministrativeAct), -) -CURRENT_ITEM_KEYS_DICT = dict(CURRENT_ITEM_KEYS) - HIERARCHIC_LEVELS = 5 LIST_FIELDS = { # key: hierarchic depth @@ -354,11 +330,11 @@ def show_source_item(request, source_id, model, name, base_dct, extra_dct): source_id, external_id = int(source_id), int(external_id) except ValueError: raise Http404() - models_rest.ApiExternalSource.objects.get() + ApiExternalSource = apps.get_model("ishtar_common", "ApiExternalSource") # TODO: check permissions try: - src = models_rest.ApiExternalSource.objects.get(pk=source_id) - except models_rest.ApiExternalSource.DoesNotExist: + src = ApiExternalSource.objects.get(pk=source_id) + except ApiExternalSource.DoesNotExist: return HttpResponse("{}", content_type="text/plain") url = src.url if not url.endswith("/"): @@ -618,6 +594,8 @@ def _get_values(request, val): else: vals = [val] new_vals = [] + Organization = apps.get_model("ishtar_common", "Organization") + Person = apps.get_model("ishtar_common", "Person") for v in vals: if callable(v): try: @@ -626,7 +604,7 @@ def _get_values(request, val): continue try: if ( - not isinstance(v, (models.Person, models.Organization)) + not isinstance(v, (Person, Organization)) and hasattr(v, "url") and v.url ): @@ -1144,7 +1122,7 @@ def _manage_dated_fields(dated_fields, dct): def _clean_type_val(val): - for prefix in GeneralType.PREFIX_CODES: + for prefix in GENERAL_TYPE_PREFIX["prefix_codes"]: val = val.replace(prefix, "") val = val.strip() if val.startswith('"') and val.endswith('"'): @@ -1210,6 +1188,7 @@ def _manage_hierarchic_fields(model, dct, and_reqs): hierarchic_fields = HIERARCHIC_FIELDS[:] if hasattr(model, "hierarchic_fields"): hierarchic_fields += model.hierarchic_fields() + Town = apps.get_model("ishtar_common", "Town") for reqs in dct.copy(): if type(reqs) not in (list, tuple): reqs = [reqs] @@ -1257,6 +1236,7 @@ def _manage_hierarchic_fields(model, dct, and_reqs): and_reqs.append(main_req) continue + Container = apps.get_model("archaeological_warehouse", "Container") for val in vals: attr = "cached_label__iexact" if val.endswith("*"): @@ -1277,7 +1257,7 @@ def _manage_hierarchic_fields(model, dct, and_reqs): vals = [v.replace('"', "") for v in val.split(";")] town_ids = [] for val in vals: - q = models.Town.objects.filter(cached_label__iexact=val).values_list( + q = Town.objects.filter(cached_label__iexact=val).values_list( "id", flat=True) if not q.count(): continue @@ -1287,7 +1267,7 @@ def _manage_hierarchic_fields(model, dct, and_reqs): for rel_query in ("parents__", "children__"): for idx in range(HIERARCHIC_LEVELS): k = rel_query * (idx + 1) + "pk" - q = models.Town.objects.filter( + q = Town.objects.filter( **{k: town_id}).values_list("id", flat=True) if not q.count(): break @@ -1595,6 +1575,7 @@ def _manage_default_search( dct, request, model, default_name, my_base_request, my_relative_session_names ): pinned_search = "" + current_item_keys_dict = get_current_item_keys_dict() pin_key = "pin-search-" + default_name base_request = my_base_request if isinstance(my_base_request, dict) else {} dct = {k: v for k, v in dct.items() if v} @@ -1606,6 +1587,7 @@ def _manage_default_search( ): # an item is pinned value = request.session[default_name] if "basket-" in value: + FindBasket = apps.get_model("archaeological_finds", "FindBasket") try: dct = {"basket__pk": request.session[default_name].split("-")[-1]} pinned_search = str(FindBasket.objects.get(pk=dct["basket__pk"])) @@ -1630,9 +1612,9 @@ def _manage_default_search( name in request.session and request.session[name] and "basket-" not in request.session[name] - and name in CURRENT_ITEM_KEYS_DICT + and name in current_item_keys_dict ): - up_model = CURRENT_ITEM_KEYS_DICT[name] + up_model = current_item_keys_dict[name] try: dct.update({key: request.session[name]}) up_item = up_model.objects.get(pk=dct[key]) @@ -2031,6 +2013,7 @@ def get_item( no_link=False, no_limit=False, return_query=False, + ishtaruser=None, # could be provided when request is None **dct, ): available_perms = [] @@ -2044,7 +2027,8 @@ def get_item( if "json" in data_type: EMPTY = "[]" - if data_type not in ("json", "csv", "json-image", "json-map", "json-stats"): + if not return_query and data_type not in ( + "json", "csv", "json-image", "json-map", "json-stats"): return HttpResponse(EMPTY, content_type="text/plain") if data_type == "json-stats" and len(model.STATISTIC_MODALITIES) < 2: @@ -2070,6 +2054,7 @@ def get_item( own = True if ( full == "shortcut" + and request and "SHORTCUT_SEARCH" in request.session and request.session["SHORTCUT_SEARCH"] == "own" ): @@ -2077,13 +2062,23 @@ def get_item( query_own = None if own: - q = models.IshtarUser.objects.filter(user_ptr=request.user) - if not q.count(): - return HttpResponse(EMPTY, content_type="text/plain") + # TODO: verify alt_query_own + """ if alt_query_own: query_own = getattr(model, alt_query_own)(q.all()[0]) else: query_own = model.get_query_owns(q.all()[0]) + print(query_own) # TODO - get old request to transform them + """ + user_pk = request.user.pk if request else ishtaruser.pk + q = UserObjectPermission.objects.filter( + user_id=user_pk, + permission__codename=f"view_own_{model._meta.model_name}", + content_type=ContentType.objects.get_for_model(model) + ) + query_own = Q( + pk__in=[int(pk) for pk in q.values_list("object_pk", flat=True)] + ) query_parameters = {} @@ -2191,14 +2186,10 @@ def get_item( request_keys.update(my_extra_request_keys) # manage search on json fields and excluded fields - if ( - search_form - and request - and request.user - and getattr(request.user, "ishtaruser", None) - ): + if search_form: + ishtaruser = request.user.ishtaruser if request else ishtaruser available, __, excluded_fields, json_fields = search_form.check_custom_form( - request.user.ishtaruser + ishtaruser ) # for now no manage on excluded_fields: should we prevent search on # some fields regarding the user concerned? @@ -2218,10 +2209,12 @@ def get_item( if "query" in dct: request_items = dct["query"] request_items["submited"] = True - elif request.method == "POST": + elif request and request.method == "POST": request_items = request.POST - else: + elif request: request_items = request.GET + else: + return HttpResponse(EMPTY, content_type="text/plain") count = dct.get("count", False) @@ -2271,7 +2264,7 @@ def get_item( key = "name__icontains" else: key = "cached_label__icontains" - dct[key] = request.GET.get("term", None) + dct[key] = (request and request.GET.get("term", None)) or None try: old = "old" in request_items and int(request_items["old"]) @@ -2332,11 +2325,12 @@ def get_item( # manage default and pinned search and not bookmark if ( not has_a_search + and request and not request_items.get("search_vector", "") and not request_items.get("submited", "") and full != "shortcut" ): - if data_type == "csv" and func_name in request.session: + if data_type == "csv" and func_name and func_name in request.session: dct = request.session[func_name] else: # default search @@ -2451,6 +2445,10 @@ def get_item( # manage hierarchic in shortcut menu if full == "shortcut": + File = apps.get_model("archaeological_files", "File") + Operation = apps.get_model("archaeological_operations", "Operation") + ContextRecord = apps.get_model("archaeological_context_records", "ContextRecord") + Find = apps.get_model("archaeological_finds", "Find") ASSOCIATED_ITEMS = { Operation: (File, "associated_file__pk"), ContextRecord: (Operation, "operation__pk"), @@ -2463,6 +2461,7 @@ def get_item( if current: dct = {upper_key: current} query &= Q(**dct) + # print("ishtar_common/views_item.py - 2455") # print(query, distinct_queries, base_query, exc_query, extras) items = model.objects.filter(query) for d_q in distinct_queries: @@ -2540,6 +2539,7 @@ def get_item( for col in cols: query_table_cols += col.split("|") + Document = apps.get_model("ishtar_common", "Document") # contextual (full, simple, etc.) col contxt = full and "full" or "simple" if ( @@ -2559,7 +2559,7 @@ def get_item( table_cols.append("cached_label") if data_type == "json-image": prefix = "" - if model != models.Document: + if model != Document: prefix = "main_image__" query_table_cols.append(prefix + "thumbnail") table_cols.append(prefix + "thumbnail") @@ -2584,7 +2584,7 @@ def get_item( for k in request_items: if not k.startswith("order["): continue - num = int(k.split("]")[0][len("order[") :]) + num = int(k.split("]")[0][len("order["):]) if num not in sorts: sorts[num] = ["", ""] # sign, col_num if k.endswith("[dir]"): @@ -2907,9 +2907,10 @@ def adapt_distant_search(params, src, model): search_vector = params["search_vector"][0] match = RE_FACET.search(search_vector) final_search_vector = "" + ApiKeyMatch = apps.get_model("ishtar_common", "ApiKeyMatch") while match: key, value, __ = match.groups() - q = models_rest.ApiKeyMatch.objects.filter( + q = ApiKeyMatch.objects.filter( source=src, search_model__model__iexact=model, search_keys__contains=[key], @@ -2929,10 +2930,11 @@ def adapt_distant_search(params, src, model): def get_distant_item(request, model, external_source_id, data_type=None): - # TODO: check permissions + # TODO: verify/test check permissions + ApiExternalSource = apps.get_model("ishtar_common", "ApiExternalSource") try: - src = models_rest.ApiExternalSource.objects.get(pk=external_source_id) - except (models_rest.ApiExternalSource.DoesNotExist, ValueError): + src = ApiExternalSource.objects.get(pk=external_source_id) + except (ApiExternalSource.DoesNotExist, ValueError): return HttpResponse("{}", content_type="text/plain") url = src.url url += reverse(f"api-search-{model}") @@ -2954,7 +2956,7 @@ def get_distant_item(request, model, external_source_id, data_type=None): "submited", "data_type", ] - app = models_rest.MAIN_MODELS[model] + app = API_MAIN_MODELS[model] model_class = ContentType.objects.get(app_label=app, model=model).model_class() bool_fields = model_class.REVERSED_BOOL_FIELDS + model_class.BOOL_FIELDS + model_class.CALLABLE_BOOL_FIELDS is_empty_params = not any( @@ -3010,9 +3012,10 @@ def external_export(request, source_id, model_name, slug): if not url: return HttpResponse('Unauthorized', status=401) + ApiExternalSource = apps.get_model("ishtar_common", "ApiExternalSource") try: - src = models_rest.ApiExternalSource.objects.get(pk=source_id) - except (models_rest.ApiExternalSource.DoesNotExist, ValueError): + src = ApiExternalSource.objects.get(pk=source_id) + except (ApiExternalSource.DoesNotExist, ValueError): return HttpResponse('Unauthorized', status=401) url = src.url + url |