# 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 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 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)