Aller au contenu

# Models

## `ClubQuerySet`

Bases: `QuerySet`

### `having_board_member(user)`

Filter all club in which the given user is a board member.

Source code in `club/models.py`

    46
    47
    48
    49
    def having_board_member(self, user: User) -> Self:
        """Filter all club in which the given user is a board member."""
        active_memberships = user.memberships.board().ongoing()
        return self.filter(Exists(active_memberships.filter(club=OuterRef("pk"))))

## `Club`

Bases: `Model`

The Club class, made as a tree to allow nice tidy organization.

### `president()`

Fetch the membership of the current president of this club.

Source code in `club/models.py`

    138
    139
    140
    141
    @cached_property
    def president(self) -> Membership | None:
        """Fetch the membership of the current president of this club."""
        return self.members.filter(end_date=None).order_by("role__order").first()

### `check_loop()`

Raise a validation error when a loop is found within the parent list.

Source code in `club/models.py`

    143
    144
    145
    146
    147
    148
    149
    150
    151
    def check_loop(self):
        """Raise a validation error when a loop is found within the parent list."""
        objs = []
        cur = self
        while cur.parent is not None:
            if cur in objs:
                raise ValidationError(_("You can not make loops in clubs"))
            objs.append(cur)
            cur = cur.parent

### `create_default_roles()`

Create some roles that should exist by default for this club.

The created roles are : president, treasurer, active member and curious.

Source code in `club/models.py`

    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    def create_default_roles(self):
        """Create some roles that should exist by default for this club.

        The created roles are : president, treasurer, active member and curious.

        Warnings:
            When calling this method, no club must exist yet for this club.
        """
        if self.roles.exists():
            raise ProgrammingError(
                "Default roles can be created only for clubs "
                "that don't have associated roles yet"
            )
        # The names are written in French, because there is no gettext involved
        # for strings stored in database, and the majority of users are french.
        roles = [
            ClubRole(name="Président⸱e", is_board=True, is_presidency=True),
            ClubRole(name="Trésorier⸱e", is_board=True, is_presidency=False),
            ClubRole(name="Membre actif⸱ve", is_board=False, is_presidency=False),
            ClubRole(
                name="Curieux⸱euse",
                description=(
                    "Les gens qui suivent l'activité "
                    "du club sans forcément y participer"
                ),
                is_board=False,
                is_presidency=False,
            ),
        ]
        for i, role in enumerate(roles):
            role.club = self
            role.order = i
        ClubRole.objects.bulk_create(roles)

### `is_owned_by(user)`

Method to see if that object can be super edited by the given user.

Source code in `club/models.py`

    228
    229
    230
    231
    232
    def is_owned_by(self, user: User) -> bool:
        """Method to see if that object can be super edited by the given user."""
        if user.is_anonymous:
            return False
        return user.is_root or user.is_board_member

### `can_be_edited_by(user)`

Method to see if that object can be edited by the given user.

Source code in `club/models.py`

    237
    238
    239
    def can_be_edited_by(self, user: User) -> bool:
        """Method to see if that object can be edited by the given user."""
        return self.has_rights_in_club(user)

### `get_membership_for(user)`

Return the current membership of the given user.

Source code in `club/models.py`

    247
    248
    249
    250
    251
    def get_membership_for(self, user: User) -> Membership | None:
        """Return the current membership of the given user."""
        if user.is_anonymous:
            return None
        return next((m for m in self.current_members if m.user_id == user.id), None)

## `ClubRole`

Bases: `OrderedModel`

## `MembershipQuerySet`

Bases: `QuerySet`

### `ongoing()`

Filter all memberships which are not finished yet.

Source code in `club/models.py`

    357
    358
    359
    def ongoing(self) -> Self:
        """Filter all memberships which are not finished yet."""
        return self.filter(Q(end_date=None) | Q(end_date__gt=localdate()))

### `board()`

Filter all memberships where the user is/was in the board.

Be aware that users who were in the board in the past are included, even if there are no more members.

If you want to get the users who are currently in the board, mind combining this with the [MembershipQuerySet.ongoing][] queryset method

Source code in `club/models.py`

    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    def board(self) -> Self:
        """Filter all memberships where the user is/was in the board.

        Be aware that users who were in the board in the past
        are included, even if there are no more members.

        If you want to get the users who are currently in the board,
        mind combining this with the [MembershipQuerySet.ongoing][]
        queryset method
        """
        return self.filter(role__is_board=True)

### `editable_by(user)`

Filter Memberships that this user can edit.

Users with the `club.change_membership` permission can edit all Membership. The other users can edit : - their own membership - if they are board members, ongoing memberships with a role lower than their own

For example, let's suppose the following users : - A : board member - B : board member - C : simple member - D : curious - E : old member

A will be able to edit the memberships of A, C and D ; C and D will be able to edit only their own membership ; nobody will be able to edit E's membership.

Source code in `club/models.py`

    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    def editable_by(self, user: User) -> Self:
        """Filter Memberships that this user can edit.

        Users with the `club.change_membership` permission can edit all Membership.
        The other users can edit :
        - their own membership
        - if they are board members, ongoing memberships with a role lower than their own

        For example, let's suppose the following users :
        - A : board member
        - B : board member
        - C : simple member
        - D : curious
        - E : old member

        A will be able to edit the memberships of A, C and D ;
        C and D will be able to edit only their own membership ;
        nobody will be able to edit E's membership.
        """
        if user.has_perm("club.change_membership"):
            return self.all()
        return self.ongoing().filter(
            Q(user=user)
            | Exists(
                Membership.objects.ongoing().filter(
                    user=user,
                    club=OuterRef("club"),
                    role__is_board=True,
                    role__order__lt=OuterRef("role__order"),
                )
            )
        )

### `update(**kwargs)`

Remove users from club groups they are no more in and add them in the club groups they should be in.

Be aware that this adds three db queries :

- one to retrieve the updated memberships
- one to perform group removal
- and one to perform group attribution.

Source code in `club/models.py`

    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    def update(self, **kwargs) -> int:
        """Remove users from club groups they are no more in
        and add them in the club groups they should be in.

        Be aware that this adds three db queries :

        - one to retrieve the updated memberships
        - one to perform group removal
        - and one to perform group attribution.
        """
        nb_rows = super().update(**kwargs)
        if nb_rows == 0:
            # if no row was affected, no need to edit club groups
            return 0

        memberships = set(self.select_related("club"))
        # delete all User-Group relations and recreate the necessary ones
        Membership._remove_club_groups(memberships)
        Membership._add_club_groups(memberships)
        return nb_rows

### `delete()`

Work just like the default Django's delete() method, but also remove the concerned users from the club groups.

Be aware that this adds some db queries :

- 1 to retrieve the deleted elements in order to perform post-delete operations. As we can't know if a delete will affect rows or not, this query will always happen
- 1 query to remove the users from the club groups. If the delete operation affected no row, this query won't happen.

Source code in `club/models.py`

    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    def delete(self) -> tuple[int, dict[str, int]]:
        """Work just like the default Django's delete() method,
        but also remove the concerned users from the club groups.

        Be aware that this adds some db queries :

        - 1 to retrieve the deleted elements in order to perform
          post-delete operations.
          As we can't know if a delete will affect rows or not,
          this query will always happen
        - 1 query to remove the users from the club groups.
          If the delete operation affected no row,
          this query won't happen.
        """
        memberships = set(self.all())
        nb_rows, rows_counts = super().delete()
        if nb_rows > 0:
            Membership._remove_club_groups(memberships)
        return nb_rows, rows_counts

## `Membership`

Bases: `Model`

The Membership class makes the connection between User and Clubs.

Both Users and Clubs can have many Membership objects

- a user can be a member of many clubs at a time
- a club can have many members at a time too

A User is currently member of all the Clubs where its Membership has an end_date set to null/None. Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past.

### `is_owned_by(user)`

Method to see if that object can be super edited by the given user.

Source code in `club/models.py`

    513
    514
    515
    516
    517
    def is_owned_by(self, user: User) -> bool:
        """Method to see if that object can be super edited by the given user."""
        if user.is_anonymous:
            return False
        return user.is_root or user.is_board_member

### `can_be_edited_by(user)`

Check if that object can be edited by the given user.

Source code in `club/models.py`

    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    def can_be_edited_by(self, user: User) -> bool:
        """Check if that object can be edited by the given user."""
        if user.is_root or user.is_board_member:
            return True
        membership = self.club.get_membership_for(user)
        if not membership:
            return False
        return membership.user_id == user.id or (
            membership.is_board and membership.role.order < self.role.order
        )

## `Mailing`

Bases: `Model`

A Mailing list for a club.

Warning

Remember that mailing lists should be validated by UTBM.

## `MailingSubscription`

Bases: `Model`

Link between user and mailing list.