Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2ba4e48
change all crisalid
remigermain Nov 27, 2025
bc839fc
fix(crisalid): add command to fetchresearch graphql
remigermain Nov 27, 2025
72fea80
rework
remigermain Nov 28, 2025
595b556
fix tests
remigermain Nov 28, 2025
be59d70
feat: permissions views crisalid
remigermain Nov 28, 2025
4a11fe8
cleanup
remigermain Nov 28, 2025
a267b90
betters tests
remigermain Nov 28, 2025
4c74117
upgrade crisalid
remigermain Dec 1, 2025
faeda88
thread safe crisalidbus
remigermain Dec 1, 2025
00330bc
fix: admin invalid count documents researcher
remigermain Dec 1, 2025
fe9ae32
better dump csv command
remigermain Dec 1, 2025
2334f82
test: fix tests
remigermain Dec 1, 2025
4b58771
test: add tasks tests
remigermain Dec 1, 2025
7c53e66
cleanup all
remigermain Dec 2, 2025
6b654ce
cleanup migrations
remigermain Dec 2, 2025
1abe97c
tests: fix invalid pk
remigermain Dec 2, 2025
9c98780
fix: permissions researcher
remigermain Dec 8, 2025
e3b19d7
feat: add structure
remigermain Dec 9, 2025
05a0a68
Merge branch 'main' of github.com:CyberCRI/projects-backend into feat…
remigermain Jan 8, 2026
3713094
Merge branch 'main' of github.com:CyberCRI/projects-backend into feat…
remigermain Jan 9, 2026
8e14c01
test: fix merge harvester
remigermain Jan 9, 2026
e8a449c
feat: add modules groups
remigermain Jan 13, 2026
f9c489f
fix: i18n messages
remigermain Jan 13, 2026
be727aa
fix: serializers import
remigermain Jan 15, 2026
3e3a3fd
Merge branch 'main' of github.com:CyberCRI/projects-backend into feat…
remigermain Jan 19, 2026
7882c4a
Merge branch 'feat--add-modules-elements' of github.com:CyberCRI/proj…
remigermain Jan 19, 2026
bfc5498
feat: add publications/conference groups
remigermain Jan 19, 2026
7d18ca5
fix: url/serializers
remigermain Jan 20, 2026
ccdbf91
feat: add group embedding
remigermain Jan 21, 2026
fb4034b
gorup embendings
remigermain Jan 21, 2026
bdb5fde
feat: subgroups in modules
remigermain Jan 22, 2026
45d51fb
feat; beeter subgroups
remigermain Jan 26, 2026
aa44513
add fields groups
remigermain Jan 28, 2026
524f530
add locations
remigermain Jan 30, 2026
12d8deb
fix:groups maps
remigermain Feb 2, 2026
c97fa2a
fix locations
remigermain Feb 3, 2026
aa76158
fix documents filters
remigermain Feb 3, 2026
fb75930
add model image people gorup
remigermain Feb 4, 2026
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
8 changes: 7 additions & 1 deletion apps/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from services.keycloak.interface import KeycloakService

from .exports import UserResource
from .models import PeopleGroup, ProjectUser
from .models import PeopleGroup, PeopleGroupLocation, ProjectUser
from .utils import get_group_permissions


Expand Down Expand Up @@ -163,6 +163,12 @@ class PeopleGroupAdmin(TranslateObjectAdminMixin, admin.ModelAdmin):
list_filter = ("organization",)


@admin.register(PeopleGroupLocation)
class PeopleGroupLocationAdmin(admin.ModelAdmin):
list_display = ("title", "description", "type")
search_fields = ("title", "description", "type")


class PermissionAdmin(admin.ModelAdmin):
list_display = ("name", "codename", "content_type")
search_fields = ("name", "codename", "content_type__model")
Expand Down
19 changes: 19 additions & 0 deletions apps/accounts/migrations/0003_peoplegroup_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.2.10 on 2026-01-21 06:35

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0002_initial"),
("skills", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="peoplegroup",
name="tags",
field=models.ManyToManyField(related_name="people_groups", to="skills.tag"),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Generated by Django 5.2.10 on 2026-01-21 10:52

import apps.commons.mixins
import django.db.models.deletion
import services.translator.mixins
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0003_peoplegroup_tags"),
]

operations = [
migrations.CreateModel(
name="PeopleGroupLocation",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(blank=True, max_length=255)),
("description", models.TextField(blank=True)),
("lat", models.FloatField()),
("lng", models.FloatField()),
(
"type",
models.CharField(
choices=[
("team", "Team"),
("impact", "Impact"),
("address", "Address"),
],
default="team",
max_length=10,
),
),
(
"title_detected_language",
models.CharField(blank=True, max_length=10, null=True),
),
("title_en", models.CharField(blank=True, max_length=1020, null=True)),
("title_fr", models.CharField(blank=True, max_length=1020, null=True)),
("title_de", models.CharField(blank=True, max_length=1020, null=True)),
("title_nl", models.CharField(blank=True, max_length=1020, null=True)),
("title_et", models.CharField(blank=True, max_length=1020, null=True)),
("title_ca", models.CharField(blank=True, max_length=1020, null=True)),
("title_es", models.CharField(blank=True, max_length=1020, null=True)),
(
"description_detected_language",
models.CharField(blank=True, max_length=10, null=True),
),
("description_en", models.TextField(blank=True, null=True)),
("description_fr", models.TextField(blank=True, null=True)),
("description_de", models.TextField(blank=True, null=True)),
("description_nl", models.TextField(blank=True, null=True)),
("description_et", models.TextField(blank=True, null=True)),
("description_ca", models.TextField(blank=True, null=True)),
("description_es", models.TextField(blank=True, null=True)),
],
options={
"abstract": False,
},
bases=(
services.translator.mixins.HasAutoTranslatedFields,
apps.commons.mixins.ProjectRelated,
apps.commons.mixins.DuplicableModel,
models.Model,
),
),
migrations.AddField(
model_name="peoplegroup",
name="location",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="people_groups",
to="accounts.peoplegrouplocation",
),
),
]
25 changes: 25 additions & 0 deletions apps/accounts/migrations/0005_alter_peoplegroup_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 5.2.10 on 2026-02-02 16:10

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("accounts", "0004_peoplegrouplocation_peoplegroup_location"),
]

operations = [
migrations.AlterField(
model_name="peoplegroup",
name="location",
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="people_group",
to="accounts.peoplegrouplocation",
),
),
]
60 changes: 39 additions & 21 deletions apps/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import uuid
from datetime import date
from functools import cached_property
from typing import Any, List, Optional, Union
from typing import Any, Optional

from django.contrib.auth.models import AbstractUser, Group, Permission
from django.contrib.contenttypes.models import ContentType
Expand All @@ -25,6 +25,8 @@
)
from apps.commons.enums import SDG, Language
from apps.commons.mixins import (
HasEmbending,
HasModulesRelated,
HasMultipleIDs,
HasOwner,
HasPermissionsSetup,
Expand All @@ -33,14 +35,20 @@
from apps.commons.models import GroupData
from apps.newsfeed.models import Event, Instruction, News
from apps.organizations.models import Organization
from apps.projects.models import Project
from apps.projects.models import AbstractLocation, Project
from services.keycloak.exceptions import RemoteKeycloakAccountNotFound
from services.keycloak.interface import KeycloakService
from services.keycloak.models import KeycloakAccount
from services.translator.mixins import HasAutoTranslatedFields


class PeopleGroupLocation(AbstractLocation):
"""base location for group"""


class PeopleGroup(
HasEmbending,
HasModulesRelated,
HasAutoTranslatedFields,
HasMultipleIDs,
HasPermissionsSetup,
Expand Down Expand Up @@ -81,12 +89,12 @@ class PeopleGroup(
The visibility setting of the group.
"""

_auto_translated_fields: List[str] = [
_auto_translated_fields: list[str] = [
"name",
"html:description",
"short_description",
]
slugified_fields: List[str] = ["name"]
slugified_fields: list[str] = ["name"]
slug_prefix: str = "group"

class PublicationStatus(models.TextChoices):
Expand Down Expand Up @@ -144,6 +152,16 @@ class PublicationStatus(models.TextChoices):
updated_at = models.DateTimeField(auto_now=True)
permissions_up_to_date = models.BooleanField(default=False)

tags = models.ManyToManyField("skills.Tag", related_name="people_groups")
location = models.OneToOneField(
PeopleGroupLocation,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="people_group",
)
# links

def __str__(self) -> str:
return str(self.name)

Expand All @@ -156,7 +174,7 @@ def get_id_field_name(cls, object_id: Any) -> str:
except ValueError:
return "slug"

def get_related_organizations(self) -> List["Organization"]:
def get_related_organizations(self) -> list["Organization"]:
"""Return the organizations related to this model."""
return [self.organization] if self.organization else []

Expand Down Expand Up @@ -265,23 +283,23 @@ class ProjectUser(
"""

organization_query_string: str = "groups__organizations"
_auto_translated_fields: List[str] = [
_auto_translated_fields: list[str] = [
"html:description",
"short_description",
"job",
]
slugified_fields: List[str] = ["given_name", "family_name"]
slugified_fields: list[str] = ["given_name", "family_name"]
slug_prefix: str = "user"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._project_queryset: Optional[QuerySet["Project"]] = None
self._user_queryset: Optional[QuerySet["ProjectUser"]] = None
self._people_group_queryset: Optional[QuerySet["PeopleGroup"]] = None
self._news_queryset: Optional[QuerySet["News"]] = None
self._event_queryset: Optional[QuerySet["Event"]] = None
self._instruction_queryset: Optional[QuerySet["Instruction"]] = None
self._related_organizations: list["Organization"] = None
self._project_queryset: QuerySet[Project] | None = None
self._user_queryset: QuerySet[ProjectUser] | None = None
self._people_group_queryset: QuerySet[PeopleGroup] | None = None
self._news_queryset: QuerySet[News] | None = None
self._event_queryset: QuerySet[Event] | None = None
self._instruction_queryset: QuerySet[Instruction] | None = None
self._related_organizations: list[Organization] = None

# AbstractUser unused fields
username_validator = None
Expand Down Expand Up @@ -358,7 +376,7 @@ class Meta:
permissions = (("get_user_by_email", "Can retrieve a user by email"),)

@property
def keycloak_id(self) -> Optional[uuid.UUID]:
def keycloak_id(self) -> uuid.UUID | None:
if hasattr(self, "keycloak_account"):
return str(self.keycloak_account.keycloak_id)
return None
Expand All @@ -384,7 +402,7 @@ def is_staff(self) -> bool:
)

@classmethod
def get_id_field_name(cls, object_id: Union[uuid.UUID, int, str]) -> str:
def get_id_field_name(cls, object_id: uuid.UUID | int | str) -> str:
"""Get the name of the field which contains the given ID."""
try:
uuid.UUID(object_id)
Expand All @@ -398,7 +416,7 @@ def get_id_field_name(cls, object_id: Union[uuid.UUID, int, str]) -> str:

@classmethod
def get_main_id(
cls, object_id: Union[uuid.UUID, int, str], returned_field: str = "id"
cls, object_id: uuid.UUID | int | str, returned_field: str = "id"
) -> Any:
try:
return super().get_main_id(object_id, returned_field)
Expand Down Expand Up @@ -470,7 +488,7 @@ def get_owner(self) -> "ProjectUser":
"""Get the owner of the object."""
return self

def get_related_organizations(self) -> List["Organization"]:
def get_related_organizations(self) -> list["Organization"]:
"""Return the organizations related to this model."""
if self._related_organizations is None:
self._related_organizations = list(
Expand Down Expand Up @@ -678,7 +696,7 @@ def can_see_project(self, project: "Project") -> bool:
"""Whether the user can see the project."""
return self.get_project_queryset().contains(project)

def get_permissions_representations(self) -> List[str]:
def get_permissions_representations(self) -> list[str]:
"""Return a list of the permissions representations."""
groups_permissions = [
get_group_permissions(group)
Expand All @@ -693,7 +711,7 @@ def get_permissions_representations(self) -> List[str]:
]
return list(set(groups_permissions))

def get_instance_permissions_representations(self) -> List[str]:
def get_instance_permissions_representations(self) -> list[str]:
"""Return a list of the instance permissions representations."""
groups = self.groups.exclude(
projects=None, people_groups=None, organizations=None
Expand Down Expand Up @@ -967,7 +985,7 @@ def get_permissions_representations(self):
"""Return a list of the permissions representations."""
return []

def get_related_organizations(self) -> List["Organization"]:
def get_related_organizations(self) -> list["Organization"]:
"""Return the organizations related to this model."""
return []

Expand Down
Loading
Loading