diff --git a/apps/accounts/models.py b/apps/accounts/models.py index 29907e258..644eaece1 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -548,7 +548,7 @@ def get_news_queryset(self) -> QuerySet["News"]: self._news_queryset = News.objects.all() else: groups = PeopleGroup.objects.filter(groups__users=self) - organizations = self.get_related_organizations() + organizations = self.get_organizations_queryset() self._news_queryset = News.objects.filter( Q(visible_by_all=True) | Q(people_groups__in=groups) @@ -570,7 +570,7 @@ def get_instruction_queryset(self) -> QuerySet["Instruction"]: self._instruction_queryset = Instruction.objects.all() else: groups = PeopleGroup.objects.filter(groups__users=self) - organizations = self.get_related_organizations() + organizations = self.get_organizations_queryset() self._instruction_queryset = Instruction.objects.filter( Q(visible_by_all=True) | Q(people_groups__in=groups) @@ -592,7 +592,7 @@ def get_event_queryset(self) -> QuerySet["Event"]: self._event_queryset = Event.objects.all() else: groups = PeopleGroup.objects.filter(groups__users=self) - organizations = self.get_related_organizations() + organizations = self.get_organizations_queryset() self._event_queryset = Event.objects.filter( Q(visible_by_all=True) | Q(people_groups__in=groups) diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py index 10ac138c9..347e6a606 100644 --- a/apps/accounts/serializers.py +++ b/apps/accounts/serializers.py @@ -18,6 +18,7 @@ from apps.commons.models import GroupData from apps.commons.serializers import ( BaseLocationSerializer, + PrivacySerializer, StringsImagesSerializer, ) from apps.files.models import Image @@ -69,6 +70,7 @@ class Meta: @auto_translated class UserSerializer( ModulesSerializers, + PrivacySerializer, StringsImagesSerializer, serializers.ModelSerializer, ): @@ -218,23 +220,6 @@ class Meta: "profile_picture_natural_ratio", ] - @cached_property - def _user_acces(self): - request = self.context.get("request") - if request: - return request.user.get_user_queryset().values_list("pk", flat=True) - return [] - - def to_representation(self, instance: ProjectUser): - force_display = self.context.get("force_display", False) - if force_display or instance.pk in self._user_acces: - return super().to_representation(instance) - - return { - **AnonymousUser.serialize(with_permissions=False), - "current_org_role": None, - } - def _validate_role( self, group: Group, @@ -405,6 +390,24 @@ def to_internal_value(self, data): return super().to_internal_value(data) + @cached_property + def _user_acces(self): + request = self.context.get("request") + if request: + return request.user.get_user_queryset().values_list("pk", flat=True) + return [] + + def to_representation(self, instance: ProjectUser): + # TODO(remi): optimize this + force_display = self.context.get("force_display", False) + if force_display or instance.pk in self._user_acces: + return super().to_representation(instance) + + return { + **AnonymousUser.serialize(with_permissions=False), + "current_org_role": None, + } + @auto_translated class UserLightSerializer(UserSerializer): diff --git a/apps/accounts/views.py b/apps/accounts/views.py index f01c6e95c..9ed72cad4 100644 --- a/apps/accounts/views.py +++ b/apps/accounts/views.py @@ -206,7 +206,7 @@ def get_queryset(self): if self.action == "admin_list": queryset = self.annotate_keycloak_email_verified(queryset) - return queryset.select_related("researcher") + return queryset.select_related("researcher", "privacy_settings") def get_object(self): """ @@ -257,7 +257,7 @@ def get_serializer_context(self): permission_classes=[HasBasePermission("get_user_by_email", "accounts")], ) def get_by_email(self, request, *args, **kwargs): - queryset = ProjectUser.objects.all() + queryset = ProjectUser.objects.all().select_related("privacy_settings") if self.organization is not None: queryset = self.annotate_organization_role(queryset, self.organization) user = queryset.filter( @@ -746,7 +746,11 @@ def remove_member(self, request, *args, **kwargs): def member(self, request, *args, **kwargs): group = self.get_object() - queryset = group.modules_by_user(request.user).members() + queryset = ( + group.modules_by_user(request.user) + .members() + .select_related("privacy_settings") + ) page = self.paginate_queryset(queryset) if page is not None: diff --git a/apps/commons/fields.py b/apps/commons/fields.py index 65fc086f5..1bce5b1a7 100644 --- a/apps/commons/fields.py +++ b/apps/commons/fields.py @@ -1,8 +1,5 @@ import inspect -from contextlib import suppress -from django.contrib.auth.models import Group -from django.db.models import Q, QuerySet from django.shortcuts import get_object_or_404 from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes @@ -12,9 +9,7 @@ from rest_framework.serializers import BaseSerializer from apps.accounts.models import PrivacySettings, ProjectUser -from apps.accounts.utils import get_superadmins_group -from apps.commons.models import GroupData -from services.crisalid.models import Researcher +from apps.commons.serializers import PrivacySerializer @extend_schema_field(OpenApiTypes.UUID) @@ -161,6 +156,8 @@ def to_internal_value(self, data): class PrivacySettingFieldMixin: + parent: PrivacySerializer + def __init__(self, **kwargs): self.privacy_field = kwargs.pop("privacy_field", "") self.default_value = kwargs.pop("default_value", None) @@ -169,90 +166,14 @@ def __init__(self, **kwargs): ] super().__init__(**kwargs) - def _get_user(self, value): - if isinstance(value, ProjectUser): - return value - if isinstance(value, Researcher): - return value.user - - user_data = getattr(self.parent, "instance", None) or getattr( - self.parent, "queryset", None - ) - if ( - user_data - and isinstance(user_data, QuerySet) - and user_data.model == ProjectUser - and user_data.count() == 1 - ): - return user_data.get() - if ( - user_data - and isinstance(user_data, QuerySet) - and user_data.model == ProjectUser - and self.source_attrs - ): - try: - return user_data.filter(**{self.source_attrs[0]: value}).first() - except TypeError: # filter raises a TypeError if queryset has been sliced - user_data = list(user_data) - if user_data and isinstance(user_data, ProjectUser): - return user_data - if user_data and isinstance(user_data, list) and len(user_data) == 1: - return user_data[0] - if user_data and isinstance(user_data, list) and self.source_attrs: - return [ - user - for user in user_data - if getattr(user, self.source_attrs[0]) == value - ][0] - if self.source_attrs: - with suppress( - ProjectUser.MultipleObjectsReturned, ProjectUser.DoesNotExist - ): - return ProjectUser.objects.get(**{self.source_attrs[0]: value}) - return None - - def _check_privacy_settings(self, value): - instance = self._get_user(value) - assert isinstance(instance, ProjectUser) - request = self.context.get("request") - assert request is not None - - if instance == request.user or request.user.groups.contains( - get_superadmins_group() - ): - return True - settings, _ = PrivacySettings.objects.get_or_create(user=instance) - match getattr(settings, self.privacy_field): - case PrivacySettings.PrivacyChoices.PUBLIC: - return True - case PrivacySettings.PrivacyChoices.ORGANIZATION: - return instance.groups.filter( - organizations__isnull=False, - organizations__in=request.user.get_related_organizations(), - ).exists() - case PrivacySettings.PrivacyChoices.HIDE: - if not request.user.is_authenticated or not isinstance( - request.user, ProjectUser - ): - return False - return Group.objects.filter( - Q( - organizations__isnull=False, - organizations__in=instance.get_organizations_queryset(), - users=request.user, - ) - & ( - Q(data__role=GroupData.Role.ADMINS) - | Q(data__role=GroupData.Role.FACILITATORS) - ) - ).exists() - return False + def get_attribute(self, instance): + self._instance = instance + return super().get_attribute(instance) def to_representation(self, value): - if self._check_privacy_settings(value): - return super().to_representation(value) - return self.default_value + if self.parent._field_is_private(self._instance, self.privacy_field): + return self.default_value + return super().to_representation(value) class PrivacySettingProtectedCharField(PrivacySettingFieldMixin, serializers.CharField): diff --git a/apps/commons/serializers.py b/apps/commons/serializers.py index a76dc521b..b0666c4d1 100644 --- a/apps/commons/serializers.py +++ b/apps/commons/serializers.py @@ -1,17 +1,24 @@ +import logging from collections.abc import Collection -from typing import Any +from functools import cache +from typing import Any, Optional +from django.contrib.auth.models import Group from django.db.models import Model, Q from django.utils.translation import gettext_lazy as _ from rest_framework import mixins, serializers, viewsets from rest_framework.settings import import_from_string -from apps.accounts.models import ProjectUser +from apps.accounts.models import PrivacySettings, ProjectUser +from apps.commons.mixins import HasOwner +from apps.commons.models import GroupData from apps.commons.utils import process_text, remove_images_text from apps.files.models import Image from apps.organizations.models import Organization from apps.projects.models import Project +logger = logging.getLogger(__name__) + class ProjectRelatedSerializer(serializers.ModelSerializer): """Base serializer for serializers related to projects.""" @@ -216,3 +223,86 @@ def valiate_lat(self, value): def valiate_lng(self, value): return self._check_gis(super().validate_lng(value)) + + +class PrivacySerializer: + instance: ProjectUser + + def __init__(self, *ar, **kw): + super().__init__(*ar, **kw) + self._privacy_settings = cache(self._privacy_settings) + + def _get_user(self, instance): + if isinstance(instance, ProjectUser): + return instance + if isinstance(instance, HasOwner): + return instance.get_owner() + + logger.warning( + "Invalid get user from privacySerializer: user=%r", type(instance) + ) + return None + + def _privacy_settings( + self, instance + ) -> tuple[Optional[PrivacySettings], bool, bool]: + + instance = self._get_user(instance) + + if instance is None: + return None, False, False + try: + settings = instance.privacy_settings + except ProjectUser.privacy_settings.RelatedObjectDoesNotExist: + # if user are not privacy_settings set, create a empty one (whitout save) + settings = PrivacySettings(user=instance) + + request = self.context.get("request") + + if request is None: + logger.warning("Request is not set in serialier %r", type(self)) + return None, False, False + + user: ProjectUser = request.user + + if user.is_anonymous: + is_in_org = is_org_admin = False + elif user.pk == instance.pk or user.is_superuser: + is_in_org = is_org_admin = True + else: + is_in_org = instance.groups.filter( + organizations__isnull=False, + organizations__in=request.user.get_organizations_queryset(), + ).exists() + + is_org_admin = Group.objects.filter( + Q( + organizations__isnull=False, + organizations__in=instance.get_organizations_queryset(), + users=request.user, + ) + & ( + Q(data__role=GroupData.Role.ADMINS) + | Q(data__role=GroupData.Role.FACILITATORS) + ) + ).exists() + + return settings, is_in_org, is_org_admin + + def _field_is_private(self, instance, field: str) -> bool: + """check if field from privacysettings is private from user""" + + privacy_settings, is_in_org, is_org_admin = self._privacy_settings(instance) + + # not privacy_settings, return all privayc field + if privacy_settings is None: + return True + + match getattr(privacy_settings, field): + case PrivacySettings.PrivacyChoices.PUBLIC: + return False + case PrivacySettings.PrivacyChoices.ORGANIZATION: + return not is_in_org + case PrivacySettings.PrivacyChoices.HIDE: + return not is_org_admin + return True diff --git a/apps/invitations/serializers.py b/apps/invitations/serializers.py index 3d6a338aa..537a8e567 100644 --- a/apps/invitations/serializers.py +++ b/apps/invitations/serializers.py @@ -93,7 +93,8 @@ class AccessRequestSerializer( slug_field="code", queryset=Organization.objects.all() ) user = UserMultipleIdRelatedField( - queryset=ProjectUser.objects.all(), allow_null=True + queryset=ProjectUser.objects.all().select_related("privacy_settings"), + allow_null=True, ) class Meta: diff --git a/apps/modules/__init__.py b/apps/modules/__init__.py index 28119950b..3c8ac6e89 100644 --- a/apps/modules/__init__.py +++ b/apps/modules/__init__.py @@ -1,6 +1,13 @@ +from .document import DocumentModules from .group import PeopleGroupModules from .project import ProjectModules from .tab import TabModules from .user import UserModules -__all__ = ["PeopleGroupModules", "ProjectModules", "TabModules", "UserModules"] +__all__ = [ + "PeopleGroupModules", + "ProjectModules", + "TabModules", + "UserModules", + "DocumentModules", +] diff --git a/apps/modules/document.py b/apps/modules/document.py new file mode 100644 index 000000000..b13ef7d59 --- /dev/null +++ b/apps/modules/document.py @@ -0,0 +1,14 @@ +from django.db.models import ( + QuerySet, +) + +from apps.modules.base import AbstractModules, register_module +from services.crisalid.models import Document + + +@register_module(Document) +class DocumentModules(AbstractModules): + instance: Document + + def similars(self) -> QuerySet[Document]: + return self.instance.similars() diff --git a/apps/projects/serializers.py b/apps/projects/serializers.py index 099ff4d04..cc31fa227 100644 --- a/apps/projects/serializers.py +++ b/apps/projects/serializers.py @@ -724,16 +724,13 @@ class ProjectVersionSerializer(serializers.ModelSerializer): linked_projects = serializers.SerializerMethodField(read_only=True) delta = serializers.SerializerMethodField(read_only=True) - @staticmethod - def get_id(version) -> int: + def get_id(self, version) -> int: return version.pk - @staticmethod - def get_project_id(version) -> str: + def get_project_id(self, version) -> str: return version.id - @staticmethod - def get_delta(version) -> dict[str, str]: + def get_delta(self, version) -> dict[str, str]: previous = version.prev_record while previous: previous_reason = previous.history_change_reason @@ -756,8 +753,7 @@ def get_delta(version) -> dict[str, str]: previous = previous.prev_record return {} - @staticmethod - def get_categories(version) -> list[str]: + def get_categories(self, version) -> list[str]: categories_ids = version.categories.all().values_list( "projectcategory_id", flat=True ) @@ -765,29 +761,27 @@ def get_categories(version) -> list[str]: "name", flat=True ) - @staticmethod - def get_tags(version) -> list[str]: + def get_tags(self, version) -> list[str]: tags_ids = version.tags.all().values_list("tag_id", flat=True) return Tag.objects.filter(id__in=tags_ids).values_list("title", flat=True) - @staticmethod - def get_members(version) -> list[str]: + def get_members(self, version) -> list[str]: members = Project.objects.get(id=version.id).get_all_members() return [m.get_full_name() for m in members] - @staticmethod - def get_comments(version) -> dict[str, Any]: + def get_comments(self, version) -> dict[str, Any]: comments = Comment.history.as_of(version.history_date).filter( project__id=version.id, deleted_at=None ) - return CommentSerializer(comments, many=True).data + return CommentSerializer(comments, many=True, context=self.context).data - @staticmethod - def get_linked_projects(version) -> dict[str, Any]: + def get_linked_projects(self, version) -> dict[str, Any]: linked_projects = LinkedProject.history.as_of(version.history_date).filter( target__id=version.id ) - return LinkedProjectSerializer(linked_projects, many=True).data + return LinkedProjectSerializer( + linked_projects, many=True, context=self.context + ).data class Meta: model = apps.get_model("projects", "HistoricalProject") @@ -813,16 +807,13 @@ class ProjectVersionListSerializer(serializers.ModelSerializer): project_id = serializers.SerializerMethodField(read_only=True) updated_fields = serializers.SerializerMethodField(read_only=True) - @staticmethod - def get_id(version) -> int: + def get_id(self, version) -> int: return version.pk - @staticmethod - def get_project_id(version) -> str: + def get_project_id(self, version) -> str: return version.id - @staticmethod - def get_updated_fields(version) -> list[str]: + def get_updated_fields(self, version) -> list[str]: previous = version.prev_record while previous: previous_reason = previous.history_change_reason diff --git a/apps/projects/views.py b/apps/projects/views.py index 00347fa28..0a2f442ca 100644 --- a/apps/projects/views.py +++ b/apps/projects/views.py @@ -365,7 +365,11 @@ class ProjectMemberViewSet( ] def get_queryset(self) -> QuerySet[ProjectUser]: - return self.project.modules_by_user(self.request.user).members() + return ( + self.project.modules_by_user(self.request.user) + .members() + .select_related("privacy_settings") + ) @extend_schema(request=ProjectAddTeamMembersSerializer, responses=ProjectSerializer) @action( diff --git a/apps/search/views.py b/apps/search/views.py index a57b379e0..002bf867f 100644 --- a/apps/search/views.py +++ b/apps/search/views.py @@ -38,7 +38,9 @@ def get_queryset(self, order: bool = True) -> QuerySet[SearchObject]: ) | (Q(type=SearchObject.SearchObjectType.USER) & Q(user__in=users)) ) - .select_related("user", "project__header_image", "people_group") + .select_related( + "user__privacy_settings", "project__header_image", "people_group" + ) .prefetch_related("people_group__organization", "project__categories") ) if order: @@ -86,10 +88,9 @@ def get_queryset(self, order: bool = True) -> QuerySet[SearchObject]: @action(detail=False, methods=["GET"], url_path="(?P.+)") def search(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) - search_objects = list(queryset) # generate ids for opensearch - search_objects_ids = [sobj.id for sobj in search_objects] + search_objects_ids = list(queryset.values_list("id", flat=True)) query = self.kwargs.get("search", "") indices = [ @@ -138,9 +139,8 @@ def search(self, request, *args, **kwargs): search_objects_ids = [hit.search_object_id for hit in response.hits] # remove search id not hits in opensearch - filtered_search_object = [ - obj for obj in search_objects if obj.id in search_objects_ids - ] + filtered_search_object = queryset.filter(id__in=search_objects_ids) + # sort filtered_search_object by hits index ordered_search_objs = sorted( filtered_search_object, diff --git a/apps/skills/views.py b/apps/skills/views.py index a3f26f8fa..2862a737b 100644 --- a/apps/skills/views.py +++ b/apps/skills/views.py @@ -714,7 +714,11 @@ def contact_mentor(self, request, *args, **kwargs): organization=organization, **serializer.validated_data, ) - return Response(MentoringSerializer(instance).data) + return Response( + MentoringSerializer( + instance, context={"request": request, "organization": organization} + ).data + ) @extend_schema( request=MentoringContactSerializer, @@ -765,7 +769,11 @@ def contact_mentoree(self, request, *args, **kwargs): organization=organization, **serializer.validated_data, ) - return Response(MentoringSerializer(instance).data) + return Response( + MentoringSerializer( + instance, context={"request": request, "organization": organization} + ).data + ) @extend_schema( request=MentoringResponseSerializer, @@ -816,4 +824,8 @@ def respond(self, request, *args, **kwargs): organization=organization, **serializer.validated_data, ) - return Response(MentoringSerializer(instance).data) + return Response( + MentoringSerializer( + instance, context={"request": request, "organization": organization} + ).data + ) diff --git a/locale/ca/LC_MESSAGES/django.po b/locale/ca/LC_MESSAGES/django.po index 53860ecc0..3dc3e4793 100644 --- a/locale/ca/LC_MESSAGES/django.po +++ b/locale/ca/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 16:41+0200\n" +"POT-Creation-Date: 2026-09-23 10:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -176,21 +176,21 @@ msgstr "Estat de publicació desconegut" msgid "Unknown publication status '{publication_status}'" msgstr "Estat de publicació desconegut '{publication_status}'" -#: apps/commons/fields.py:32 apps/skills/serializers.py:89 +#: apps/commons/fields.py:27 apps/skills/serializers.py:89 msgid "This field is required." msgstr "Aquest camp és obligatori." -#: apps/commons/fields.py:33 +#: apps/commons/fields.py:28 #, python-brace-format msgid "Invalid id \"{user_id}\" - object does not exist." msgstr "Id no vàlid \"{user_id}\" - l'objecte no existeix." -#: apps/commons/fields.py:35 apps/skills/serializers.py:94 +#: apps/commons/fields.py:30 apps/skills/serializers.py:94 #, python-brace-format msgid "Incorrect type. Expected str value, received {data_type}." msgstr "Tipus incorrecte. S'esperava un valor str, s'ha rebut {data_type}." -#: apps/commons/serializers.py:210 +#: apps/commons/serializers.py:217 msgid "The value must be between -90 and 90." msgstr "" @@ -2619,63 +2619,63 @@ msgstr "" "\n" "Vols ser mentoritzat en %(skill)s? (recordatori)\n" -#: services/crisalid/models.py:189 +#: services/crisalid/models.py:198 msgid "Document" msgstr "" -#: services/crisalid/models.py:190 +#: services/crisalid/models.py:199 msgid "Scholarly Publication" msgstr "" -#: services/crisalid/models.py:191 +#: services/crisalid/models.py:200 msgid "Article" msgstr "" -#: services/crisalid/models.py:192 +#: services/crisalid/models.py:201 msgid "Journal Article" msgstr "" -#: services/crisalid/models.py:193 +#: services/crisalid/models.py:202 msgid "Conference Article" msgstr "Article de conferència" -#: services/crisalid/models.py:194 +#: services/crisalid/models.py:203 msgid "Conference Abstract" msgstr "" -#: services/crisalid/models.py:195 +#: services/crisalid/models.py:204 msgid "Preface" msgstr "" -#: services/crisalid/models.py:196 +#: services/crisalid/models.py:205 msgid "Comment" msgstr "Comentari" -#: services/crisalid/models.py:197 +#: services/crisalid/models.py:206 msgid "Book Chapter" msgstr "" -#: services/crisalid/models.py:198 +#: services/crisalid/models.py:207 msgid "Book" msgstr "" -#: services/crisalid/models.py:199 +#: services/crisalid/models.py:208 msgid "Monograph" msgstr "" -#: services/crisalid/models.py:200 +#: services/crisalid/models.py:209 msgid "Proceedings" msgstr "" -#: services/crisalid/models.py:201 +#: services/crisalid/models.py:210 msgid "Book Of Chapters" msgstr "" -#: services/crisalid/models.py:202 +#: services/crisalid/models.py:211 msgid "Presentation" msgstr "" -#: services/crisalid/models.py:203 +#: services/crisalid/models.py:212 msgid "Unknown" msgstr "" diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index bd3943abd..f0902d5d8 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 16:41+0200\n" +"POT-Creation-Date: 2026-09-23 10:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -178,22 +178,22 @@ msgstr "Unbekannter Veröffentlichungsstatus" msgid "Unknown publication status '{publication_status}'" msgstr "Unbekannter Veröffentlichungsstatus '{publication_status}'" -#: apps/commons/fields.py:32 apps/skills/serializers.py:89 +#: apps/commons/fields.py:27 apps/skills/serializers.py:89 msgid "This field is required." msgstr "Dieses Feld ist erforderlich." -#: apps/commons/fields.py:33 +#: apps/commons/fields.py:28 #, python-brace-format msgid "Invalid id \"{user_id}\" - object does not exist." msgstr "Ungültige ID \"{user_id}\" – Objekt existiert nicht." -#: apps/commons/fields.py:35 apps/skills/serializers.py:94 +#: apps/commons/fields.py:30 apps/skills/serializers.py:94 #, python-brace-format msgid "Incorrect type. Expected str value, received {data_type}." msgstr "" "Falscher Typ. Erwartet wurde ein String-Wert, erhalten wurde {data_type}." -#: apps/commons/serializers.py:210 +#: apps/commons/serializers.py:217 msgid "The value must be between -90 and 90." msgstr "" @@ -2647,63 +2647,63 @@ msgstr "" "\n" "Möchten Sie in %(skill)s betreut werden? (Erinnerung)\n" -#: services/crisalid/models.py:189 +#: services/crisalid/models.py:198 msgid "Document" msgstr "" -#: services/crisalid/models.py:190 +#: services/crisalid/models.py:199 msgid "Scholarly Publication" msgstr "" -#: services/crisalid/models.py:191 +#: services/crisalid/models.py:200 msgid "Article" msgstr "" -#: services/crisalid/models.py:192 +#: services/crisalid/models.py:201 msgid "Journal Article" msgstr "" -#: services/crisalid/models.py:193 +#: services/crisalid/models.py:202 msgid "Conference Article" msgstr "Konferenzartikel" -#: services/crisalid/models.py:194 +#: services/crisalid/models.py:203 msgid "Conference Abstract" msgstr "" -#: services/crisalid/models.py:195 +#: services/crisalid/models.py:204 msgid "Preface" msgstr "" -#: services/crisalid/models.py:196 +#: services/crisalid/models.py:205 msgid "Comment" msgstr "Kommentar" -#: services/crisalid/models.py:197 +#: services/crisalid/models.py:206 msgid "Book Chapter" msgstr "" -#: services/crisalid/models.py:198 +#: services/crisalid/models.py:207 msgid "Book" msgstr "" -#: services/crisalid/models.py:199 +#: services/crisalid/models.py:208 msgid "Monograph" msgstr "" -#: services/crisalid/models.py:200 +#: services/crisalid/models.py:209 msgid "Proceedings" msgstr "" -#: services/crisalid/models.py:201 +#: services/crisalid/models.py:210 msgid "Book Of Chapters" msgstr "" -#: services/crisalid/models.py:202 +#: services/crisalid/models.py:211 msgid "Presentation" msgstr "" -#: services/crisalid/models.py:203 +#: services/crisalid/models.py:212 msgid "Unknown" msgstr "" diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 6a796cc5c..5d0c2c923 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 16:41+0200\n" +"POT-Creation-Date: 2026-09-23 10:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -154,21 +154,21 @@ msgstr "" msgid "Unknown publication status '{publication_status}'" msgstr "" -#: apps/commons/fields.py:32 apps/skills/serializers.py:89 +#: apps/commons/fields.py:27 apps/skills/serializers.py:89 msgid "This field is required." msgstr "" -#: apps/commons/fields.py:33 +#: apps/commons/fields.py:28 #, python-brace-format msgid "Invalid id \"{user_id}\" - object does not exist." msgstr "" -#: apps/commons/fields.py:35 apps/skills/serializers.py:94 +#: apps/commons/fields.py:30 apps/skills/serializers.py:94 #, python-brace-format msgid "Incorrect type. Expected str value, received {data_type}." msgstr "" -#: apps/commons/serializers.py:210 +#: apps/commons/serializers.py:217 msgid "The value must be between -90 and 90." msgstr "" @@ -1958,63 +1958,63 @@ msgid "" "Do you want to be mentored in %(skill)s? (reminder)\n" msgstr "" -#: services/crisalid/models.py:189 +#: services/crisalid/models.py:198 msgid "Document" msgstr "" -#: services/crisalid/models.py:190 +#: services/crisalid/models.py:199 msgid "Scholarly Publication" msgstr "" -#: services/crisalid/models.py:191 +#: services/crisalid/models.py:200 msgid "Article" msgstr "" -#: services/crisalid/models.py:192 +#: services/crisalid/models.py:201 msgid "Journal Article" msgstr "" -#: services/crisalid/models.py:193 +#: services/crisalid/models.py:202 msgid "Conference Article" msgstr "" -#: services/crisalid/models.py:194 +#: services/crisalid/models.py:203 msgid "Conference Abstract" msgstr "" -#: services/crisalid/models.py:195 +#: services/crisalid/models.py:204 msgid "Preface" msgstr "" -#: services/crisalid/models.py:196 +#: services/crisalid/models.py:205 msgid "Comment" msgstr "" -#: services/crisalid/models.py:197 +#: services/crisalid/models.py:206 msgid "Book Chapter" msgstr "" -#: services/crisalid/models.py:198 +#: services/crisalid/models.py:207 msgid "Book" msgstr "" -#: services/crisalid/models.py:199 +#: services/crisalid/models.py:208 msgid "Monograph" msgstr "" -#: services/crisalid/models.py:200 +#: services/crisalid/models.py:209 msgid "Proceedings" msgstr "" -#: services/crisalid/models.py:201 +#: services/crisalid/models.py:210 msgid "Book Of Chapters" msgstr "" -#: services/crisalid/models.py:202 +#: services/crisalid/models.py:211 msgid "Presentation" msgstr "" -#: services/crisalid/models.py:203 +#: services/crisalid/models.py:212 msgid "Unknown" msgstr "" diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 060aea8ca..a273003f5 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 16:41+0200\n" +"POT-Creation-Date: 2026-09-23 10:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -176,22 +176,22 @@ msgstr "Estado de publicación desconocido" msgid "Unknown publication status '{publication_status}'" msgstr "Estado de publicación desconocido '{publication_status}'" -#: apps/commons/fields.py:32 apps/skills/serializers.py:89 +#: apps/commons/fields.py:27 apps/skills/serializers.py:89 msgid "This field is required." msgstr "Este campo es obligatorio." -#: apps/commons/fields.py:33 +#: apps/commons/fields.py:28 #, python-brace-format msgid "Invalid id \"{user_id}\" - object does not exist." msgstr "ID no válido \"{user_id}\" - el objeto no existe." -#: apps/commons/fields.py:35 apps/skills/serializers.py:94 +#: apps/commons/fields.py:30 apps/skills/serializers.py:94 #, python-brace-format msgid "Incorrect type. Expected str value, received {data_type}." msgstr "" "Tipo incorrecto. Se esperaba un valor de cadena, se recibió {data_type}." -#: apps/commons/serializers.py:210 +#: apps/commons/serializers.py:217 msgid "The value must be between -90 and 90." msgstr "" @@ -2620,63 +2620,63 @@ msgstr "" "\n" "¿Quieres que te guíen en %(skill)s? (recordatorio)\n" -#: services/crisalid/models.py:189 +#: services/crisalid/models.py:198 msgid "Document" msgstr "" -#: services/crisalid/models.py:190 +#: services/crisalid/models.py:199 msgid "Scholarly Publication" msgstr "" -#: services/crisalid/models.py:191 +#: services/crisalid/models.py:200 msgid "Article" msgstr "" -#: services/crisalid/models.py:192 +#: services/crisalid/models.py:201 msgid "Journal Article" msgstr "" -#: services/crisalid/models.py:193 +#: services/crisalid/models.py:202 msgid "Conference Article" msgstr "Artículo de conferencia" -#: services/crisalid/models.py:194 +#: services/crisalid/models.py:203 msgid "Conference Abstract" msgstr "" -#: services/crisalid/models.py:195 +#: services/crisalid/models.py:204 msgid "Preface" msgstr "" -#: services/crisalid/models.py:196 +#: services/crisalid/models.py:205 msgid "Comment" msgstr "Comentario" -#: services/crisalid/models.py:197 +#: services/crisalid/models.py:206 msgid "Book Chapter" msgstr "" -#: services/crisalid/models.py:198 +#: services/crisalid/models.py:207 msgid "Book" msgstr "" -#: services/crisalid/models.py:199 +#: services/crisalid/models.py:208 msgid "Monograph" msgstr "" -#: services/crisalid/models.py:200 +#: services/crisalid/models.py:209 msgid "Proceedings" msgstr "" -#: services/crisalid/models.py:201 +#: services/crisalid/models.py:210 msgid "Book Of Chapters" msgstr "" -#: services/crisalid/models.py:202 +#: services/crisalid/models.py:211 msgid "Presentation" msgstr "" -#: services/crisalid/models.py:203 +#: services/crisalid/models.py:212 msgid "Unknown" msgstr "" diff --git a/locale/et/LC_MESSAGES/django.po b/locale/et/LC_MESSAGES/django.po index de8d2db01..8f47ac676 100644 --- a/locale/et/LC_MESSAGES/django.po +++ b/locale/et/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 16:41+0200\n" +"POT-Creation-Date: 2026-09-23 10:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -175,21 +175,21 @@ msgstr "Tundmatu avaldamise olek" msgid "Unknown publication status '{publication_status}'" msgstr "Tundmatu avaldamise olek '{publication_status}'" -#: apps/commons/fields.py:32 apps/skills/serializers.py:89 +#: apps/commons/fields.py:27 apps/skills/serializers.py:89 msgid "This field is required." msgstr "See väli on kohustuslik." -#: apps/commons/fields.py:33 +#: apps/commons/fields.py:28 #, python-brace-format msgid "Invalid id \"{user_id}\" - object does not exist." msgstr "Vigane ID \"{user_id}\" - objekti ei eksisteeri." -#: apps/commons/fields.py:35 apps/skills/serializers.py:94 +#: apps/commons/fields.py:30 apps/skills/serializers.py:94 #, python-brace-format msgid "Incorrect type. Expected str value, received {data_type}." msgstr "Vale tüüp. Oodati stringi väärtust, saadi {data_type}." -#: apps/commons/serializers.py:210 +#: apps/commons/serializers.py:217 msgid "The value must be between -90 and 90." msgstr "" @@ -2602,63 +2602,63 @@ msgstr "" "\n" "Kas soovite, et teid juhendataks %(skill)s valdkonnas? (meeldetuletus)\n" -#: services/crisalid/models.py:189 +#: services/crisalid/models.py:198 msgid "Document" msgstr "" -#: services/crisalid/models.py:190 +#: services/crisalid/models.py:199 msgid "Scholarly Publication" msgstr "" -#: services/crisalid/models.py:191 +#: services/crisalid/models.py:200 msgid "Article" msgstr "" -#: services/crisalid/models.py:192 +#: services/crisalid/models.py:201 msgid "Journal Article" msgstr "" -#: services/crisalid/models.py:193 +#: services/crisalid/models.py:202 msgid "Conference Article" msgstr "Konverentsiartikkel" -#: services/crisalid/models.py:194 +#: services/crisalid/models.py:203 msgid "Conference Abstract" msgstr "" -#: services/crisalid/models.py:195 +#: services/crisalid/models.py:204 msgid "Preface" msgstr "" -#: services/crisalid/models.py:196 +#: services/crisalid/models.py:205 msgid "Comment" msgstr "Kommentaar" -#: services/crisalid/models.py:197 +#: services/crisalid/models.py:206 msgid "Book Chapter" msgstr "" -#: services/crisalid/models.py:198 +#: services/crisalid/models.py:207 msgid "Book" msgstr "" -#: services/crisalid/models.py:199 +#: services/crisalid/models.py:208 msgid "Monograph" msgstr "" -#: services/crisalid/models.py:200 +#: services/crisalid/models.py:209 msgid "Proceedings" msgstr "" -#: services/crisalid/models.py:201 +#: services/crisalid/models.py:210 msgid "Book Of Chapters" msgstr "" -#: services/crisalid/models.py:202 +#: services/crisalid/models.py:211 msgid "Presentation" msgstr "" -#: services/crisalid/models.py:203 +#: services/crisalid/models.py:212 msgid "Unknown" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 3f5557bea..60a2605a1 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 16:41+0200\n" +"POT-Creation-Date: 2026-09-23 10:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -178,21 +178,21 @@ msgstr "Statut de publication inconnu" msgid "Unknown publication status '{publication_status}'" msgstr "Statut de publication inconnu '{publication_status}'" -#: apps/commons/fields.py:32 apps/skills/serializers.py:89 +#: apps/commons/fields.py:27 apps/skills/serializers.py:89 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: apps/commons/fields.py:33 +#: apps/commons/fields.py:28 #, python-brace-format msgid "Invalid id \"{user_id}\" - object does not exist." msgstr "identifiant invalide \"{user_id}\" - cet objet n'existe pas." -#: apps/commons/fields.py:35 apps/skills/serializers.py:94 +#: apps/commons/fields.py:30 apps/skills/serializers.py:94 #, python-brace-format msgid "Incorrect type. Expected str value, received {data_type}." msgstr "Type incorrect. Valeur str attendue, {data_type} reçue." -#: apps/commons/serializers.py:210 +#: apps/commons/serializers.py:217 msgid "The value must be between -90 and 90." msgstr "" @@ -2622,63 +2622,63 @@ msgstr "" "\n" "Voulez-vous être mentoré·e en %(skill)s? (rappel)\n" -#: services/crisalid/models.py:189 +#: services/crisalid/models.py:198 msgid "Document" msgstr "" -#: services/crisalid/models.py:190 +#: services/crisalid/models.py:199 msgid "Scholarly Publication" msgstr "" -#: services/crisalid/models.py:191 +#: services/crisalid/models.py:200 msgid "Article" msgstr "" -#: services/crisalid/models.py:192 +#: services/crisalid/models.py:201 msgid "Journal Article" msgstr "" -#: services/crisalid/models.py:193 +#: services/crisalid/models.py:202 msgid "Conference Article" msgstr "Article de conférence" -#: services/crisalid/models.py:194 +#: services/crisalid/models.py:203 msgid "Conference Abstract" msgstr "" -#: services/crisalid/models.py:195 +#: services/crisalid/models.py:204 msgid "Preface" msgstr "" -#: services/crisalid/models.py:196 +#: services/crisalid/models.py:205 msgid "Comment" msgstr "Commentaire" -#: services/crisalid/models.py:197 +#: services/crisalid/models.py:206 msgid "Book Chapter" msgstr "" -#: services/crisalid/models.py:198 +#: services/crisalid/models.py:207 msgid "Book" msgstr "" -#: services/crisalid/models.py:199 +#: services/crisalid/models.py:208 msgid "Monograph" msgstr "" -#: services/crisalid/models.py:200 +#: services/crisalid/models.py:209 msgid "Proceedings" msgstr "" -#: services/crisalid/models.py:201 +#: services/crisalid/models.py:210 msgid "Book Of Chapters" msgstr "" -#: services/crisalid/models.py:202 +#: services/crisalid/models.py:211 msgid "Presentation" msgstr "" -#: services/crisalid/models.py:203 +#: services/crisalid/models.py:212 msgid "Unknown" msgstr "" diff --git a/locale/nl/LC_MESSAGES/django.po b/locale/nl/LC_MESSAGES/django.po index 9fca88446..5762a8bc2 100644 --- a/locale/nl/LC_MESSAGES/django.po +++ b/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-16 16:41+0200\n" +"POT-Creation-Date: 2026-09-23 10:01+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -178,21 +178,21 @@ msgstr "Onbekende publicatiestatus" msgid "Unknown publication status '{publication_status}'" msgstr "Onbekende publicatiestatus '{publication_status}'" -#: apps/commons/fields.py:32 apps/skills/serializers.py:89 +#: apps/commons/fields.py:27 apps/skills/serializers.py:89 msgid "This field is required." msgstr "Dit veld is verplicht." -#: apps/commons/fields.py:33 +#: apps/commons/fields.py:28 #, python-brace-format msgid "Invalid id \"{user_id}\" - object does not exist." msgstr "Ongeldige id \"{user_id}\" - object bestaat niet." -#: apps/commons/fields.py:35 apps/skills/serializers.py:94 +#: apps/commons/fields.py:30 apps/skills/serializers.py:94 #, python-brace-format msgid "Incorrect type. Expected str value, received {data_type}." msgstr "Onjuist type. Verwachte stringwaarde, ontvangen {data_type}." -#: apps/commons/serializers.py:210 +#: apps/commons/serializers.py:217 msgid "The value must be between -90 and 90." msgstr "" @@ -2636,63 +2636,63 @@ msgstr "" "\n" "Wil je begeleid worden in %(skill)s? (herinnering)\n" -#: services/crisalid/models.py:189 +#: services/crisalid/models.py:198 msgid "Document" msgstr "" -#: services/crisalid/models.py:190 +#: services/crisalid/models.py:199 msgid "Scholarly Publication" msgstr "" -#: services/crisalid/models.py:191 +#: services/crisalid/models.py:200 msgid "Article" msgstr "" -#: services/crisalid/models.py:192 +#: services/crisalid/models.py:201 msgid "Journal Article" msgstr "" -#: services/crisalid/models.py:193 +#: services/crisalid/models.py:202 msgid "Conference Article" msgstr "Conferentieartikel" -#: services/crisalid/models.py:194 +#: services/crisalid/models.py:203 msgid "Conference Abstract" msgstr "" -#: services/crisalid/models.py:195 +#: services/crisalid/models.py:204 msgid "Preface" msgstr "" -#: services/crisalid/models.py:196 +#: services/crisalid/models.py:205 msgid "Comment" msgstr "Commentaar" -#: services/crisalid/models.py:197 +#: services/crisalid/models.py:206 msgid "Book Chapter" msgstr "" -#: services/crisalid/models.py:198 +#: services/crisalid/models.py:207 msgid "Book" msgstr "" -#: services/crisalid/models.py:199 +#: services/crisalid/models.py:208 msgid "Monograph" msgstr "" -#: services/crisalid/models.py:200 +#: services/crisalid/models.py:209 msgid "Proceedings" msgstr "" -#: services/crisalid/models.py:201 +#: services/crisalid/models.py:210 msgid "Book Of Chapters" msgstr "" -#: services/crisalid/models.py:202 +#: services/crisalid/models.py:211 msgid "Presentation" msgstr "" -#: services/crisalid/models.py:203 +#: services/crisalid/models.py:212 msgid "Unknown" msgstr "" diff --git a/services/crisalid/models.py b/services/crisalid/models.py index c74dc39ec..102c24967 100644 --- a/services/crisalid/models.py +++ b/services/crisalid/models.py @@ -6,7 +6,12 @@ from django.db.models.functions import Lower from django.utils.translation import gettext_lazy as _ -from apps.commons.mixins import HasEmbedding, OrganizationRelated +from apps.commons.mixins import ( + HasEmbedding, + HasOwner, + HasRelatedModules, + OrganizationRelated, +) from apps.organizations.models import Organization from services.crisalid.relators import RolesChoices from services.translator.mixins import HasAutoTranslatedFields @@ -119,7 +124,7 @@ def __str__(self): return f"{self.harvester} :: {self.value}" -class Researcher(CrisalidDataModel): +class Researcher(HasOwner, CrisalidDataModel): """Link to a crisalid""" PRIVACY_HARVESTER = ( @@ -152,6 +157,9 @@ def __str__(self): def display_name(self): return f"{self.given_name.capitalize()} {self.family_name.capitalize()}" + def get_owner(self): + return self.user + class DocumentContributor(models.Model): roles = ChoiceArrayField( @@ -170,6 +178,7 @@ class Meta: class Document( + HasRelatedModules, HasEmbedding, OrganizationRelated, HasAutoTranslatedFields, diff --git a/services/crisalid/serializers.py b/services/crisalid/serializers.py index 9b71db0db..184cd8522 100644 --- a/services/crisalid/serializers.py +++ b/services/crisalid/serializers.py @@ -2,6 +2,8 @@ from apps.accounts.models import ProjectUser from apps.commons.fields import PrivacySettingProtectedMethodField +from apps.commons.serializers import PrivacySerializer +from apps.modules.serializers import ModulesSerializers from services.crisalid.models import Document, Identifier, Researcher from services.translator.serializers import auto_translated @@ -23,7 +25,7 @@ class Meta: exclude = ("id",) -class ResearcherSerializer(serializers.ModelSerializer): +class ResearcherSerializer(PrivacySerializer, serializers.ModelSerializer): user = ProjectUserMinimalSerializer() # TODO(remi): change privacy field for identifiers (not based in socials) identifiers = PrivacySettingProtectedMethodField(privacy_field="socials") @@ -53,25 +55,19 @@ class Meta(ResearcherSerializer.Meta): @auto_translated -class DocumentLightSerializer(serializers.ModelSerializer): - class Meta: - model = Document - fields = ("title", "publication_date", "document_type") - - -@auto_translated -class DocumentSerializer(DocumentLightSerializer): +class DocumentSerializer(ModulesSerializers, serializers.ModelSerializer): contributors = ResearcherDocumentsSerializer(many=True) identifiers = IdentifierSerializer(many=True) - similars = serializers.SerializerMethodField() class Meta: model = Document exclude = ("updated",) - def get_similars(self, instance: Document): - """return similar count""" - return instance.similars().count() + +class DocumentLightSerializer(DocumentSerializer): + class Meta(DocumentSerializer.Meta): + fields = ("title", "publication_date", "document_type", "modules") + modules_keys = () class DocumentAnalyticsSerializer(serializers.Serializer): diff --git a/services/crisalid/views.py b/services/crisalid/views.py index 8a6751efc..7f7d75ea6 100644 --- a/services/crisalid/views.py +++ b/services/crisalid/views.py @@ -129,7 +129,7 @@ def get_queryset(self) -> QuerySet[Document]: def similars(self, request, *args, **kwargs): """methods to return similars projects""" obj: Document = self.get_object() - queryset = obj.similars() + queryset = obj.modules_by_user(request.user).similars() queryset_page = self.paginate_queryset(queryset) data = self.serializer_class( diff --git a/services/mistral/views.py b/services/mistral/views.py index d2239b4fb..8c736bf77 100644 --- a/services/mistral/views.py +++ b/services/mistral/views.py @@ -11,15 +11,16 @@ from apps.accounts.serializers import UserLightSerializer from apps.commons.cache import redis_cache_viewset_method from apps.commons.permissions import ReadOnly -from apps.commons.views import MultipleIDViewsetMixin -from apps.organizations.utils import get_below_hierarchy_codes +from apps.commons.views import MultipleIDViewsetMixin, NestedOrganizationViewMixins from apps.projects.models import Project from apps.projects.serializers import ProjectLightSerializer from .models import ProjectEmbedding, UserEmbedding -class RecommendationsViewset(MultipleIDViewsetMixin, GenericViewSet): +class RecommendationsViewset( + NestedOrganizationViewMixins, MultipleIDViewsetMixin, GenericViewSet +): filter_backends = [DjangoFilterBackend] ordering_fields = [] permission_classes = [ReadOnly] @@ -28,6 +29,11 @@ class RecommendationsViewset(MultipleIDViewsetMixin, GenericViewSet): queryset: QuerySet[Project | ProjectUser] serializer_class: ProjectLightSerializer | UserLightSerializer + def get_serializer_context(self): + context = super().get_serializer_context() + context["request"] = self.request + return context + def _list(self, request, *args, **kwargs): """ Redefinition of the ListModelMixin list method to allow for pagination. @@ -86,9 +92,7 @@ def get_queryset(self) -> QuerySet[Project | ProjectUser]: project = get_object_or_404( self.request.user.get_project_queryset(), id=self.kwargs["project_id"], - organizations__code__in=get_below_hierarchy_codes( - [self.kwargs["organization_code"]] - ), + organizations__in=self.organizations, ) return self.get_queryset_for_project(project) return self.get_queryset_for_user(self.request.user) @@ -198,9 +202,7 @@ def get_queryset_for_project(self, project: Project) -> QuerySet[Project]: queryset = ( self.request.user.get_project_queryset() .filter( - organizations__code__in=get_below_hierarchy_codes( - [self.kwargs["organization_code"]] - ), + organizations__in=self.organizations, score__activity__gte=0.37, # 6 months of inactivity ) .exclude(id=project.id) @@ -215,9 +217,7 @@ def get_queryset_for_user(self, user: ProjectUser) -> QuerySet[Project]: queryset = ( user.get_project_queryset() .filter( - organizations__code__in=get_below_hierarchy_codes( - [self.kwargs["organization_code"]] - ), + organizations__in=self.organizations, score__activity__gte=0.37, # 6 months of inactivity ) .prefetch_related("categories") @@ -238,12 +238,11 @@ def get_queryset_for_project(self, project: Project) -> QuerySet[ProjectUser]: queryset = ( self.request.user.get_user_queryset() .filter( - groups__organizations__code__in=get_below_hierarchy_codes( - [self.kwargs["organization_code"]] - ), + groups__organizations__in=self.organizations, score__activity__gte=0.1, # 49 weeks of inactivity ) .exclude(groups__projects__id=project.id) + .select_related("privacy_settings") ) embedding = self.get_project_embedding(project) if self.request.user.is_authenticated: @@ -253,11 +252,13 @@ def get_queryset_for_project(self, project: Project) -> QuerySet[ProjectUser]: return queryset.none() def get_queryset_for_user(self, user: ProjectUser) -> QuerySet[ProjectUser]: - queryset = user.get_user_queryset().filter( - groups__organizations__code__in=get_below_hierarchy_codes( - [self.kwargs["organization_code"]] - ), - score__activity__gte=0.1, # 49 weeks of inactivity + queryset = ( + user.get_user_queryset() + .filter( + groups__organizations__in=self.organizations, + score__activity__gte=0.1, # 49 weeks of inactivity + ) + .select_related("privacy_settings") ) embedding = self.get_user_embedding(user) if user.is_authenticated: