Source code for dal_alight.views

from collections import OrderedDict

from django import http
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db.models import F
from django.utils.html import format_html
from django.utils.translation import gettext as _
from django.views.generic.list import View

from dal.views import BaseQuerySetView, ViewMixin


[docs] class AlightQuerySetView(BaseQuerySetView): """Autocomplete view returning HTML fragments for autocomplete-light. Each result is rendered as ``<div data-value="{pk}">{label}</div>``. When ``create_field`` is set and the query has no case-insensitive exact match on page 1, a ``<div data-create>Create "…"</div>`` is appended. POST is handled by the inherited ``BaseQuerySetView.post()`` which creates the object and returns a ``<div data-value="…">…</div>`` HTML fragment. """
[docs] def post(self, request, *args, **kwargs): """Create an object and return an HTML fragment for the new choice.""" try: result = self._post(request) except ValidationError as error: msg = None if hasattr(error, 'message_dict'): msgs = error.message_dict.get(self.create_field) if msgs: msg = msgs[0] if isinstance(msgs, list) else msgs return http.HttpResponse(str(msg or error), status=422) if isinstance(result, http.HttpResponse): return result return http.HttpResponse( format_html( '<div data-value="{}">{}</div>', self.get_result_value(result), self.get_selected_result_label(result), ), content_type='text/html; charset=utf-8', )
def render_to_response(self, context): q = self.request.GET.get('q', '') html = [] for result in context['object_list']: label = self.get_result_label(result) html.append(format_html( '<div data-value="{}">{}</div>', self.get_result_value(result), label, )) if self._should_show_create(context, q): html.append(format_html( '<div data-create data-value="{}">{}</div>', q, _('Create "%(new_value)s"') % {'new_value': q}, )) if self.has_more(context): html.append(format_html( '<div data-next-page="{}">{}</div>', context['page_obj'].next_page_number(), _('More results…'), )) return http.HttpResponse( ''.join(html), content_type='text/html; charset=utf-8', )
[docs] class AlightGroupQuerySetView(AlightQuerySetView): """Grouped queryset view — mirrors Select2GroupQuerySetView. Results are rendered as:: <div class="autocomplete-light-group">Group label</div> <div data-value="pk">Label</div> Group header divs carry no ``data-value`` so the component treats them as non-selectable. """ group_by_related = None related_field_name = 'name'
[docs] def get_queryset(self): if not self.group_by_related: raise ImproperlyConfigured('Missing group_by_related.') return super().get_queryset().annotate( _group_name=F(f'{self.group_by_related}__{self.related_field_name}') )
def render_to_response(self, context): groups = OrderedDict() for result in context['object_list']: groups.setdefault(getattr(result, '_group_name'), []).append(result) html = [] for group_name, results in groups.items(): html.append(format_html( '<div class="autocomplete-light-group">{}</div>', group_name or '', )) for result in results: label = self.get_result_label(result) html.append(format_html( '<div data-value="{}">{}</div>', self.get_result_value(result), label, )) return http.HttpResponse( ''.join(html), content_type='text/html; charset=utf-8', )
[docs] class AlightListView(ViewMixin, View): """Autocomplete from a list of strings or (value, label) pairs. Mirrors ``Select2ListView`` but returns HTML fragments instead of JSON. Override ``get_list()`` to return your items. Each item may be a plain string (value == label) or a ``(value, label)`` tuple/list. Define ``create(text)`` on the view to enable POST-based creation; it should return the created text/value or raise an error. """ def get_list(self): return [] def _parse_item(self, item): """Return (value, label) for a list item.""" if isinstance(item, (list, tuple)) and len(item) >= 2: return str(item[0]), str(item[1]) return str(item), str(item) def get(self, request, *args, **kwargs): results = self.get_list() q = self.q if q: q_lower = q.lower() filtered = [] for item in results: value, label = self._parse_item(item) if q_lower in label.lower(): filtered.append((value, label)) else: filtered = [self._parse_item(item) for item in results] html = [] for value, label in filtered: html.append(format_html( '<div data-value="{}">{}</div>', value, label, )) if q and hasattr(self, 'create'): html.append(format_html( '<div data-create data-value="{}">{}</div>', q, _('Create "%(new_value)s"') % {'new_value': q}, )) return http.HttpResponse( ''.join(html), content_type='text/html; charset=utf-8', ) def post(self, request, *args, **kwargs): if not hasattr(self, 'create'): return http.HttpResponse(status=405) text = request.POST.get('text', None) if text is None: return http.HttpResponseBadRequest() result = self.create(text) if result is None: return http.HttpResponseBadRequest() return http.HttpResponse( format_html('<div data-value="{}">{}</div>', result, result), content_type='text/html; charset=utf-8', )
[docs] class AlightTagAutocompleteView(AlightQuerySetView): """Convenience base for taggit tag autocomplete views. Returns ``result.name`` as the option value so ``TaggitAlight`` can match tags by name rather than by PK. """
[docs] def get_result_value(self, result): return result.name
[docs] class AlightGroupListView(AlightListView): """Grouped list view — mirrors Select2GroupListView. ``get_list()`` should return items of the form ``((group_id, group_label), [(value, label), …])`` or plain strings grouped by a leading group item. Simpler usage: return ``[(group, item), …]`` tuples where the first element is the group name and the second is the item string or ``(value, label)`` tuple. Items with ``group=None`` are ungrouped. """ def _parse_grouped(self, results): """Yield (group_name, value, label) triples.""" for entry in results: if isinstance(entry, (list, tuple)) and len(entry) == 2: group, item = entry if isinstance(item, (list, tuple)): # item is (value, label) value, label = str(item[0]), str(item[1]) else: value = label = str(item) group_name = str(group) if group is not None else None else: group_name = None value = label = str(entry) yield group_name, value, label def get(self, request, *args, **kwargs): results = self.get_list() q = self.q q_lower = q.lower() if q else None groups = OrderedDict() for group_name, value, label in self._parse_grouped(results): if q_lower and q_lower not in label.lower(): continue groups.setdefault(group_name, []).append((value, label)) html = [] ungrouped = groups.pop(None, []) for value, label in ungrouped: html.append(format_html('<div data-value="{}">{}</div>', value, label)) for group_name, items in groups.items(): html.append(format_html( '<div class="autocomplete-light-group">{}</div>', group_name, )) for value, label in items: html.append(format_html('<div data-value="{}">{}</div>', value, label)) return http.HttpResponse( ''.join(html), content_type='text/html; charset=utf-8', )