diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index f65941f8592fa08946bc457364313cc5e4cb8ebb..856187d79a5735616a5ba3f2abb65a8fc82e8f7d 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Callable, Optional, Union +from typing import Any, Callable, List, Optional, Tuple, Union from django.contrib.contenttypes.models import ContentType from django.contrib.sites.managers import CurrentSiteManager @@ -7,6 +7,7 @@ from django.contrib.sites.models import Site from django.db import models from django.db.models import QuerySet from django.forms.models import ModelForm, ModelFormMetaclass +from django.utils.functional import lazy import reversion from easyaudit.models import CRUDEvent @@ -176,6 +177,30 @@ class ExtensibleModel(models.Model): cls._safe_add(field, name) + @classmethod + def syncable_fields(cls) -> List[models.Field]: + """ Collect all fields that can be synced on a model """ + + return [ + field + for field in cls._meta.fields + if (field.editable and not field.auto_created and not field.is_relation) + ] + + @classmethod + def syncable_fields_choices(cls) -> Tuple[Tuple[str, str]]: + """ Collect all fields that can be synced on a model """ + + return tuple( + [(field.name, field.verbose_name or field.name) for field in cls.syncable_fields()] + ) + + @classmethod + def syncable_fields_choices_lazy(cls) -> Callable[[], Tuple[Tuple[str, str]]]: + """ Collect all fields that can be synced on a model """ + + return lazy(cls.syncable_fields_choices, tuple) + class Meta: abstract = True diff --git a/aleksis/core/models.py b/aleksis/core/models.py index b2bcf3968b51fd01a91d54996af6eee0aab36477..c72f6e1430d994343f0f1436d7f73ba844570103 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -192,7 +192,9 @@ class Person(ExtensibleModel): ) admin.save() - def auto_select_primary_group(self, pattern: Optional[str] = None, force: bool = False) -> None: + def auto_select_primary_group( + self, pattern: Optional[str] = None, field: Optional[str] = None, force: bool = False + ) -> None: """Auto-select the primary group among the groups the person is member of. Uses either the pattern passed as argument, or the pattern configured system-wide. @@ -201,10 +203,11 @@ class Person(ExtensibleModel): a primary group, unless force is True. """ pattern = pattern or get_site_preferences()["account__primary_group_pattern"] + field = field or get_site_preferences()["account__primary_group_field"] if pattern: if force or not self.primary_group: - self.primary_group = self.member_of.filter(name__regex=pattern).first() + self.primary_group = self.member_of.filter(**{f"{field}__regex": pattern}).first() class DummyPerson(Person): @@ -325,7 +328,7 @@ class PersonGroupThrough(ExtensibleModel): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - for field in self.group.additional_fields: + for field in self.group.additional_fields.all(): field_class = getattr(jsonstore, field.field_type) field_name = slugify(field.title).replace("-", "_") field_instance = field_class(verbose_name=field.title) diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py index 77322db0f5f64d3e79f0756805208fc54cc5e6e8..6693aff1fba605180b6f83e7741fe1cb005267fe 100644 --- a/aleksis/core/preferences.py +++ b/aleksis/core/preferences.py @@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _ from dynamic_preferences.preferences import Section from dynamic_preferences.types import ChoicePreference, FilePreference, StringPreference +from .models import Person from .registries import person_preferences_registry, site_preferences_registry from .util.notifications import get_notification_choices_lazy @@ -149,6 +150,18 @@ class PrimaryGroupPattern(StringPreference): verbose_name = _("Regular expression to match primary group, e.g. '^Class .*'") +@site_preferences_registry.register +class PrimaryGroupField(ChoicePreference): + section = account + name = "primary_group_field" + default = "name" + required = False + verbose_name = _("Field on person to match primary group against") + + def get_choices(self): + return Person.syncable_fields_choices() + + @site_preferences_registry.register class SchoolName(StringPreference): section = school