forms.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. from mptt.forms import TreeNodeChoiceField, TreeNodeMultipleChoiceField
  2. from django import forms
  3. from django.db import models
  4. from django.utils.html import conditional_escape, mark_safe
  5. from django.utils.translation import ugettext_lazy as _
  6. from misago.core.forms import YesNoSwitch
  7. from misago.core.validators import validate_sluggable
  8. from misago.threads.threadtypes import trees_map
  9. from . import THREADS_ROOT_NAME
  10. from .models import Category, CategoryRole
  11. """
  12. Fields
  13. """
  14. class AdminCategoryFieldMixin(object):
  15. def __init__(self, *args, **kwargs):
  16. self.base_level = kwargs.pop('base_level', 1)
  17. kwargs['level_indicator'] = kwargs.get('level_indicator', '- - ')
  18. threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
  19. queryset = Category.objects.filter(tree_id=threads_tree_id)
  20. if not kwargs.pop('include_root', False):
  21. queryset = queryset.exclude(special_role="root_category")
  22. kwargs.setdefault('queryset', queryset)
  23. super(AdminCategoryFieldMixin, self).__init__(*args, **kwargs)
  24. def _get_level_indicator(self, obj):
  25. level = getattr(obj, obj._mptt_meta.level_attr) - self.base_level
  26. if level > 0:
  27. return mark_safe(conditional_escape(self.level_indicator) * level)
  28. else:
  29. return ''
  30. class AdminCategoryChoiceField(AdminCategoryFieldMixin, TreeNodeChoiceField):
  31. pass
  32. class AdminCategoryMultipleChoiceField(AdminCategoryFieldMixin, TreeNodeMultipleChoiceField):
  33. pass
  34. """
  35. Forms
  36. """
  37. class CategoryFormBase(forms.ModelForm):
  38. name = forms.CharField(label=_("Name"), validators=[validate_sluggable()])
  39. description = forms.CharField(
  40. label=_("Description"),
  41. max_length=2048,
  42. required=False,
  43. widget=forms.Textarea(attrs={'rows': 3}),
  44. help_text=_("Optional description explaining category intented purpose.")
  45. )
  46. css_class = forms.CharField(
  47. label=_("CSS class"),
  48. required=False,
  49. help_text=_(
  50. "Optional CSS class used to customize this category "
  51. "appearance from templates."
  52. )
  53. )
  54. is_closed = YesNoSwitch(
  55. label=_("Closed category"),
  56. required=False,
  57. help_text=_("Only members with valid permissions can post in "
  58. "closed categories.")
  59. )
  60. css_class = forms.CharField(
  61. label=_("CSS class"),
  62. required=False,
  63. help_text=_(
  64. "Optional CSS class used to customize this category "
  65. "appearance from templates."
  66. )
  67. )
  68. prune_started_after = forms.IntegerField(
  69. label=_("Thread age"),
  70. min_value=0,
  71. help_text=_(
  72. "Prune thread if number of days since its creation is "
  73. "greater than specified. Enter 0 to disable this "
  74. "pruning criteria."
  75. )
  76. )
  77. prune_replied_after = forms.IntegerField(
  78. label=_("Last reply"),
  79. min_value=0,
  80. help_text=_(
  81. "Prune thread if number of days since last reply is "
  82. "greater than specified. Enter 0 to disable this "
  83. "pruning criteria."
  84. )
  85. )
  86. class Meta:
  87. model = Category
  88. fields = [
  89. 'name',
  90. 'description',
  91. 'css_class',
  92. 'is_closed',
  93. 'prune_started_after',
  94. 'prune_replied_after',
  95. 'archive_pruned_in',
  96. ]
  97. def clean_copy_permissions(self):
  98. data = self.cleaned_data['copy_permissions']
  99. if data and data.pk == self.instance.pk:
  100. message = _("Permissions cannot be copied from category into itself.")
  101. raise forms.ValidationError(message)
  102. return data
  103. def clean_archive_pruned_in(self):
  104. data = self.cleaned_data['archive_pruned_in']
  105. if data and data.pk == self.instance.pk:
  106. message = _("Category cannot act as archive for itself.")
  107. raise forms.ValidationError(message)
  108. return data
  109. def clean(self):
  110. data = super(CategoryFormBase, self).clean()
  111. self.instance.set_name(data.get('name'))
  112. return data
  113. def CategoryFormFactory(instance):
  114. parent_queryset = Category.objects.all_categories(True).order_by('lft')
  115. if instance.pk:
  116. not_siblings = models.Q(lft__lt=instance.lft)
  117. not_siblings = not_siblings | models.Q(rght__gt=instance.rght)
  118. parent_queryset = parent_queryset.filter(not_siblings)
  119. return type(
  120. 'CategoryFormFinal', (CategoryFormBase, ), {
  121. 'new_parent':
  122. AdminCategoryChoiceField(
  123. label=_("Parent category"),
  124. queryset=parent_queryset,
  125. initial=instance.parent,
  126. empty_label=None
  127. ),
  128. 'copy_permissions':
  129. AdminCategoryChoiceField(
  130. label=_("Copy permissions"),
  131. help_text=_(
  132. "You can replace this category permissions with "
  133. "permissions copied from category selected here."
  134. ),
  135. queryset=Category.objects.all_categories(),
  136. empty_label=_("Don't copy permissions"),
  137. required=False
  138. ),
  139. 'archive_pruned_in':
  140. AdminCategoryChoiceField(
  141. label=_("Archive"),
  142. help_text=_(
  143. "Instead of being deleted, pruned threads can be "
  144. "moved to designated category."
  145. ),
  146. queryset=Category.objects.all_categories(),
  147. empty_label=_("Don't archive pruned threads"),
  148. required=False
  149. ),
  150. }
  151. )
  152. class DeleteCategoryFormBase(forms.ModelForm):
  153. class Meta:
  154. model = Category
  155. fields = []
  156. def clean(self):
  157. data = super(DeleteCategoryFormBase, self).clean()
  158. if data.get('move_threads_to'):
  159. if data['move_threads_to'].pk == self.instance.pk:
  160. message = _("You are trying to move this category threads to " "itself.")
  161. raise forms.ValidationError(message)
  162. moving_to_child = self.instance.has_child(data['move_threads_to'])
  163. if moving_to_child and not data.get('move_children_to'):
  164. message = _(
  165. "You are trying to move this category threads to a "
  166. "child category that will be deleted together with "
  167. "this category."
  168. )
  169. raise forms.ValidationError(message)
  170. return data
  171. def DeleteFormFactory(instance):
  172. content_queryset = Category.objects.all_categories().order_by('lft')
  173. fields = {
  174. 'move_threads_to':
  175. AdminCategoryChoiceField(
  176. label=_("Move category threads to"),
  177. queryset=content_queryset,
  178. initial=instance.parent,
  179. empty_label=_('Delete with category'),
  180. required=False
  181. )
  182. }
  183. not_siblings = models.Q(lft__lt=instance.lft)
  184. not_siblings = not_siblings | models.Q(rght__gt=instance.rght)
  185. children_queryset = Category.objects.all_categories(True)
  186. children_queryset = children_queryset.filter(not_siblings).order_by('lft')
  187. if children_queryset.exists():
  188. fields['move_children_to'] = AdminCategoryChoiceField(
  189. label=_("Move child categories to"),
  190. queryset=children_queryset,
  191. empty_label=_('Delete with category'),
  192. required=False
  193. )
  194. return type('DeleteCategoryFormFinal', (DeleteCategoryFormBase, ), fields)
  195. class CategoryRoleForm(forms.ModelForm):
  196. name = forms.CharField(label=_("Role name"))
  197. class Meta:
  198. model = CategoryRole
  199. fields = ['name']
  200. def RoleCategoryACLFormFactory(category, category_roles, selected_role):
  201. attrs = {
  202. 'category':
  203. category,
  204. 'role':
  205. forms.ModelChoiceField(
  206. label=_("Role"),
  207. required=False,
  208. queryset=category_roles,
  209. initial=selected_role,
  210. empty_label=_("No access")
  211. )
  212. }
  213. return type('RoleCategoryACLForm', (forms.Form, ), attrs)
  214. def CategoryRolesACLFormFactory(role, category_roles, selected_role):
  215. attrs = {
  216. 'role':
  217. role,
  218. 'category_role':
  219. forms.ModelChoiceField(
  220. label=_("Role"),
  221. required=False,
  222. queryset=category_roles,
  223. initial=selected_role,
  224. empty_label=_("No access")
  225. )
  226. }
  227. return type('CategoryRolesACLForm', (forms.Form, ), attrs)