Aller au contenu

# Views

## `sith = Sith.objects.first` `module-attribute`

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

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

## `NewsDateForm(*args, **kwargs)`

Bases: `ModelForm`

Form to select the dates of an event.

Source code in `com/forms.py`

    80
    81
    82
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.label_suffix = ""

### `get_occurrences(number)` `classmethod`

Find the occurrence choice corresponding to numeric number of occurrences.

Source code in `com/forms.py`

    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    @classmethod
    def get_occurrences(cls, number: int) -> tuple[str, str] | None:
        """Find the occurrence choice corresponding to numeric number of occurrences."""
        if number < 2:
            # If only 0 or 1 date, there cannot be weekly events
            return None
        # occurrences have all a numeric value, except "SEMESTER_END"
        str_num = str(number)
        occurrences = next((c for c in cls.occurrence_choices if c[0] == str_num), None)
        if occurrences:
            return occurrences
        return next((c for c in cls.occurrence_choices if c[0] == "SEMESTER_END"), None)

## `NewsForm(*args, author, date_form, **kwargs)`

Bases: `ModelForm`

Form to create or edit news.

Source code in `com/forms.py`

    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    def __init__(self, *args, author: User, date_form: NewsDateForm, **kwargs):
        super().__init__(*args, **kwargs)
        self.author = author
        self.date_form = date_form
        self.label_suffix = ""
        # if the author is an admin, he/she can choose any club,
        # otherwise, only clubs for which he/she is a board member can be selected
        if author.is_root or author.is_com_admin:
            self.fields["club"].widget = AutoCompleteSelectClub()
        else:
            self.fields["club"].queryset = Club.objects.having_board_member(author)

## `PosterForm(*args, user, **kwargs)`

Bases: `ModelForm`

Source code in `com/forms.py`

    43
    44
    45
    46
    47
    48
    def __init__(self, *args, user: User, **kwargs):
        super().__init__(*args, **kwargs)
        if user.is_root or user.is_com_admin:
            self.fields["club"].widget = AutoCompleteSelectClub()
        else:
            self.fields["club"].queryset = Club.objects.having_board_member(user)

## `IcsCalendar`

## `News`

Bases: `Model`

News about club events.

## `NewsDate`

Bases: `Model`

A date associated with news.

A News can have multiple dates, for example if it is a recurring event.

## `Poster`

Bases: `Model`

## `Screen`

Bases: `Model`

## `Sith`

Bases: `Model`

A one instance class storing all the modifiable infos.

## `Weekmail`

Bases: `Model`

The weekmail class.

:ivar title: Title of the weekmail :ivar intro: Introduction of the weekmail :ivar joke: Joke of the week :ivar protip: Tip of the week :ivar conclusion: Conclusion of the weekmail :ivar sent: Track if the weekmail has been sent

### `send()`

Send the weekmail to all users with the receive weekmail option opt-in.

Also send the weekmail to the mailing list in settings.SITH_COM_EMAIL.

Source code in `com/models.py`

    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    def send(self):
        """Send the weekmail to all users with the receive weekmail option opt-in.

        Also send the weekmail to the mailing list in settings.SITH_COM_EMAIL.
        """
        dest = [
            i[0]
            for i in Preferences.objects.filter(receive_weekmail=True).values_list(
                "user__email"
            )
        ]
        with transaction.atomic():
            email = EmailMultiAlternatives(
                subject=self.title,
                body=self.render_text(),
                from_email=settings.SITH_COM_EMAIL,
                to=Sith.objects.first().weekmail_destinations.split(" "),
                bcc=dest,
            )
            email.attach_alternative(self.render_html(), "text/html")
            email.send()
            self.sent = True
            self.save()
            Weekmail().save()

### `render_text()`

Renders a pure text version of the mail for readers without HTML support.

Source code in `com/models.py`

    286
    287
    288
    289
    290
    def render_text(self):
        """Renders a pure text version of the mail for readers without HTML support."""
        return render(
            None, "com/weekmail_renderer_text.jinja", context={"weekmail": self}
        ).content.decode("utf-8")

### `render_html()`

Renders an HTML version of the mail with images and fancy CSS.

Source code in `com/models.py`

    292
    293
    294
    295
    296
    def render_html(self):
        """Renders an HTML version of the mail with images and fancy CSS."""
        return render(
            None, "com/weekmail_renderer_html.jinja", context={"weekmail": self}
        ).content.decode("utf-8")

### `get_banner()`

Return an absolute link to the banner.

Source code in `com/models.py`

    298
    299
    300
    301
    302
    def get_banner(self):
        """Return an absolute link to the banner."""
        return (
            "http://" + settings.SITH_URL + static("com/img/weekmail_bannerV2P22.png")
        )

### `get_footer()`

Return an absolute link to the footer.

Source code in `com/models.py`

    304
    305
    306
    def get_footer(self):
        """Return an absolute link to the footer."""
        return "http://" + settings.SITH_URL + static("com/img/weekmail_footerP22.png")

## `WeekmailArticle`

Bases: `Model`

## `ComTabsMixin`

Bases: `TabedViewMixin`

## `ComEditView`

Bases: `ComTabsMixin`, `CanEditPropMixin`, `UpdateView`

## `AlertMsgEditView`

Bases: `ComEditView`

## `InfoMsgEditView`

Bases: `ComEditView`

## `WeekmailDestinationEditView`

Bases: `ComEditView`

## `NewsCreateView`

Bases: `PermissionRequiredMixin`, `CreateView`

View to either create or update News.

### `get_date_form_kwargs()`

Get initial data for NewsDateForm

Source code in `com/views.py`

    148
    149
    150
    151
    152
    def get_date_form_kwargs(self) -> dict[str, Any]:
        """Get initial data for NewsDateForm"""
        if self.request.method == "POST":
            return {"data": self.request.POST}
        return {}

## `NewsUpdateView`

Bases: `PermissionOrAuthorRequiredMixin`, `UpdateView`

### `get_date_form_kwargs()`

Get initial data for NewsDateForm

Source code in `com/views.py`

    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    def get_date_form_kwargs(self) -> dict[str, Any]:
        """Get initial data for NewsDateForm"""
        response = {}
        if self.request.method == "POST":
            response["data"] = self.request.POST
        dates = list(self.object.dates.order_by("id"))
        if len(dates) == 0:
            return {}
        response["instance"] = dates[0]
        occurrences = NewsDateForm.get_occurrences(len(dates))
        if occurrences is not None:
            response["initial"] = {"is_weekly": True, "occurrences": occurrences}
        return response

## `NewsDeleteView`

Bases: `PermissionOrAuthorRequiredMixin`, `DeleteView`

## `NewsModerateView`

Bases: `PermissionRequiredMixin`, `DetailView`

## `NewsAdminListView`

Bases: `PermissionRequiredMixin`, `ListView`

## `NewsListView`

Bases: `TemplateView`

### `get_last_day()`

Get the last day when news will be displayed

The returned day is the third one where something happen. For example, if there are 6 events : A on 15/03, B and C on 17/03, D on 20/03, E on 21/03 and F on 22/03 ; then the result is 20/03.

Source code in `com/views.py`

    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    def get_last_day(self) -> date | None:
        """Get the last day when news will be displayed

        The returned day is the third one where something happen.
        For example, if there are 6 events : A on 15/03, B and C on 17/03,
        D on 20/03, E on 21/03 and F on 22/03 ;
        then the result is 20/03.
        """
        dates = list(
            NewsDate.objects.filter(end_date__gt=now())
            .order_by("start_date")
            .values_list("start_date__date", flat=True)
            .distinct()[:4]
        )
        return dates[-1] if len(dates) > 0 else None

### `get_news_dates(until)`

Return the event dates to display.

The selected events are the ones that happens between right now and the given day (included).

Source code in `com/views.py`

    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    def get_news_dates(self, until: date) -> dict[date, list[date]]:
        """Return the event dates to display.

        The selected events are the ones that happens between
        right now and the given day (included).
        """
        return {
            date: list(dates)
            for date, dates in itertools.groupby(
                NewsDate.objects.viewable_by(self.request.user)
                .filter(end_date__gt=now(), start_date__date__lte=until)
                .order_by("start_date")
                .select_related("news", "news__club"),
                key=lambda d: d.start_date.date(),
            )
        }

## `NewsDetailView`

Bases: `CanViewMixin`, `DetailView`

## `NewsFeed`

Bases: `Feed`

## `WeekmailPreviewView`

Bases: `ComTabsMixin`, `CanEditPropMixin`, `DetailView`

### `get_context_data(**kwargs)`

Add rendered weekmail.

Source code in `com/views.py`

    370
    371
    372
    373
    374
    375
    def get_context_data(self, **kwargs):
        """Add rendered weekmail."""
        kwargs = super().get_context_data(**kwargs)
        kwargs["weekmail_rendered"] = self.object.render_html()
        kwargs["bad_recipients"] = self.bad_recipients
        return kwargs

## `WeekmailEditView`

Bases: `ComTabsMixin`, `CanEditPropMixin`, `UpdateView`

### `get_context_data(**kwargs)`

Add orphan articles.

Source code in `com/views.py`

    462
    463
    464
    465
    466
    def get_context_data(self, **kwargs):
        """Add orphan articles."""
        kwargs = super().get_context_data(**kwargs)
        kwargs["orphans"] = WeekmailArticle.objects.filter(weekmail=None)
        return kwargs

## `WeekmailArticleEditView`

Bases: `ComTabsMixin`, `CanEditPropMixin`, `UpdateView`

Edit an article.

## `WeekmailArticleCreateView`

Bases: `CreateView`

Post an article.

## `WeekmailArticleDeleteView`

Bases: `CanEditPropMixin`, `DeleteView`

Delete an article.

## `MailingListAdminView`

Bases: `ComTabsMixin`, `ListView`

## `MailingModerateView`

Bases: `View`

## `PosterListBaseView`

Bases: `ListView`

List communication posters.

## `PosterCreateBaseView`

Bases: `PermissionOrClubBoardRequiredMixin`, `CreateView`

Create communication poster.

## `PosterEditBaseView`

Bases: `PermissionOrClubBoardRequiredMixin`, `UpdateView`

Edit communication poster.

## `PosterDeleteBaseView`

Bases: `PermissionOrClubBoardRequiredMixin`, `ComTabsMixin`, `DeleteView`

Edit communication poster.

## `PosterListView`

Bases: `PermissionRequiredMixin`, `ComTabsMixin`, `PosterListBaseView`

List communication posters.

## `PosterCreateView`

Bases: `ComTabsMixin`, `PosterCreateBaseView`

Create communication poster.

## `PosterEditView`

Bases: `ComTabsMixin`, `PosterEditBaseView`

Edit communication poster.

## `PosterDeleteView`

Bases: `PosterDeleteBaseView`

Delete communication poster.

## `PosterModerateView`

Bases: `PermissionRequiredMixin`, `ComTabsMixin`, `View`

Moderate communication poster.

## `ScreenListView`

Bases: `PermissionRequiredMixin`, `ComTabsMixin`, `ListView`

List communication screens.

## `ScreenSlideshowView`

Bases: `DetailView`

Slideshow of actives posters.

## `ScreenCreateView`

Bases: `PermissionRequiredMixin`, `ComTabsMixin`, `CreateView`

Create communication screen.

## `ScreenEditView`

Bases: `PermissionRequiredMixin`, `ComTabsMixin`, `UpdateView`

Edit communication screen.

## `ScreenDeleteView`

Bases: `PermissionRequiredMixin`, `ComTabsMixin`, `DeleteView`

Delete communication screen.