generic.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. from urllib import urlencode
  2. from django.contrib import messages
  3. from django.core.paginator import Paginator, EmptyPage
  4. from django.db import transaction
  5. from django.shortcuts import redirect
  6. from django.utils.translation import ugettext_lazy as _
  7. from django.views.generic import View
  8. from misago.core.exceptions import ExplicitFirstPage
  9. from misago.admin import site
  10. from misago.admin.views import render
  11. class AdminBaseMixin(object):
  12. """
  13. Admin mixin abstraciton used for configuring admin CRUD views.
  14. Takes following attributes:
  15. Model = Model instance
  16. root_link = name of link leading to root action (eg. list of all items
  17. templates_dir = directory with templates
  18. message_404 = string used in "requested item not found" messages
  19. """
  20. Model = None
  21. root_link = None
  22. templates_dir = None
  23. message_404 = None
  24. def get_model(self):
  25. """
  26. Basic method for retrieving Model, used in cases such as User model.
  27. """
  28. return self.Model
  29. class AdminView(View):
  30. def final_template(self):
  31. return '%s/%s' % (self.templates_dir, self.template)
  32. def current_link(self, request):
  33. matched_url = request.resolver_match.url_name
  34. return '%s:%s' % (request.resolver_match.namespace, matched_url)
  35. def process_context(self, request, context):
  36. """
  37. Simple hook for extending and manipulating template context.
  38. """
  39. return context
  40. def render(self, request, context=None):
  41. context = context or {}
  42. context['root_link'] = self.root_link
  43. context['current_link'] = self.current_link(request)
  44. context = self.process_context(request, context)
  45. return render(request, self.final_template(), context)
  46. class ListView(AdminView):
  47. """
  48. Admin items list view
  49. Uses following attributes:
  50. template = template name used to render items list
  51. items_per_page = number of items displayed on single page
  52. (enter 0 or don't define for no pagination)
  53. ordering = tuple of tuples defining allowed orderings
  54. typles should follow this format: (name, order_by)
  55. """
  56. template = 'list.html'
  57. items_per_page = 0
  58. ordering = None
  59. extra_actions = None
  60. @classmethod
  61. def add_item_action(cls, name, icon, link, style=None):
  62. if not cls.extra_actions:
  63. cls.extra_actions = []
  64. cls.extra_actions.append({
  65. 'name': name,
  66. 'icon': icon,
  67. 'link': link,
  68. 'style': style,
  69. })
  70. def get_queryset(self):
  71. return self.get_model().objects.all()
  72. def paginate_items(self, context, page):
  73. try:
  74. page = int(page)
  75. if page == 1:
  76. raise ExplicitFirstPage()
  77. elif page == 0:
  78. page = 1
  79. except ValueError:
  80. page_no = 1
  81. context['paginator'] = Paginator(context['items'],
  82. self.items_per_page,
  83. allow_empty_first_page=True)
  84. context['page'] = context['paginator'].page(page)
  85. context['items'] = context['page'].object_list
  86. @property
  87. def filters_token(self):
  88. return '%s:filters' % self.root_link
  89. def search_form(self, request, context):
  90. pass
  91. def filter_items(self, context):
  92. pass
  93. @property
  94. def ordering_session_key(self):
  95. return 'misago_admin_%s_order_by' % self.root_link
  96. def get_ordering_from_get(self, request):
  97. sort = request.GET.get('sort')
  98. if request.GET.get('direction') == 'desc':
  99. new_ordering = '-%s' % sort
  100. elif request.GET.get('direction') == 'asc':
  101. new_ordering = sort
  102. else:
  103. new_ordering = '?nope'
  104. return self.clean_ordering(new_ordering)
  105. def get_ordering_from_session(self, request):
  106. new_ordering = request.session.get(self.ordering_session_key)
  107. return self.clean_ordering(new_ordering)
  108. def get_default_ordering(self):
  109. pass
  110. def clean_ordering(self, new_ordering):
  111. for order_by, name in self.ordering:
  112. if order_by == new_ordering:
  113. return order_by
  114. else:
  115. return None
  116. def get_ordering_methods(self, request):
  117. methods = {
  118. 'GET': self.get_ordering_from_get(request),
  119. 'session': self.get_ordering_from_session(request),
  120. 'default': self.get_default_ordering(),
  121. }
  122. if methods['GET'] and methods['GET'] != methods['session']:
  123. request.session[self.ordering_session_key] = methods['GET']
  124. return methods
  125. def get_ordering_method(self, methods):
  126. for method in ('GET', 'session', 'default'):
  127. if methods.get(method):
  128. return methods.get(method)
  129. def order_items(self, method, context):
  130. for order_by, name in self.ordering:
  131. order_as_dict = {
  132. 'type': 'desc' if order_by[0] == '-' else 'asc',
  133. 'order_by': order_by,
  134. 'name': name,
  135. }
  136. if order_by == method:
  137. context['order'] = order_as_dict
  138. context['items'] = context['items'].order_by(
  139. order_as_dict['order_by'])
  140. elif order_as_dict['name']:
  141. if order_as_dict['type'] == 'desc':
  142. order_as_dict['order_by'] = order_as_dict['order_by'][1:]
  143. context['order_by'].append(order_as_dict)
  144. def make_querystrings(self, request, context):
  145. values = {}
  146. filter_values = {}
  147. order_values = {}
  148. if context['active_filters']:
  149. filter_values = context['active_filters']
  150. values.update(filter_values)
  151. if context['order']:
  152. order_values = {
  153. 'sort': context['order']['order_by'],
  154. 'direction': context['order']['type'],
  155. }
  156. values.update(order_values)
  157. if values:
  158. context['querystring'] = '?%s' % urlencode(values)
  159. if order_values:
  160. context['querystring_order'] = '?%s' % urlencode(order_values)
  161. if filter_values:
  162. context['querystring_filter'] = '?%s' % urlencode(filter_values)
  163. def dispatch(self, request, *args, **kwargs):
  164. active_filters = request.session.get(self.filters_token, None)
  165. extra_actions_list = self.extra_actions or []
  166. set_querystring = False
  167. context = {
  168. 'items': self.get_queryset(),
  169. 'paginator': None,
  170. 'page': None,
  171. 'order_by': [],
  172. 'order': None,
  173. 'search_form': None,
  174. 'active_filters': active_filters,
  175. 'querystring': '',
  176. 'querystring_order': '',
  177. 'querystring_filter': '',
  178. 'extra_actions': extra_actions_list,
  179. 'extra_actions_len': len(extra_actions_list),
  180. }
  181. if self.ordering:
  182. ordering_methods = self.get_ordering_methods(request)
  183. current_method = self.get_ordering_method(ordering_methods)
  184. self.order_items(current_method, context)
  185. if len(self.ordering) > 1 and not ordering_methods.get('GET'):
  186. set_querystring = True
  187. self.search_form(request, context)
  188. if active_filters:
  189. self.filter_items(context)
  190. if self.items_per_page:
  191. self.paginate_items(context, kwargs.get('page', 0))
  192. self.make_querystrings(request, context)
  193. if set_querystring:
  194. return redirect('%s%s' % (request.path, context['querystring']))
  195. return self.render(request, context)
  196. class TargetedView(AdminView):
  197. def check_permissions(self, request, target):
  198. pass
  199. def get_target(self, kwargs):
  200. if len(kwargs) == 1:
  201. select_for_update = self.get_model().objects.select_for_update()
  202. return select_for_update.get(pk=kwargs[kwargs.keys()[0]])
  203. else:
  204. return self.get_model()()
  205. def get_target_or_none(self, request, kwargs):
  206. try:
  207. return self.get_target(kwargs)
  208. except self.get_model().DoesNotExist:
  209. return None
  210. def dispatch(self, request, *args, **kwargs):
  211. with transaction.atomic():
  212. target = self.get_target_or_none(request, kwargs)
  213. if not target:
  214. messages.error(request, self.message_404)
  215. return redirect(self.root_link)
  216. error = self.check_permissions(request, target)
  217. if error:
  218. messages.error(request, error)
  219. return redirect(self.root_link)
  220. return self.real_dispatch(request, target)
  221. def real_dispatch(self, request, target):
  222. pass
  223. class FormView(TargetedView):
  224. Form = None
  225. template = 'form.html'
  226. def create_form_type(self, request):
  227. return self.Form
  228. def initialize_form(self, FormType, request):
  229. if request.method == 'POST':
  230. return FormType(request.POST, request.FILES)
  231. else:
  232. return FormType()
  233. def handle_form(self, form, request):
  234. raise NotImplementedError(
  235. "You have to define your own handle_form method to handle "
  236. "form submissions.")
  237. def real_dispatch(self, request, target):
  238. FormType = self.create_form_type(request)
  239. form = self.initialize_form(FormType, request)
  240. if request.method == 'POST' and form.is_valid():
  241. response = self.handle_form(form, request)
  242. if response:
  243. return response
  244. elif 'stay' in request.POST:
  245. return redirect(request.path)
  246. else:
  247. return redirect(self.root_link)
  248. return self.render(request, {'form': form})
  249. class ModelFormView(FormView):
  250. message_submit = None
  251. def create_form_type(self, request, target):
  252. return self.Form
  253. def initialize_form(self, FormType, request, target):
  254. if request.method == 'POST':
  255. return FormType(request.POST, request.FILES, instance=target)
  256. else:
  257. return FormType(instance=target)
  258. def handle_form(self, form, request, target):
  259. form.instance.save()
  260. if self.message_submit:
  261. messages.success(request, self.message_submit % target.name)
  262. def real_dispatch(self, request, target):
  263. FormType = self.create_form_type(request, target)
  264. form = self.initialize_form(FormType, request, target)
  265. if request.method == 'POST' and form.is_valid():
  266. response = self.handle_form(form, request, target)
  267. if response:
  268. return response
  269. elif 'stay' in request.POST:
  270. return redirect(request.path)
  271. else:
  272. return redirect(self.root_link)
  273. return self.render(request, {'form': form, 'target': target})
  274. class ButtonView(TargetedView):
  275. def real_dispatch(self, request, target):
  276. if request.method == 'POST':
  277. new_response = self.button_action(request, target)
  278. if new_response:
  279. return new_response
  280. return redirect(self.root_link)
  281. def button_action(self, request, target):
  282. raise NotImplementedError("You have to define custom button_action.")