generic.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. from django.contrib import messages
  2. from django.core.paginator import Paginator, EmptyPage
  3. from django.db import transaction
  4. from django.shortcuts import redirect
  5. from django.utils.translation import ugettext_lazy as _
  6. from django.views.generic import View
  7. from misago.core.exceptions import ExplicitFirstPage
  8. from misago.admin import site
  9. from misago.admin.views import render
  10. class AdminBaseMixin(object):
  11. """
  12. Admin mixin abstraciton used for configuring admin CRUD views.
  13. Takes following attributes:
  14. Model = Model instance
  15. root_link = name of link leading to root action (eg. list of all items
  16. templates_dir = directory with templates
  17. message_404 = string used in "requested item not found" messages
  18. """
  19. Model = None
  20. root_link = None
  21. templates_dir = None
  22. message_404 = None
  23. def get_model(self):
  24. """
  25. Basic method for retrieving Model, used in cases such as User model.
  26. """
  27. return self.Model
  28. class AdminView(View):
  29. def final_template(self):
  30. return '%s/%s' % (self.templates_dir, self.template)
  31. def current_link(self, request):
  32. matched_url = request.resolver_match.url_name
  33. return '%s:%s' % (request.resolver_match.namespace, matched_url)
  34. def process_context(self, request, context):
  35. """
  36. Simple hook for extending and manipulating template context.
  37. """
  38. return context
  39. def render(self, request, context=None):
  40. context = context or {}
  41. context['root_link'] = self.root_link
  42. context['current_link'] = self.current_link(request)
  43. context = self.process_context(request, context)
  44. return render(request, self.final_template(), context)
  45. class ListView(AdminView):
  46. """
  47. Admin items list view
  48. Uses following attributes:
  49. template = template name used to render items list
  50. items_per_page = number of items displayed on single page
  51. (enter 0 or don't define for no pagination)
  52. ordering = tuple of tuples defining allowed orderings
  53. typles should follow this format: (name, order_by)
  54. """
  55. template = 'list.html'
  56. items_per_page = 0
  57. ordering = None
  58. def get_queryset(self):
  59. return self.get_model().objects.all()
  60. def paginate_items(self, context, page):
  61. try:
  62. page = int(page)
  63. if page == 1:
  64. raise ExplicitFirstPage()
  65. elif page == 0:
  66. page = 1
  67. except ValueError:
  68. page_no = 1
  69. context['paginator'] = Paginator(context['items'],
  70. self.items_per_page,
  71. allow_empty_first_page=True)
  72. context['page'] = context['paginator'].page(page)
  73. context['items'] = context['page'].object_list
  74. def set_filters(self, request):
  75. pass
  76. def filter_items(self, request, context):
  77. context['is_filtering'] = False
  78. def ordering_session_key(self):
  79. return 'misago_admin_%s_order_by' % self.root_link
  80. def set_ordering(self, request, new_order):
  81. for order_by, name in self.ordering:
  82. if order_by == new_order:
  83. request.session[self.ordering_session_key()] = order_by
  84. return redirect(self.current_link(request))
  85. else:
  86. messages.error(request, _("New sorting method is incorrect."))
  87. raise ValueError()
  88. def order_items(self, request, context):
  89. current_ordering = request.session.get(self.ordering_session_key())
  90. for order_by, name in self.ordering:
  91. order_as_dict = {
  92. 'order_by': order_by,
  93. 'type': 'desc' if order_by[0] == '-' else 'asc',
  94. 'name': name,
  95. }
  96. if order_by == current_ordering:
  97. context['order'] = order_as_dict
  98. context['items'] = context['items'].order_by(
  99. order_as_dict['order_by'])
  100. else:
  101. context['order_by'].append(order_as_dict)
  102. if not context['order']:
  103. current_ordering = context['order_by'].pop(0)
  104. context['order'] = current_ordering
  105. context['items'] = context['items'].order_by(
  106. current_ordering['order_by'])
  107. def dispatch(self, request, *args, **kwargs):
  108. context = {
  109. 'items': self.get_queryset(),
  110. 'paginator': None,
  111. 'page': None,
  112. 'order_by': [],
  113. 'order': None,
  114. }
  115. if self.ordering:
  116. if request.method == 'POST' and 'order_by' in request.POST:
  117. try:
  118. return self.set_ordering(request,
  119. request.POST.get('order_by'))
  120. except ValueError:
  121. pass
  122. self.order_items(request, context)
  123. if self.items_per_page:
  124. self.paginate_items(context, kwargs.get('page', 0))
  125. return self.render(request, context)
  126. class TargetedView(AdminView):
  127. def check_permissions(self, request, target):
  128. pass
  129. def get_target(self, kwargs):
  130. if len(kwargs) == 1:
  131. select_for_update = self.get_model().objects.select_for_update()
  132. return select_for_update.get(pk=kwargs[kwargs.keys()[0]])
  133. else:
  134. return self.get_model()()
  135. def get_target_or_none(self, request, kwargs):
  136. try:
  137. return self.get_target(kwargs)
  138. except self.get_model().DoesNotExist:
  139. return None
  140. def dispatch(self, request, *args, **kwargs):
  141. with transaction.atomic():
  142. target = self.get_target_or_none(request, kwargs)
  143. if not target:
  144. messages.error(request, self.message_404)
  145. return redirect(self.root_link)
  146. error = self.check_permissions(request, target)
  147. if error:
  148. messages.error(request, error)
  149. return redirect(self.root_link)
  150. return self.real_dispatch(request, target)
  151. def real_dispatch(self, request, target):
  152. pass
  153. class FormView(TargetedView):
  154. Form = None
  155. template = 'form.html'
  156. def create_form_type(self, request):
  157. return self.Form
  158. def initialize_form(self, FormType, request):
  159. if request.method == 'POST':
  160. return FormType(request.POST, request.FILES)
  161. else:
  162. return FormType()
  163. def handle_form(self, form, request):
  164. raise NotImplementedError(
  165. "You have to define your own handle_form method to handle "
  166. "form submissions.")
  167. def real_dispatch(self, request, target):
  168. FormType = self.create_form_type(request)
  169. form = self.initialize_form(FormType, request)
  170. if request.method == 'POST' and form.is_valid():
  171. response = self.handle_form(form, request)
  172. if response:
  173. return response
  174. elif 'stay' in request.POST:
  175. return redirect(request.path)
  176. else:
  177. return redirect(self.root_link)
  178. return self.render(request, {'form': form})
  179. class ModelFormView(FormView):
  180. message_submit = None
  181. def create_form_type(self, request, target):
  182. return self.Form
  183. def initialize_form(self, FormType, request, target):
  184. if request.method == 'POST':
  185. return FormType(request.POST, request.FILES, instance=target)
  186. else:
  187. return FormType(instance=target)
  188. def transaction_pre_save(self, form, request, target):
  189. pass
  190. def transaction_after_save(self, form, request, target):
  191. pass
  192. def handle_form(self, form, request, target):
  193. form.instance.save()
  194. if self.message_submit:
  195. messages.success(request, self.message_submit % target.name)
  196. def real_dispatch(self, request, target):
  197. FormType = self.create_form_type(request, target)
  198. form = self.initialize_form(FormType, request, target)
  199. if request.method == 'POST' and form.is_valid():
  200. response = self.handle_form(form, request, target)
  201. if response:
  202. return response
  203. elif 'stay' in request.POST:
  204. return redirect(request.path)
  205. else:
  206. return redirect(self.root_link)
  207. return self.render(request, {'form': form, 'target': target})
  208. class ButtonView(TargetedView):
  209. def real_dispatch(self, request, target):
  210. if request.method == 'POST':
  211. new_response = self.button_action(request, target)
  212. if new_response:
  213. return new_response
  214. return redirect(self.root_link)
  215. def button_action(self, request, target):
  216. raise NotImplementedError("You have to define custom button_action.")