Skip to content

views

management.views ¤

Classes¤

CustomCreateView ¤

Bases: URLMixin, LoginRequiredMixin, CreateView

Generic create view.

This view is used to create a new model object. The user must have the permission to create the object, otherwise a 403 error is returned.

The view includes a form with the object data, and the context includes the title of the view and the URL to the list view.

If provided, the foreign_key_fields attribute is used to limit the queryset for foreign key fields.

If successful, the view redirects to the detail view of the created object.

Users need to be logged in to access this view.

Attributes:

Name Type Description
template_name str

Template to be used.

Functions¤
form_valid(form) ¤

Set the owner of the object to the current user.

This is done before saving the object to the database.

Parameters:

Name Type Description Default
form ModelForm

Form with the object data.

required

Returns:

Name Type Description
HttpResponse HttpResponse

Redirect to the detail view of the created object.

Source code in management/views.py
480
481
482
483
484
485
486
487
488
489
490
491
492
def form_valid(self, form: forms.ModelForm) -> HttpResponse:
    """Set the owner of the object to the current user.

    This is done before saving the object to the database.

    Args:
        form (forms.ModelForm): Form with the object data.

    Returns:
        HttpResponse: Redirect to the detail view of the created object.
    """
    form.instance.owner = self.request.user
    return super().form_valid(form)
get_form_kwargs() ¤

Add the user to the form kwargs, so we can filter the options.

Source code in management/views.py
518
519
520
521
522
def get_form_kwargs(self):
    """Add the user to the form kwargs, so we can filter the options."""
    kwargs = super().get_form_kwargs()
    kwargs["user"] = self.request.user
    return kwargs

CustomDeleteView ¤

Bases: URLMixin, LoginRequiredMixin, DeleteView

Generic delete view.

This view is used to delete a model object. The user must have the permission to delete the object, otherwise a 403 error is returned. A confirmation page is shown with the related objects that will be deleted.

The permissions required to delete the object are app_label.delete_model_name. For example, the permission required to delete a DataImport object would be importing.delete_dataimport.

If successful, the view redirects to the list view.

Users need to be logged in to access this view.

Attributes:

Name Type Description
template_name str

Template to be used.

CustomDetailView ¤

Bases: URLMixin, LoginRequiredMixin, DetailView

Generic detail view.

This view is used to show the details of a model object. The user must have the permission to view the object, otherwise a 403 error is returned.

The view includes a form with the object data, and the context includes the URLs for the list, delete, and edit views.

The permissions required to view the object are app_label.view_model_name. For example, the permission required to view a DataImport object would be importing.view_dataimport.

Users need to be logged in to access this view.

Attributes:

Name Type Description
template_name str

Template to be used.

fields str

Fields to be shown in the form.

Functions¤
get_inline() ¤

Return the inline data for the format.

If provided, this method should return a dictionary with the inline data to be shown in the detail view. The dictionary should have the following keys:

  • title: Title of the inline data.
  • header: List with the header of the table.
  • objects: List with the objects to be shown in the table. Each object should be a list with the same length as the header.

Returns:

Type Description
dict | None

dict | None: Inline data for the format.

Source code in management/views.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def get_inline(self) -> dict | None:
    """Return the inline data for the format.

    If provided, this method should return a dictionary with the inline data to be
    shown in the detail view. The dictionary should have the following keys:

    - title: Title of the inline data.
    - header: List with the header of the table.
    - objects: List with the objects to be shown in the table. Each object should be
        a list with the same length as the header.

    Returns:
        dict | None: Inline data for the format.
    """
    return None

CustomEditView ¤

Bases: URLMixin, LoginRequiredMixin, UpdateView

Generic edit view.

This view is used to edit a model object. The user must have the permission to edit the object, otherwise a 403 error is returned.

The view includes a form with the object data, and the context includes the title of the view and the URL to the list view.

The permissions required to edit the object are app_label.change_model_name. For example, the permission required to edit a DataImport object would be importing.change_dataimport.

If successful or cancelled, the view redirects to the detail view of the created object.

Users need to be logged in to access this view.

Attributes:

Name Type Description
template_name str

Template to be used.

Functions¤
get_form_kwargs() ¤

Add the user to the form kwargs, so we can filter the options.

Source code in management/views.py
415
416
417
418
419
def get_form_kwargs(self):
    """Add the user to the form kwargs, so we can filter the options."""
    kwargs = super().get_form_kwargs()
    kwargs["user"] = self.request.user
    return kwargs

CustomTableView ¤

Bases: URLMixin, LoginRequiredMixin, SingleTableMixin, FilterView

This view is used to show a list of model objects.

The view includes a table with the objects, and the context includes the title of the view, the refresh URL, and the URL to create a new object.

The permissions required to view the objects are app_label.view_model_name. For example, the permission required to view a DataImport object would be importing.view_dataimport.

If provided, the filter_class attribute is used to create a filter form on top of the table.

Users need to be logged in to access this view.

Attributes:

Name Type Description
model Model

Model to be used.

table_class Table

Table class to be used.

filterset_class FilterSet

Filter class to be used. If not provided, the model's default filter is used.

template_name str

Template to be used.

paginate_by int

Number of objects per page.

show_refresh_btn bool

If True, a refresh url is included in the context.

show_new_btn bool

If True, a create url is included in the context.

Functions¤
get_filterset_class() ¤

Return the filter class for the view.

If no filter class is provided in the view, the default filter for the model is used. The default filter is created by the FilterSet class, and includes only the 'visibility'.

Source code in management/views.py
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
def get_filterset_class(self):
    """Return the filter class for the view.

    If no filter class is provided in the view, the default filter for the model is
    used. The default filter is created by the `FilterSet` class, and includes only
    the 'visibility'.
    """
    if not self.filterset_class:

        class VisbilityFilter(FilterSet):
            class Meta:
                model = self.model
                fields = ["visibility"]

        return VisbilityFilter

    return super().get_filterset_class()

CustomUserCreationForm ¤

Bases: UserCreationForm

SignUpView ¤

Bases: CreateView

ThingsboardCredentials ¤

Bases: Model

Credentials for Thingsboard integration.

ThingsboardCredentialsForm(*args, **kwargs) ¤

Bases: ModelForm

Source code in management/forms.py
38
39
40
41
42
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    # Ensure empty string initial when no values
    for name in self.fields:
        self.fields[name].initial = self.initial.get(name, "") or ""

URLMixin ¤

Mixin to add URLs to a view.

This mixin adds the URLs for the list, create, edit, and delete views to a view. The URLs follow the pattern app_label:model_name_action. For example, the list URL for the DataImport model would be importing:dataimport_list.

Attributes:

Name Type Description
app_label str

Application label.

model_name str

Model name.

User ¤

Bases: AbstractUser

Custom user model.

All users are given staff status and added to the standard group.

UserProfileForm ¤

Bases: ModelForm

UserProfileView ¤

Bases: LoginRequiredMixin, UpdateView

Functions¤

get_deleted_objects(objs) ¤

Return information about related objects to be deleted.

How to do this has been taken from https://stackoverflow.com/a/39533619/3778792

Parameters:

Name Type Description Default
objs list[Model]

List of objects to be deleted.

required

Returns:

Type Description
tuple[list[str], dict[str, int], list[str]]

tuple[list[str], dict[str, int], list[str]]: Tuple containing the following: - List of strings representing the objects to be deleted. - Dictionary containing the count of objects to be deleted for each model. - List of strings representing the objects that are protected from deletion

Source code in management/tools.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def get_deleted_objects(
    objs: list[models.Model],
) -> tuple[list[str], dict[str, int], list[str]]:
    """Return information about related objects to be deleted.

    How to do this has been taken from https://stackoverflow.com/a/39533619/3778792

    Args:
        objs (list[models.Model]): List of objects to be deleted.

    Returns:
        tuple[list[str], dict[str, int], list[str]]: Tuple containing the following:
            - List of strings representing the objects to be deleted.
            - Dictionary containing the count of objects to be deleted for each model.
            - List of strings representing the objects that are protected from deletion
    """
    collector = NestedObjects(using="default")
    collector.collect(objs)

    def format_callback(obj):
        opts = obj._meta
        no_edit_link = f"{capfirst(opts.verbose_name)}: {force_str(obj)}"
        return no_edit_link

    to_delete = collector.nested(format_callback)
    protected = [format_callback(obj) for obj in collector.protected]
    model_count = {
        model._meta.verbose_name_plural: len(objs)
        for model, objs in collector.model_objs.items()
    }
    if len(to_delete) == 0:
        to_delete.append("None")

    return to_delete, model_count, protected

get_queryset(db_field, user) ¤

Return a queryset based on the permissions of the user.

Returns queryset of public objects and objects that the user has change permisions for. For the case of Station objects, having the change permission is necessary to include the object in the queryset - being Public is not enough.

Parameters:

Name Type Description Default
db_field Field

Field to filter.

required
user Model

User to check permissions for.

required

Returns:

Type Description
QuerySet

model.QuerySet: Queryset of objects that the user has permissions for.

Source code in management/permissions.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def get_queryset(db_field: model.Field, user: model.Model) -> model.QuerySet:
    """Return a queryset based on the permissions of the user.

    Returns queryset of public objects and objects that the user has change permisions
    for. For the case of `Station` objects, having the `change` permission is
    necessary to include the object in the queryset - being `Public` is not enough.

    Args:
        db_field (model.Field): Field to filter.
        user (model.Model): User to check permissions for.

    Returns:
        model.QuerySet: Queryset of objects that the user has permissions for.
    """
    app_name = db_field.related_model._meta.app_label
    model_name = db_field.related_model._meta.model_name
    user_objects = get_objects_for_user(user, f"{app_name}.change_{model_name}")
    public_objects = (
        db_field.related_model.objects.none()
        if model_name == "station"
        else db_field.related_model.objects.filter(visibility="public")
    )
    return user_objects | public_objects

retrieve_thingsboard_customerid(token) ¤

Retrieve the customer ID for the authenticated user.

Source code in management/tools.py
 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
def retrieve_thingsboard_customerid(token: str):
    """Retrieve the customer ID for the authenticated user."""

    ip = settings.TB_HOST
    if ip is None:
        raise Exception("TB_HOST environment variable is not set.")
    url = f"https://{ip}/api/auth/user"

    headers = {"X-Authorization": f"Bearer {token}"}
    try:
        response = requests.get(
            url, headers=headers, timeout=THINGSBOARD_REQUEST_TIMEOUT
        )
    except requests.RequestException as exc:
        raise Exception(
            f"Failed to retrieve user info from Thingsboard: {exc}"
        ) from exc

    if response.status_code == 200:
        user_info = response.json()
        customer_id = user_info.get("customerId", {}).get("id")
        return customer_id
    else:
        raise Exception(
            f"Failed to retrieve user info: {response.status_code} - {response.text}"
        )

thingsboard_token_generator(tb_username, tb_password) ¤

Generate a token for Thingsboard API authentication.

Source code in management/tools.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def thingsboard_token_generator(tb_username: str, tb_password: str):
    """Generate a token for Thingsboard API authentication."""

    ip = settings.TB_HOST
    if ip is None:
        raise Exception("TB_HOST environment variable is not set.")

    login_url = f"https://{ip}/api/auth/login"
    login_payload = json.dumps({"username": tb_username, "password": tb_password})
    headers = {"Content-Type": "application/json", "Accept": "application/json"}

    try:
        response = requests.post(
            login_url,
            headers=headers,
            data=login_payload,
            timeout=THINGSBOARD_REQUEST_TIMEOUT,
        )
    except requests.RequestException as exc:
        raise Exception(f"Failed to authenticate with Thingsboard API: {exc}") from exc

    if response.status_code == 200:
        token = response.json().get("token")
        if not token:
            raise Exception(
                "Token not found in Thingsboard API response."
                f"Response content: {response.text}"
            )
        return token
    else:
        raise Exception(f"Failed to authenticate with Thingsboard API: {response.text}")