We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
What to do with active members who don't have memberships?
I noticed that we have some/at-least-1 users who don't have an active membership any more, but who are part of a society (or probably also committee). That's not really allowed of course.

I would like to think about and discuss how this should ideally be handled. I'm not sure if we would want to change any behaviour (and even if we decide something _should_ be done, it likely has no priority at all).

The case of committee/society members without a membership occurs naturally, for example for a short time when people forget to renew before september. Hence, I don't think it would be sensible to e.g. automatically end committee memberships at that point. (I also haven't read the HR to check whether people with no membership can be part of a committee or society officially.)

In principle, it would be the intern's responsibility to check for this kind of thing. However, that's probably not feasible, especially for societies. Secondarily, it makes sense that society chairs would be responsible, but those don't quite have an overview of Thalia memberships.

While it's probably not really desirable to prevent committee memberships existing without thalia memberships, we could think about the corresponding permissions on the website, appearance on the committee/society pages, and possibly some kind of warning system.

- Maybe it would be good to not allow people to use committee permissions when they don't have an active membership? On the other hand, there _might_ be cases where we do want non-members in a group? It would be good to check if the HR allows that.

- Non-member group members do currently show up on the website. This is clearly nice for boards, but is also the case for societies and committees. I don't like the idea of hiding non-members as that would be a difference between boards and other groups.

- Would it be worth it to warn people about this. For example, to periodically send an email to committee chairs or the board about active members who don't have a membership any more? This would be pretty easy to implement, but we should only do it if it's valuable.

@WidadMajdoubi24 @JeeVee11 Perhaps you guys should give this some thought (but there's no rush).



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `website/activemembers/backends.py`
Content:
```
1 """Authentication backend to check permissions."""
2 from django.contrib.auth.models import Permission
3 from django.db.models import Q
4 from django.utils import timezone
5 
6 from members.models import Member
7 
8 
9 class MemberGroupBackend:
10     """Check permissions against MemberGroups."""
11 
12     def authenticate(self, *args, **kwargs):
13         """Not implemented in this backend."""
14         return
15 
16     def get_user(self, *args, **kwargs):
17         """Not implemented in this backend."""
18         return
19 
20     @staticmethod
21     def _get_permissions(user, obj):
22         if not user.is_active or user.is_anonymous or obj is not None:
23             return set()
24 
25         perm_cache_name = "_membergroup_perm_cache"
26         if not hasattr(user, perm_cache_name):
27             if isinstance(user, Member):
28                 member = user
29             else:
30                 try:
31                     member = Member.objects.get(pk=user.pk)
32                 except Member.DoesNotExist:
33                     return set()
34             now = timezone.now()
35             groups = member.membergroup_set.filter(
36                 Q(membergroupmembership__until=None)
37                 | Q(
38                     membergroupmembership__since__lte=now,
39                     membergroupmembership__until__gte=now,
40                 )
41             )
42 
43             chair_permission_groups = member.membergroup_set.filter(
44                 Q(
45                     membergroupmembership__until=None,
46                     membergroupmembership__has_chair_permissions=True,
47                 )
48                 | Q(
49                     membergroupmembership__since__lte=now,
50                     membergroupmembership__until__gte=now,
51                     membergroupmembership__has_chair_permissions=True,
52                 )
53             )
54             perms = (
55                 Permission.objects.filter(
56                     Q(permissions_groups__in=groups)
57                     | Q(chair_permissions_groups__in=chair_permission_groups)
58                 )
59                 .values_list("content_type__app_label", "codename")
60                 .order_by()
61             )
62             setattr(
63                 user,
64                 perm_cache_name,
65                 set(f"{ct}.{name}" for ct, name in perms),
66             )
67         return getattr(user, perm_cache_name)
68 
69     def get_all_permissions(self, user, obj=None):
70         return self._get_permissions(user, obj)
71 
72     def get_group_permissions(self, user, obj=None):
73         return self._get_permissions(user, obj)
74 
75     def has_perm(self, user, perm, obj=None):
76         if not user.is_active:
77             return False
78         return perm in self.get_all_permissions(user, obj)
79 
80     def has_module_perms(self, user, app_label):
81         """Return True if user has any permissions in the given app_label."""
82         if not user.is_active:
83             return False
84         for perm in self.get_all_permissions(user):
85             if perm[: perm.index(".")] == app_label:
86                 return True
87         return False
88
```

Path: `website/members/models/membership.py`
Content:
```
1 import datetime
2 
3 from django.conf import settings
4 from django.core.exceptions import ValidationError
5 from django.db import models
6 from django.utils import timezone
7 from django.utils.translation import gettext_lazy as _
8 from django.utils.translation import pgettext_lazy
9 
10 from utils.snippets import overlaps
11 
12 
13 class Membership(models.Model):
14     MEMBER = "member"
15     BENEFACTOR = "benefactor"
16     HONORARY = "honorary"
17 
18     MEMBERSHIP_TYPES = (
19         (MEMBER, _("Member")),
20         (BENEFACTOR, _("Benefactor")),
21         (HONORARY, _("Honorary Member")),
22     )
23 
24     type = models.CharField(
25         max_length=40,
26         choices=MEMBERSHIP_TYPES,
27         verbose_name=_("Membership type"),
28     )
29 
30     user = models.ForeignKey(
31         settings.AUTH_USER_MODEL,
32         on_delete=models.CASCADE,
33         verbose_name=_("User"),
34     )
35 
36     since = models.DateField(
37         verbose_name=_("Membership since"),
38         help_text=_("The date the member started holding this membership."),
39         default=datetime.date.today,
40     )
41 
42     until = models.DateField(
43         verbose_name=_("Membership until"),
44         help_text=_("The date the member stops holding this membership."),
45         blank=True,
46         null=True,
47     )
48 
49     def __str__(self):
50         s = _("Membership of type {} for {} ({}) starting {}").format(
51             self.get_type_display(),
52             self.user.get_full_name(),
53             self.user.username,
54             self.since,
55         )
56         if self.until is not None:
57             s += pgettext_lazy("Membership until x", " until {}").format(self.until)
58         return s
59 
60     def clean(self):
61         super().clean()
62 
63         errors = {}
64         if self.until and (not self.since or self.until < self.since):
65             raise ValidationError({"until": _("End date can't be before start date")})
66 
67         if self.since is not None:
68             memberships = self.user.membership_set.all()
69             if overlaps(self, memberships):
70                 errors.update(
71                     {
72                         "since": _("A membership already exists for that period"),
73                         "until": _("A membership already exists for that period"),
74                     }
75                 )
76 
77         if errors:
78             raise ValidationError(errors)
79 
80     def is_active(self):
81         return not self.until or self.until > timezone.now().date()
82
```

Path: `website/members/models/member.py`
Content:
```
1 import logging
2 import operator
3 from datetime import timedelta
4 from functools import reduce
5 
6 from django.contrib.auth.models import User, UserManager
7 from django.db.models import Q
8 from django.urls import reverse
9 from django.utils import timezone
10 from django.utils.functional import cached_property
11 from django.utils.translation import gettext_lazy as _
12 
13 from activemembers.models import MemberGroup, MemberGroupMembership
14 
15 logger = logging.getLogger(__name__)
16 
17 
18 class MemberManager(UserManager):
19     """Get all members, i.e. all users with a profile."""
20 
21     def get_queryset(self):
22         return super().get_queryset().exclude(profile=None)
23 
24 
25 class ActiveMemberManager(MemberManager):
26     """Get all active members, i.e. who have a committee membership."""
27 
28     def get_queryset(self):
29         """Select all committee members."""
30         active_memberships = MemberGroupMembership.active_objects.filter(
31             group__board=None
32         ).filter(group__society=None)
33 
34         return (
35             super()
36             .get_queryset()
37             .filter(membergroupmembership__in=active_memberships)
38             .distinct()
39         )
40 
41 
42 class CurrentMemberManager(MemberManager):
43     """Get all members with an active membership."""
44 
45     def get_queryset(self):
46         """Select all members who have a current membership."""
47         return (
48             super()
49             .get_queryset()
50             .exclude(membership=None)
51             .filter(
52                 Q(membership__until__isnull=True)
53                 | Q(membership__until__gt=timezone.now().date())
54             )
55             .distinct()
56         )
57 
58     def with_birthdays_in_range(self, from_date, to_date):
59         """Select all who are currently a Thalia member and have a birthday within the specified range.
60 
61         :param from_date: the start of the range (inclusive)
62         :param to_date: the end of the range (inclusive)
63         :paramtype from_date: datetime
64         :paramtype to_date: datetime
65 
66         :return: the filtered queryset
67         :rtype: Queryset
68         """
69         queryset = self.get_queryset().filter(profile__birthday__lte=to_date)
70 
71         if (to_date - from_date).days >= 366:
72             # 366 is important to also account for leap years
73             # Everyone that's born before to_date has a birthday
74             return queryset
75 
76         delta = to_date - from_date
77         dates = [from_date + timedelta(days=i) for i in range(delta.days + 1)]
78         monthdays = [
79             {"profile__birthday__month": d.month, "profile__birthday__day": d.day}
80             for d in dates
81         ]
82         # Don't get me started (basically, we are making a giant OR query with
83         # all days and months that are in the range)
84         query = reduce(operator.or_, [Q(**d) for d in monthdays])
85         return queryset.filter(query)
86 
87 
88 class Member(User):
89     class Meta:
90         proxy = True
91         ordering = ("first_name", "last_name")
92 
93     objects = MemberManager()
94     current_members = CurrentMemberManager()
95     active_members = ActiveMemberManager()
96 
97     def __str__(self):
98         return f"{self.get_full_name()} ({self.username})"
99 
100     def refresh_from_db(self, **kwargs):
101         # Clear the cached latest_membership
102         if hasattr(self, "_latest_membership"):
103             del self._latest_membership
104         if hasattr(self, "latest_membership"):
105             del self.latest_membership
106 
107         return super().refresh_from_db(**kwargs)
108 
109     @property
110     def current_membership(self):
111         """Return the currently active membership of the user, None if not active.
112 
113         Warning: this property uses the *cached* `latest_membership`.
114         You can use `refresh_from_db` to clear it.
115         """
116         membership = self.latest_membership
117         if membership and not membership.is_active():
118             return None
119         return membership
120 
121     @cached_property
122     def latest_membership(self):
123         """Get the most recent membership of this user.
124 
125         Warning: this property is cached.
126         You can use `refresh_from_db` to clear it.
127         """
128         # Use membership from a Prefetch object if available.
129         if hasattr(self, "_latest_membership"):
130             return self._latest_membership[0]
131 
132         if not self.membership_set.exists():
133             return None
134         return self.membership_set.latest("since")
135 
136     @property
137     def earliest_membership(self):
138         """Get the earliest membership of this user."""
139         if not self.membership_set.exists():
140             return None
141         return self.membership_set.earliest("since")
142 
143     def has_been_member(self):
144         """Has this user ever been a member?."""
145         return self.membership_set.filter(type="member").exists()
146 
147     def has_been_honorary_member(self):
148         """Has this user ever been an honorary member?."""
149         return self.membership_set.filter(type="honorary").exists()
150 
151     def has_active_membership(self):
152         """Is this member currently active.
153 
154         Tested by checking if the expiration date has passed.
155         """
156         return self.current_membership is not None
157 
158     # Special properties for admin site
159     has_active_membership.boolean = True
160     has_active_membership.short_description = _("Is this user currently active")
161 
162     @classmethod
163     def all_with_membership(cls, membership_type):
164         """Get all users who have a specific membership.
165 
166         :param membership_type: The membership to select by
167         :return: List of users
168         :rtype: [Member]
169         """
170         return [
171             x
172             for x in cls.objects.all()
173             if x.current_membership and x.current_membership.type == membership_type
174         ]
175 
176     @property
177     def can_attend_events(self):
178         """May this user attend events."""
179         if not self.profile:
180             return False
181 
182         return (
183             self.profile.event_permissions in ("all", "no_drinks")
184             and self.current_membership is not None
185         )
186 
187     @property
188     def can_attend_events_without_membership(self):
189         if not self.profile:
190             return False
191 
192         return self.profile.event_permissions in ("all", "no_drinks")
193 
194     def get_member_groups(self):
195         """Get the groups this user is a member of."""
196         now = timezone.now()
197         return MemberGroup.objects.filter(
198             Q(membergroupmembership__member=self),
199             Q(membergroupmembership__until=None)
200             | Q(
201                 membergroupmembership__since__lte=now,
202                 membergroupmembership__until__gte=now,
203             ),
204             active=True,
205         )
206 
207     def get_absolute_url(self):
208         return reverse("members:profile", args=[str(self.pk)])
209
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/website/activemembers/backends.py b/website/activemembers/backends.py
--- a/website/activemembers/backends.py
+++ b/website/activemembers/backends.py
@@ -31,7 +31,11 @@
                     member = Member.objects.get(pk=user.pk)
                 except Member.DoesNotExist:
                     return set()
+
             now = timezone.now()
+            if not member.has_active_membership():
+                return set()
+
             groups = member.membergroup_set.filter(
                 Q(membergroupmembership__until=None)
                 | Q(
diff --git a/website/members/models/member.py b/website/members/models/member.py
--- a/website/members/models/member.py
+++ b/website/members/models/member.py
@@ -11,6 +11,7 @@
 from django.utils.translation import gettext_lazy as _
 
 from activemembers.models import MemberGroup, MemberGroupMembership
+from members.models.membership import Membership
 
 logger = logging.getLogger(__name__)
 
@@ -107,7 +108,7 @@
         return super().refresh_from_db(**kwargs)
 
     @property
-    def current_membership(self):
+    def current_membership(self) -> Membership | None:
         """Return the currently active membership of the user, None if not active.
 
         Warning: this property uses the *cached* `latest_membership`.
@@ -119,7 +120,7 @@
         return membership
 
     @cached_property
-    def latest_membership(self):
+    def latest_membership(self) -> Membership | None:
         """Get the most recent membership of this user.
 
         Warning: this property is cached.
@@ -134,21 +135,21 @@
         return self.membership_set.latest("since")
 
     @property
-    def earliest_membership(self):
+    def earliest_membership(self) -> Membership | None:
         """Get the earliest membership of this user."""
         if not self.membership_set.exists():
             return None
         return self.membership_set.earliest("since")
 
-    def has_been_member(self):
+    def has_been_member(self) -> bool:
         """Has this user ever been a member?."""
         return self.membership_set.filter(type="member").exists()
 
-    def has_been_honorary_member(self):
+    def has_been_honorary_member(self) -> bool:
         """Has this user ever been an honorary member?."""
         return self.membership_set.filter(type="honorary").exists()
 
-    def has_active_membership(self):
+    def has_active_membership(self) -> bool:
         """Is this member currently active.
 
         Tested by checking if the expiration date has passed.
diff --git a/website/members/models/membership.py b/website/members/models/membership.py
--- a/website/members/models/membership.py
+++ b/website/members/models/membership.py
@@ -78,4 +78,5 @@
             raise ValidationError(errors)
 
     def is_active(self):
-        return not self.until or self.until > timezone.now().date()
+        today = timezone.now().date()
+        return self.since <= today and (not self.until or self.until > today)

{"golden_diff": "diff --git a/website/activemembers/backends.py b/website/activemembers/backends.py\n--- a/website/activemembers/backends.py\n+++ b/website/activemembers/backends.py\n@@ -31,7 +31,11 @@\n                     member = Member.objects.get(pk=user.pk)\n                 except Member.DoesNotExist:\n                     return set()\n+\n             now = timezone.now()\n+            if not member.has_active_membership():\n+                return set()\n+\n             groups = member.membergroup_set.filter(\n                 Q(membergroupmembership__until=None)\n                 | Q(\ndiff --git a/website/members/models/member.py b/website/members/models/member.py\n--- a/website/members/models/member.py\n+++ b/website/members/models/member.py\n@@ -11,6 +11,7 @@\n from django.utils.translation import gettext_lazy as _\n \n from activemembers.models import MemberGroup, MemberGroupMembership\n+from members.models.membership import Membership\n \n logger = logging.getLogger(__name__)\n \n@@ -107,7 +108,7 @@\n         return super().refresh_from_db(**kwargs)\n \n     @property\n-    def current_membership(self):\n+    def current_membership(self) -> Membership | None:\n         \"\"\"Return the currently active membership of the user, None if not active.\n \n         Warning: this property uses the *cached* `latest_membership`.\n@@ -119,7 +120,7 @@\n         return membership\n \n     @cached_property\n-    def latest_membership(self):\n+    def latest_membership(self) -> Membership | None:\n         \"\"\"Get the most recent membership of this user.\n \n         Warning: this property is cached.\n@@ -134,21 +135,21 @@\n         return self.membership_set.latest(\"since\")\n \n     @property\n-    def earliest_membership(self):\n+    def earliest_membership(self) -> Membership | None:\n         \"\"\"Get the earliest membership of this user.\"\"\"\n         if not self.membership_set.exists():\n             return None\n         return self.membership_set.earliest(\"since\")\n \n-    def has_been_member(self):\n+    def has_been_member(self) -> bool:\n         \"\"\"Has this user ever been a member?.\"\"\"\n         return self.membership_set.filter(type=\"member\").exists()\n \n-    def has_been_honorary_member(self):\n+    def has_been_honorary_member(self) -> bool:\n         \"\"\"Has this user ever been an honorary member?.\"\"\"\n         return self.membership_set.filter(type=\"honorary\").exists()\n \n-    def has_active_membership(self):\n+    def has_active_membership(self) -> bool:\n         \"\"\"Is this member currently active.\n \n         Tested by checking if the expiration date has passed.\ndiff --git a/website/members/models/membership.py b/website/members/models/membership.py\n--- a/website/members/models/membership.py\n+++ b/website/members/models/membership.py\n@@ -78,4 +78,5 @@\n             raise ValidationError(errors)\n \n     def is_active(self):\n-        return not self.until or self.until > timezone.now().date()\n+        today = timezone.now().date()\n+        return self.since <= today and (not self.until or self.until > today)\n", "issue": "What to do with active members who don't have memberships?\nI noticed that we have some/at-least-1 users who don't have an active membership any more, but who are part of a society (or probably also committee). That's not really allowed of course.\r\n\r\nI would like to think about and discuss how this should ideally be handled. I'm not sure if we would want to change any behaviour (and even if we decide something _should_ be done, it likely has no priority at all).\r\n\r\nThe case of committee/society members without a membership occurs naturally, for example for a short time when people forget to renew before september. Hence, I don't think it would be sensible to e.g. automatically end committee memberships at that point. (I also haven't read the HR to check whether people with no membership can be part of a committee or society officially.)\r\n\r\nIn principle, it would be the intern's responsibility to check for this kind of thing. However, that's probably not feasible, especially for societies. Secondarily, it makes sense that society chairs would be responsible, but those don't quite have an overview of Thalia memberships.\r\n\r\nWhile it's probably not really desirable to prevent committee memberships existing without thalia memberships, we could think about the corresponding permissions on the website, appearance on the committee/society pages, and possibly some kind of warning system.\r\n\r\n- Maybe it would be good to not allow people to use committee permissions when they don't have an active membership? On the other hand, there _might_ be cases where we do want non-members in a group? It would be good to check if the HR allows that.\r\n\r\n- Non-member group members do currently show up on the website. This is clearly nice for boards, but is also the case for societies and committees. I don't like the idea of hiding non-members as that would be a difference between boards and other groups.\r\n\r\n- Would it be worth it to warn people about this. For example, to periodically send an email to committee chairs or the board about active members who don't have a membership any more? This would be pretty easy to implement, but we should only do it if it's valuable.\r\n\r\n@WidadMajdoubi24 @JeeVee11 Perhaps you guys should give this some thought (but there's no rush).\r\n\r\n\n", "before_files": [{"content": "\"\"\"Authentication backend to check permissions.\"\"\"\nfrom django.contrib.auth.models import Permission\nfrom django.db.models import Q\nfrom django.utils import timezone\n\nfrom members.models import Member\n\n\nclass MemberGroupBackend:\n    \"\"\"Check permissions against MemberGroups.\"\"\"\n\n    def authenticate(self, *args, **kwargs):\n        \"\"\"Not implemented in this backend.\"\"\"\n        return\n\n    def get_user(self, *args, **kwargs):\n        \"\"\"Not implemented in this backend.\"\"\"\n        return\n\n    @staticmethod\n    def _get_permissions(user, obj):\n        if not user.is_active or user.is_anonymous or obj is not None:\n            return set()\n\n        perm_cache_name = \"_membergroup_perm_cache\"\n        if not hasattr(user, perm_cache_name):\n            if isinstance(user, Member):\n                member = user\n            else:\n                try:\n                    member = Member.objects.get(pk=user.pk)\n                except Member.DoesNotExist:\n                    return set()\n            now = timezone.now()\n            groups = member.membergroup_set.filter(\n                Q(membergroupmembership__until=None)\n                | Q(\n                    membergroupmembership__since__lte=now,\n                    membergroupmembership__until__gte=now,\n                )\n            )\n\n            chair_permission_groups = member.membergroup_set.filter(\n                Q(\n                    membergroupmembership__until=None,\n                    membergroupmembership__has_chair_permissions=True,\n                )\n                | Q(\n                    membergroupmembership__since__lte=now,\n                    membergroupmembership__until__gte=now,\n                    membergroupmembership__has_chair_permissions=True,\n                )\n            )\n            perms = (\n                Permission.objects.filter(\n                    Q(permissions_groups__in=groups)\n                    | Q(chair_permissions_groups__in=chair_permission_groups)\n                )\n                .values_list(\"content_type__app_label\", \"codename\")\n                .order_by()\n            )\n            setattr(\n                user,\n                perm_cache_name,\n                set(f\"{ct}.{name}\" for ct, name in perms),\n            )\n        return getattr(user, perm_cache_name)\n\n    def get_all_permissions(self, user, obj=None):\n        return self._get_permissions(user, obj)\n\n    def get_group_permissions(self, user, obj=None):\n        return self._get_permissions(user, obj)\n\n    def has_perm(self, user, perm, obj=None):\n        if not user.is_active:\n            return False\n        return perm in self.get_all_permissions(user, obj)\n\n    def has_module_perms(self, user, app_label):\n        \"\"\"Return True if user has any permissions in the given app_label.\"\"\"\n        if not user.is_active:\n            return False\n        for perm in self.get_all_permissions(user):\n            if perm[: perm.index(\".\")] == app_label:\n                return True\n        return False\n", "path": "website/activemembers/backends.py"}, {"content": "import datetime\n\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.db import models\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom django.utils.translation import pgettext_lazy\n\nfrom utils.snippets import overlaps\n\n\nclass Membership(models.Model):\n    MEMBER = \"member\"\n    BENEFACTOR = \"benefactor\"\n    HONORARY = \"honorary\"\n\n    MEMBERSHIP_TYPES = (\n        (MEMBER, _(\"Member\")),\n        (BENEFACTOR, _(\"Benefactor\")),\n        (HONORARY, _(\"Honorary Member\")),\n    )\n\n    type = models.CharField(\n        max_length=40,\n        choices=MEMBERSHIP_TYPES,\n        verbose_name=_(\"Membership type\"),\n    )\n\n    user = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        on_delete=models.CASCADE,\n        verbose_name=_(\"User\"),\n    )\n\n    since = models.DateField(\n        verbose_name=_(\"Membership since\"),\n        help_text=_(\"The date the member started holding this membership.\"),\n        default=datetime.date.today,\n    )\n\n    until = models.DateField(\n        verbose_name=_(\"Membership until\"),\n        help_text=_(\"The date the member stops holding this membership.\"),\n        blank=True,\n        null=True,\n    )\n\n    def __str__(self):\n        s = _(\"Membership of type {} for {} ({}) starting {}\").format(\n            self.get_type_display(),\n            self.user.get_full_name(),\n            self.user.username,\n            self.since,\n        )\n        if self.until is not None:\n            s += pgettext_lazy(\"Membership until x\", \" until {}\").format(self.until)\n        return s\n\n    def clean(self):\n        super().clean()\n\n        errors = {}\n        if self.until and (not self.since or self.until < self.since):\n            raise ValidationError({\"until\": _(\"End date can't be before start date\")})\n\n        if self.since is not None:\n            memberships = self.user.membership_set.all()\n            if overlaps(self, memberships):\n                errors.update(\n                    {\n                        \"since\": _(\"A membership already exists for that period\"),\n                        \"until\": _(\"A membership already exists for that period\"),\n                    }\n                )\n\n        if errors:\n            raise ValidationError(errors)\n\n    def is_active(self):\n        return not self.until or self.until > timezone.now().date()\n", "path": "website/members/models/membership.py"}, {"content": "import logging\nimport operator\nfrom datetime import timedelta\nfrom functools import reduce\n\nfrom django.contrib.auth.models import User, UserManager\nfrom django.db.models import Q\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _\n\nfrom activemembers.models import MemberGroup, MemberGroupMembership\n\nlogger = logging.getLogger(__name__)\n\n\nclass MemberManager(UserManager):\n    \"\"\"Get all members, i.e. all users with a profile.\"\"\"\n\n    def get_queryset(self):\n        return super().get_queryset().exclude(profile=None)\n\n\nclass ActiveMemberManager(MemberManager):\n    \"\"\"Get all active members, i.e. who have a committee membership.\"\"\"\n\n    def get_queryset(self):\n        \"\"\"Select all committee members.\"\"\"\n        active_memberships = MemberGroupMembership.active_objects.filter(\n            group__board=None\n        ).filter(group__society=None)\n\n        return (\n            super()\n            .get_queryset()\n            .filter(membergroupmembership__in=active_memberships)\n            .distinct()\n        )\n\n\nclass CurrentMemberManager(MemberManager):\n    \"\"\"Get all members with an active membership.\"\"\"\n\n    def get_queryset(self):\n        \"\"\"Select all members who have a current membership.\"\"\"\n        return (\n            super()\n            .get_queryset()\n            .exclude(membership=None)\n            .filter(\n                Q(membership__until__isnull=True)\n                | Q(membership__until__gt=timezone.now().date())\n            )\n            .distinct()\n        )\n\n    def with_birthdays_in_range(self, from_date, to_date):\n        \"\"\"Select all who are currently a Thalia member and have a birthday within the specified range.\n\n        :param from_date: the start of the range (inclusive)\n        :param to_date: the end of the range (inclusive)\n        :paramtype from_date: datetime\n        :paramtype to_date: datetime\n\n        :return: the filtered queryset\n        :rtype: Queryset\n        \"\"\"\n        queryset = self.get_queryset().filter(profile__birthday__lte=to_date)\n\n        if (to_date - from_date).days >= 366:\n            # 366 is important to also account for leap years\n            # Everyone that's born before to_date has a birthday\n            return queryset\n\n        delta = to_date - from_date\n        dates = [from_date + timedelta(days=i) for i in range(delta.days + 1)]\n        monthdays = [\n            {\"profile__birthday__month\": d.month, \"profile__birthday__day\": d.day}\n            for d in dates\n        ]\n        # Don't get me started (basically, we are making a giant OR query with\n        # all days and months that are in the range)\n        query = reduce(operator.or_, [Q(**d) for d in monthdays])\n        return queryset.filter(query)\n\n\nclass Member(User):\n    class Meta:\n        proxy = True\n        ordering = (\"first_name\", \"last_name\")\n\n    objects = MemberManager()\n    current_members = CurrentMemberManager()\n    active_members = ActiveMemberManager()\n\n    def __str__(self):\n        return f\"{self.get_full_name()} ({self.username})\"\n\n    def refresh_from_db(self, **kwargs):\n        # Clear the cached latest_membership\n        if hasattr(self, \"_latest_membership\"):\n            del self._latest_membership\n        if hasattr(self, \"latest_membership\"):\n            del self.latest_membership\n\n        return super().refresh_from_db(**kwargs)\n\n    @property\n    def current_membership(self):\n        \"\"\"Return the currently active membership of the user, None if not active.\n\n        Warning: this property uses the *cached* `latest_membership`.\n        You can use `refresh_from_db` to clear it.\n        \"\"\"\n        membership = self.latest_membership\n        if membership and not membership.is_active():\n            return None\n        return membership\n\n    @cached_property\n    def latest_membership(self):\n        \"\"\"Get the most recent membership of this user.\n\n        Warning: this property is cached.\n        You can use `refresh_from_db` to clear it.\n        \"\"\"\n        # Use membership from a Prefetch object if available.\n        if hasattr(self, \"_latest_membership\"):\n            return self._latest_membership[0]\n\n        if not self.membership_set.exists():\n            return None\n        return self.membership_set.latest(\"since\")\n\n    @property\n    def earliest_membership(self):\n        \"\"\"Get the earliest membership of this user.\"\"\"\n        if not self.membership_set.exists():\n            return None\n        return self.membership_set.earliest(\"since\")\n\n    def has_been_member(self):\n        \"\"\"Has this user ever been a member?.\"\"\"\n        return self.membership_set.filter(type=\"member\").exists()\n\n    def has_been_honorary_member(self):\n        \"\"\"Has this user ever been an honorary member?.\"\"\"\n        return self.membership_set.filter(type=\"honorary\").exists()\n\n    def has_active_membership(self):\n        \"\"\"Is this member currently active.\n\n        Tested by checking if the expiration date has passed.\n        \"\"\"\n        return self.current_membership is not None\n\n    # Special properties for admin site\n    has_active_membership.boolean = True\n    has_active_membership.short_description = _(\"Is this user currently active\")\n\n    @classmethod\n    def all_with_membership(cls, membership_type):\n        \"\"\"Get all users who have a specific membership.\n\n        :param membership_type: The membership to select by\n        :return: List of users\n        :rtype: [Member]\n        \"\"\"\n        return [\n            x\n            for x in cls.objects.all()\n            if x.current_membership and x.current_membership.type == membership_type\n        ]\n\n    @property\n    def can_attend_events(self):\n        \"\"\"May this user attend events.\"\"\"\n        if not self.profile:\n            return False\n\n        return (\n            self.profile.event_permissions in (\"all\", \"no_drinks\")\n            and self.current_membership is not None\n        )\n\n    @property\n    def can_attend_events_without_membership(self):\n        if not self.profile:\n            return False\n\n        return self.profile.event_permissions in (\"all\", \"no_drinks\")\n\n    def get_member_groups(self):\n        \"\"\"Get the groups this user is a member of.\"\"\"\n        now = timezone.now()\n        return MemberGroup.objects.filter(\n            Q(membergroupmembership__member=self),\n            Q(membergroupmembership__until=None)\n            | Q(\n                membergroupmembership__since__lte=now,\n                membergroupmembership__until__gte=now,\n            ),\n            active=True,\n        )\n\n    def get_absolute_url(self):\n        return reverse(\"members:profile\", args=[str(self.pk)])\n", "path": "website/members/models/member.py"}], "after_files": [{"content": "\"\"\"Authentication backend to check permissions.\"\"\"\nfrom django.contrib.auth.models import Permission\nfrom django.db.models import Q\nfrom django.utils import timezone\n\nfrom members.models import Member\n\n\nclass MemberGroupBackend:\n    \"\"\"Check permissions against MemberGroups.\"\"\"\n\n    def authenticate(self, *args, **kwargs):\n        \"\"\"Not implemented in this backend.\"\"\"\n        return\n\n    def get_user(self, *args, **kwargs):\n        \"\"\"Not implemented in this backend.\"\"\"\n        return\n\n    @staticmethod\n    def _get_permissions(user, obj):\n        if not user.is_active or user.is_anonymous or obj is not None:\n            return set()\n\n        perm_cache_name = \"_membergroup_perm_cache\"\n        if not hasattr(user, perm_cache_name):\n            if isinstance(user, Member):\n                member = user\n            else:\n                try:\n                    member = Member.objects.get(pk=user.pk)\n                except Member.DoesNotExist:\n                    return set()\n\n            now = timezone.now()\n            if not member.has_active_membership():\n                return set()\n\n            groups = member.membergroup_set.filter(\n                Q(membergroupmembership__until=None)\n                | Q(\n                    membergroupmembership__since__lte=now,\n                    membergroupmembership__until__gte=now,\n                )\n            )\n\n            chair_permission_groups = member.membergroup_set.filter(\n                Q(\n                    membergroupmembership__until=None,\n                    membergroupmembership__has_chair_permissions=True,\n                )\n                | Q(\n                    membergroupmembership__since__lte=now,\n                    membergroupmembership__until__gte=now,\n                    membergroupmembership__has_chair_permissions=True,\n                )\n            )\n            perms = (\n                Permission.objects.filter(\n                    Q(permissions_groups__in=groups)\n                    | Q(chair_permissions_groups__in=chair_permission_groups)\n                )\n                .values_list(\"content_type__app_label\", \"codename\")\n                .order_by()\n            )\n            setattr(\n                user,\n                perm_cache_name,\n                set(f\"{ct}.{name}\" for ct, name in perms),\n            )\n        return getattr(user, perm_cache_name)\n\n    def get_all_permissions(self, user, obj=None):\n        return self._get_permissions(user, obj)\n\n    def get_group_permissions(self, user, obj=None):\n        return self._get_permissions(user, obj)\n\n    def has_perm(self, user, perm, obj=None):\n        if not user.is_active:\n            return False\n        return perm in self.get_all_permissions(user, obj)\n\n    def has_module_perms(self, user, app_label):\n        \"\"\"Return True if user has any permissions in the given app_label.\"\"\"\n        if not user.is_active:\n            return False\n        for perm in self.get_all_permissions(user):\n            if perm[: perm.index(\".\")] == app_label:\n                return True\n        return False\n", "path": "website/activemembers/backends.py"}, {"content": "import datetime\n\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.db import models\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom django.utils.translation import pgettext_lazy\n\nfrom utils.snippets import overlaps\n\n\nclass Membership(models.Model):\n    MEMBER = \"member\"\n    BENEFACTOR = \"benefactor\"\n    HONORARY = \"honorary\"\n\n    MEMBERSHIP_TYPES = (\n        (MEMBER, _(\"Member\")),\n        (BENEFACTOR, _(\"Benefactor\")),\n        (HONORARY, _(\"Honorary Member\")),\n    )\n\n    type = models.CharField(\n        max_length=40,\n        choices=MEMBERSHIP_TYPES,\n        verbose_name=_(\"Membership type\"),\n    )\n\n    user = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        on_delete=models.CASCADE,\n        verbose_name=_(\"User\"),\n    )\n\n    since = models.DateField(\n        verbose_name=_(\"Membership since\"),\n        help_text=_(\"The date the member started holding this membership.\"),\n        default=datetime.date.today,\n    )\n\n    until = models.DateField(\n        verbose_name=_(\"Membership until\"),\n        help_text=_(\"The date the member stops holding this membership.\"),\n        blank=True,\n        null=True,\n    )\n\n    def __str__(self):\n        s = _(\"Membership of type {} for {} ({}) starting {}\").format(\n            self.get_type_display(),\n            self.user.get_full_name(),\n            self.user.username,\n            self.since,\n        )\n        if self.until is not None:\n            s += pgettext_lazy(\"Membership until x\", \" until {}\").format(self.until)\n        return s\n\n    def clean(self):\n        super().clean()\n\n        errors = {}\n        if self.until and (not self.since or self.until < self.since):\n            raise ValidationError({\"until\": _(\"End date can't be before start date\")})\n\n        if self.since is not None:\n            memberships = self.user.membership_set.all()\n            if overlaps(self, memberships):\n                errors.update(\n                    {\n                        \"since\": _(\"A membership already exists for that period\"),\n                        \"until\": _(\"A membership already exists for that period\"),\n                    }\n                )\n\n        if errors:\n            raise ValidationError(errors)\n\n    def is_active(self):\n        today = timezone.now().date()\n        return self.since <= today and (not self.until or self.until > today)\n", "path": "website/members/models/membership.py"}, {"content": "import logging\nimport operator\nfrom datetime import timedelta\nfrom functools import reduce\n\nfrom django.contrib.auth.models import User, UserManager\nfrom django.db.models import Q\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _\n\nfrom activemembers.models import MemberGroup, MemberGroupMembership\nfrom members.models.membership import Membership\n\nlogger = logging.getLogger(__name__)\n\n\nclass MemberManager(UserManager):\n    \"\"\"Get all members, i.e. all users with a profile.\"\"\"\n\n    def get_queryset(self):\n        return super().get_queryset().exclude(profile=None)\n\n\nclass ActiveMemberManager(MemberManager):\n    \"\"\"Get all active members, i.e. who have a committee membership.\"\"\"\n\n    def get_queryset(self):\n        \"\"\"Select all committee members.\"\"\"\n        active_memberships = MemberGroupMembership.active_objects.filter(\n            group__board=None\n        ).filter(group__society=None)\n\n        return (\n            super()\n            .get_queryset()\n            .filter(membergroupmembership__in=active_memberships)\n            .distinct()\n        )\n\n\nclass CurrentMemberManager(MemberManager):\n    \"\"\"Get all members with an active membership.\"\"\"\n\n    def get_queryset(self):\n        \"\"\"Select all members who have a current membership.\"\"\"\n        return (\n            super()\n            .get_queryset()\n            .exclude(membership=None)\n            .filter(\n                Q(membership__until__isnull=True)\n                | Q(membership__until__gt=timezone.now().date())\n            )\n            .distinct()\n        )\n\n    def with_birthdays_in_range(self, from_date, to_date):\n        \"\"\"Select all who are currently a Thalia member and have a birthday within the specified range.\n\n        :param from_date: the start of the range (inclusive)\n        :param to_date: the end of the range (inclusive)\n        :paramtype from_date: datetime\n        :paramtype to_date: datetime\n\n        :return: the filtered queryset\n        :rtype: Queryset\n        \"\"\"\n        queryset = self.get_queryset().filter(profile__birthday__lte=to_date)\n\n        if (to_date - from_date).days >= 366:\n            # 366 is important to also account for leap years\n            # Everyone that's born before to_date has a birthday\n            return queryset\n\n        delta = to_date - from_date\n        dates = [from_date + timedelta(days=i) for i in range(delta.days + 1)]\n        monthdays = [\n            {\"profile__birthday__month\": d.month, \"profile__birthday__day\": d.day}\n            for d in dates\n        ]\n        # Don't get me started (basically, we are making a giant OR query with\n        # all days and months that are in the range)\n        query = reduce(operator.or_, [Q(**d) for d in monthdays])\n        return queryset.filter(query)\n\n\nclass Member(User):\n    class Meta:\n        proxy = True\n        ordering = (\"first_name\", \"last_name\")\n\n    objects = MemberManager()\n    current_members = CurrentMemberManager()\n    active_members = ActiveMemberManager()\n\n    def __str__(self):\n        return f\"{self.get_full_name()} ({self.username})\"\n\n    def refresh_from_db(self, **kwargs):\n        # Clear the cached latest_membership\n        if hasattr(self, \"_latest_membership\"):\n            del self._latest_membership\n        if hasattr(self, \"latest_membership\"):\n            del self.latest_membership\n\n        return super().refresh_from_db(**kwargs)\n\n    @property\n    def current_membership(self) -> Membership | None:\n        \"\"\"Return the currently active membership of the user, None if not active.\n\n        Warning: this property uses the *cached* `latest_membership`.\n        You can use `refresh_from_db` to clear it.\n        \"\"\"\n        membership = self.latest_membership\n        if membership and not membership.is_active():\n            return None\n        return membership\n\n    @cached_property\n    def latest_membership(self) -> Membership | None:\n        \"\"\"Get the most recent membership of this user.\n\n        Warning: this property is cached.\n        You can use `refresh_from_db` to clear it.\n        \"\"\"\n        # Use membership from a Prefetch object if available.\n        if hasattr(self, \"_latest_membership\"):\n            return self._latest_membership[0]\n\n        if not self.membership_set.exists():\n            return None\n        return self.membership_set.latest(\"since\")\n\n    @property\n    def earliest_membership(self) -> Membership | None:\n        \"\"\"Get the earliest membership of this user.\"\"\"\n        if not self.membership_set.exists():\n            return None\n        return self.membership_set.earliest(\"since\")\n\n    def has_been_member(self) -> bool:\n        \"\"\"Has this user ever been a member?.\"\"\"\n        return self.membership_set.filter(type=\"member\").exists()\n\n    def has_been_honorary_member(self) -> bool:\n        \"\"\"Has this user ever been an honorary member?.\"\"\"\n        return self.membership_set.filter(type=\"honorary\").exists()\n\n    def has_active_membership(self) -> bool:\n        \"\"\"Is this member currently active.\n\n        Tested by checking if the expiration date has passed.\n        \"\"\"\n        return self.current_membership is not None\n\n    # Special properties for admin site\n    has_active_membership.boolean = True\n    has_active_membership.short_description = _(\"Is this user currently active\")\n\n    @classmethod\n    def all_with_membership(cls, membership_type):\n        \"\"\"Get all users who have a specific membership.\n\n        :param membership_type: The membership to select by\n        :return: List of users\n        :rtype: [Member]\n        \"\"\"\n        return [\n            x\n            for x in cls.objects.all()\n            if x.current_membership and x.current_membership.type == membership_type\n        ]\n\n    @property\n    def can_attend_events(self):\n        \"\"\"May this user attend events.\"\"\"\n        if not self.profile:\n            return False\n\n        return (\n            self.profile.event_permissions in (\"all\", \"no_drinks\")\n            and self.current_membership is not None\n        )\n\n    @property\n    def can_attend_events_without_membership(self):\n        if not self.profile:\n            return False\n\n        return self.profile.event_permissions in (\"all\", \"no_drinks\")\n\n    def get_member_groups(self):\n        \"\"\"Get the groups this user is a member of.\"\"\"\n        now = timezone.now()\n        return MemberGroup.objects.filter(\n            Q(membergroupmembership__member=self),\n            Q(membergroupmembership__until=None)\n            | Q(\n                membergroupmembership__since__lte=now,\n                membergroupmembership__until__gte=now,\n            ),\n            active=True,\n        )\n\n    def get_absolute_url(self):\n        return reverse(\"members:profile\", args=[str(self.pk)])\n", "path": "website/members/models/member.py"}]}