Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
)
from apps.commons.enums import SDG, Language
from apps.commons.mixins import (
HasModulesRelated,
HasMultipleIDs,
HasOwner,
HasPermissionsSetup,
Expand All @@ -41,6 +42,7 @@


class PeopleGroup(
HasModulesRelated,
HasAutoTranslatedFields,
HasMultipleIDs,
HasPermissionsSetup,
Expand Down
10 changes: 7 additions & 3 deletions apps/accounts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)
from apps.commons.mixins import HasPermissionsSetup
from apps.commons.models import GroupData
from apps.commons.serializers import StringsImagesSerializer
from apps.commons.serializers import ModulesSerializers, StringsImagesSerializer
from apps.files.models import Image
from apps.files.serializers import ImageSerializer
from apps.notifications.models import Notification
Expand Down Expand Up @@ -250,6 +250,7 @@ class PeopleGroupLightSerializer(
)
organization = serializers.SlugRelatedField(read_only=True, slug_field="code")

# TODO(remi): replace this by modules
def get_members_count(self, group: PeopleGroup) -> int:
return group.get_all_members().count()

Expand Down Expand Up @@ -402,7 +403,10 @@ def create(self, validated_data):


class PeopleGroupSerializer(
StringsImagesSerializer, AutoTranslatedModelSerializer, serializers.ModelSerializer
ModulesSerializers,
StringsImagesSerializer,
AutoTranslatedModelSerializer,
serializers.ModelSerializer,
):

string_images_forbid_fields: List[str] = [
Expand Down Expand Up @@ -527,7 +531,7 @@ def save(self, **kwargs):

class Meta:
model = PeopleGroup
read_only_fields = ["is_root", "slug"]
read_only_fields = ["is_root", "slug", "modules"]
fields = read_only_fields + [
"id",
"name",
Expand Down
50 changes: 8 additions & 42 deletions apps/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
from apps.files.views import ImageStorageView
from apps.organizations.models import Organization
from apps.organizations.permissions import HasOrganizationPermission
from apps.projects.models import Project
from apps.projects.serializers import ProjectLightSerializer
from apps.skills.models import Skill
from services.google.models import GoogleAccount, GoogleGroup
Expand Down Expand Up @@ -682,27 +681,10 @@ def remove_member(self, request, *args, **kwargs):
)
def member(self, request, *args, **kwargs):
group = self.get_object()
managers_ids = group.managers.all().values_list("id", flat=True)
leaders_ids = group.leaders.all().values_list("id", flat=True)
skills_prefetch = Prefetch(
"skills", queryset=Skill.objects.select_related("tag")
)
queryset = (
group.get_all_members()
.distinct()
.annotate(
is_leader=Case(
When(id__in=leaders_ids, then=True), default=Value(False)
)
)
.annotate(
is_manager=Case(
When(id__in=managers_ids, then=True), default=Value(False)
)
)
.order_by("-is_leader", "-is_manager")
.prefetch_related(skills_prefetch, "groups")
)

modules_manager = group.get_related_module()
modules = modules_manager(group, request.user)
queryset = modules.members()

page = self.paginate_queryset(queryset)
if page is not None:
Expand Down Expand Up @@ -790,26 +772,10 @@ def remove_featured_project(self, request, *args, **kwargs):
)
def project(self, request, *args, **kwargs):
group = self.get_object()
group_projects_ids = (
Project.objects.filter(groups__people_groups=group)
.distinct()
.values_list("id", flat=True)
)
queryset = (
self.request.user.get_project_queryset()
.filter(Q(groups__people_groups=group) | Q(people_groups=group))
.annotate(
is_group_project=Case(
When(id__in=group_projects_ids, then=True), default=Value(False)
),
is_featured=Case(
When(people_groups=group, then=True), default=Value(False)
),
)
.distinct()
.order_by("-is_featured", "-is_group_project")
.prefetch_related("categories")
)
modules_manager = group.get_related_module()
modules = modules_manager(group, request.user)
queryset = modules.featured_projects()

page = self.paginate_queryset(queryset)
if page is not None:
project_serializer = ProjectLightSerializer(
Expand Down
9 changes: 9 additions & 0 deletions apps/commons/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,12 @@ def get_slug(self) -> str:
if self.get_id_field_name(slug) != "slug":
slug = f"{self.slug_prefix}-{slug}"
return slug


class HasModulesRelated:
"""Mixins for related modules class"""

def get_related_module(self):
from apps.modules.base import get_module

return get_module(type(self))
12 changes: 12 additions & 0 deletions apps/commons/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,15 @@ def save(self, **kwargs):
return self.instance
instance = super().save(**kwargs)
return self.add_string_images_to_instance(instance, images)


class ModulesSerializers(serializers.ModelSerializer):
"""Modules serializers to return how many elements is linked to objects"""

modules = serializers.SerializerMethodField()

def get_modules(self, instance):
request = self.context.get("request")

modules_manager = instance.get_related_module()
return modules_manager(instance, user=request.user).count()
3 changes: 3 additions & 0 deletions apps/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .group import PeopleGroupModules

__all__ = ["PeopleGroupModules"]
49 changes: 49 additions & 0 deletions apps/modules/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import inspect

from django.db import models


class AbstractModules:
"""abstract class for modules/queryset declarations"""

def __init__(self, instance, /, user, **kw):
self.instance = instance
self.user = user

def count(self):
members = inspect.getmembers(
self,
predicate=inspect.ismethod,
)

modules = {}
for name, func in members:
# ignore private_method and "count" method (this method :D)
if name.startswith("_") or name in ("count",):
continue

# func return queryset
modules[name] = func().count()

return modules


_modules: dict[models.Model] = {}


def register_module(model: models.Model):
"""decorator to register modules assoiate on models

:param model: _description_
"""

def _wrap(cls):
_modules[model] = cls
return cls

return _wrap


def get_module(model: models.Model):
"""get regisered module"""
return _modules[model]
57 changes: 57 additions & 0 deletions apps/modules/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from django.db.models import Case, Prefetch, Q, QuerySet, Value, When

from apps.accounts.models import PeopleGroup, ProjectUser
from apps.modules.base import AbstractModules, register_module
from apps.projects.models import Project
from apps.skills.models import Skill


@register_module(PeopleGroup)
class PeopleGroupModules(AbstractModules):
def members(self) -> QuerySet[ProjectUser]:
managers_ids = self.instance.managers.all().values_list("id", flat=True)
leaders_ids = self.instance.leaders.all().values_list("id", flat=True)
skills_prefetch = Prefetch(
"skills", queryset=Skill.objects.select_related("tag")
)
return (
self.instance.get_all_members()
.distinct()
.annotate(
is_leader=Case(
When(id__in=leaders_ids, then=True), default=Value(False)
)
)
.annotate(
is_manager=Case(
When(id__in=managers_ids, then=True), default=Value(False)
)
)
.order_by("-is_leader", "-is_manager")
.prefetch_related(skills_prefetch, "groups")
)

def featured_projects(self) -> QuerySet[Project]:
group_projects_ids = (
Project.objects.filter(groups__people_groups=self.instance)
.distinct()
.values_list("id", flat=True)
)

return (
self.user.get_project_queryset()
.filter(
Q(groups__people_groups=self.instance) | Q(people_groups=self.instance)
)
.annotate(
is_group_project=Case(
When(id__in=group_projects_ids, then=True), default=Value(False)
),
is_featured=Case(
When(people_groups=self.instance, then=True), default=Value(False)
),
)
.distinct()
.order_by("-is_featured", "-is_group_project")
.prefetch_related("categories")
)
4 changes: 2 additions & 2 deletions locale/ca/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-17 16:58+0100\n"
"POT-Creation-Date: 2026-01-13 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -110,7 +110,7 @@ msgstr "No pots assignar aquest rol a un usuari"
msgid "You cannot assign this role to a user : {role}"
msgstr "No pots assignar aquest rol a un usuari: {role}"

#: apps/accounts/models.py:140 apps/projects/models.py:161
#: apps/accounts/models.py:142 apps/projects/models.py:161
msgid "visibility"
msgstr "visibilitat"

Expand Down
4 changes: 2 additions & 2 deletions locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-17 16:58+0100\n"
"POT-Creation-Date: 2026-01-13 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -112,7 +112,7 @@ msgstr "Sie können diese Rolle keinem Benutzer zuweisen"
msgid "You cannot assign this role to a user : {role}"
msgstr "Sie können diese Rolle keinem Benutzer zuweisen: {role}"

#: apps/accounts/models.py:140 apps/projects/models.py:161
#: apps/accounts/models.py:142 apps/projects/models.py:161
msgid "visibility"
msgstr "Sichtbarkeit"

Expand Down
4 changes: 2 additions & 2 deletions locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-17 16:58+0100\n"
"POT-Creation-Date: 2026-01-13 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -108,7 +108,7 @@ msgstr ""
msgid "You cannot assign this role to a user : {role}"
msgstr ""

#: apps/accounts/models.py:140 apps/projects/models.py:161
#: apps/accounts/models.py:142 apps/projects/models.py:161
msgid "visibility"
msgstr ""

Expand Down
4 changes: 2 additions & 2 deletions locale/es/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-17 16:58+0100\n"
"POT-Creation-Date: 2026-01-13 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -110,7 +110,7 @@ msgstr "No puedes asignar este rol a un usuario"
msgid "You cannot assign this role to a user : {role}"
msgstr "No puedes asignar este rol a un usuario: {role}"

#: apps/accounts/models.py:140 apps/projects/models.py:161
#: apps/accounts/models.py:142 apps/projects/models.py:161
msgid "visibility"
msgstr "visibilidad"

Expand Down
4 changes: 2 additions & 2 deletions locale/et/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-17 16:58+0100\n"
"POT-Creation-Date: 2026-01-13 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -110,7 +110,7 @@ msgstr "Sa ei saa seda rolli kasutajale määrata"
msgid "You cannot assign this role to a user : {role}"
msgstr "Sa ei saa seda rolli kasutajale määrata: {role}"

#: apps/accounts/models.py:140 apps/projects/models.py:161
#: apps/accounts/models.py:142 apps/projects/models.py:161
msgid "visibility"
msgstr "nähtavus"

Expand Down
4 changes: 2 additions & 2 deletions locale/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-17 16:58+0100\n"
"POT-Creation-Date: 2026-01-13 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -112,7 +112,7 @@ msgstr "Vous ne pouvez pas assigner ce rôle à un·e utilisateur·ice"
msgid "You cannot assign this role to a user : {role}"
msgstr "Vous ne pouvez pas assigner ce rôle à un·e utilisateur·ice : {role}"

#: apps/accounts/models.py:140 apps/projects/models.py:161
#: apps/accounts/models.py:142 apps/projects/models.py:161
msgid "visibility"
msgstr "visibilité"

Expand Down
4 changes: 2 additions & 2 deletions locale/nl/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-17 16:58+0100\n"
"POT-Creation-Date: 2026-01-13 18:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -112,7 +112,7 @@ msgstr "Je kunt deze rol niet toewijzen aan een gebruiker"
msgid "You cannot assign this role to a user : {role}"
msgstr "Je kunt deze rol niet toewijzen aan een gebruiker: {role}"

#: apps/accounts/models.py:140 apps/projects/models.py:161
#: apps/accounts/models.py:142 apps/projects/models.py:161
msgid "visibility"
msgstr "zichtbaarheid"

Expand Down