123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- from django.contrib import messages
- from django.core.paginator import EmptyPage, Paginator
- from django.db import transaction
- from django.shortcuts import redirect
- from django.urls import reverse
- from django.utils.translation import ugettext_lazy as _
- from misago.core.exceptions import ExplicitFirstPage
- from six.moves.urllib.parse import urlencode
- from .base import AdminView
- class MassActionError(Exception):
- pass
- class ListView(AdminView):
- """
- Admin items list view
- Uses following attributes:
- template = template name used to render items list
- items_per_page = number of items displayed on single page
- (enter 0 or don't define for no pagination)
- ordering = tuple of tuples defining allowed orderings
- typles should follow this format: (name, order_by)
- """
- template = 'list.html'
- items_per_page = 0
- ordering = None
- extra_actions = None
- mass_actions = None
- selection_label = _('Selected: 0')
- empty_selection_label = _('Select items')
- @classmethod
- def add_mass_action(cls, action, name, icon, confirmation=None):
- if not cls.mass_actions:
- cls.mass_actions = []
- cls.extra_actions.append({
- 'action': action,
- 'name': name,
- 'icon': icon,
- 'confirmation': confirmation
- })
- @classmethod
- def add_item_action(cls, name, icon, link, style=None):
- if not cls.extra_actions:
- cls.extra_actions = []
- cls.extra_actions.append({
- 'name': name,
- 'icon': icon,
- 'link': link,
- 'style': style,
- })
- def get_queryset(self):
- return self.get_model().objects.all()
- """
- Dispatch response
- """
- def dispatch(self, request, *args, **kwargs):
- mass_actions_list = self.mass_actions or []
- extra_actions_list = self.extra_actions or []
- refresh_querystring = False
- context = {
- 'items': self.get_queryset(),
- 'paginator': None,
- 'page': None,
- 'order_by': [],
- 'order': None,
- 'search_form': None,
- 'active_filters': {},
- 'querystring': '',
- 'query_order': {},
- 'query_filters': {},
- 'selected_items': [],
- 'selection_label': self.selection_label,
- 'empty_selection_label': self.empty_selection_label,
- 'mass_actions': mass_actions_list,
- 'extra_actions': extra_actions_list,
- 'extra_actions_len': len(extra_actions_list),
- }
- if request.method == 'POST' and mass_actions_list:
- try:
- response = self.handle_mass_action(request, context)
- if response:
- return response
- else:
- return redirect(request.path_info)
- except MassActionError as e:
- messages.error(request, e.args[0])
- if self.ordering:
- ordering_methods = self.get_ordering_methods(request)
- used_method = self.get_ordering_method_to_use(ordering_methods)
- self.set_ordering_in_context(context, used_method)
- if (ordering_methods['GET'] and
- ordering_methods['GET'] != ordering_methods['session']):
- # Store GET ordering in session for future requests
- session_key = self.ordering_session_key
- request.session[session_key] = ordering_methods['GET']
- if context['order_by'] and not ordering_methods['GET']:
- # Make view redirect to itself with querystring,
- # So address ball contains copy-friendly link
- refresh_querystring = True
- SearchForm = self.get_search_form(request)
- if SearchForm:
- filtering_methods = self.get_filtering_methods(request)
- active_filters = self.get_filtering_method_to_use(
- filtering_methods)
- if request.GET.get('clear_filters'):
- # Clear filters from querystring
- request.session.pop(self.filters_session_key, None)
- active_filters = {}
- self.apply_filtering_on_context(
- context, active_filters, SearchForm)
- if (filtering_methods['GET'] and
- filtering_methods['GET'] != filtering_methods['session']):
- # Store GET filters in session for future requests
- session_key = self.filters_session_key
- request.session[session_key] = filtering_methods['GET']
- if request.GET.get('set_filters'):
- # Force store filters in session
- session_key = self.filters_session_key
- request.session[session_key] = context['active_filters']
- refresh_querystring = True
- if context['active_filters'] and not filtering_methods['GET']:
- # Make view redirect to itself with querystring,
- # so address bar contains copy-friendly link
- refresh_querystring = True
- self.make_querystring(context)
- if self.items_per_page:
- try:
- self.paginate_items(context, kwargs.get('page', 0))
- except EmptyPage:
- return redirect(
- '%s%s' % (reverse(self.root_link), context['querystring']))
- if refresh_querystring and not request.GET.get('redirected'):
- return redirect('%s%s' % (request.path_info, context['querystring']))
- return self.render(request, context)
- def paginate_items(self, context, page):
- try:
- page = int(page)
- if page == 1:
- raise ExplicitFirstPage()
- elif page == 0:
- page = 1
- except ValueError:
- page = 1
- context['paginator'] = Paginator(
- context['items'], self.items_per_page, allow_empty_first_page=True)
- context['page'] = context['paginator'].page(page)
- context['items'] = context['page'].object_list
- """
- Filter list items
- """
- SearchForm = None
- def get_search_form(self, request):
- return self.SearchForm
- @property
- def filters_session_key(self):
- return 'misago_admin_%s_filters' % self.root_link
- def get_filters_from_GET(self, SearchForm, request):
- form = SearchForm(request.GET)
- form.is_valid()
- return self.clean_filtering_data(form.cleaned_data)
- def get_filters_from_session(self, SearchForm, request):
- session_filters = request.session.get(self.filters_session_key, {})
- form = SearchForm(session_filters)
- form.is_valid()
- return self.clean_filtering_data(form.cleaned_data)
- def clean_filtering_data(self, data):
- for key, value in list(data.items()):
- if not value:
- del data[key]
- return data
- def get_filtering_methods(self, request):
- SearchForm = self.get_search_form(request)
- methods = {
- 'GET': self.get_filters_from_GET(SearchForm, request),
- 'session': self.get_filters_from_session(SearchForm, request),
- }
- if request.GET.get('set_filters'):
- methods['session'] = {}
- return methods
- def get_filtering_method_to_use(self, methods):
- for method in ('GET', 'session'):
- if methods.get(method):
- return methods.get(method)
- else:
- return {}
- def apply_filtering_on_context(self, context, active_filters, SearchForm):
- context['active_filters'] = active_filters
- context['search_form'] = SearchForm(initial=context['active_filters'])
- if context['active_filters']:
- context['items'] = context['search_form'].filter_queryset(
- active_filters, context['items'])
- """
- Order list items
- """
- @property
- def ordering_session_key(self):
- return 'misago_admin_%s_order_by' % self.root_link
- def get_ordering_from_GET(self, request):
- sort = request.GET.get('sort')
- if request.GET.get('direction') == 'desc':
- new_ordering = '-%s' % sort
- elif request.GET.get('direction') == 'asc':
- new_ordering = sort
- else:
- new_ordering = '?nope'
- return self.clean_ordering(new_ordering)
- def get_ordering_from_session(self, request):
- new_ordering = request.session.get(self.ordering_session_key)
- return self.clean_ordering(new_ordering)
- def clean_ordering(self, new_ordering):
- for order_by, name in self.ordering:
- if order_by == new_ordering:
- return order_by
- else:
- return None
- def get_ordering_methods(self, request):
- return {
- 'GET': self.get_ordering_from_GET(request),
- 'session': self.get_ordering_from_session(request),
- 'default': self.clean_ordering(self.ordering[0][0]),
- }
- def get_ordering_method_to_use(self, methods):
- for method in ('GET', 'session', 'default'):
- if methods.get(method):
- return methods.get(method)
- def set_ordering_in_context(self, context, method):
- for order_by, name in self.ordering:
- order_as_dict = {
- 'type': 'desc' if order_by[0] == '-' else 'asc',
- 'order_by': order_by,
- 'name': name,
- }
- if order_by == method:
- context['order'] = order_as_dict
- context['items'] = context['items'].order_by(
- order_as_dict['order_by'])
- elif order_as_dict['name']:
- if order_as_dict['type'] == 'desc':
- order_as_dict['order_by'] = order_as_dict['order_by'][1:]
- context['order_by'].append(order_as_dict)
- """
- Mass actions
- """
- def handle_mass_action(self, request, context):
- limit = self.items_per_page or 64
- action = self.select_mass_action(request.POST.get('action'))
- items = [x for x in request.POST.getlist('selected_items')[:limit]]
- context['selected_items'] = items
- if not context['selected_items']:
- raise MassActionError(_("You have to select one or more items."))
- action_queryset = context['items'].filter(pk__in=items)
- if not action_queryset.exists():
- raise MassActionError(_("You have to select one or more items."))
- action_callable = getattr(self, 'action_%s' % action['action'])
- if action.get('is_atomic', True):
- with transaction.atomic():
- return action_callable(request, action_queryset)
- else:
- return action_callable(request, action_queryset)
- def select_mass_action(self, action):
- for definition in self.mass_actions:
- if definition['action'] == action:
- return definition
- else:
- raise MassActionError(_("Action is not allowed."))
- """
- Querystring builder
- """
- def make_querystring(self, context):
- values = {}
- filter_values = {}
- order_values = {}
- if context['active_filters']:
- filter_values = context['active_filters']
- values.update(filter_values)
- if context['order_by']:
- order_values = {
- 'sort': context['order']['order_by'],
- 'direction': context['order']['type'],
- }
- if order_values['sort'][0] == '-':
- # We don't start sorting criteria with minus in querystring
- order_values['sort'] = order_values['sort'][1:]
- values.update(order_values)
- if values:
- values['redirected'] = 1
- context['querystring'] = '?%s' % urlencode(values, 'utf-8')
- if order_values:
- context['query_order'] = order_values
- if filter_values:
- context['query_filters'] = filter_values
|