Source code for superdjango.ui.views.extended

# Imports

from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.utils.translation import gettext_lazy as _
import logging
from superdjango.forms import SearchForm
from superdjango.views import RedirectView
from urllib.parse import quote_plus
from ..constants import VERB
from ..options.lists.tables import Table
from ..options.utils import Choice
from .crud import UIListView, UIModelView, UIUpdateView
from .forms import UIFormMixin
from .templates import UITemplateView

log = logging.getLogger(__name__)

# Exports

__all__ = (
    "UIChooserView",
    "UIDashboardView",
    "UIDuplicateView",
    "UIHistoryView",
    "UIIntermediateChoiceView",
    "UIIntermediateFormView",
    "UIMarkArchivedRedirect",
    "UIMarkCompleteRedirect",
    "UIMarkPublishedRedirect",
    "UIMarkReviewedRedirect",
    "UIMarkResolvedRedirect",
    "UISaveAsView",
    "UISearchView",
)

# Classes


[docs]class UIChooserView(UITemplateView): """Select a record before continuing to the next view.""" criteria = None """Additional criteria to apply when getting the queryset.""" description_field = "description" """The field to use for the choice description.""" display_choices_as = "ul" """The desired output of the choices; ``cards``, (list) ``group``, ``ol``, or ``ul``.""" link_field = None """The field on the queryset's model to use as the name of the link.""" link_to = None """The verb to which the choice is linked.""" parameter_prefix = "i_" """The prefix used to identify the choice in GET. ``f_``, ``i_``, or anything recognized by the next view.""" template_name_suffix = "_chooser.html" def get_choices(self): qs = self.ui.get_queryset(self.request, criteria=self.criteria) choices = list() for row in qs: if not self.ui.check_permission(self.request, VERB.LIST, record=row): continue try: description = getattr(row, self.description_field) except AttributeError: description = None try: icon = getattr(row, "icon") except AttributeError: icon = None if self.link_field is not None: label = getattr(row, self.link_field) else: label = str(row) url = self.ui.get_url(self.link_to, record=row) choice = Choice(label, row.pk, description=description, icon=icon, url=url) choices.append(choice) return choices
[docs] def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context['choices'] = self.get_choices() context['display_choices_as'] = self.display_choices_as return context
[docs] def get_verb(self): return VERB.CHOOSER
[docs]class UIDashboardView(UITemplateView): organize_panels_by_size = True panels = None verb = VERB.DASHBOARD
[docs] def get_breadcrumbs(self): crumbs = super().get_breadcrumbs() crumbs.add(self.ui.get_verbose_name_plural(), self.ui.get_url(VERB.LIST)) crumbs.add(_("Dashboard"), "") return crumbs
[docs] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['active_subpage'] = "%s_%s_dashboard" % (self.ui.meta.app_label, self.ui.meta.model_name) context['organize_panels_by_size'] = self.organize_panels_by_size context['panels'] = self.get_panels() return context
[docs] def get_title(self): """Get title or plural verbose name.""" return self.title or self.ui.get_verbose_name_plural()
def get_panels(self): if self.panels is None: return None a = list() for panel_class in self.panels: _panel = panel_class() panel = _panel.load(self.request, self.ui) a.append(panel) return a
[docs]class UIDuplicateView(UIUpdateView): """Provides a means to duplicate a record using GET to make the initial identification. This differs from :py:class:`UISaveAsView` in that it may be called directly, whereas save as requires that a new record has already been created. """ copy_field = "title" """The name of the field to which the ``copy_text`` will be appended.""" copy_text = _("(Copy)") """Text used to indicate that the record is a duplicate.""" save_as_message = None """The message to display which indicates the record is a duplicate. This is displayed *before* the form is submitted. """
[docs] def form_valid(self, form, formsets=None): """Duplicate the current instance.""" obj = form.save(commit=False) obj.pk = None obj.save() form.save_m2m() self.ui.save_history(obj, self.request, VERB.DUPLICATE) # noinspection PyAttributeOutsideInit self.object = obj # Handle submit actions. if "save_and_add" in self.request.POST: self.success_url = self.ui.get_url(VERB.CREATE) elif "save_and_continue" in self.request.POST: self.success_url = self.ui.get_url(VERB.UPDATE, record=self.object) else: pass self.messages.success(self.get_success_message()) return HttpResponseRedirect(self.get_success_url())
[docs] def get(self, request, *args, **kwargs): """Get the current model instance and rename the ``copy_field``.""" self.object = self.get_object() try: current_value = getattr(self.object, self.copy_field) new_value = "%s %s" % (current_value, self.copy_text) setattr(self.object, self.copy_field, new_value) except AttributeError: log.error("copy_field (%s) does not exist on: %s" % (self.copy_field, self.ui.meta.model_name)) self.messages.info(self.get_save_as_message()) form = self.ui.get_form(request, record=self.object) context = self.get_context_data(form=form) return self.render_to_response(context)
[docs] def get_save_as_message(self): """Get the message to display for duplication.""" context = self._get_message_context() return self.save_as_message % context
[docs] def get_save_as_text(self): """The duplicate view cannot be used with the ``save_as`` option.""" return None
[docs]class UIHistoryView(UIListView): """View record history.""" history_class = None """The model class used to store historical activity."""
[docs] def get_object_list(self): """Get the current record and ask the history class for the history records to be displayed.""" record = self.get_object() return self.history_class.get_for(record, limit=None)
[docs]class UIIntermediateChoiceView(UITemplateView): """An intermediate view prompts the user one (and only one) choice before moving to the next view.""" choices = None """The choices to be presented. These may be provided as a list of Choice instances or a callable. The callable must accept args for ``request`` and ``ui`` and return a list of Choice instances. """ display_choices_as = "ul" """The desired output of the choices; ``cards``, (list) ``group``, ``ol``, or ``ul``.""" template_name_suffix = "_chooser.html" def get_choices(self): if type(self.choices) is list: return self.choices if callable(self.choices): return self.choices(self.request, self.ui) raise ImproperlyConfigured("Choices have not been provided as a list or callable.")
[docs] def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context['choices'] = self.get_choices() context['display_choices_as'] = self.display_choices_as return context
[docs] def get_verb(self): return VERB.INTERMEDIATE
[docs]class UIIntermediateFormView(UIFormMixin, UIModelView): """An intermediate view prompts the user for input before moving to the next view.""" callback = None """A callback which accepts ``form``, ``request``, and ``ui`` parameters and returns the ``success_url``. If provided, no other processing occurs in ``form_valid()``. """ parameter_prefix = "i_" """The prefix used to identify the choice in GET. ``f_``, ``i_``, or anything recognized by the next view.""" template_name_suffix = "_form.html"
[docs] def form_valid(self, form, formsets=None): if self.callback is not None: return HttpResponseRedirect(self.callback(form, self.request)) success_url = self.success_url + "?" for key, value in form.cleaned_data.items(): success_url += "%s%s=%s&" % (self.parameter_prefix, key, value) success_url = success_url[:-1] return HttpResponseRedirect(quote_plus(success_url))
[docs] def get_verb(self): return VERB.INTERMEDIATE
[docs]class UIMarkArchivedRedirect(RedirectView, UIModelView): """Mark a record as archived, then redirect.""" error_message = _("Failed to mark the record as archived.") success_message = _("The record has been marked as archived.") update_fields = None
[docs] def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() self.object.mark_archived(self.request.user, commit=False) if self.update_fields is not None: for key, value in self.update_fields.items(): setattr(self.object, key, value) if self.ui.behaviors.is_audit_model(): self.object.audit(self.request.user, commit=False) self.object.save() self.messages.success(self.get_success_message()) if self.url: return self.url return self.ui.get_index_url()
def get_success_message(self): if not self.success_message: self.success_message = _("The record has archived.") context = self._get_message_context() return self.success_message % context
[docs] def get_verb(self): return VERB.MARK_ARCHIVED
[docs]class UIMarkCompleteRedirect(RedirectView, UIModelView): """Mark a record as complete, then redirect.""" error_message = _("Failed to mark the record as complete.") success_message = _("The record has been marked as complete.") update_fields = None
[docs] def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() self.object.mark_complete(self.request.user, commit=False) if self.update_fields is not None: for key, value in self.update_fields.items(): setattr(self.object, key, value) if self.ui.behaviors.is_audit_model(): self.object.audit(self.request.user, commit=False) self.object.save() self.messages.success(self.get_success_message()) if self.url: return self.url return self.ui.get_index_url()
def get_success_message(self): if not self.success_message: self.success_message = _("The record has been marked complete.") context = self._get_message_context() return self.success_message % context
[docs] def get_verb(self): return VERB.MARK_COMPLETE
[docs]class UIMarkPublishedRedirect(RedirectView, UIModelView): """Mark a record as published, then redirect.""" error_message = _("Failed to mark the record as published.") success_message = _("The record has been marked as published.") update_fields = None
[docs] def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() self.object.mark_published(self.request.user, commit=False) if self.update_fields is not None: for key, value in self.update_fields.items(): setattr(self.object, key, value) if self.ui.behaviors.is_audit_model(): self.object.audit(self.request.user, commit=False) self.object.save() self.messages.success(self.get_success_message()) if self.url: return self.url return self.ui.get_index_url()
def get_success_message(self): if not self.success_message: self.success_message = _("The record has been marked as published.") context = self._get_message_context() return self.success_message % context
[docs] def get_verb(self): return VERB.MARK_PUBLISHED
[docs]class UIMarkReviewedRedirect(RedirectView, UIModelView): """Mark a record as reviewed, then redirect.""" error_message = _("Failed to mark the record as reviewed.") success_message = _("The record has been marked as reviewed.") update_fields = None
[docs] def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() self.object.mark_reviewed(self.request.user, commit=False) if self.update_fields is not None: for key, value in self.update_fields.items(): setattr(self.object, key, value) if self.ui.behaviors.is_audit_model(): self.object.audit(self.request.user, commit=False) self.object.save() self.messages.success(self.get_success_message()) if self.url: return self.url return self.ui.get_index_url()
def get_success_message(self): if not self.success_message: self.success_message = _("The record has been marked as reviewed.") context = self._get_message_context() return self.success_message % context
[docs] def get_verb(self): return VERB.MARK_REVIEWED
[docs]class UIMarkResolvedRedirect(RedirectView, UIModelView): """Mark a record as resolved, then redirect.""" error_message = _("Failed to mark the record as resolved.") success_message = _("The record has been marked as resolved.") update_fields = None
[docs] def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() self.object.mark_resolved(self.request.user, audit=False, commit=False) if self.update_fields is not None: for key, value in self.update_fields.items(): setattr(self.object, key, value) if self.ui.behaviors.is_audit_model(): self.object.audit(self.request.user, commit=False) self.object.save() self.messages.success(self.get_success_message()) if self.url: return self.url return self.ui.get_index_url()
def get_success_message(self): if not self.success_message: self.success_message = _("The record has been marked as reviewed.") context = self._get_message_context() return self.success_message % context
[docs] def get_verb(self): return VERB.MARK_REVIEWED
[docs]class UISaveAsView(UIUpdateView): """Provide a means to save a record as a different record. The objective given to the view must already be a new record. See :py:class:`UIFormMixin` for how this is implemented. """ copy_field = "title" """The name of the field to which the ``copy_text`` will be appended.""" copy_text = _("(Copy)") """Text used to indicate that the record is a duplicate.""" save_as_message = None """The message to display which indicates the record is a duplicate. This is displayed *before* the form is submitted. """
[docs] def get(self, request, *args, **kwargs): """Get the current model instance.""" self.object = self.get_object() try: current_value = getattr(self.object, self.copy_field) new_value = "%s %s" % (current_value, self.copy_text) setattr(self.object, self.copy_field, new_value) except AttributeError: log.error("copy_field (%s) does not exist on: %s" % (self.copy_field, self.ui.meta.model_name)) self.messages.info(self.get_save_as_message()) form = self.ui.get_form(request, record=self.object) # form = self.get_form(instance=self.object) context = self.get_context_data(form=form) return self.render_to_response(context)
[docs] def get_save_as_message(self): """Get the message to display for duplication.""" context = self._get_message_context() return self.save_as_message % context
[docs] def get_save_as_text(self): """The save as view cannot itself be used with the ``save_as`` option.""" return None
[docs]class UISearchView(UIModelView): """Search results for a specific model.""" empty_message = None link_field = None link_to = None # noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs): """The search view does not fetch the object list immediate, but instead displays the search form.""" context = self.get_context_data(**kwargs) # Allow keywords to be passed via GET. keywords = request.GET.get("keywords", None) context['cancel_url'] = self.get_cancel_url() context['form'] = SearchForm(initial={'keywords': keywords}) return self.render_to_response(context)
# def get_breadcrumbs(self): # crumbs = super().get_breadcrumbs() # crumbs.add(_("Search"), "") # # return crumbs def get_cancel_url(self): return self.ui.get_url(VERB.LIST)
[docs] def get_verb(self): return VERB.SEARCH
[docs] def get_results(self, term, case_sensitive=False, exact_matching=False): """Get the records matching the given search term. :rtype: django.db.models.QuerySet .. note:: SQLite does not support case sensitive matching. See https://docs.djangoproject.com/en/stable/ref/models/querysets/#contains """ match = "contains" if exact_matching: match = "exact" if not case_sensitive: match = "i%s" % match or_condition = Q() for field_name in self.fields: lookup = "%s__%s" % (field_name, match) criteria = {lookup: term} or_condition.add(Q(**criteria), Q.OR) return self.ui.model.objects.filter(or_condition)
# noinspection PyUnusedLocal
[docs] def post(self, request, *args, **kwargs): """Search for the terms submitted with the form.""" # Make sure the form is valid. form = SearchForm(data=request.POST, **kwargs) if not form.is_valid(): context = self.get_context_data(**kwargs) context['form'] = form return self.render_to_response(context) # Get search terms from POST. case_sensitive = request.POST.get("case_sensitive", False) if case_sensitive: case_sensitive = True exact_matching = request.POST.get("exact_matching", False) if exact_matching: exact_matching = True term = request.POST.get("keywords") # Get the results. self.object_list = self.get_results( term, case_sensitive=case_sensitive, exact_matching=exact_matching ) search_terms = { 'case sensitive': case_sensitive, 'exact matching': exact_matching, 'keywords': term, } # Get the renderer. table = Table(*self.fields, controls=self.get_controls(), link_field=self.link_field, link_to=self.link_to) # Assemble the context context = self.get_context_data(**kwargs) context['table'] = table.load(self.object_list, request, self.ui) context['cancel_url'] = self.get_cancel_url() context['form'] = form context['keyword'] = term context['search_results'] = self.object_list context['search_terms'] = search_terms context['total_results'] = len(self.object_list) return self.render_to_response(context)