Aller au contenu

# Views

## `ClubAddMemberForm(*args, club, request_user, **kwargs)`

Bases: `ClubMemberForm`

Form to add a member to the club, as a board member.

Source code in `club/forms.py`

    230
    231
    232
    233
    234
    235
    236
    def __init__(self, *args, club: Club, request_user: User, **kwargs):
        self.club = club
        self.request_user = request_user
        self.request_user_membership = self.club.get_membership_for(self.request_user)
        super().__init__(*args, **kwargs)
        self.fields["role"].queryset = self.available_roles
        self.instance.club = club

### `available_roles()`

The roles that will be obtainable with this form.

Admins and the club president can attribute any role. Board members can attribute roles lower than their own. Other users cannot attribute roles with this form

Source code in `club/forms.py`

    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    @cached_property
    def available_roles(self):
        """The roles that will be obtainable with this form.

        Admins and the club president can attribute any role.
        Board members can attribute roles lower than their own.
        Other users cannot attribute roles with this form
        """
        qs = self.club.roles.filter(is_active=True)
        if self.request_user.has_perm("club.add_membership"):
            return qs.all()
        membership = self.request_user_membership
        if membership is None or not membership.role.is_board:
            return ClubRole.objects.none()
        if membership.role.is_presidency:
            return qs.all()
        return qs.above_instance(membership.role)

### `clean_user()`

Check that the user is not trying to add a user already in the club.

Also check that the user is valid and has a valid subscription.

Source code in `club/forms.py`

    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    def clean_user(self):
        """Check that the user is not trying to add a user already in the club.

        Also check that the user is valid and has a valid subscription.
        """
        user = self.cleaned_data["user"]
        if not user.is_subscribed:
            raise forms.ValidationError(
                _("User must be subscriber to take part to a club"), code="invalid"
            )
        if self.club.get_membership_for(user):
            raise forms.ValidationError(
                _("You can not add the same user twice"), code="invalid"
            )
        return user

## `ClubAdminEditForm`

Bases: `ClubEditForm`

## `ClubEditForm`

Bases: `ModelForm`

## `ClubOldMemberForm(*args, user, club, **kwargs)`

Bases: `Form`

Source code in `club/forms.py`

    215
    216
    217
    def __init__(self, *args, user: User, club: Club, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["members_old"].queryset = club.members.ongoing().editable_by(user)

## `ClubSearchForm(*args, data=None, **kwargs)`

Bases: `ModelForm`

Source code in `club/forms.py`

    325
    326
    327
    328
    329
    330
    331
    332
    def __init__(self, *args, data: dict | None = None, **kwargs):
        super().__init__(*args, data=data, **kwargs)
        if data is not None and "club_status" not in data:
            # if the key is missing, it is considered as None,
            # even though we want the default True value to be applied in such a case
            # so we enforce it.
            self.fields["club_status"].value = True
        self.fields["name"].required = False

## `JoinClubForm(*args, club, request_user, **kwargs)`

Bases: `ClubMemberForm`

Form to join a club.

Source code in `club/forms.py`

    290
    291
    292
    def __init__(self, *args, club: Club, request_user: User, **kwargs):
        super().__init__(*args, club=club, request_user=request_user, **kwargs)
        self.instance.user = self.request_user

### `clean()`

Check that the user is subscribed and isn't already in the club.

Source code in `club/forms.py`

    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    def clean(self):
        """Check that the user is subscribed and isn't already in the club."""
        if not self.request_user.is_subscribed:
            raise forms.ValidationError(
                _("You must be subscribed to join a club"), code="invalid"
            )
        if self.club.get_membership_for(self.request_user):
            raise forms.ValidationError(
                _("You are already a member of this club"), code="invalid"
            )
        return super().clean()

## `MailingForm(club_id, user_id, mailings, *args, **kwargs)`

Bases: `Form`

Form handling mailing lists right.

Source code in `club/forms.py`

     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    def __init__(self, club_id, user_id, mailings, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields["action"] = forms.TypedChoiceField(
            choices=(
                (self.ACTION_NEW_MAILING, _("New Mailing")),
                (self.ACTION_NEW_SUBSCRIPTION, _("Subscribe")),
                (self.ACTION_REMOVE_SUBSCRIPTION, _("Remove")),
            ),
            coerce=int,
            label=_("Action"),
            initial=1,
            required=True,
            widget=forms.HiddenInput(),
        )

        # Generate bulk removal forms, they are never required
        for mailing in mailings:
            self.fields["removal_" + str(mailing.id)] = forms.ModelMultipleChoiceField(
                mailing.subscriptions.all(),
                label=_("Remove"),
                required=False,
                widget=forms.CheckboxSelectMultiple,
            )

        # Include fields for handling mailing creation
        mailing_fields = ("email",)
        self.fields.update(forms.fields_for_model(Mailing, fields=mailing_fields))
        for field in mailing_fields:
            self.fields["mailing_" + field] = self.fields.pop(field)
            self.fields["mailing_" + field].required = False

        # Include fields for handling subscription creation
        subscription_fields = ("mailing", "email")
        self.fields.update(
            forms.fields_for_model(MailingSubscription, fields=subscription_fields)
        )
        for field in subscription_fields:
            self.fields["subscription_" + field] = self.fields.pop(field)
            self.fields["subscription_" + field].required = False

        self.fields["subscription_mailing"].queryset = Mailing.objects.filter(
            club__id=club_id, is_moderated=True
        )

### `check_required(cleaned_data, field)`

If the given field doesn't exist or has no value, add a required error on it.

Source code in `club/forms.py`

    119
    120
    121
    122
    def check_required(self, cleaned_data, field):
        """If the given field doesn't exist or has no value, add a required error on it."""
        if not cleaned_data.get(field, None):
            self.add_error(field, _("This field is required"))

### `clean_subscription_users()`

Convert given users into real users and check their validity.

Source code in `club/forms.py`

    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    def clean_subscription_users(self):
        """Convert given users into real users and check their validity."""
        cleaned_data = super().clean()
        users = []
        for user in cleaned_data["subscription_users"]:
            if not user.email:
                raise forms.ValidationError(
                    _("One of the selected users doesn't have an email address"),
                    code="invalid",
                )
            users.append(user)
        return users

## `SellingsForm(club, *args, **kwargs)`

Bases: `Form`

Source code in `club/forms.py`

    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    def __init__(self, club, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # postgres struggles really hard with a single query having three WHERE conditions,
        # but deals perfectly fine with UNION of multiple queryset with their own WHERE clause,
        # so we do this to get the ids, which we use to build another queryset that can be used by django.
        club_sales_subquery = Selling.objects.filter(counter=OuterRef("pk"), club=club)
        ids = (
            Counter.objects.filter(Q(club=club) | Q(products__club=club))
            .union(Counter.objects.filter(Exists(club_sales_subquery)))
            .values_list("id", flat=True)
        )
        counters_qs = Counter.objects.filter(id__in=ids).order_by(Lower("name"))
        self.fields["counters"] = forms.ModelMultipleChoiceField(
            counters_qs, label=_("Counter"), required=False
        )
        self.fields["products"] = forms.ModelMultipleChoiceField(
            club.products.order_by("name").filter(archived=False).all(),
            label=_("Products"),
            required=False,
        )
        self.fields["archived_products"] = forms.ModelMultipleChoiceField(
            club.products.order_by("name").filter(archived=True).all(),
            label=_("Archived products"),
            required=False,
        )

## `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)

## `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.

## `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
        )

## `ClubTabsMixin`

Bases: `TabedViewMixin`

## `ClubListView`

Bases: `AllowFragment`, `FormMixin`, `ListView`

List the clubs of the AE, with a form to perform basic search.

Notes

This view is fully public, because we want to advertise as much as possible the cultural life of the AE. In accordance with that matter, searching and listing the clubs is done entirely server-side (no AlpineJS involved) ; this is done this way in order to be sure the page is the most accessible and SEO-friendly possible, even if it makes the UX slightly less smooth.

## `ClubView`

Bases: `ClubTabsMixin`, `DetailView`

Front page of a Club.

## `ClubRevView`

Bases: `LoginRequiredMixin`, `ClubView`

Display a specific page revision.

## `ClubPageEditView`

Bases: `ClubTabsMixin`, `BasePageEditView`

## `ClubPageHistView`

Bases: `ClubTabsMixin`, `PermissionRequiredMixin`, `DetailView`

Modification history of the page.

## `ClubToolsView`

Bases: `ClubTabsMixin`, `CanEditMixin`, `DetailView`

Tools page of a Club.

## `ClubAddMembersFragment`

Bases: `FragmentMixin`, `PermissionRequiredMixin`, `SuccessMessageMixin`, `CreateView`

## `ClubMembersView`

Bases: `ClubTabsMixin`, `UseFragmentsMixin`, `PermissionRequiredMixin`, `DetailFormView`

View of a club's members.

## `ClubOldMembersView`

Bases: `ClubTabsMixin`, `PermissionRequiredMixin`, `DetailView`

Old members of a club.

## `ClubSellingView`

Bases: `ClubTabsMixin`, `CanEditMixin`, `DetailFormView`

Sales of a club.

## `ClubSellingCSVView`

Bases: `ClubSellingView`

Generate sellings in csv for a given period.

### `StreamWriter`

Implements a file-like interface for streaming the CSV.

#### `write(value)`

Write the value by returning it, instead of storing in a buffer.

Source code in `club/views.py`

    482
    483
    484
    def write(self, value):
        """Write the value by returning it, instead of storing in a buffer."""
        return value

## `ClubEditView`

Bases: `ClubTabsMixin`, `CanEditMixin`, `UpdateView`

Edit a Club.

Regular club board members will be able to edit the main infos (like the logo and the description). Admins will also be able to edit the club properties (like the name and the parent club).

## `ClubCreateView`

Bases: `PermissionRequiredMixin`, `CreateView`

Create a club (for the Sith admin).

## `MembershipSetOldView`

Bases: `CanEditMixin`, `SingleObjectMixin`, `View`

Set a membership as being old.

## `MembershipDeleteView`

Bases: `PermissionRequiredMixin`, `DeleteView`

Delete a membership (for admins only).

## `ClubMailingView`

Bases: `ClubTabsMixin`, `CanEditMixin`, `DetailFormView`

A list of mailing for a given club.

### `add_new_mailing(cleaned_data)`

Create a new mailing list from the form.

Source code in `club/views.py`

    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    def add_new_mailing(self, cleaned_data) -> ValidationError | None:
        """Create a new mailing list from the form."""
        mailing = Mailing(
            club=self.object,
            email=cleaned_data["mailing_email"],
            moderator=self.request.user,
            is_moderated=False,
        )
        try:
            mailing.clean()
        except ValidationError as validation_error:
            return validation_error
        mailing.save()
        return None

### `add_new_subscription(cleaned_data)`

Add mailing subscriptions for each user given and/or for the specified email in form.

Source code in `club/views.py`

    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    def add_new_subscription(self, cleaned_data) -> ValidationError | None:
        """Add mailing subscriptions for each user given and/or for the specified email in form."""
        users_to_save = []

        for user in cleaned_data["subscription_users"]:
            sub = MailingSubscription(
                mailing=cleaned_data["subscription_mailing"], user=user
            )
            try:
                sub.clean()
            except ValidationError as validation_error:
                return validation_error

            sub.save()
            users_to_save.append(sub)

        if cleaned_data["subscription_email"]:
            sub = MailingSubscription(
                mailing=cleaned_data["subscription_mailing"],
                email=cleaned_data["subscription_email"],
            )

        try:
            sub.clean()
        except ValidationError as validation_error:
            return validation_error
        sub.save()

        # Save users after we are sure there is no error
        for user in users_to_save:
            user.save()

        return None

### `remove_subscription(cleaned_data)`

Remove specified users from a mailing list.

Source code in `club/views.py`

    694
    695
    696
    697
    698
    699
    700
    701
    def remove_subscription(self, cleaned_data):
        """Remove specified users from a mailing list."""
        fields = [
            val for key, val in cleaned_data.items() if key.startswith("removal_")
        ]
        for field in fields:
            for sub in field:
                sub.delete()

## `MailingDeleteView`

Bases: `CanEditMixin`, `DeleteView`

## `MailingSubscriptionDeleteView`

Bases: `CanEditMixin`, `DeleteView`

## `MailingAutoGenerationView`

Bases: `View`

## `PosterListView`

Bases: `PermissionOrClubBoardRequiredMixin`, `ClubTabsMixin`, `PosterListBaseView`

List communication posters.

## `PosterCreateView`

Bases: `ClubTabsMixin`, `PosterCreateBaseView`

Create communication poster.

## `PosterEditView`

Bases: `ClubTabsMixin`, `PosterEditBaseView`

Edit communication poster.

## `PosterDeleteView`

Bases: `ClubTabsMixin`, `PosterDeleteBaseView`

Delete communication poster.