From b71d66fb397b354d51893221623f864cb41902b8 Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Fri, 10 Feb 2023 07:20:30 +0000 Subject: [PATCH] Get rid of src directory for Django app compatibility --- mixins/__init__.py | 0 mixins/__main__.py | 0 mixins/restrictions.py | 100 ++++ mixins/static/modal.js | 44 ++ .../mixins/partials/close-modal.html | 1 + .../mixins/partials/close-widget.html | 3 + .../mixins/partials/close-window.html | 3 + .../mixins/partials/generic-detail.html | 72 +++ mixins/templates/mixins/partials/notify.html | 7 + .../mixins/window-content/object-form.html | 34 ++ .../mixins/window-content/object.html | 45 ++ .../mixins/window-content/objects.html | 45 ++ mixins/templates/mixins/wm/modal.html | 20 + mixins/templates/mixins/wm/page.html | 6 + mixins/templates/mixins/wm/panel.html | 17 + mixins/templates/mixins/wm/widget.html | 37 ++ mixins/templates/mixins/wm/window.html | 10 + mixins/views.py | 442 ++++++++++++++++++ 18 files changed, 886 insertions(+) create mode 100644 mixins/__init__.py create mode 100644 mixins/__main__.py create mode 100644 mixins/restrictions.py create mode 100644 mixins/static/modal.js create mode 100644 mixins/templates/mixins/partials/close-modal.html create mode 100644 mixins/templates/mixins/partials/close-widget.html create mode 100644 mixins/templates/mixins/partials/close-window.html create mode 100644 mixins/templates/mixins/partials/generic-detail.html create mode 100644 mixins/templates/mixins/partials/notify.html create mode 100644 mixins/templates/mixins/window-content/object-form.html create mode 100644 mixins/templates/mixins/window-content/object.html create mode 100644 mixins/templates/mixins/window-content/objects.html create mode 100644 mixins/templates/mixins/wm/modal.html create mode 100644 mixins/templates/mixins/wm/page.html create mode 100644 mixins/templates/mixins/wm/panel.html create mode 100644 mixins/templates/mixins/wm/widget.html create mode 100644 mixins/templates/mixins/wm/window.html create mode 100644 mixins/views.py diff --git a/mixins/__init__.py b/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mixins/__main__.py b/mixins/__main__.py new file mode 100644 index 0000000..e69de29 diff --git a/mixins/restrictions.py b/mixins/restrictions.py new file mode 100644 index 0000000..26666d2 --- /dev/null +++ b/mixins/restrictions.py @@ -0,0 +1,100 @@ +from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured +from django.core.paginator import Paginator +from django.db.models import QuerySet + + +class RestrictedViewMixin: + """ + This mixin overrides two helpers in order to pass the user object to the filters. + get_queryset alters the objects returned for list views. + get_form_kwargs passes the request object to the form class. Remaining permissions + checks are in forms.py + """ + + allow_empty = True + queryset = None + model = None + paginate_by = None + paginate_orphans = 0 + context_object_name = None + paginator_class = Paginator + page_kwarg = "page" + ordering = None + + def set_extra_args(self, user): + """ + This function is overriden to filter the objects by the requesting user. + """ + self.extra_permission_args = {} + + def get_queryset(self, **kwargs): + """ + This function is overriden to filter the objects by the requesting user. + """ + self.set_extra_args(self.request.user) + if self.queryset is not None: + queryset = self.queryset + if isinstance(queryset, QuerySet): + # queryset = queryset.all() + queryset = queryset.filter( + user=self.request.user, **self.extra_permission_args + ) + elif self.model is not None: + queryset = self.model._default_manager.filter( + user=self.request.user, **self.extra_permission_args + ) + else: + raise ImproperlyConfigured( + "%(cls)s is missing a QuerySet. Define " + "%(cls)s.model, %(cls)s.queryset, or override " + "%(cls)s.get_queryset()." % {"cls": self.__class__.__name__} + ) + if hasattr(self, "get_ordering"): + ordering = self.get_ordering() + if ordering: + if isinstance(ordering, str): + ordering = (ordering,) + queryset = queryset.order_by(*ordering) + + return queryset + + def get_form_kwargs(self): + """Passes the request object to the form class. + This is necessary to only display members that belong to a given user""" + + kwargs = super().get_form_kwargs() + kwargs["request"] = self.request + return kwargs + + +class RestrictedFormMixin: + """ + This mixin is used to restrict the queryset of a form to the current user. + The request object is passed from the view. + Fieldargs is used to pass additional arguments to the queryset filter. + """ + + fieldargs = {} + + # TODO: implement set_extra_args to check more permissions here + # for completeness, however as views open forms, the permissions + # are already checked there, so it may not be necessary. + def __init__(self, *args, **kwargs): + # self.fieldargs = {} + self.request = kwargs.pop("request") + super().__init__(*args, **kwargs) + for field in self.fields: + # Check it's not something like a CharField which has no queryset + if not hasattr(self.fields[field], "queryset"): + continue + + model = self.fields[field].queryset.model + # Check if the model has a user field + try: + model._meta.get_field("user") + # Add the user to the queryset filters + self.fields[field].queryset = model.objects.filter( + user=self.request.user, **self.fieldargs.get(field, {}) + ) + except FieldDoesNotExist: + pass diff --git a/mixins/static/modal.js b/mixins/static/modal.js new file mode 100644 index 0000000..0535526 --- /dev/null +++ b/mixins/static/modal.js @@ -0,0 +1,44 @@ +// var modal = document.querySelector('.modal'); // assuming you have only 1 +var modal = document.getElementById("modal"); +var html = document.querySelector('html'); + +var disableModal = function() { + modal.classList.remove('is-active'); + html.classList.remove('is-clipped'); + var modal_refresh = document.getElementsByClassName("modal-refresh"); + for(var i = 0; i < modal_refresh.length; i++) { + modal_refresh[i].remove(); + } +} + +var elements = document.querySelectorAll('.modal-background'); +for(var i = 0; i < elements.length; i++) { + elements[i].addEventListener('click', function(e) { + // elements[i].preventDefault(); + disableModal(); + }); +} + +var elements = document.querySelectorAll('.modal-close'); +for(var i = 0; i < elements.length; i++) { + elements[i].addEventListener('click', function(e) { + // elements[i].preventDefault(); + disableModal(); + }); +} + +function activateButtons() { + var elements = document.querySelectorAll('.modal-close-button'); + for(var i = 0; i < elements.length; i++) { + elements[i].addEventListener('click', function(e) { + // elements[i].preventDefault(); + disableModal(); + }); + } +} +activateButtons(); +// modal.querySelector('.modal-close-button').addEventListener('click', function(e) { +// e.preventDefault(); +// modal.classList.remove('is-active'); +// html.classList.remove('is-clipped'); +// }); diff --git a/mixins/templates/mixins/partials/close-modal.html b/mixins/templates/mixins/partials/close-modal.html new file mode 100644 index 0000000..6c0173c --- /dev/null +++ b/mixins/templates/mixins/partials/close-modal.html @@ -0,0 +1 @@ + diff --git a/mixins/templates/mixins/partials/close-widget.html b/mixins/templates/mixins/partials/close-widget.html new file mode 100644 index 0000000..5c6f6ea --- /dev/null +++ b/mixins/templates/mixins/partials/close-widget.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/mixins/templates/mixins/partials/close-window.html b/mixins/templates/mixins/partials/close-window.html new file mode 100644 index 0000000..894974c --- /dev/null +++ b/mixins/templates/mixins/partials/close-window.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/mixins/templates/mixins/partials/generic-detail.html b/mixins/templates/mixins/partials/generic-detail.html new file mode 100644 index 0000000..3eee173 --- /dev/null +++ b/mixins/templates/mixins/partials/generic-detail.html @@ -0,0 +1,72 @@ +{% load pretty %} +{% include 'partials/notify.html' %} + +{% if live is not None %} +

Live {{ context_object_name_singular }} info

+ + + + + + + {% block live_tbody %} + {% for key, item in live.items %} + {% if key in pretty %} + + + + + {% else %} + + + + + {% endif %} + {% endfor %} + {% endblock %} + +
attributevalue
{{ key }} + {% if item is not None %} +
{{ item|pretty }}
+ {% endif %} +
{{ key }} + {% if item is not None %} + {{ item }} + {% endif %} +
+{% endif %} + +{% if object is not None %} +

{{ title_singular }} info

+ + + + + + + {% block tbody %} + {% for key, item in object.items %} + {% if key in pretty %} + + + + + {% else %} + + + + + {% endif %} + {% endfor %} + {% endblock %} + +
attributevalue
{{ key }} + {% if item is not None %} +
{{ item|pretty }}
+ {% endif %} +
{{ key }} + {% if item is not None %} + {{ item }} + {% endif %} +
+{% endif %} \ No newline at end of file diff --git a/mixins/templates/mixins/partials/notify.html b/mixins/templates/mixins/partials/notify.html new file mode 100644 index 0000000..fcf970b --- /dev/null +++ b/mixins/templates/mixins/partials/notify.html @@ -0,0 +1,7 @@ +
+ {% if message is not None %} +
+ {{ message }} +
+ {% endif %} +
\ No newline at end of file diff --git a/mixins/templates/mixins/window-content/object-form.html b/mixins/templates/mixins/window-content/object-form.html new file mode 100644 index 0000000..bdfb43a --- /dev/null +++ b/mixins/templates/mixins/window-content/object-form.html @@ -0,0 +1,34 @@ +{% include 'partials/notify.html' %} +{% if page_title is not None %} +

{{ page_title }}

+{% endif %} +{% if page_subtitle is not None %} +

{{ page_subtitle }}

+{% endif %} +{% load crispy_forms_tags %} + +{% load crispy_forms_bulma_field %} +
+ {% csrf_token %} + {{ form|crispy }} + {% if hide_cancel is not True %} + + {% endif %} + +
+ + + + + + + + diff --git a/mixins/templates/mixins/window-content/object.html b/mixins/templates/mixins/window-content/object.html new file mode 100644 index 0000000..69b4f3c --- /dev/null +++ b/mixins/templates/mixins/window-content/object.html @@ -0,0 +1,45 @@ +{% include 'partials/notify.html' %} +{% if page_title is not None %} +

{{ page_title }}

+{% endif %} +{% if page_subtitle is not None %} +

{{ page_subtitle }}

+{% endif %} +
+ + {% if submit_url is not None %} + + {% endif %} + {% if delete_all_url is not None %} + + {% endif %} +
+ +{% include detail_template %} + diff --git a/mixins/templates/mixins/window-content/objects.html b/mixins/templates/mixins/window-content/objects.html new file mode 100644 index 0000000..d7a2e9b --- /dev/null +++ b/mixins/templates/mixins/window-content/objects.html @@ -0,0 +1,45 @@ +{% include 'partials/notify.html' %} +{% if page_title is not None %} +

{{ page_title }}

+{% endif %} +{% if page_subtitle is not None %} +

{{ page_subtitle }}

+{% endif %} +
+ + {% if submit_url is not None %} + + {% endif %} + {% if delete_all_url is not None %} + + {% endif %} +
+ +{% include list_template %} + diff --git a/mixins/templates/mixins/wm/modal.html b/mixins/templates/mixins/wm/modal.html new file mode 100644 index 0000000..b8e5614 --- /dev/null +++ b/mixins/templates/mixins/wm/modal.html @@ -0,0 +1,20 @@ +{% load static %} + + +{% block scripts %} +{% endblock %} + +{% block styles %} +{% endblock %} + + \ No newline at end of file diff --git a/mixins/templates/mixins/wm/page.html b/mixins/templates/mixins/wm/page.html new file mode 100644 index 0000000..93ea8c1 --- /dev/null +++ b/mixins/templates/mixins/wm/page.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} + + +{% block content %} + {% include window_content %} +{% endblock %} diff --git a/mixins/templates/mixins/wm/panel.html b/mixins/templates/mixins/wm/panel.html new file mode 100644 index 0000000..b180b38 --- /dev/null +++ b/mixins/templates/mixins/wm/panel.html @@ -0,0 +1,17 @@ + + diff --git a/mixins/templates/mixins/wm/widget.html b/mixins/templates/mixins/wm/widget.html new file mode 100644 index 0000000..d8822bb --- /dev/null +++ b/mixins/templates/mixins/wm/widget.html @@ -0,0 +1,37 @@ +
+
+
+ + +
+
+
+ + +{% block custom_end %} +{% endblock %} \ No newline at end of file diff --git a/mixins/templates/mixins/wm/window.html b/mixins/templates/mixins/wm/window.html new file mode 100644 index 0000000..3a9a776 --- /dev/null +++ b/mixins/templates/mixins/wm/window.html @@ -0,0 +1,10 @@ + + {% extends 'wm/panel.html' %} + {% block heading %} + {{ title }} + {% endblock %} + + {% block panel_content %} + {% include window_content %} + {% endblock %} + \ No newline at end of file diff --git a/mixins/views.py b/mixins/views.py new file mode 100644 index 0000000..8443586 --- /dev/null +++ b/mixins/views.py @@ -0,0 +1,442 @@ +import uuid + +from django.http import Http404, HttpResponse, HttpResponseBadRequest +from django.urls import reverse +from django.views.generic.detail import DetailView +from django.views.generic.edit import CreateView, DeleteView, UpdateView +from django.views.generic.list import ListView +from rest_framework.parsers import FormParser + +from mixins.restrictions import RestrictedViewMixin + + +class AbortSave(Exception): + pass + + +class ObjectNameMixin(object): + def __init__(self, *args, **kwargs): + if self.model is None: + self.title = self.context_object_name.title() + self.title_singular = self.context_object_name_singular.title() + else: + self.title_singular = self.model._meta.verbose_name.title() # Hook + self.context_object_name_singular = self.title_singular.lower() # hook + self.title = self.model._meta.verbose_name_plural.title() # Hooks + self.context_object_name = self.title.lower() # hooks + + self.context_object_name = self.context_object_name.replace(" ", "") + self.context_object_name_singular = ( + self.context_object_name_singular.replace(" ", "") + ) + super().__init__(*args, **kwargs) + + +class ObjectList(RestrictedViewMixin, ObjectNameMixin, ListView): + allowed_types = ["modal", "widget", "window", "page"] + window_content = "window-content/objects.html" + list_template = None + + page_title = None + page_subtitle = None + + list_url_name = None + # WARNING: TAKEN FROM locals() + list_url_args = ["type"] + + submit_url_name = None + submit_url_args = ["type"] + + delete_all_url_name = None + widget_options = None + + # copied from BaseListView + def get(self, request, *args, **kwargs): + type = kwargs.get("type", None) + if not type: + return HttpResponseBadRequest("No type specified") + if type not in self.allowed_types: + return HttpResponseBadRequest("Invalid type specified") + + self.request = request + self.object_list = self.get_queryset(**kwargs) + if isinstance(self.object_list, HttpResponse): + return self.object_list + if isinstance(self.object_list, HttpResponseBadRequest): + return self.object_list + allow_empty = self.get_allow_empty() + + self.template_name = f"wm/{type}.html" + unique = str(uuid.uuid4())[:8] + + list_url_args = {} + for arg in self.list_url_args: + if arg in locals(): + list_url_args[arg] = locals()[arg] + elif arg in kwargs: + list_url_args[arg] = kwargs[arg] + + orig_type = type + if type == "page": + type = "modal" + + if not allow_empty: + # When pagination is enabled and object_list is a queryset, + # it's better to do a cheap query than to load the unpaginated + # queryset in memory. + if self.get_paginate_by(self.object_list) is not None and hasattr( + self.object_list, "exists" + ): + is_empty = not self.object_list.exists() + else: + is_empty = not self.object_list + if is_empty: + raise Http404("Empty list") + + submit_url_args = {} + for arg in self.submit_url_args: + if arg in locals(): + submit_url_args[arg] = locals()[arg] + elif arg in kwargs: + submit_url_args[arg] = kwargs[arg] + + context = self.get_context_data() + context["title"] = self.title + f" ({type})" + context["title_singular"] = self.title_singular + context["unique"] = unique + context["window_content"] = self.window_content + context["list_template"] = self.list_template + context["page_title"] = self.page_title + context["page_subtitle"] = self.page_subtitle + context["type"] = type + context["context_object_name"] = self.context_object_name + context["context_object_name_singular"] = self.context_object_name_singular + + if self.submit_url_name is not None: + context["submit_url"] = reverse( + self.submit_url_name, kwargs=submit_url_args + ) + + if self.list_url_name is not None: + context["list_url"] = reverse(self.list_url_name, kwargs=list_url_args) + + if self.delete_all_url_name: + context["delete_all_url"] = reverse(self.delete_all_url_name) + if self.widget_options: + context["widget_options"] = self.widget_options + + # Return partials for HTMX + if self.request.htmx: + if request.headers["HX-Target"] == self.context_object_name + "-table": + self.template_name = self.list_template + elif orig_type == "page": + self.template_name = self.list_template + else: + context["window_content"] = self.list_template + return self.render_to_response(context) + + +class ObjectCreate(RestrictedViewMixin, ObjectNameMixin, CreateView): + allowed_types = ["modal", "widget", "window", "page"] + window_content = "window-content/object-form.html" + parser_classes = [FormParser] + + page_title = None + page_subtitle = None + + model = None + submit_url_name = None + submit_url_args = ["type"] + + request = None + + # Whether to hide the cancel button in the form + hide_cancel = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.title = "Create " + self.context_object_name_singular + + def post_save(self, obj): + pass + + def pre_save_mutate(self, user, obj): + pass + + def form_valid(self, form): + obj = form.save(commit=False) + if self.request is None: + raise Exception("Request is None") + obj.user = self.request.user + try: + self.pre_save_mutate(self.request.user, obj) + except AbortSave as e: + context = {"message": f"Failed to save: {e}", "class": "danger"} + return self.render_to_response(context) + obj.save() + form.save_m2m() + self.post_save(obj) + context = {"message": "Object created", "class": "success"} + response = self.render_to_response(context) + response["HX-Trigger"] = f"{self.context_object_name_singular}Event" + return response + + def form_invalid(self, form): + """If the form is invalid, render the invalid form.""" + return self.get(self.request, **self.kwargs, form=form) + + def get(self, request, *args, **kwargs): + type = kwargs.get("type", None) + if not type: + return HttpResponseBadRequest("No type specified") + if type not in self.allowed_types: + return HttpResponseBadRequest("Invalid type specified") + self.template_name = f"wm/{type}.html" + unique = str(uuid.uuid4())[:8] + + self.request = request + self.kwargs = kwargs + + if type == "widget": + self.hide_cancel = True + + if type == "page": + type = "modal" + + self.object = None + + submit_url_args = {} + for arg in self.submit_url_args: + if arg in locals(): + submit_url_args[arg] = locals()[arg] + elif arg in kwargs: + submit_url_args[arg] = kwargs[arg] + submit_url = reverse(self.submit_url_name, kwargs=submit_url_args) + + context = self.get_context_data() + form = kwargs.get("form", None) + if form: + context["form"] = form + context["unique"] = unique + context["window_content"] = self.window_content + context["context_object_name"] = self.context_object_name + context["context_object_name_singular"] = self.context_object_name_singular + context["submit_url"] = submit_url + context["type"] = type + context["hide_cancel"] = self.hide_cancel + if self.page_title: + context["page_title"] = self.page_title + if self.page_subtitle: + context["page_subtitle"] = self.page_subtitle + response = self.render_to_response(context) + # response["HX-Trigger"] = f"{self.context_object_name_singular}Event" + return response + + def post(self, request, *args, **kwargs): + self.request = request + self.template_name = "partials/notify.html" + return super().post(request, *args, **kwargs) + + +class ObjectRead(RestrictedViewMixin, ObjectNameMixin, DetailView): + allowed_types = ["modal", "widget", "window", "page"] + window_content = "window-content/object.html" + detail_template = "partials/generic-detail.html" + + page_title = None + page_subtitle = None + + model = None + # submit_url_name = None + + detail_url_name = None + # WARNING: TAKEN FROM locals() + detail_url_args = ["type"] + + request = None + + def get(self, request, *args, **kwargs): + type = kwargs.get("type", None) + if not type: + return HttpResponseBadRequest("No type specified") + if type not in self.allowed_types: + return HttpResponseBadRequest() + self.template_name = f"wm/{type}.html" + unique = str(uuid.uuid4())[:8] + + detail_url_args = {} + for arg in self.detail_url_args: + if arg in locals(): + detail_url_args[arg] = locals()[arg] + elif arg in kwargs: + detail_url_args[arg] = kwargs[arg] + + self.request = request + self.object = self.get_object(**kwargs) + if isinstance(self.object, HttpResponse): + return self.object + + orig_type = type + if type == "page": + type = "modal" + + context = self.get_context_data() + + context["title"] = self.title + f" ({type})" + context["title_singular"] = self.title_singular + context["unique"] = unique + context["window_content"] = self.window_content + context["detail_template"] = self.detail_template + if self.page_title: + context["page_title"] = self.page_title + if self.page_subtitle: + context["page_subtitle"] = self.page_subtitle + context["type"] = type + context["context_object_name"] = self.context_object_name + context["context_object_name_singular"] = self.context_object_name_singular + + if self.detail_url_name is not None: + context["detail_url"] = reverse( + self.detail_url_name, kwargs=detail_url_args + ) + + # Return partials for HTMX + if self.request.htmx: + if request.headers["HX-Target"] == self.context_object_name + "-info": + self.template_name = self.detail_template + elif orig_type == "page": + self.template_name = self.detail_template + else: + context["window_content"] = self.detail_template + + return self.render_to_response(context) + + +class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView): + allowed_types = ["modal", "widget", "window", "page"] + window_content = "window-content/object-form.html" + parser_classes = [FormParser] + + page_title = None + page_subtitle = None + + model = None + submit_url_name = None + submit_url_args = ["type", "pk"] + + request = None + + # Whether pk is required in the get request + pk_required = True + + # Whether to hide the cancel button in the form + hide_cancel = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.title = "Update " + self.context_object_name_singular + + def post_save(self, obj): + pass + + def form_valid(self, form): + obj = form.save(commit=False) + if self.request is None: + raise Exception("Request is None") + obj.save() + form.save_m2m() + self.post_save(obj) + context = {"message": "Object updated", "class": "success"} + response = self.render_to_response(context) + response["HX-Trigger"] = f"{self.context_object_name_singular}Event" + return response + + def form_invalid(self, form): + """If the form is invalid, render the invalid form.""" + return self.get(self.request, **self.kwargs, form=form) + + def get(self, request, *args, **kwargs): + self.request = request + type = kwargs.get("type", None) + pk = kwargs.get("pk", None) + if not type: + return HttpResponseBadRequest("No type specified") + if not pk: + if self.pk_required: + return HttpResponseBadRequest("No pk specified") + if type not in self.allowed_types: + return HttpResponseBadRequest("Invalid type specified") + self.template_name = f"wm/{type}.html" + unique = str(uuid.uuid4())[:8] + if type == "widget": + self.hide_cancel = True + + if type == "page": + type = "modal" + + self.object = self.get_object() + + submit_url_args = {} + for arg in self.submit_url_args: + if arg in locals(): + submit_url_args[arg] = locals()[arg] + elif arg in kwargs: + submit_url_args[arg] = kwargs[arg] + submit_url = reverse(self.submit_url_name, kwargs=submit_url_args) + + context = self.get_context_data() + form = kwargs.get("form", None) + if form: + context["form"] = form + context["title"] = self.title + f" ({type})" + context["title_singular"] = self.title_singular + context["unique"] = unique + context["window_content"] = self.window_content + context["context_object_name"] = self.context_object_name + context["context_object_name_singular"] = self.context_object_name_singular + context["submit_url"] = submit_url + context["type"] = type + context["hide_cancel"] = self.hide_cancel + if self.page_title: + context["page_title"] = self.page_title + if self.page_subtitle: + context["page_subtitle"] = self.page_subtitle + response = self.render_to_response(context) + # response["HX-Trigger"] = f"{self.context_object_name_singular}Event" + return response + + def post(self, request, *args, **kwargs): + self.request = request + self.template_name = "partials/notify.html" + return super().post(request, *args, **kwargs) + + +class ObjectDelete(RestrictedViewMixin, ObjectNameMixin, DeleteView): + model = None + template_name = "partials/notify.html" + + # Overriden to prevent success URL from being used + def delete(self, request, *args, **kwargs): + """ + Call the delete() method on the fetched object and then redirect to the + success URL. + """ + self.object = self.get_object() + # success_url = self.get_success_url() + self.object.delete() + context = {"message": "Object deleted", "class": "success"} + response = self.render_to_response(context) + response["HX-Trigger"] = f"{self.context_object_name_singular}Event" + return response + + # This will be used in newer Django versions, until then we get a warning + def form_valid(self, form): + """ + Call the delete() method on the fetched object. + """ + self.object = self.get_object() + self.object.delete() + context = {"message": "Object deleted", "class": "success"} + response = self.render_to_response(context) + response["HX-Trigger"] = f"{self.context_object_name_singular}Event" + return response