diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d381cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..23c9d5b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + exclude: ^core/migrations + - repo: https://github.com/PyCQA/isort + rev: 5.11.5 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + args: [--max-line-length=88] + exclude: ^core/migrations + - repo: https://github.com/rtts/djhtml + rev: v2.0.0 + hooks: + - id: djhtml + args: [-t 2] + - id: djcss + exclude : ^core/static/css # slow + - id: djjs + exclude: ^core/static/js # slow + - repo: https://github.com/sirwart/ripsecrets.git + rev: v0.1.5 + hooks: + - id: ripsecrets diff --git a/src/mixins/__init__.py b/src/mixins/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mixins/__main__.py b/src/mixins/__main__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mixins/restrictions.py b/src/mixins/restrictions.py deleted file mode 100644 index 26666d2..0000000 --- a/src/mixins/restrictions.py +++ /dev/null @@ -1,100 +0,0 @@ -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/src/mixins/static/modal.js b/src/mixins/static/modal.js deleted file mode 100644 index 0535526..0000000 --- a/src/mixins/static/modal.js +++ /dev/null @@ -1,44 +0,0 @@ -// 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/src/mixins/templates/mixins/partials/close-modal.html b/src/mixins/templates/mixins/partials/close-modal.html deleted file mode 100644 index 6c0173c..0000000 --- a/src/mixins/templates/mixins/partials/close-modal.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/mixins/templates/mixins/partials/close-widget.html b/src/mixins/templates/mixins/partials/close-widget.html deleted file mode 100644 index 5c6f6ea..0000000 --- a/src/mixins/templates/mixins/partials/close-widget.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/src/mixins/templates/mixins/partials/close-window.html b/src/mixins/templates/mixins/partials/close-window.html deleted file mode 100644 index 894974c..0000000 --- a/src/mixins/templates/mixins/partials/close-window.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/src/mixins/templates/mixins/partials/generic-detail.html b/src/mixins/templates/mixins/partials/generic-detail.html deleted file mode 100644 index 3eee173..0000000 --- a/src/mixins/templates/mixins/partials/generic-detail.html +++ /dev/null @@ -1,72 +0,0 @@ -{% 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/src/mixins/templates/mixins/partials/notify.html b/src/mixins/templates/mixins/partials/notify.html deleted file mode 100644 index fcf970b..0000000 --- a/src/mixins/templates/mixins/partials/notify.html +++ /dev/null @@ -1,7 +0,0 @@ -
- {% if message is not None %} -
- {{ message }} -
- {% endif %} -
\ No newline at end of file diff --git a/src/mixins/templates/mixins/window-content/object-form.html b/src/mixins/templates/mixins/window-content/object-form.html deleted file mode 100644 index bdfb43a..0000000 --- a/src/mixins/templates/mixins/window-content/object-form.html +++ /dev/null @@ -1,34 +0,0 @@ -{% 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/src/mixins/templates/mixins/window-content/object.html b/src/mixins/templates/mixins/window-content/object.html deleted file mode 100644 index 69b4f3c..0000000 --- a/src/mixins/templates/mixins/window-content/object.html +++ /dev/null @@ -1,45 +0,0 @@ -{% 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/src/mixins/templates/mixins/window-content/objects.html b/src/mixins/templates/mixins/window-content/objects.html deleted file mode 100644 index d7a2e9b..0000000 --- a/src/mixins/templates/mixins/window-content/objects.html +++ /dev/null @@ -1,45 +0,0 @@ -{% 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/src/mixins/templates/mixins/wm/modal.html b/src/mixins/templates/mixins/wm/modal.html deleted file mode 100644 index b8e5614..0000000 --- a/src/mixins/templates/mixins/wm/modal.html +++ /dev/null @@ -1,20 +0,0 @@ -{% load static %} - - -{% block scripts %} -{% endblock %} - -{% block styles %} -{% endblock %} - - \ No newline at end of file diff --git a/src/mixins/templates/mixins/wm/page.html b/src/mixins/templates/mixins/wm/page.html deleted file mode 100644 index 93ea8c1..0000000 --- a/src/mixins/templates/mixins/wm/page.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.html" %} - - -{% block content %} - {% include window_content %} -{% endblock %} diff --git a/src/mixins/templates/mixins/wm/panel.html b/src/mixins/templates/mixins/wm/panel.html deleted file mode 100644 index b180b38..0000000 --- a/src/mixins/templates/mixins/wm/panel.html +++ /dev/null @@ -1,17 +0,0 @@ - - diff --git a/src/mixins/templates/mixins/wm/widget.html b/src/mixins/templates/mixins/wm/widget.html deleted file mode 100644 index d8822bb..0000000 --- a/src/mixins/templates/mixins/wm/widget.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
-
- - -
-
-
- - -{% block custom_end %} -{% endblock %} \ No newline at end of file diff --git a/src/mixins/templates/mixins/wm/window.html b/src/mixins/templates/mixins/wm/window.html deleted file mode 100644 index 3a9a776..0000000 --- a/src/mixins/templates/mixins/wm/window.html +++ /dev/null @@ -1,10 +0,0 @@ - - {% extends 'wm/panel.html' %} - {% block heading %} - {{ title }} - {% endblock %} - - {% block panel_content %} - {% include window_content %} - {% endblock %} - \ No newline at end of file diff --git a/src/mixins/views.py b/src/mixins/views.py deleted file mode 100644 index 8443586..0000000 --- a/src/mixins/views.py +++ /dev/null @@ -1,442 +0,0 @@ -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