MAKENTNU/web

View on GitHub
src/make_queue/views/machine.py

Summary

Maintainability
A
0 mins
Test Coverage
from abc import ABC

from django.contrib.auth.mixins import PermissionRequiredMixin
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView

from util.locale_utils import get_current_year_and_week, year_and_week_to_monday
from util.view_utils import CustomFieldsetFormMixin, PreventGetRequestsMixin, QueryParameterFormMixin
from ..forms.machine import AddMachineForm, ChangeMachineForm, MachineDetailQueryForm
from ..forms.reservation import ReservationListQueryForm
from ..models.machine import Machine, MachineType, MachineUsageRule
from ..models.reservation import Quota
from ..templatetags.reservation_extra import reservation_denied_message


# noinspection PyUnresolvedReferences
class MachineTypeRelatedViewMixin:
    """
    NOTE: When extending this mixin class, it's required to have an ``int`` path converter named ``pk`` as part of the view's path,
    which will be used to query the database for the machine type that the object(s) are related to.
    If found, the machine type will be assigned to a ``machine_type`` field on the view, otherwise, a 404 error will be raised.
    """
    machine_type: MachineType

    def setup(self, request, *args, **kwargs):
        super().setup(request, *args, **kwargs)
        machine_type_pk = self.kwargs['pk']
        self.machine_type = get_object_or_404(MachineType, pk=machine_type_pk)

    def get_context_data(self, **kwargs):
        return super().get_context_data(**{
            'machine_type': self.machine_type,
            **kwargs,
        })


class MachineUsageRuleDetailView(MachineTypeRelatedViewMixin, DetailView):
    model = MachineUsageRule
    template_name = 'make_queue/machine_usage_rule_detail.html'
    context_object_name = 'usage_rules'

    def get_object(self, queryset=None):
        usage_rule, _created = MachineUsageRule.objects.get_or_create(machine_type=self.machine_type)
        return usage_rule

    def get_context_data(self, **kwargs):
        return super().get_context_data(**{
            # This translation is here instead of in the template, to avoid two translation entries
            # differing only in format syntax (`{var}` vs `%(var)s`)
            'title': _("Usage rules for {machine_type}").format(machine_type=self.machine_type),
            **kwargs,
        })


class MachineUsageRuleUpdateView(PermissionRequiredMixin, CustomFieldsetFormMixin, MachineTypeRelatedViewMixin, UpdateView):
    permission_required = ('make_queue.change_machineusagerule',)
    model = MachineUsageRule
    fields = ('content',)
    template_name = 'make_queue/machine_usage_rule_form.html'

    narrow = False

    def get_object(self, queryset=None):
        return self.machine_type.usage_rule

    def get_form_title(self):
        return _("Change Usage Rules for {machine_type}").format(machine_type=self.machine_type)

    def get_back_button_link(self):
        return self.get_success_url()

    def get_back_button_text(self):
        return _("View usage rules for {machine_type}").format(machine_type=self.machine_type)

    def get_success_url(self):
        return self.get_object().get_absolute_url()


class MachineListView(ListView):
    """View that shows all the machines -- listed per machine type."""
    model = MachineType
    template_name = 'make_queue/machine_list.html'
    context_object_name = 'machine_types'
    extra_context = {
        'ReservationOwner': ReservationListQueryForm.Owner,
    }

    def get_queryset(self):
        machine_queryset = Machine.objects.visible_to(self.request.user).default_order_by()
        return MachineType.objects.default_order_by().prefetch_machines(
            machine_queryset=machine_queryset, machines_attr_name='shown_machines',
        )


class MachineDetailView(QueryParameterFormMixin, DetailView):
    """Main view for showing the reservation calendar for a machine."""
    model = Machine
    form_class = MachineDetailQueryForm
    template_name = 'make_queue/machine_detail.html'
    context_object_name = 'machine'
    extra_context = {
        'ReservationOwner': ReservationListQueryForm.Owner,
    }

    def get_queryset(self):
        return Machine.objects.visible_to(self.request.user)

    def get_context_data(self, **kwargs):
        """
        Create the context required for the controls and the information to be displayed.

        :return: context required to show the reservation calendar with controls
        """
        context = super().get_context_data(**kwargs)
        machine: Machine = self.object
        if self.request.GET:
            selected_year, selected_week = self.query_params['calendar_year'], self.query_params['calendar_week']
        else:
            selected_year, selected_week = get_current_year_and_week()

        context.update({
            'reservation_denied_message': reservation_denied_message(self.request.user, machine),
            'can_ignore_rules': False,
            'other_machines': (
                Machine.objects.exclude(pk=machine.pk).filter(
                    machine_type=machine.machine_type,
                ).visible_to(self.request.user).default_order_by()
            ),
            'selected_year': selected_year,
            'selected_week': selected_week,
            'selected_date': year_and_week_to_monday(selected_year, selected_week),
        })

        if self.request.user.is_authenticated:
            context.update({
                'can_ignore_rules': any(
                    quota.can_create_more_reservations(self.request.user)
                    for quota in Quota.get_user_quotas(self.request.user, machine.machine_type).filter(ignore_rules=True)
                )
            })

        return context


class MachineFormMixin(CustomFieldsetFormMixin, ABC):
    model = Machine
    success_url = reverse_lazy('machine_list')

    narrow = False
    back_button_link = success_url
    back_button_text = _("Machine list")

    should_include_machine_type: bool

    def get_custom_fieldsets(self):
        return [
            {'fields': ('machine_type' if self.should_include_machine_type else None, 'machine_model'), 'layout_class': "ui two fields"},
            {'fields': ('name',), 'layout_class': "ui two fields"},
            {'fields': ('stream_name',), 'layout_class': "ui two fields"} if self.should_include_stream_name() else None,
            {'fields': ('location', 'location_url'), 'layout_class': "ui two fields"},
            {'fields': ('priority', 'status'), 'layout_class': "ui two fields"},
            {'fields': ('info_message', 'info_message_date'), 'layout_class': "ui two fields"},
            {'fields': ('internal',), 'layout_class': "ui segment field"},
            {'fields': ('notes',)},
        ]

    def should_include_stream_name(self):
        return True


class MachineCreateView(PermissionRequiredMixin, MachineFormMixin, CreateView):
    permission_required = ('make_queue.add_machine',)
    form_class = AddMachineForm

    form_title = _("Add Machine")
    save_button_text = _("Add")

    should_include_machine_type = True


class MachineUpdateView(PermissionRequiredMixin, MachineFormMixin, UpdateView):
    permission_required = ('make_queue.change_machine',)
    form_class = ChangeMachineForm

    form_title = _("Change Machine")

    should_include_machine_type = False

    def should_include_stream_name(self):
        return self.object.machine_type.has_stream


class MachineDeleteView(PermissionRequiredMixin, PreventGetRequestsMixin, DeleteView):
    permission_required = ('make_queue.delete_machine',)
    model = Machine
    success_url = reverse_lazy('machine_list')


# noinspection PyUnresolvedReferences
class MachineRelatedViewMixin:
    """
    NOTE: When extending this mixin class, it's required to have an ``int`` path converter named ``pk`` as part of the view's path,
    which will be used to query the database for the machine that the object(s) are related to.
    If found, the machine will be assigned to a ``machine`` field on the view, otherwise, a 404 error will be raised.
    """
    machine: Machine

    def setup(self, request, *args, **kwargs):
        super().setup(request, *args, **kwargs)
        machine_pk = self.kwargs['pk']
        self.machine = get_object_or_404(Machine.objects.visible_to(self.request.user), pk=machine_pk)