Aller au contenu

# Views

## `FragmentMixin`

Bases: `TemplateResponseMixin`, `ContextMixin`

Make a view buildable as a fragment that can be embedded in a template.

Most fragments are used in two different ways : - in the request/response cycle, like any regular view - in templates, where the rendering is done in another view

This mixin aims to simplify the initial fragment rendering. The rendered fragment will then be able to re-render itself through the request/response cycle if it uses HTMX.

Example

    class MyFragment(FragmentMixin, FormView):
        template_name = "app/fragment.jinja"
        form_class = MyForm
        success_url = reverse_lazy("foo:bar")

    # in another view :
    def some_view(request):
        fragment = MyFragment.as_fragment()
        return render(
            request,
            "app/template.jinja",
            context={"fragment": fragment(request)
        }

    # in urls.py
    urlpatterns = [
        path("foo/view", some_view),
        path("foo/fragment", MyFragment.as_view()),
    ]

### `reload_on_redirect = False` `class-attribute` `instance-attribute`

If True, this fragment will trigger a full page reload on redirect.

## `UseFragmentsMixin`

Bases: `ContextMixin`

Mark a view as using fragments.

This mixin is not mandatory (you may as well render manually your fragments in the `get_context_data` method). However, the interface of this class bring some distinction between fragments and other context data, which may reduce boilerplate.

Example

    class FooFragment(FragmentMixin, FormView): ...

    class BarFragment(FragmentMixin, FormView): ...

    class AdminFragment(FragmentMixin, FormView): ...

    class MyView(UseFragmentsMixin, TemplateView)
        template_name = "app/view.jinja"
        fragments = {
            "foo": FooFragment
            "bar": BarFragment(template_name="some_template.jinja")
        }
        fragments_data = {
            "foo": {"some": "data"}  # this will be passed to the FooFragment renderer
        }

        def get_fragments(self):
            res = super().get_fragments()
            if self.request.user.is_superuser:
                res["admin_fragment"] = AdminFragment
            return res

### `get_fragment_data()`

Return eventual data used to initialize the fragments.

Source code in `core/views/mixins.py`

    179
    180
    181
    def get_fragment_data(self) -> dict[str, dict[str, Any]]:
        """Return eventual data used to initialize the fragments."""
        return self.fragment_data if self.fragment_data is not None else {}

### `get_fragment_context_data()`

Return the rendered fragments as context data.

Source code in `core/views/mixins.py`

    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    def get_fragment_context_data(self) -> dict[str, SafeString]:
        """Return the rendered fragments as context data."""
        res = {}
        data = self.get_fragment_data()
        for name, fragment in self.get_fragments().items():
            is_cls = inspect.isclass(fragment) and issubclass(fragment, FragmentMixin)
            _fragment = fragment.as_fragment() if is_cls else fragment
            fragment_data = data.get(name, {})
            res[name] = _fragment(self.request, **fragment_data)
        return res

## `PermissionGroupsUpdateView`

Bases: `PermissionRequiredMixin`, `SuccessMessageMixin`, `UpdateView`

Manage the groups that have a specific permission.

Notes

This is an `UpdateView`, but unlike typical `UpdateView`, it doesn't accept url arguments to retrieve the object to update. As such, a `PermissionGroupsUpdateView` can only deal with a single hardcoded permission.

This is not a limitation, but an on-purpose design, mainly for security matters.

Example

    class SubscriptionPermissionView(PermissionGroupsUpdateView):
        permission = "subscription.add_subscription"

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

Bases: `Form`

Source code in `subscription/forms.py`

    17
    18
    19
    20
    21
    22
    23
    24
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["start_date"] = forms.DateTimeField(
            label=_("Start date"), widget=SelectDateTime, required=True
        )
        self.fields["end_date"] = forms.DateTimeField(
            label=_("End date"), widget=SelectDateTime, required=True
        )

## `SubscriptionExistingUserForm(*args, initial=None, **kwargs)`

Bases: `SubscriptionForm`

Form to add a subscription to an existing user.

Source code in `subscription/forms.py`

    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    def __init__(self, *args, initial=None, **kwargs):
        super().__init__(*args, initial=initial, **kwargs)
        self.fields["birthdate"].required = True
        if not initial:
            return
        member: str | None = initial.get("member")
        if member and member.isdigit():
            member: User | None = User.objects.filter(id=int(member)).first()
        else:
            member = None
        if member and member.date_of_birth:
            # if there is an initial member with a birthdate,
            # there is no need to ask this to the user
            self.fields["birthdate"].initial = member.date_of_birth
        elif member:
            # if there is an initial member without a birthdate,
            # then the field must be displayed
            self.fields["birthdate"].widget.attrs.update({"hidden": False})

## `SubscriptionNewUserForm(*args, initial=None, **kwargs)`

Bases: `SubscriptionForm`

Form to create subscriptions with the user they belong to.

Examples:

```py assert not User.objects.filter(email=request.POST.get("email")).exists() form = SubscriptionNewUserForm(request.POST) if form.is_valid(): form.save()

### now the user exists and is subscribed

user = User.objects.get(email=request.POST.get("email")) assert user.is_subscribed

Source code in `subscription/forms.py`

    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    def __init__(self, *args, initial=None, **kwargs):
        initial = initial or {}
        if "subscription_type" not in initial:
            initial["subscription_type"] = "deux-semestres"
        if "payment_method" not in initial:
            initial["payment_method"] = "CARD"
        super().__init__(*args, initial=initial, **kwargs)
        self.fields["payment_method"].choices = [
            m
            for m in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD
            if m[0] in self.allowed_payment_methods
        ]
        self.fields["location"].choices = [
            m for m in settings.SITH_SUBSCRIPTION_LOCATIONS if m[0] != "EBOUTIC"
        ]

### `clean()`

Initialize the User linked to this subscription.

Warning

The `User` is initialized, but not saved. Don't use it for operations that expect a persisted object.

Source code in `subscription/forms.py`

    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    def clean(self) -> dict[str, Any]:
        """Initialize the [User][core.models.User] linked to this subscription.

        Warning:
            The `User` is initialized, but not saved.
            Don't use it for operations that expect
            a persisted object.
        """
        member = User(
            first_name=self.cleaned_data.get("first_name"),
            last_name=self.cleaned_data.get("last_name"),
            email=self.cleaned_data.get("email"),
            date_of_birth=self.cleaned_data.get("date_of_birth"),
        )
        if self.errors:
            # don't bother generating username, password and other data.
            # The form validation failed anyway, so using a dummy User
            # (just for Subscription.clean not to crash) is enough
            self.instance.member = member
            return super().clean()
        if self.cleaned_data.get("subscription_type") in [
            "un-semestre",
            "deux-semestres",
            "cursus-tronc-commun",
            "cursus-branche",
            "cursus-alternant",
        ]:
            member.role = "STUDENT"
            member.school = "UTBM"
        if self.cleaned_data.get("subscription_type") == "cursus-tronc-commun":
            member.promo = get_last_promo()
        if self.cleaned_data.get("subscription_type") in [
            "cursus-branche",
            "cursus-alternant",
        ]:
            member.promo = get_last_promo() - 2
        member.generate_username()
        member.set_password(secrets.token_urlsafe(nbytes=10))
        self.instance.member = member
        return super().clean()

## `Subscription`

Bases: `Model`

### `semester_duration` `property`

Duration of this subscription, in number of semester.

Notes

The `Subscription` object doesn't have to actually exist in the database to access this property

Examples:

    subscription = Subscription(subscription_type="deux-semestres")
    assert subscription.semester_duration == 2.0

### `compute_start(d=None, duration=1, user=None)` `staticmethod`

Computes the start date of the subscription.

The computation is done with respect to the given date (default is today) and the start date given in settings.SITH_SEMESTER_START_AUTUMN. It takes the nearest past start date. Exemples: with SITH_SEMESTER_START_AUTUMN = (8, 15) Today -> Start date 2015-03-17 -> 2015-02-15 2015-01-11 -> 2014-08-15.

Source code in `subscription/models.py`

    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    @staticmethod
    def compute_start(
        d: date | None = None, duration: int | float = 1, user: User | None = None
    ) -> date:
        """Computes the start date of the subscription.

        The computation is done with respect to the given date (default is today)
        and the start date given in settings.SITH_SEMESTER_START_AUTUMN.
        It takes the nearest past start date.
        Exemples: with SITH_SEMESTER_START_AUTUMN = (8, 15)
            Today      -> Start date
            2015-03-17 -> 2015-02-15
            2015-01-11 -> 2014-08-15.
        """
        if not d:
            d = date.today()
        if user is not None and user.subscriptions.exists():
            last = user.subscriptions.last()
            if last.is_valid_now():
                d = last.subscription_end
        if duration <= 2:  # Sliding subscriptions for 1 or 2 semesters
            return d
        return get_start_of_semester(d)

### `compute_end(duration, start=None, user=None)` `staticmethod`

Compute the end date of the subscription.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`duration` | `int | float` | the duration of the subscription, in semester (for example, 2 => 2 semesters => 1 year) | required
`start` | `date | None` | The start date of the subscription | `None`
`user` | `User | None` | the user which is (or will be) subscribed | `None`

Exemples

Start - Duration -> End date 2015-09-18 - 1 -> 2016-03-18 2015-09-18 - 2 -> 2016-09-18 2015-09-18 - 3 -> 2017-03-18 2015-09-18 - 4 -> 2017-09-18.

Source code in `subscription/models.py`

    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    @staticmethod
    def compute_end(
        duration: int | float, start: date | None = None, user: User | None = None
    ) -> date:
        """Compute the end date of the subscription.

        Args:
            duration:
                the duration of the subscription, in semester
                (for example, 2 => 2 semesters => 1 year)
            start: The start date of the subscription
            user: the user which is (or will be) subscribed

        Exemples:
            Start - Duration -> End date
            2015-09-18 - 1 -> 2016-03-18
            2015-09-18 - 2 -> 2016-09-18
            2015-09-18 - 3 -> 2017-03-18
            2015-09-18 - 4 -> 2017-09-18.
        """
        if start is None:
            start = Subscription.compute_start(duration=duration, user=user)

        return start + relativedelta(
            months=round(6 * duration),
            days=math.ceil((6 * duration - round(6 * duration)) * 30),
        )

## `CreateSubscriptionFragment`

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

## `CreateSubscriptionExistingUserFragment`

Bases: `CreateSubscriptionFragment`

Create a subscription for a user who already exists.

## `CreateSubscriptionNewUserFragment`

Bases: `CreateSubscriptionFragment`

Create a subscription for a user who doesn't exist yet.

## `NewSubscription`

Bases: `PermissionRequiredMixin`, `UseFragmentsMixin`, `TemplateView`

## `SubscriptionCreatedFragment`

Bases: `PermissionRequiredMixin`, `DetailView`

## `SubscriptionPermissionView`

Bases: `PermissionGroupsUpdateView`

Manage the groups that have access to the subscription creation page.

## `SubscriptionsStatsView`

Bases: `FormView`