forms.py 8.1 KB

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