Source code for superdjango.ui.views.ajax

# Imports

from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
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
import os
from superdjango.interfaces.hooks import hooks
from superdjango.ui.runtime.data import Record
from superdjango.utils import smart_cast
from superdjango.views import JSONMixin
from ..constants import VERB
from .access import UIAccessMixin

log = logging.getLogger(__name__)

# Exports

__all__ = (
    "UIAjaxAutoCompleteView",
    "UIAjaxChainedLookupView",
    "UIAjaxChooserView",
    "UIAjaxCreateView",
    "UIAjaxDetailView",
    "UIAjaxDragAndDropView",
    "UIAjaxFormMixin",
    "UIAjaxListView",
    "UIAjaxMarkCompleteView",
    "UIAjaxMixin",
    "UIAjaxReorderView",
    "UIAjaxSearchView",
    "UIAjaxUpdateView",
)

# Mixins


[docs]class UIAjaxMixin(UIAccessMixin, JSONMixin): """Base class for UI classes implementing AJAX functionality. The :py:class:`JSONMixin` provides the following attributes: - ``ajax_required`` - ``content_type`` - ``decoder_class`` - ``encoder_class`` - ``json_required`` This mixin provides the following attributes: - ``logging_enabled`` - ``pattern`` - ``ui`` """ logging_enabled = False """Indicates logging is enabled for view activities.""" object = None """For views that operate on a single record, this is the record (model instance). See ``get_object()``.""" pattern_name = None """The pattern name of the view. Overrides the default.""" ui = None """The current ModelUI instance.""" # 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_object(self): """Get the record for the current request. Applies only to views that deal with one record at a time. :rtype: BaseType[django.db.models.Model] | None """ lookup_field = self.ui.get_lookup_field() try: criteria = {lookup_field: self.args[0]} except IndexError: message = 'The "%s" lookup_field does not exist in args[0] for the "%s" view.' log.debug(message % (lookup_field, self.__class__.__name__)) return None try: return self.ui.model.objects.get(**criteria) except self.ui.model.DoesNotExist: log.debug("Object not found: %s" % criteria) return None
[docs]class UIAjaxFormMixin(UIAjaxMixin): """Base class for AJAX forms.""" cancel_text = _("Close") """The text to display as part of the cancel URL.""" fields = None """A list of field names that may be used during record creation.""" fieldsets = None """A list of Fieldset instances into which form fields should be divided.""" fields_from_get = None """A list of field names whose initial value may be acquired from GET. This functionality is NOT provided by the mixin and must be implemented (if desired) in child classes. """ form_class = None """The class to use for rendering forms.""" object = None selector = None """The selector used to live update page content after successful form submission.""" submit_text = _("Submit") """The text to display for the submit button.""" template_name = None """The template to use for rendering the form.""" template_name_suffix = "_ajax_form" """The suffix for the form template.""" def form_valid(self, form): self.object = self.ui.save_form(form, self.request, self.get_verb()) resp = { 'record': self.object, 'success': True, } return self.get_json_response(resp) def form_invalid(self, form): resp = { 'errors': form.errors, 'success': False, } return self.get_json_response(resp) # noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs): """Handle GET requests.""" # noinspection PyUnresolvedReferences form = self.ui.get_form(request) # noinspection PyUnresolvedReferences context = self.get_context_data(form=form) # noinspection PyUnresolvedReferences return self.render_to_response(context)
def get_context_data(self, **kwargs): # noinspection PyUnresolvedReferences context = kwargs.copy() context['cancel_text'] = self.cancel_text context['submit_text'] = self.submit_text context['verbose_name'] = self.ui.get_verbose_name() return context
[docs] def get_template_names(self): """Get the templates that may be used to render the form. :rtype: list[str] """ templates = list() if self.template_name: templates.append(self.template_name) if self.template_name_suffix is not None: file_name = "%s%s.html" % (self.ui.meta.model_name, self.template_name_suffix) templates.append(os.path.join(self.ui.meta.app_label, file_name)) file_name = "model%s.html" % self.template_name_suffix templates.append(os.path.join("superdjango", "ui", file_name)) return templates
def get_verb(self): raise NotImplementedError() # noinspection PyUnusedLocal
[docs] def post(self, request, *args, **kwargs): """Handle POST requests, i.e form submission.""" # noinspection PyUnresolvedReferences form = self.ui.get_form(request, data=request.POST, files=request.FILES, **kwargs) if form.is_valid(): return self.form_valid(form) return self.form_invalid(form)
[docs] def render_to_response(self, context): """Render the current context. :param context: The context variables to use for rendering. :rtype: TemplateResponse """ return TemplateResponse(self.request, self.get_template_names(), context=context)
# Views
[docs]class UIAjaxAutoCompleteView(UIAjaxMixin, View): """Facilitate AJAX lookups for foreign keys and many to many fields. .. note:: This differs from :py:class:`UIAjaxSearchView` in that the format of the search results is provided for use with select2. To invoke auto-complete, use the ``auto_complete_fields`` on the form options for the model that refers to remote fields. """ case_sensitive = False """Indicates whether the search will be case sensitive.""" criteria = None """Additional criteria by which the results should be filtered.""" exact_matching = False """When ``True``, exact matching is required.""" fields = None """A list of field names on the model to be searched for results.""" label_field = None """The field to use for the label of each result. If omitted, ``get_choice_name()`` will be called. Failing that, the string representation of the result is used. """ term_keyword = "term" """The GET keyword used to identify the search term.""" ui = None """The current ModelUI instance.""" # noinspection PyUnusedLocal def get(self, request, *args, **kwargs): term = request.GET.get(self.term_keyword, None) criteria = self.get_criteria(request) output = { 'results': list(), } qs = self.get_queryset(request, term) for row in qs: identifier = self.ui.get_identifier(row) if not self.ui.check_permission(request, VERB.LIST, record=row): continue if self.label_field is not None: label = getattr(row, self.label_field) else: try: label = self.ui.get_display_value(row, as_choice=True, label_field=self.label_field) # label = row.get_choice_name() except AttributeError: label = str(row) output['results'].append({'id': identifier, 'text': label}) return self.get_json_response(output)
[docs] def get_criteria(self, request): """Get any additional criteria. :param request: The current request instance. :rtype: dict """ if self.criteria is not None: criteria = self.criteria.copy() else: criteria = dict() # criteria=key:value,key:value if 'criteria' in request.GET: pairs = request.GET.get("criteria").split(",") for p in pairs: key, value = p.split(":") key = key.strip() value = smart_cast(value.strip()) criteria[key] = value return criteria
[docs] def get_queryset(self, request, term): """Get the records matching the given search term. :rtype: django.db.models.QuerySet """ criteria = self.get_criteria(request) if term is None: return self.ui.get_queryset(self.request, criteria=criteria) if self.exact_matching: match = "exact" else: match = "contains" if not self.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) qs = self.ui.model.objects.filter(or_condition) if criteria: qs = qs.filter(**criteria) return qs
[docs]class UIAjaxChainedLookupView(UIAjaxMixin, View): """Provide results for a reference field (foreign key or many to many) based on the value of another selected field. The following steps are required to set up a chain: **For This Example*** Imagine you have three models; Book, Part, and Chapter. A chapter is associated with both Book and Part. We want the part drop-down to populate based on the selected book. **Add Chain Options to Dependent Model** Define ``ajax_chained_options`` for Part: .. code-block:: python class PartUI(ui.ModelUI): model = Part ajax_chained_lookup_options = ui.AjaxChainedLookupOptions("book", identifier="pk") # ... This sets up the AJAX view that responds to the chain request. **Tell the Chapter Form to Use Chaining** Next define the chain on ``form_options`` (or ``create_options`` and ``update_options``): .. code-block:: python class ChapterUI(ui.ModelUI): model = Chapter form_options = ui.FormOptions( "book", "part", "title", "body", "description", chained_fields=[ui.ChainedLookup("book", "part")], ) The first field of ``ChainedLookup`` (``book``) is the field used to filter the dropdown. The second field is the name of the dropdown field (``part``). """ criteria = None """Additional criteria, if any, to be applied to the lookup.""" remote_field_identifier = None """Used when the remote field is also a reference field.""" remote_field_name = None """The name of the field that provides the key to chained results.""" # noinspection PyUnusedLocal def get(self, request, *args, **kwargs): output = list() # Add empty option in case the target field is not required. The template (chained_lookup.js) is responsible for # removing this from the output if the target field IS required. output.append({'': "---"}) # jquery.chained passes the name of the remote field and its value via GET. For example: "client=1234" remote_field_value = request.GET.get(self.remote_field_name, "") if len(remote_field_value) == 0: return self.get_json_response(output) # An identifier indicates that the remote field is itself a remote field. If so, the lookup needs to be handled # accordingly. if self.remote_field_identifier is not None: # Cast pk fields by default. This won't work if the pk is not an AutoField. if self.remote_field_identifier == "pk": remote_field_value = int(remote_field_value) lookup = "%s__%s" % (self.remote_field_name, self.remote_field_identifier) criteria = { lookup: remote_field_value, } else: criteria = {self.remote_field_name: remote_field_value} # Apply additional criteria. if self.criteria is not None: criteria.update(self.criteria) # Get the queryset. model = self.ui.model qs = model.objects.filter(**criteria) for row in qs: if not self.ui.site.check_permission(model, request, VERB.LIST, record=row): continue if self.remote_field_identifier is not None: identifier = getattr(row, self.remote_field_identifier) else: identifier = getattr(row, self.remote_field_name) try: label = row.get_choice_name() except AttributeError: label = str(row) output.append({identifier: label}) return self.get_json_response(output)
[docs]class UIAjaxChooserView(UIAjaxMixin, View): """A view for selecting a foreign key via a modal.""" criteria = None """Additional criteria by which results should be filtered.""" fields = None """The fields to be included in the output. Note: ``pk`` is always included.""" template = None """The template to use when ``html`` is the requested ``output_format``.""" # noinspection PyUnusedLocal def get(self, request, *args, **kwargs): output_format = request.GET.get("output_format", "json") _criteria = request.GET.get("criteria", None) if _criteria: criteria = self.criteria or dict() for i in _criteria.split(","): key, value = i.split(":") criteria[key] = value else: criteria = self.criteria qs = self.ui.get_queryset(request, criteria=criteria) # Handle JSON output. if output_format == "json": data = dict() data['records'] = list() data['success'] = True data['count'] = qs.count() for row in qs: if not self.ui.check_permission(request, VERB.LIST, record=row): continue record = {'pk': row.pk} for f in self.fields: record[f] = getattr(row, f) data['records'].append(record) return self.get_json_response(data) # Handle HTML output. controls = self.get_controls() context = { 'columns': controls, 'output_format': output_format, 'records': list(), } for row in qs: if not self.ui.check_permission(request, VERB.LIST, record=row): continue data = list() for control in controls: data.append(control.get_datum(row, request)) identifier = self.ui.get_identifier(row) context['records'].append(Record(identifier, row, data=data)) return self.render_to_response(context, self.template)
[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) if control is None: message = "%s %s has requested a control for field that has not been defined: %s" log.warning(message % (self.ui.meta.model_name, self.__class__.__name__, field_name)) continue controls.append(control) return controls
[docs] def render_to_response(self, context, template): """Render the current context. :param context: The context variables to use for rendering. :type context: dict :param template: The template to use for rendering. :type template: str :rtype: TemplateResponse """ return TemplateResponse(self.request, template, context=context)
[docs]class UIAjaxCreateView(UIAjaxFormMixin, View): """A view for creating a record via AJAX.""" display_name = None """The field on the record to use as the human-friendly name in the form control for the created record.""" identifier = "pk" """The unique identifier of the record to be passed back as the value for the form control for the created record. """ template_name_suffix = "_ajax_create" """The suffix of the template used to display the creation form.""" def get_verb(self): return VERB.AJAX_CREATE
[docs]class UIAjaxDetailView(UIAjaxMixin, View): """Display model detail via AJAX.""" fields = None object = None output_format = "json" template_name_suffix = "_ajax_detail" # noinspection PyUnusedLocal def get(self, request, *args, **kwargs): self.object = self.get_object() if self.object is None: message = _("The requested %s record could not be found." % self.ui.get_verbose_name()) return self.get_json_response({'error': message}) if self.output_format == "html": fields = list() controls = self.get_controls() for control in controls: fields.append(control.get_datum(self.object)) template = os.path.join("superdjango", "ui", "includes", "model_detail_table.html") return self.render_to_response({'fields': fields}, template) else: output = list() controls = self.get_controls() for control in controls: datum = control.get_datum(self.object) output.append([datum.label, datum.display_value]) return self.get_json_response(output)
[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) if control is None: message = "%s %s has requested a control for field that has not been defined: %s" log.warning(message % (self.ui.meta.model_name, self.__class__.__name__, field_name)) continue controls.append(control) return controls
[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() try: criteria = {lookup_field: self.args[0]} except IndexError: message = 'The "%s" lookup_field does not exist in args[0] for the "%s" view.' log.debug(message % (lookup_field, self.__class__.__name__)) return None try: return self.ui.model.objects.get(**criteria) except self.ui.model.DoesNotExist: log.debug("Object not found: %s" % criteria) return None
[docs] def render_to_response(self, context, template): """Render the current context. :param context: The context variables to use for rendering. :type context: dict :param template: The template to use for rendering. :type template: str :rtype: TemplateResponse """ return TemplateResponse(self.request, template, context=context)
[docs]class UIAjaxDragAndDropView(UIAjaxMixin, View): """Support for AJAX drag and drop. The ``hook_ajax_drag_and_drop`` is called after the column change and (optionally) sort order is applied to the record, the function signature is ``hook(column_value, record, sort_order=sort_order)``. """ column_field = "stage" """"The name of the field used to define Kanban columns. This may be a foreign key or a field with choices defined. """ column_model = None """The model class of the foreign key field used to identify columns.""" sort_order_field = "sort_order" """The field to use for sorting a record within the ``column_field``.""" def get(self, request, *args, **kwargs): # Get the column ID or column value. column_model = self._get_column_model() column_value = None if column_model is not None: column_id = request.GET.get("column_id", None) if column_id: try: column_value = column_model.objects.get(pk=int(column_id)) except column_model.DoesNotExist: return self.get_json_response({'error': "Column does not exist: %s" % column_id}) else: column_value = request.GET.get("column_id", None) if column_value is None: return self.get_json_response(dict()) # Extract record IDs. identifiers = request.GET.get("identifiers", None) if identifiers is None: return self.get_json_response(dict()) identifiers = identifiers.split(",") # Update the column field and re-order the records, if supported. sort_order = 1 for identifier in identifiers: try: record = self.ui.model.objects.get(pk=int(identifier)) except self.ui.model.DoesNotExist: continue setattr(record, self.column_field, column_value) if self.ui.is_sort_model(): setattr(record, self.sort_order_field, sort_order) sort_order += 1 for hook in hooks.get("hook_ajax_drag_and_drop"): hook(column_value, record, sort_order=sort_order) if self.ui.is_audit_model(): record.audit(request.user) else: record.save() return self.get_json_response({'success': "Drag and drop finished."}) def _get_column_model(self): """Get the model class the column represents, which is ``None`` if the column is a choice field and not a foreign key. """ if self.column_model is not None: return self.column_model # Instance of ForwardManyToOneDescriptor. column_field = getattr(self.ui.model, self.column_field) try: return column_field.field.remote_field.model except AttributeError: return None
[docs]class UIAjaxListView(UIAjaxMixin, View): """AJAX list of records. .. note:: This is currently only supporting the DataTable list type and is very specific to the output format required by the JavaScript implementation. """ fields = None link_field = None link_to = None minimum_length = 3 prefetch_related = None queryset = None search_fields = None select_related = None serializer = None # noinspection PyUnusedLocal def get(self, request, *args, **kwargs): # from pprint import pprint # pprint(request.GET) queryset = self.get_queryset() queryset = self.get_filtered_queryset(queryset) filtered_records = queryset.count() queryset = self.get_ordered_queryset(queryset) queryset = self.get_paginated_queryset(queryset) # noinspection PyProtectedMember total_records = self.ui.model._default_manager.count() output = { "recordsTotal": total_records, "recordsFiltered": filtered_records, "data": list(), } controls = self.get_controls() for row in queryset: if not self.ui.check_permission(request, VERB.LIST, record=row): continue data = dict() # identifier = self.ui.get_identifier(row) for control in controls: url = None if self.link_field == control.name: url = self.ui.get_url(self.link_to, record=row) data[control.name] = control.get_datum(row, url=url).display_value output['data'].append(data) return self.get_json_response(output)
[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) if control is None: message = "%s %s has requested a control for field that has not been defined: %s" log.warning(message % (self.ui.meta.model_name, self.__class__.__name__, field_name)) continue controls.append(control) return controls
def get_filtered_queryset(self, queryset): if self.search_fields is None: return queryset term = self.request.GET.get('search[value]', "") if len(term) < self.minimum_length: return queryset or_condition = Q() for field_name, match in self.search_fields.items(): lookup = "%s__%s" % (field_name, match) criteria = {lookup: term} or_condition.add(Q(**criteria), Q.OR) return queryset.filter(or_condition)
[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() # noinspection PyProtectedMember queryset = self.ui.model._default_manager.all() 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_ordered_queryset(self, queryset): # print(self.request.GET['order[0][column']) # 'order[0][column]': ['1'], 'order[0][dir]': ['asc'] order_column_index = self.request.GET.get('order[0][column]', None) if order_column_index is None: return queryset order_direction = self.request.GET.get('order[0][dir]') order_by = self.fields[int(order_column_index)] if order_direction == "desc": order_by = "-" + order_by return queryset.order_by(order_by) def get_paginated_queryset(self, queryset): limit = int(self.request.GET.get("length", "10")) start = int(self.request.GET.get('start', "0")) return queryset[start:limit + start]
[docs]class UIAjaxMarkCompleteView(UIAjaxMixin, View): """Mark a record complete.""" callback = None """A callable that overrides all processing. It must accept the model instance and request as arguments and return the data (dict) to be processed through ``get_json_response()``. Data includes: - heading: Optional. The heading, if any, for the toast displayed to the user. - hideAfter: Required. The number of milliseconds to display the toast, or ``False`` to disable automatic fade out. - position: Optional. The position of the toast. - text: Required. The message to display. Additionally (and obviously), the callback is expected to mark the record as complete. """ error_message = None success_message = None update_fields = None def get(self, request, *args, **kwargs): self.object = self.get_object() if self.object is None: message = _("The requested %s record could not be found." % self.ui.get_verbose_name()) return self.get_json_response({'heading': _("Error"), 'text': message, 'hideAfter': False}) if callable(self.callback): data = self.callback(self.object, request) return self.get_json_response(data) self.object.mark_complete(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(request.user, commit=False) self.object.save() data = { 'heading': _("Completed"), 'text': self.success_message, 'hideAfter': 10000, 'position': "top-right", } return self.get_json_response(data)
[docs]class UIAjaxUpdateView(UIAjaxFormMixin, View): """A view for updating a record via AJAX.""" fields = None """A list of field names that may be used during record update.""" display_name = None """The field on the record to use as the human-friendly name in the form control for the updated record.""" identifier = "pk" """The unique identifier of the record to be passed back as the value for the form control for the updated record. """ template_name_suffix = "_ajax_update" """The suffix of the template used to display the update form."""
[docs] def get(self, request, *args, **kwargs): """Get the HTML form for the modal.""" self.args = args self.kwargs = kwargs self.request = request self.object = self.get_object() 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_object(self): lookup_field = self.ui.get_lookup_field() 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(self.ui.model, **criteria)
def get_verb(self): return VERB.AJAX_UPDATE
[docs] def post(self, request, *args, **kwargs): """Get the current model instance.""" self.args = args self.kwargs = kwargs self.request = request self.object = self.get_object() form = self.ui.get_form(request, data=request.POST, files=request.FILES, record=self.object) if form.is_valid(): return self.form_valid(form) return self.form_invalid(form)
class UIAjaxFileUploadView(UIAjaxMixin, View): """A UI view which supports uploading a file via AJAX.""" # https://simpleisbetterthancomplex.com/tutorial/2016/11/22/django-multiple-file-upload-using-ajax.html pass
[docs]class UIAjaxReorderView(UIAjaxMixin, View): sort_order_field = "sort_order" # noinspection PyUnusedLocal def get(self, request, *args, **kwargs): # TODO: Ajax reordering is buggy and needs work. Testing should confirm reorder logic is working. # Extract the record ids. identifiers = request.GET.get("identifiers", None) if identifiers is None: return self.get_json_response(dict()) identifiers = identifiers.split(",") # Get the actual records. lookup_field = self.ui.get_lookup_field() records = list() relative_position = 1 for identifier in identifiers: # Kind of a hack, but if the lookup field is a standard primary key, then we need to convert the identifier # received from GET to an integer or the model lookup will fail. if lookup_field == "pk": identifier = int(identifier) criteria = { lookup_field: identifier, } try: record = self.ui.model.objects.get(**criteria) records.append((record, relative_position)) relative_position += 1 except self.ui.model.DoesNotExist: continue # Now re-order the records. If this is a sortable model, we use that functionality. Otherwise a rather naive # re-sort is used. if hasattr(self.ui.model, "update_sort_order") and callable(self.ui.model.update_sort_order): for record, relative_position in records: record.update_sort_order(record.sort_order + relative_position) else: default_sort_order = 1 for record, relative_position in records: current_sort_order = getattr(record, self.sort_order_field, default_sort_order) new_sort_order = current_sort_order + relative_position setattr(record, self.sort_order_field, new_sort_order) record.save() data = {'success': "Reordered %s %s." % (len(records), self.ui.get_verbose_name_plural())} return self.get_json_response(data)
[docs]class UIAjaxSearchView(UIAjaxMixin, View): """Facilitates model search via AJAX.""" case_sensitive = False """Indicates whether the search will be case sensitive.""" criteria = None """Additional criteria by which results should be filtered.""" exact_matching = False """When ``True``, exact matching is required.""" fields = None """A list of field names on the model to be searched for results.""" label_field = None """The field to use for the label of each result. If omitted, ``get_display_name()`` will be called. Failing that, the string representation of the result is used. """ link_to = VERB.DETAIL """The name of the view (verb) to which search results should be linked.""" minimum_length = 3 """The minimum number of characters required to generate a queryset.""" term_keyword = "term" """The GET keyword used to identify the search term.""" # noinspection PyUnusedLocal
[docs] def get(self, request, *args, **kwargs): """Get the search results.""" # Prepare for output. output = dict() # Get the term from GET and validate it. term = request.GET.get(self.term_keyword, None) if term is None: output[0] = { 'label': _("GET term_keyword missing or has no value: %s" % self.term_keyword), 'url': "#", } return self.get_json_response(output) if len(term) < self.minimum_length: output[0] = { 'label': _("Enter %s or more characters." % self.minimum_length), 'url': "#", } return self.get_json_response(output) # Search for records. qs = self.get_queryset(term) if qs.count() == 0: output[0] = { 'label': _('No results for "%s".' % term), 'url': "#", } return self.get_json_response(output) # Add records to the output. for row in qs: # Make sure the user is allowed to see (or otherwise do something) with the record. if not self.ui.check_permission(request, self.link_to, record=row): continue # Get the record identifier (pk, uuid, etc.) identifier = self.ui.get_identifier(row) # Get the link for the found record. url = self.ui.get_url(self.link_to, record=row) if url is None: url = "#" # How the record is displayed in the output. if self.label_field is not None: label = getattr(row, self.label_field) else: label = self.get_display_name(row) # Add the record to the output. output[identifier] = { 'label': label, 'url': url, } return self.get_json_response(output)
[docs] def get_queryset(self, term): """Get the records matching the given search term. :rtype: django.db.models.QuerySet """ match = "contains" if self.exact_matching: match = "exact" if not self.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) qs = self.ui.model.objects.filter(or_condition) if self.criteria is not None: qs = qs.filter(**self.criteria) return qs