From f9397b6dd09fc62ebc154420fa35ea7d562f227d Mon Sep 17 00:00:00 2001
From: Tom Teichler <tom.teichler@teckids.org>
Date: Sat, 28 Mar 2020 20:26:11 +0000
Subject: [PATCH] Auto-select primary group if pattern is configured

---
 aleksis/core/apps.py      | 10 ++++++++++
 aleksis/core/models.py    | 20 ++++++++++++++++++--
 aleksis/core/util/apps.py | 22 ++++++++++++++++++++++
 3 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index b373367ef..13e3b3ddc 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -1,9 +1,12 @@
 from typing import Any, List, Optional, Tuple
 
 import django.apps
+from django.contrib.auth.signals import user_logged_in
+from django.http import HttpRequest
 
 from .signals import clean_scss
 from .util.apps import AppConfig
+from .util.core_helpers import has_person
 
 
 class CoreConfig(AppConfig):
@@ -29,3 +32,10 @@ class CoreConfig(AppConfig):
         apps.get_model("otp_yubikey", "ValidationService").objects.using(using).update_or_create(
             name="default", defaults={"use_ssl": True, "param_sl": "", "param_timeout": ""}
         )
+
+    def user_logged_in(
+        self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs
+    ) -> None:
+        if has_person(user):
+            # Save the associated person to pick up defaults
+            user.person.save()
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 490d79775..ffb60ae67 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -176,8 +176,6 @@ class Person(ExtensibleModel):
             return f"{self.first_name} {self.last_name}"
 
     def save(self, *args, **kwargs):
-        super().save(*args, **kwargs)
-
         # Synchronise user fields to linked User object to keep it up to date
         if self.user:
             self.user.first_name = self.first_name
@@ -185,6 +183,9 @@ class Person(ExtensibleModel):
             self.user.email = self.email
             self.user.save()
 
+        self.auto_select_primary_group()
+        super().save(*args, **kwargs)
+
     def __str__(self) -> str:
         return self.full_name
 
@@ -204,6 +205,21 @@ class Person(ExtensibleModel):
             person = Person(user=admin)
             person.save()
 
+    def auto_select_primary_group(self, pattern: 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.
+
+        Does not do anything if either no pattern is defined or the user already has
+        a primary group, unless force is True.
+        """
+
+        pattern = pattern or config.PRIMARY_GROUP_PATTERN
+
+        if pattern:
+            if force or not self.primary_group:
+                self.primary_group = self.member_of.filter(name__regex=pattern).first()
+
 
 class Group(ExtensibleModel):
     """Any kind of group of persons in a school, including, but not limited
diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py
index 2e4ab1d0a..7eef538ae 100644
--- a/aleksis/core/util/apps.py
+++ b/aleksis/core/util/apps.py
@@ -2,7 +2,9 @@ from importlib import import_module
 from typing import Any, List, Optional, Tuple
 
 import django.apps
+from django.contrib.auth.signals import user_logged_in, user_logged_out
 from django.db.models.signals import post_migrate, pre_migrate
+from django.http import HttpRequest
 
 from constance.signals import config_updated
 
@@ -26,6 +28,8 @@ class AppConfig(django.apps.AppConfig):
         pre_migrate.connect(self.pre_migrate, sender=self)
         post_migrate.connect(self.post_migrate, sender=self)
         config_updated.connect(self.config_updated)
+        user_logged_in.connect(self.user_logged_in)
+        user_logged_out.connect(self.user_logged_out)
 
         # Getting an app ready means it should look at its config once
         self.config_updated()
@@ -82,6 +86,24 @@ class AppConfig(django.apps.AppConfig):
         """
         self._maintain_default_data()
 
+    def user_logged_in(
+        self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs
+    ) -> None:
+        """ Called after a user logged in
+
+        By default, it does nothing.
+        """
+        pass
+
+    def user_logged_out(
+        self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs
+    ) -> None:
+        """ Called after a user logged out
+
+        By default, it does nothing.
+        """
+        pass
+
     def _maintain_default_data(self):
         if not self.models_module:
             # This app does not have any models, so bail out early
-- 
GitLab