list.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. from urllib import urlencode
  2. from django.core.paginator import Paginator, EmptyPage
  3. from django.core.urlresolvers import reverse
  4. from django.shortcuts import redirect
  5. from misago.core.exceptions import ExplicitFirstPage
  6. from misago.admin.views.generic.base import AdminView
  7. class ListView(AdminView):
  8. """
  9. Admin items list view
  10. Uses following attributes:
  11. template = template name used to render items list
  12. items_per_page = number of items displayed on single page
  13. (enter 0 or don't define for no pagination)
  14. ordering = tuple of tuples defining allowed orderings
  15. typles should follow this format: (name, order_by)
  16. """
  17. template = 'list.html'
  18. items_per_page = 0
  19. ordering = None
  20. extra_actions = None
  21. @classmethod
  22. def add_item_action(cls, name, icon, link, style=None):
  23. if not cls.extra_actions:
  24. cls.extra_actions = []
  25. cls.extra_actions.append({
  26. 'name': name,
  27. 'icon': icon,
  28. 'link': link,
  29. 'style': style,
  30. })
  31. def get_queryset(self):
  32. return self.get_model().objects.all()
  33. def paginate_items(self, context, page):
  34. try:
  35. page = int(page)
  36. if page == 1:
  37. raise ExplicitFirstPage()
  38. elif page == 0:
  39. page = 1
  40. except ValueError:
  41. page_no = 1
  42. context['paginator'] = Paginator(context['items'],
  43. self.items_per_page,
  44. allow_empty_first_page=True)
  45. context['page'] = context['paginator'].page(page)
  46. context['items'] = context['page'].object_list
  47. """
  48. Filter list items
  49. """
  50. SearchForm = None
  51. def get_search_form(self, request):
  52. return self.SearchForm
  53. @property
  54. def filters_session_key(self):
  55. return 'misago_admin_%s_filters' % self.root_link
  56. def get_filters_from_GET(self, SearchForm, request):
  57. form = SearchForm(request.GET)
  58. form.is_valid()
  59. return self.clean_filtering_data(form.cleaned_data)
  60. def get_filters_from_session(self, SearchForm, request):
  61. session_filters = request.session.get(self.filters_session_key, {})
  62. form = SearchForm(session_filters)
  63. form.is_valid()
  64. return self.clean_filtering_data(form.cleaned_data)
  65. def clean_filtering_data(self, data):
  66. for key, value in data.items():
  67. if not value:
  68. del data[key]
  69. return data
  70. def get_filtering_methods(self, request):
  71. SearchForm = self.get_search_form(request)
  72. return {
  73. 'GET': self.get_filters_from_GET(SearchForm, request),
  74. 'session': self.get_filters_from_session(SearchForm, request),
  75. }
  76. def get_filtering_method_to_use(self, methods):
  77. for method in ('GET', 'session'):
  78. if methods.get(method):
  79. return methods.get(method)
  80. else:
  81. return {}
  82. def apply_filtering_on_context(self, context, active_filters, SearchForm):
  83. context['active_filters'] = active_filters
  84. context['search_form'] = SearchForm(initial=context['active_filters'])
  85. if context['active_filters']:
  86. context['items'] = context['search_form'].filter_queryset(
  87. active_filters, context['items'])
  88. """
  89. Order list items
  90. """
  91. @property
  92. def ordering_session_key(self):
  93. return 'misago_admin_%s_order_by' % self.root_link
  94. def get_ordering_from_GET(self, request):
  95. sort = request.GET.get('sort')
  96. if request.GET.get('direction') == 'desc':
  97. new_ordering = '-%s' % sort
  98. elif request.GET.get('direction') == 'asc':
  99. new_ordering = sort
  100. else:
  101. new_ordering = '?nope'
  102. return self.clean_ordering(new_ordering)
  103. def get_ordering_from_session(self, request):
  104. new_ordering = request.session.get(self.ordering_session_key)
  105. return self.clean_ordering(new_ordering)
  106. def clean_ordering(self, new_ordering):
  107. for order_by, name in self.ordering:
  108. if order_by == new_ordering:
  109. return order_by
  110. else:
  111. return None
  112. def get_ordering_methods(self, request):
  113. return {
  114. 'GET': self.get_ordering_from_GET(request),
  115. 'session': self.get_ordering_from_session(request),
  116. 'default': self.clean_ordering(self.ordering[0][0]),
  117. }
  118. def get_ordering_method_to_use(self, methods):
  119. for method in ('GET', 'session', 'default'):
  120. if methods.get(method):
  121. return methods.get(method)
  122. def set_ordering_in_context(self, context, method):
  123. for order_by, name in self.ordering:
  124. order_as_dict = {
  125. 'type': 'desc' if order_by[0] == '-' else 'asc',
  126. 'order_by': order_by,
  127. 'name': name,
  128. }
  129. if order_by == method:
  130. context['order'] = order_as_dict
  131. context['items'] = context['items'].order_by(
  132. order_as_dict['order_by'])
  133. elif order_as_dict['name']:
  134. if order_as_dict['type'] == 'desc':
  135. order_as_dict['order_by'] = order_as_dict['order_by'][1:]
  136. context['order_by'].append(order_as_dict)
  137. """
  138. Querystrings builder
  139. """
  140. def make_querystrings(self, context):
  141. values = {}
  142. filter_values = {}
  143. order_values = {}
  144. if context['active_filters']:
  145. filter_values = context['active_filters']
  146. values.update(filter_values)
  147. if context['order_by']:
  148. order_values = {
  149. 'sort': context['order']['order_by'],
  150. 'direction': context['order']['type'],
  151. }
  152. if order_values['sort'][0] == '-':
  153. # We don't start sorting criteria with minus in querystring
  154. order_values['sort'] = order_values['sort'][1:]
  155. values.update(order_values)
  156. if values:
  157. context['querystring'] = '?%s' % urlencode(values)
  158. if order_values:
  159. context['query_order'] = order_values
  160. if filter_values:
  161. context['query_filters'] = filter_values
  162. """
  163. Dispatch response
  164. """
  165. def dispatch(self, request, *args, **kwargs):
  166. extra_actions_list = self.extra_actions or []
  167. refresh_querystring = False
  168. context = {
  169. 'items': self.get_queryset(),
  170. 'paginator': None,
  171. 'page': None,
  172. 'order_by': [],
  173. 'order': None,
  174. 'search_form': None,
  175. 'active_filters': {},
  176. 'querystring': '',
  177. 'query_order': {},
  178. 'query_filters': {},
  179. 'extra_actions': extra_actions_list,
  180. 'extra_actions_len': len(extra_actions_list),
  181. }
  182. if self.ordering:
  183. ordering_methods = self.get_ordering_methods(request)
  184. used_method = self.get_ordering_method_to_use(ordering_methods)
  185. self.set_ordering_in_context(context, used_method)
  186. if (ordering_methods['GET'] and
  187. ordering_methods['GET'] != ordering_methods['session']):
  188. # Store GET ordering in session for future requests
  189. session_key = self.ordering_session_key
  190. request.session[session_key] = ordering_methods['GET']
  191. if context['order_by'] and not ordering_methods['GET']:
  192. # Make view redirect to itself with querystring,
  193. # So address ball contains copy-friendly link
  194. refresh_querystring = True
  195. SearchForm = self.get_search_form(request)
  196. if SearchForm:
  197. filtering_methods = self.get_filtering_methods(request)
  198. active_filters = self.get_filtering_method_to_use(filtering_methods)
  199. self.apply_filtering_on_context(context, active_filters, SearchForm)
  200. if (filtering_methods['GET'] and
  201. filtering_methods['GET'] != filtering_methods['session']):
  202. # Store GET filters in session for future requests
  203. session_key = self.filters_session_key
  204. request.session[session_key] = filtering_methods['GET']
  205. if request.GET.get('clear_filters'):
  206. # Clear filters from querystring
  207. request.session.pop(self.filters_session_key, None)
  208. context['active_filters'] = {}
  209. if context['active_filters'] and not filtering_methods['GET']:
  210. # Make view redirect to itself with querystring,
  211. # So address ball contains copy-friendly link
  212. refresh_querystring = True
  213. self.make_querystrings(context)
  214. if self.items_per_page:
  215. try:
  216. self.paginate_items(context, kwargs.get('page', 0))
  217. except EmptyPage:
  218. return redirect(
  219. '%s%s' % (reverse(self.root_link), context['querystring']))
  220. if refresh_querystring:
  221. return redirect('%s%s' % (request.path, context['querystring']))
  222. return self.render(request, context)