Source code for superdjango.ui.views.crud

# Imports

from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import InvalidPage, Paginator
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.utils.translation import gettext_lazy as _
from django.views import View
import logging
from superdjango.assets.library import JavaScript, StyleSheet
from superdjango.conf import SUPERDJANGO
from superdjango.sessions import Session
from superdjango.ui.runtime.actions import Actions
from superdjango.ui.runtime.data import Record
from superdjango.ui.runtime.lists import CurrentOrdering, CurrentPagination
from ..constants import ORIENTATION, VERB
from .base import UIBaseMixin
from .forms import UIFormMixin

log = logging.getLogger(__name__)

# Classes


# noinspection PyAbstractClass
[docs]class UIModelView(UIBaseMixin, View): # Direct Attributes: Assigned via kwargs when the view is created using as_view(). # actions = None # actions_label = None # ui = None # Dynamic Attributes: Assigned during processing. # active_page = None # """The name of the active page that may be used to identify the current view within a menu.""" # # active_subpage = None # """The name of the active subpage that may be used to identify the current view within a submenu.""" object = None """The record (instance) of a model for create, detail, delete, and update views. This is not used by list views.""" object_list = None """The record instances of a model for list views.""" queryset = None """The queryset used to acquire the object or object list. See ``get_queryset()``.""" # Option Attributes: Assigned via an Options instance. Assigned via kwargs whe nthe view is created using as_view(). # base_template = settings.SUPERDJANGO_BASE_TEMPLATE_NAME # base_template = "base.html" # """The base template to extend.""" context_object_name = None """The name of the model object (or objects) when used in a template.""" fields = None """The fields to be displayed in form, list, and detail views.""" inlines = None """The inline models, if any, to be displayed with a record.""" prefetch_related = None """A list of foreign key or many to many fields that should be called using Django's `prefetch_related <https://docs.djangoproject.com/en/stable/ref/models/querysets/#prefetch-related>`_. """ # raise_exception = settings.DEBUG # """Indicates whether exceptions should be raised. When ``False``, errors are logged if possible.""" select_related = None """A list of foreign key fields that should be called using Django's `select_related <https://docs.djangoproject.com/en/stable/ref/models/querysets/#select-related>`_. """ session = None lookup_field = None lookup_key = None pattern_name = None # subtitle = None # template_name = None # template_name_suffix = None # title = None # @property # def css(self): # """Alias for ``get_css()``.""" # return self.get_css() # # noinspection PyMethodMayBeStatic # def dispatch_not_found(self, message=None): # """Dispatch a 404 (page not found) error. By default, this just raises an ``Http404``. # # :param message: The 404 message. # # """ # if message is None: # message = _("The requested page could not be located.") # # raise Http404(message) # def get_base_template(self): # """Get the name of the base template to use for rendering the view. # # :rtype: str # # """ # if self.base_template is not None: # return self.base_template # # return self.ui.get_base_template(self.request, self.get_verb())
[docs] def get_breadcrumbs(self): """Get breadcrumbs for the model view.""" return self.ui.get_breadcrumbs(self.request, self.get_verb(), record=self.object)
[docs] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Start with the common things. # kwargs['actions_label'] = self.actions_label # kwargs['active_page'] = self.active_page # kwargs['active_subpage'] = self.active_subpage or "%s_%s" % (self.active_page, self.get_verb()) # kwargs['base_template'] = self.get_base_template() # kwargs['breadcrumbs'] = self.get_breadcrumbs() # kwargs['subtitle'] = self.get_subtitle() # kwargs['title'] = self.get_title() # kwargs['verbose_name'] = self.get_verbose_name() # kwargs['verbose_name_plural'] = self.get_verbose_name_plural() # kwargs['view'] = self # Add the object, object_list, and the same under the context object name. context_object_name = self.get_context_object_name() if self.object is not None: context['object'] = self.object if context_object_name is not None: context[context_object_name] = self.object # context['inlines'] = self.get_inlines(self.request) elif self.object_list is not None: context['object_list'] = self.object_list if context_object_name is not None: context[context_object_name] = self.object_list else: pass context['help'] = self.ui.get_help(self.request) context['HELP_ENABLED'] = SUPERDJANGO.HELP_ENABLED # m = self.ui.get_messages(self.request, self.object or self.object_list) # print(type(m), m) for level, message in self.ui.get_messages(self.request, self.object or self.object_list): if level == "error": self.messages.error(message) elif level == "success": self.messages.success(message) elif level == "warning": self.messages.warning(message) else: self.messages.info(message) # Include view-level action instances. actions = None if self.actions is not None: actions = Actions( enabled=self.actions.enabled, label=self.actions.label, location=self.actions.location ) for action in self.actions: _action = self.ui.get_action(self.request, action, record=self.object) if _action is not None: actions.append(_action) context['actions'] = actions return context
[docs] def get_context_object_name(self): """Get the name of the model (or models) used in templates. :rtype: str | None """ return self.context_object_name
[docs] def get_controls(self): """Get the controls used to display fields/columns. :rtype: list[BaseControl] """ controls = list() for field_name in self.fields: control = self.ui.get_control(field_name, request=self.request) if control is None: message = "%s %s has requested a control for field that has not been defined: %s" log.debug(message % (self.ui.meta.model_name, self.__class__.__name__, field_name)) continue controls.append(control) return controls
[docs] def get_css(self): """Get the URLs or markup of any CSS to be included in the view. :rtype: StyleSheet """ css = super().get_css() _css = self.ui.get_css(self.request, self.get_verb(), queryset=self.object_list, record=self.object) css.merge(_css) return css
# noinspection PyMethodMayBeStatic
[docs] def get_display_name(self, record): """Get the human-friendly name of a model instance. :param record: The model instance. :rtype: str """ try: return record.get_display_name() except AttributeError: return str(record)
[docs] def get_inlines(self, request): """Get the inline instances associated with the model. :param request: The current HTTP request instance. :rtype: list[InlineModelUI] | None """ if self.inlines is None: return None a = list() for inline_class in self.inlines: inline = inline_class(self.ui.model, current_site=self.ui.site) a.append(inline) return a
[docs] def get_js(self): """Get the URLs or markup of any JavaScript to be included in the view. :rtype: JavaScript """ js = super().get_js() _js = self.ui.get_js(self.request, self.get_verb(), queryset=self.object_list, record=self.object) js.merge(_js) return js
[docs] def get_object(self): """Get the object (model instance) that may be used in :py:class:`CreateView`, :py:class:`DetailView`, :py:class:`DeleteView`, and :py:class:`UpdateView`. """ lookup_field = self.ui.get_lookup_field() queryset = self.get_queryset() try: criteria = {lookup_field: self.args[0]} except IndexError: message = 'The "%s" lookup_field does not exist in args for the "%s" view' raise ImproperlyConfigured(message % (lookup_field, self.__class__.__name__)) return get_object_or_404(queryset, **criteria)
[docs] def get_object_list(self): """Get the objects to be displayed by list views. Used by :py:class:`ListView`. :rtype: django.db.models.QuerySet """ return self.get_queryset()
[docs] def get_queryset(self): """Get the queryset used by the view. This will either be a list or individual instance. :rtype: django.db.models.QuerySet """ if self.queryset is not None: # noinspection PyProtectedMember return self.queryset._clone() queryset = self.ui.get_queryset(self.request) if isinstance(self.select_related, (list, tuple)): queryset = queryset.select_related(*self.select_related) if isinstance(self.prefetch_related, (list, tuple)): queryset = queryset.prefetch_related(*self.prefetch_related) return queryset
def get_session(self): if self.session is not None: return self.session return Session(self.request, prefix="ui") # def get_subtitle(self): # """Get the page subtitle. # # :rtype: str # # """ # if self.subtitle is not None: # return self.subtitle # # return "" # def get_template_name_suffix(self): # """Get the suffix for the current view template. # # :rtype: str # # .. tip:: # Extending classes should define the ``template_name_suffix``. The suffix should include an underscore for # separation. For example, the suffix for a view for creating a new record would be ``_add``. # # The default behavior here is to return an empty string if ``template_name_suffix`` is not defined. # # """ # if self.template_name_suffix is not None: # return self.template_name_suffix # # return "" # def get_template_names(self): # """Get the template names that may be used for rendering the response. # # :rtype: list[str] # # The possible names are generated like so: # # 1. If the child class defines a ``template_name``, this is always returned as the first element of the list. # 2. This method first defines templates that may be defined by the local project in the form of # ``{app_label}/{model_name_lower}{template_name_suffix}.html``. # 3. Finally, the generic template is supplied for SuperDjango UI. # # """ # templates = list() # # # This will be the first template that is checked, which means it takes priority over any other possible # # template names. # if self.template_name is not None: # templates.append(self.template_name) # # # The default template name is based on the suffix, which may supplied by the extending class or by view # # options. It takes second preference. # suffix = self.get_template_name_suffix() # if suffix is not None: # file_name = "%s%s.html" % (self.ui.meta.model_name, suffix) # # templates.append(os.path.join(self.ui.meta.app_label, file_name)) # # # Include UI generic templates last. # file_name = "model%s.html" % suffix # # templates.append(os.path.join("superdjango", "ui", HTML_FRAMEWORK, file_name)) # # return templates
[docs] def get_title(self): """Get the page title. :rtype: str """ if self.title is not None: return self.title if self.object is not None: return self.get_display_name(self.object) return str(self.ui.get_verbose_name_plural())
# def get_verb(self): # """Get the verb/action associated with the view. # # :rtype: str # # .. important:: # Child classes must implement this method. # # """ # raise NotImplementedError() # def get_verbose_name(self): # """Get the verbose name for the model. # # :rtype: str # # """ # return self.ui.meta.verbose_name # def get_verbose_name_plural(self): # """Get the plural verbose name for the model. # # :rtype: str # # """ # return self.ui.meta.verbose_name_plural # @property # def js(self): # """Alias for ``get_js()``.""" # return self.get_js()
[docs] def render_to_response(self, context): """Render the current context. :param context: The context variables to use for rendering. :rtype: TemplateResponse """ self.save_url_history(context) return TemplateResponse(self.request, self.get_template_names(), context=context)
[docs] def save_url_history(self, context): """Call the underlying method on ModelUI. Called from ``render_to_response()``. :param context: The current context. :type context: dict By default, ``title`` and ``subtitle`` are passed to history. Child views may override to add their own info. """ # HACK: A delete page in history is okay, but will result in a 404 unless the user canceled. So it's safer just # to do nothing for now. But is this necessary? See UIDeleteView. if self.get_verb() == VERB.DELETE: return title = context['title'] if context['subtitle']: title = "%s, %s" % (title, context['subtitle']) self.ui.save_url_history(self.request, title)
def _get_message_context(self): """Get the context for processing messages. :rtype: dict() """ d = { 'verbose_name': self.ui.get_verbose_name(), 'verbose_name_plural': self.ui.get_verbose_name_plural(), } if self.object is not None: d['display_name'] = self.get_display_name(self.object) return d
[docs]class UICreateView(UIFormMixin, UIModelView): """A model create view.""" # def get_breadcrumbs(self): # """Get delete view breadcrumbs.""" # crumbs = super().get_breadcrumbs() # crumbs.add(self.get_subtitle() or _("Create"), "") # return crumbs
[docs] def get_subtitle(self): """Return "Create" by default.""" subtitle = super().get_subtitle() return subtitle or _("Create")
[docs] def get_verb(self): return VERB.CREATE
[docs]class UIDeleteView(UIFormMixin, UIModelView): """A model delete view.""" warning_message = None # noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs): """Display the delete confirmation page.""" self.object = self.get_object() context = self.get_context_data() context['warning_message'] = self.get_warning_message() return self.render_to_response(context)
# def get_breadcrumbs(self): # """Get delete view breadcrumbs.""" # crumbs = super().get_breadcrumbs() # crumbs.add(self.get_subtitle() or _("Delete"), "") # return crumbs
[docs] def get_css(self): """Delete views do not incorporate standard CSS by default. :rtype: StyleSheet """ return StyleSheet()
[docs] def get_js(self): """Delete views do not incorporate standard JavaScript by default. :rtype: JavaScript """ return JavaScript()
[docs] def get_success_message(self): """Get the post-delete message to display to a user.""" context = self._get_message_context() if self.success_message is not None: return self.success_message % context return _("The %(display_name)s %(verbose_name)s has been removed." % context)
[docs] def get_verb(self): """Returns ``delete``.""" return VERB.DELETE
[docs] def get_subtitle(self): """Return "Delete" by default.""" subtitle = super().get_subtitle() return subtitle or _("Delete")
[docs] def get_warning_message(self): """Get the warning to display to the user on the delete confirmation page.""" context = self._get_message_context() if self.warning_message is not None: return self.warning_message % context return None
[docs] def post(self, request, *args, **kwargs): """Delete the object and redirect using the success URL.""" self.object = self.get_object() self.ui.delete_record(self.object, request) self.messages.success(self.get_success_message()) return HttpResponseRedirect(self.get_success_url())
[docs] def save_url_history(self, context): """A delete page in history is okay, but will result in a 404 unless the user canceled. So it's safer to exclude delete from history. """ pass
[docs]class UIDetailView(UIModelView): """A model detail view.""" orientation = ORIENTATION.VERTICAL template_name_suffix = "_detail" # noinspection PyUnusedLocal def get(self, request, *args, **kwargs): self.object = self.get_object() inlines = self.get_inlines(request) context = self.get_context_data(inlines=inlines) # context = self.get_context_data() fields = list() controls = self.get_controls() for control in controls: fields.append(control.get_datum(self.object, request)) context['fields'] = fields return self.render_to_response(context)
[docs] def get_inlines(self, request): """Get inlines for the detail view.""" inlines = self.ui.get_inlines(request, self.get_verb()) if inlines is None: return None for i in inlines: i.orientation = i.detail_options.orientation i.template = i.detail_options.template create = i.get_action(request, VERB.CREATE) if create is not None: fk = i.get_foreign_key().name create.url += "?i_%s=%s&next=%s" % ( fk, self.object.pk, self.ui.get_url(VERB.DETAIL, record=self.object) ) i.create_action = create qs = i.get_queryset(request, self.object) for row in qs: actions = None if i.detail_options.actions: actions = list() for action in i.detail_options.actions: _action = i.get_action(request, action, record=row) if _action is not None: actions.append(_action) record = Record( i.get_identifier(row), row, actions=actions, record_type=i.meta.model_name, display_name=i.get_display_value(row) ) for field in i.detail_options.fields: control = i.get_control(field) if control is None: continue url = None if i.detail_options.link_field == field: url = i.get_url(i.detail_options.link_to, record=row) record.append(control.get_datum(row, request, url=url)) i.records.append(record) return inlines
[docs] def get_verb(self): """Returns ``detail``.""" return VERB.DETAIL
[docs]class UIListView(UIModelView): """Model list view.""" allow_empty = True """Indicates whether an empty queryset is allowed. When ``False`` an empty queryset will raise an ``Http404``.""" auto_refresh = None """Indicates whether auto-refresh is enabled on the output. May be given as ``True``, the number of seconds between refresh (``int``), or the JavaScript to use for the refresh (``str``).""" bulk_actions = None """A list of standard (global) action names or action classes that may be used for bulk actions.""" context_menu = None """The context menu instance.""" default_list_type = None """The name of the default list type.""" empty_message = None """The message to display when there are no results.""" filtering = None """The filtering instance used to provide filter controls.""" link_field = None """The field, if any, upon which a link will be enabled for further action.""" link_to = None """The action (verb) to use for the ``link_field``.""" limit = None """The total records to be displayed on a page. Setting this value will invoke pagination.""" list_types = None """A dictionary of instances extending BaseList that may be used to render lists.""" list_types_label = None """The label to use for switching list types.""" ordering = None """The ordering instance used to sort/re-order the list.""" pagination = None """The :py:class:`Pagination` instance used to control list pagination.""" page_keyword = "page" """The GET key used to indicate the pagination page number. Defaults to ``page``.""" record_actions = None """A list of action names or action instances that may be added to individual roles.""" record_actions_label = _("Actions") """The name of actions as displayed to users.""" template_name_suffix = "_list" total_count_enabled = False """Indicates whether a total count display should be enabled.""" # noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs): """Get the queryset and optionally apply pagination. The context includes ``empty_message`` when ``allow_empty`` is ``True`` and no results are found. ``is_paginated`` is also included and is either ``True`` or ``False``. If pagination is enabled, additional variables are also added to the context: - ``current_page``: The same as ``page_object``. - ``page_keyword``: The GET keyword used to indicate the current page number. - ``page_obj``: The current page object from ``get_paginated_queryset()``. - ``pagination_style``: The preferred output of pagination links. - ``paginator``: The paginator instance from ``get_paginator()``. """ from superdjango.ui.runtime.tools import Tools tools = Tools() # noinspection PyAttributeOutsideInit self._list_type = self.get_list_type() queryset = self.get_object_list() # Apply filters to the queryset. queryset = self.get_filtered_queryset(queryset) if not queryset.exists(): message = self.get_empty_message() if not self.allow_empty: self.dispatch_not_found(message=message) else: context = self.get_context_data(empty_message=message) if self.ui.check_permission(self.request, VERB.CREATE): context['create_url'] = self.ui.get_url(VERB.CREATE) context['request_is_filtered'] = self._request_is_filtered() return self.render_to_response(context) # Get the ordered version of the queryset. queryset = self.get_ordered_queryset(queryset) # Create the context with optional pagination. _pagination = self.get_pagination() limit = self.get_limit(_pagination) if limit is not None: page = self.get_paginated_queryset(limit, queryset) self.object_list = page pagination = CurrentPagination( page, request, current_limit=limit, keyword=_pagination.keyword, limit=_pagination.limit, location=_pagination.location, paginator=page.paginator, style=_pagination.style, ) context = self.get_context_data( pagination=pagination, total_objects=len(self.object_list) ) else: self.object_list = queryset context = self.get_context_data( pagination=None, total_objects=len(self.object_list) ) # Create and add the list object. list_object = self.get_list_object(self.object_list) context['list_object'] = list_object context[list_object.type] = list_object # Add optional/alternative list types. if len(self.list_types) > 1: context['list_types'] = self.list_types # context['list_types_label'] = self.list_types_label tools.add("model_list_type_switcher", location="top", sort_order=3) # Determine whether filters are enabled. context['filters_enabled'] = False context['filters'] = self.get_filters() if context['filters'] is not None: context['request_is_filtered'] = self._request_is_filtered() context['filters_enabled'] = True tools.add( "model_list_filters", location="top", sort_order=2, ) # Handle modal controls. modal_enabled = False if self.record_actions: modal_test = ( VERB.AJAX_CREATE in self.record_actions, VERB.AJAX_DELETE in self.record_actions, VERB.AJAX_DETAIL in self.record_actions, VERB.AJAX_UPDATE in self.record_actions, ) if any(modal_test): modal_enabled = True context['modal_enabled'] = modal_enabled # Determine whether search is enabled. context['search_enabled'] = False if self.ui.ajax_search_options is not None: context['search_enabled'] = True tools.add("model_list_ajax_search_form", location="top", sort_order=1) # Show all controls. if "show_all" in request.GET: context['show_all_enabled'] = False elif _pagination is not None: context['show_all_enabled'] = _pagination.show_all_enabled else: context['show_all_enabled'] = False # Flag the list as sortable. if self.ui.ajax_reorder_options is not None: context['sort_enabled'] = True # TODO: When default filtering is applied, the total_objects and total_count will appear to be incorrect. # Add total count. if any([self._list_type.total_count_enabled, self.total_count_enabled]): context['total_count'] = self.ui.model.objects.count() context['total_count_enabled'] = True tools.sort() context['model_tools'] = tools return self.render_to_response(context)
[docs] def get_bulk_actions(self): """Get the bulk actions for the list. :rtype: list[Action] | None """ _bulk_actions = self._list_type.bulk_actions or self.bulk_actions if _bulk_actions is None: return None bulk_actions = list() for action in _bulk_actions: _action = self.ui.get_bulk_action(self.request, action, queryset=self.object_list) if _action is not None: bulk_actions.append(_action) # Actions may be defined, but the corresponding views may not be configured, resulting in an empty list. if len(bulk_actions) == 0: return None return bulk_actions
[docs] def get_context_menu(self): """Get the context menu to display for the list. :rtype: Actions | None """ context_menu = self._list_type.context_menu or self.context_menu if context_menu is None: return None # Normalize named actions (strings) to action instances. These need to be "raw" action instances, so we can't # use the UI's get_action() method. index = 0 for item in context_menu: _item = None if type(item) is str: if item in self.ui.actions: _item = self.ui.actions[item] context_menu.items[index] = _item index += 1 return context_menu
[docs] def get_css(self): css = super().get_css() # Merge any CSS required for the list type. css.merge(self._list_type.get_css(self.request, self.ui)) # Context menu CSS does not require another call to get_context_menu(). context_menu = self._list_type.context_menu or self.context_menu if context_menu is not None: css.merge(context_menu.get_css()) return css
[docs] def get_empty_message(self): """Get the message to be displayed for an empty queryset. :rtype: str """ default = _("There are currently no %(verbose_name_plural)s.") empty_message = self._list_type.empty_message or self.empty_message or default context = self._get_message_context() return empty_message % context
def get_filtered_queryset(self, queryset): # No filtering is configured. Just return the qs. filtering = self._list_type.filtering or self.filtering if filtering is None: return queryset # The request contains filtering instructions. if self._request_is_filtered(): qs = queryset for option in filtering: for field_name in option.fields: _filter = self.ui.get_filter(field_name, self.request) if _filter is not None: updated_qs = _filter.get_queryset(qs, self.request, self.ui) if updated_qs is not None: qs = updated_qs # if field_name in self.ui.filters: # _filter = self.ui.filters[field_name] # updated_qs = _filter.get_queryset(qs, self.request, self.ui) # if updated_qs is not None: # qs = updated_qs return qs # A default filter has been defined. if filtering[0].default is not None: updated_qs = queryset.filter(**filtering[0].default) if updated_qs is not None: return updated_qs # Filtering is configured, but no filtering is applied. return queryset
[docs] def get_filters(self): """Get the available filters for display. :rtype: list[BaseType[BaseFilter]] | None """ filtering = self._list_type.filtering or self.filtering if filtering is None: return None filters = list() for option in filtering: for field_name in option.fields: _filter = self.ui.get_filter(field_name, self.request) if _filter is not None: filters.append(_filter.load(self.request, self.ui)) return filters
[docs] def get_js(self): """Get the JavaScript for bulk-actions and filters.""" js = super().get_js() if not self.object_list: return js # Merge any JavaScript required for the list type. js.merge(self._list_type.get_js(self.request, self.ui)) if self.has_bulk_actions(): context = { 'select_action_message': _("Please select an action."), 'select_items_message': _("Please select one or more items."), 'total_objects': len(self.object_list), } js.from_template("bulk_actions", "superdjango/ui/js/bulk_actions.js", context) context_menu = self.get_context_menu() if context_menu is not None: if context_menu.base_url is None: context_menu.base_url = self.ui.get_url(VERB.LIST) js.merge(context_menu.get_js()) if self.has_filters(): js.append("ui-js-common", url="superdjango/ui/js/common.js") js.append("ui-js-filters", url="superdjango/ui/js/filters.js") # context = { # 'request': self.request, # 'select_filters_message': _("Please select one or more filters."), # } # # js.from_template("filters", "superdjango/ui/js/filters.js", context) if self.ui.ajax_reorder_options is not None: js.append("jquery-sortable", url="bundled/sortable/jquery-sortable.js") context = { 'url': self.ui.get_url("ajax_reorder"), } template = "superdjango/ui/js/reorder.js" js.from_template("ajax re-order", template, context=context) # Handle search scripting. search_url = self.ui.get_url(VERB.AJAX_SEARCH) if search_url is not None: js.from_template( "%s-ajax-search" % self.ui.meta.model_name, "superdjango/ui/js/search.js", context={'term_keyword': self.ui.ajax_search_options.term_keyword, 'url': search_url} ) return js
[docs] def get_limit(self, pagination): """Get the number of records to display per page. :param pagination: Pagination options. :type pagination: Pagination :rtype: int | None """ if pagination is None or not pagination.enabled: return None if "show_all" in self.request.GET: return pagination.show_all_limit if "limit" in self.request.GET: limit = int(self.request.GET.get("limit")) if limit > pagination.show_all_limit: return pagination.show_all_limit return limit return pagination.limit
[docs] def get_list_object(self, queryset): """Get the list class instance.""" bulk_actions = self.get_bulk_actions() if bulk_actions is not None: self._list_type.bulk_actions_enabled = True self._list_type.current_ordering = self._get_current_ordering() list_object = self._list_type.load(queryset, self.request, self.ui) list_object.bulk_actions = bulk_actions return list_object
[docs] def get_list_type(self): """Get the list type instance. :rtype: BaseType[BaseList] """ list_type = self.request.GET.get("list_type") if list_type in self.list_types: return self.list_types[list_type] return self.list_types[self.default_list_type]
[docs] def get_ordered_queryset(self, queryset): """Get the ordered/sorted queryset based on list ordering options and the current request. :param queryset: django.db.models.QuerySet :rtype: django.db.models.QuerySet """ current_ordering = self._get_current_ordering() if not current_ordering.exists: return queryset order_by = list() for field_name, direction in current_ordering.fields.items(): if direction is None: continue if direction == "desc": field_name = "-" + field_name order_by.append(field_name) return queryset.order_by(*order_by)
[docs] def get_page_keyword(self): """Get the keyword used to identify the page number in GET. :rtype: str | None """ pagination = self._list_type.pagination or self.pagination if pagination is not None: return pagination.keyword
[docs] def get_paginated_queryset(self, limit, queryset): """Paginate the given queryset. :param limit: The number of objects per page. :type limit: int :param queryset: The query set to be paginated. :type queryset: django.db.Queryset :rtype: Page """ # Paginator is needed for num_pages below. paginator = self.get_paginator(queryset, limit) # Get the current page number. page_number = self._get_current_page_number(paginator) if page_number is None: message = _("The page number could not be determined.") self.dispatch_not_found(message) # Return the page instance or die trying. try: return paginator.page(page_number) except InvalidPage as e: message = _("Invalid page (%s): %s" % (page_number, e)) self.dispatch_not_found(message)
[docs] def get_pagination(self): """Get the pagination instance. :rtype: Pagination | None """ return self._list_type.pagination or self.pagination
# noinspection PyMethodMayBeStatic
[docs] def get_paginator(self, queryset, per_page): """Get a paginator instance. :rtype: Paginator """ return Paginator(queryset, per_page)
[docs] def get_verb(self): return VERB.LIST
[docs] def has_bulk_actions(self): """Indicates whether bulk actions have been defined. :rtype: bool """ if any([self._list_type.bulk_actions is not None, self.bulk_actions is not None]): return True return False
[docs] def has_filters(self): """Indicates whether filtering has been defined. :rtype: bool """ if any([self._list_type.filtering is not None, self.filtering is not None]): return True return False
[docs] def save_url_history(self, context): """Add list/filter info.""" title = context['title'] if self._request_is_filtered(): title = "%s (%s)" % (title, _("filtered")) title = "%s - %s" % (title, self.get_list_type().label) self.ui.save_url_history(self.request, title)
def _get_current_ordering(self): """Get the current list ordering. :rtype: CurrentOrdering """ ordering = self._list_type.ordering or self.ordering if ordering is not None: return CurrentOrdering( self.request, direction=ordering.direction, fields=ordering.fields, initial_field=ordering.initial_field ) return CurrentOrdering(self.request) def _get_current_page_number(self, paginator): """Get the current page number from GET. :param paginator: The current paginator instance. :type paginator: Paginator :rtype: int | None """ # The page keyword determines what how the page number is identified in the URL. page_keyword = self.get_page_keyword() # Get the current page number. page_query_param = self.request.GET.get(page_keyword) page_number = page_query_param or 1 try: page_number = int(page_number) except ValueError: if page_number == "first": page_number = 1 elif page_number == "last": page_number = paginator.num_pages else: page_number = None return page_number def _request_is_filtered(self): """Indicates whether the current request has any filtering enabled. :rtype: bool """ for key in self.request.GET.keys(): if key.startswith("f_"): return True return False
[docs]class UIUpdateView(UIFormMixin, UIModelView): """Update model view."""
[docs] def get(self, request, *args, **kwargs): """Get the current model instance.""" self.object = self.get_object() form = self.ui.get_form(request, record=self.object) # form = self.get_form(instance=self.object) formsets = self.get_formsets(request) context = self.get_context_data(form=form, formsets=formsets) return self.render_to_response(context)
# def get_breadcrumbs(self): # """Get update view breadcrumbs.""" # crumbs = super().get_breadcrumbs() # crumbs.add(self.get_subtitle() or _("Update"), "") # return crumbs
[docs] def get_subtitle(self): """Return "Update" by default.""" subtitle = super().get_subtitle() return subtitle or _("Update")
[docs] def get_verb(self): return VERB.UPDATE
[docs] def post(self, request, *args, **kwargs): """Get the current model instance.""" self.object = self.get_object() form = self.ui.get_form(request, data=request.POST, files=request.FILES, record=self.object) formsets = self.get_formsets(request) valid = list() valid.append(form.is_valid()) if formsets: for fs in formsets: valid.append(fs.is_valid()) if all(valid): return self.form_valid(form, formsets=formsets) return self.form_invalid(form, formsets=formsets)