admin.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. from django.contrib.auth import get_user_model
  2. from django.utils.translation import ugettext_lazy as _, ungettext
  3. from misago.conf import settings
  4. from misago.core import forms, threadstore
  5. from misago.core.validators import validate_sluggable
  6. from misago.acl.models import Role
  7. from misago.users.models import (BANS_CHOICES, RESTRICTIONS_CHOICES,
  8. Ban, Rank, WarningLevel)
  9. from misago.users.validators import (validate_username, validate_email,
  10. validate_password)
  11. """
  12. Users
  13. """
  14. class UserBaseForm(forms.ModelForm):
  15. username = forms.CharField(
  16. label=_("Username"))
  17. title = forms.CharField(
  18. label=_("Custom title"), required=False)
  19. email = forms.EmailField(
  20. label=_("E-mail address"))
  21. class Meta:
  22. model = get_user_model()
  23. fields = ['username', 'email', 'title']
  24. def clean_username(self):
  25. data = self.cleaned_data['username']
  26. validate_username(data, exclude=self.instance)
  27. return data
  28. def clean_email(self):
  29. data = self.cleaned_data['email']
  30. validate_email(data, exclude=self.instance)
  31. return data
  32. def clean_new_password(self):
  33. data = self.cleaned_data['new_password']
  34. if data:
  35. validate_password(data)
  36. return data
  37. def clean_roles(self):
  38. data = self.cleaned_data['roles']
  39. for role in data:
  40. if role.special_role == 'authenticated':
  41. break
  42. else:
  43. message = _('All registered members must have "Member" role.')
  44. raise forms.ValidationError(message)
  45. return data
  46. class NewUserForm(UserBaseForm):
  47. new_password = forms.CharField(
  48. label=_("Password"), widget=forms.PasswordInput)
  49. class Meta:
  50. model = get_user_model()
  51. fields = ['username', 'email', 'title']
  52. class EditUserForm(UserBaseForm):
  53. new_password = forms.CharField(
  54. label=_("Change password to"),
  55. widget=forms.PasswordInput,
  56. required=False)
  57. signature = forms.CharField(
  58. label=_("Signature contents"),
  59. widget=forms.Textarea(attrs={'rows': 3}),
  60. required=False)
  61. is_signature_banned = forms.YesNoSwitch(
  62. label=_("Ban editing signature"),
  63. help_text=_("Changing this to yes will ban user from "
  64. "making changes to his/her signature."))
  65. signature_ban_user_message = forms.CharField(
  66. label=_("User ban message"),
  67. help_text=_("Optional message for user explaining "
  68. "why he/she is banned form editing signature."),
  69. widget=forms.Textarea(attrs={'rows': 3}),
  70. required=False)
  71. signature_ban_staff_message = forms.CharField(
  72. label=_("Staff ban message"),
  73. help_text=_("Optional message for forum team members explaining "
  74. "why user is banned form editing signature."),
  75. widget=forms.Textarea(attrs={'rows': 3}),
  76. required=False)
  77. class Meta:
  78. model = get_user_model()
  79. fields = ['username', 'email', 'title', 'signature',
  80. 'is_signature_banned', 'signature_ban_user_message',
  81. 'signature_ban_staff_message']
  82. def clean_signature(self):
  83. data = self.cleaned_data['signature']
  84. length_limit = settings.signature_length_max
  85. if len(data) > length_limit:
  86. raise forms.ValidationError(ungettext(
  87. "Signature can't be longer than %(limit)s character.",
  88. "Signature can't be longer than %(limit)s characters.",
  89. length_limit) % {'limit': length_limit})
  90. return data
  91. def UserFormFactory(FormType, instance):
  92. extra_fields = {}
  93. extra_fields['rank'] = forms.ModelChoiceField(
  94. label=_("Rank"),
  95. help_text=_("Ranks are used to group and distinguish users. "
  96. "They are also used to add permissions to groups of "
  97. "users."),
  98. queryset=Rank.objects.order_by('name'),
  99. initial=instance.rank)
  100. roles = Role.objects.order_by('name')
  101. extra_fields['roles'] = forms.ModelMultipleChoiceField(
  102. label=_("Roles"),
  103. help_text=_('Individual roles of this user. '
  104. 'All users must have "member" role.'),
  105. queryset=roles,
  106. initial=instance.roles.all() if instance.pk else None,
  107. widget=forms.CheckboxSelectMultiple)
  108. return type('UserFormFinal', (FormType,), extra_fields)
  109. def StaffFlagUserFormFactory(FormType, instance, add_staff_field):
  110. FormType = UserFormFactory(FormType, instance)
  111. if add_staff_field:
  112. staff_levels = (
  113. (0, _("No access")),
  114. (1, _("Administrator")),
  115. (2, _("Superadmin")),
  116. )
  117. staff_fields = {
  118. 'staff_level': forms.TypedChoiceField(
  119. label=_("Admin level"),
  120. help_text=_('Only administrators can access admin sites. '
  121. 'In addition to admin site access, superadmins '
  122. 'can also change other members admin levels.'),
  123. coerce=int,
  124. choices=staff_levels,
  125. initial=instance.staff_level),
  126. }
  127. return type('StaffUserForm', (FormType,), staff_fields)
  128. else:
  129. return FormType
  130. class SearchUsersFormBase(forms.Form):
  131. username = forms.CharField(label=_("Username starts with"), required=False)
  132. email = forms.CharField(label=_("E-mail starts with"), required=False)
  133. inactive = forms.YesNoSwitch(label=_("Inactive only"))
  134. is_staff = forms.YesNoSwitch(label=_("Admins only"))
  135. def filter_queryset(self, search_criteria, queryset):
  136. criteria = search_criteria
  137. if criteria.get('username'):
  138. queryset = queryset.filter(
  139. username_slug__startswith=criteria.get('username').lower())
  140. if criteria.get('email'):
  141. queryset = queryset.filter(
  142. email__istartswith=criteria.get('email'))
  143. if criteria.get('rank'):
  144. queryset = queryset.filter(
  145. rank_id=criteria.get('rank'))
  146. if criteria.get('role'):
  147. queryset = queryset.filter(
  148. roles__id=criteria.get('role'))
  149. if criteria.get('inactive'):
  150. queryset = queryset.filter(requires_activation__gt=0)
  151. if criteria.get('is_staff'):
  152. queryset = queryset.filter(is_staff=True)
  153. return queryset
  154. def SearchUsersForm(*args, **kwargs):
  155. """
  156. Factory that uses cache for ranks and roles,
  157. and makes those ranks and roles typed choice fields that play nice
  158. with passing values via GET
  159. """
  160. ranks_choices = threadstore.get('misago_admin_ranks_choices', 'nada')
  161. if ranks_choices == 'nada':
  162. ranks_choices = [('', _("All ranks"))]
  163. for rank in Rank.objects.order_by('name').iterator():
  164. ranks_choices.append((rank.pk, rank.name))
  165. threadstore.set('misago_admin_ranks_choices', ranks_choices)
  166. roles_choices = threadstore.get('misago_admin_roles_choices', 'nada')
  167. if roles_choices == 'nada':
  168. roles_choices = [('', _("All roles"))]
  169. for role in Role.objects.order_by('name').iterator():
  170. roles_choices.append((role.pk, role.name))
  171. threadstore.set('misago_admin_roles_choices', roles_choices)
  172. extra_fields = {
  173. 'rank': forms.TypedChoiceField(label=_("Has rank"),
  174. coerce=int,
  175. required=False,
  176. choices=ranks_choices),
  177. 'role': forms.TypedChoiceField(label=_("Has role"),
  178. coerce=int,
  179. required=False,
  180. choices=roles_choices)
  181. }
  182. FinalForm = type('SearchUsersFormFinal',
  183. (SearchUsersFormBase,),
  184. extra_fields)
  185. return FinalForm(*args, **kwargs)
  186. """
  187. Ranks
  188. """
  189. class RankForm(forms.ModelForm):
  190. name = forms.CharField(
  191. label=_("Name"),
  192. validators=[validate_sluggable()],
  193. help_text=_('Short and descriptive name of all users with this rank. '
  194. '"The Team" or "Game Masters" are good examples.'))
  195. title = forms.CharField(
  196. label=_("User title"), required=False,
  197. help_text=_('Optional, singular version of rank name displayed by '
  198. 'user names. For example "GM" or "Dev".'))
  199. description = forms.CharField(
  200. label=_("Description"), max_length=2048, required=False,
  201. widget=forms.Textarea(attrs={'rows': 3}),
  202. help_text=_("Optional description explaining function or status of "
  203. "members distincted with this rank."))
  204. roles = forms.ModelMultipleChoiceField(
  205. label=_("User roles"), queryset=Role.objects.order_by('name'),
  206. required=False, widget=forms.CheckboxSelectMultiple,
  207. help_text=_('Rank can give additional roles to users with it.'))
  208. css_class = forms.CharField(
  209. label=_("CSS class"), required=False,
  210. help_text=_("Optional css class added to content belonging to this "
  211. "rank owner."))
  212. is_tab = forms.BooleanField(
  213. label=_("Give rank dedicated tab on users list"), required=False,
  214. help_text=_("Selecting this option will make users with this rank "
  215. "easily discoverable by others trough dedicated page on "
  216. "forum users list."))
  217. is_on_index = forms.BooleanField(
  218. label=_("Show users online on forum index"), required=False,
  219. help_text=_("Selecting this option will make forum inform other "
  220. "users of their availability by displaying them on forum "
  221. "index page."))
  222. class Meta:
  223. model = Rank
  224. fields = [
  225. 'name',
  226. 'description',
  227. 'css_class',
  228. 'title',
  229. 'roles',
  230. 'is_tab',
  231. 'is_on_index',
  232. ]
  233. def clean(self):
  234. data = super(RankForm, self).clean()
  235. self.instance.set_name(data.get('name'))
  236. return data
  237. """
  238. Bans
  239. """
  240. class BanForm(forms.ModelForm):
  241. test = forms.TypedChoiceField(
  242. label=_("Ban type"),
  243. coerce=int,
  244. choices=BANS_CHOICES)
  245. banned_value = forms.CharField(
  246. label=_("Banned value"), max_length=250,
  247. help_text=_('This value is case-insensitive and accepts asterisk (*) '
  248. 'for rought matches. For example, making IP ban for value '
  249. '"83.*" will ban all IP addresses beginning with "83.".'),
  250. error_messages={
  251. 'max_length': _("Banned value can't be longer than 250 characters.")
  252. })
  253. user_message = forms.CharField(
  254. label=_("User message"), required=False, max_length=1000,
  255. help_text=_("Optional message displayed instead of default one."),
  256. widget=forms.Textarea(attrs={'rows': 3}),
  257. error_messages={
  258. 'max_length': _("Message can't be longer than 1000 characters.")
  259. })
  260. staff_message = forms.CharField(
  261. label=_("Team message"), required=False, max_length=1000,
  262. help_text=_("Optional ban message for moderators and administrators."),
  263. widget=forms.Textarea(attrs={'rows': 3}),
  264. error_messages={
  265. 'max_length': _("Message can't be longer than 1000 characters.")
  266. })
  267. valid_until = forms.DateField(
  268. label=_("Expiration date"),
  269. required=False, input_formats=['%m-%d-%Y'],
  270. widget=forms.DateInput(
  271. format='%m-%d-%Y', attrs={'data-date-format': 'MM-DD-YYYY'}),
  272. help_text=_('Leave this field empty for this ban to never expire.'))
  273. class Meta:
  274. model = Ban
  275. fields = [
  276. 'test',
  277. 'banned_value',
  278. 'user_message',
  279. 'staff_message',
  280. 'valid_until',
  281. ]
  282. def clean_banned_value(self):
  283. data = self.cleaned_data['banned_value']
  284. while '**' in data:
  285. data = data.replace('**', '*')
  286. if data == '*':
  287. raise forms.ValidationError(_("Banned value is too vague."))
  288. return data
  289. SARCH_BANS_CHOICES = (
  290. ('', _('All bans')),
  291. ('names', _('Usernames')),
  292. ('emails', _('E-mails')),
  293. ('ips', _('IPs')),
  294. )
  295. class SearchBansForm(forms.Form):
  296. test = forms.ChoiceField(
  297. label=_("Type"), required=False,
  298. choices=SARCH_BANS_CHOICES)
  299. value = forms.CharField(
  300. label=_("Banned value begins with"),
  301. required=False)
  302. state = forms.ChoiceField(
  303. label=_("State"), required=False,
  304. choices=(
  305. ('', _('All states')),
  306. ('valid', _('Valid bans')),
  307. ('expired', _('Expired bans')),
  308. ))
  309. def filter_queryset(self, search_criteria, queryset):
  310. criteria = search_criteria
  311. if criteria.get('test') == 'names':
  312. queryset = queryset.filter(test=0)
  313. if criteria.get('test') == 'emails':
  314. queryset = queryset.filter(test=1)
  315. if criteria.get('test') == 'ips':
  316. queryset = queryset.filter(test=2)
  317. if criteria.get('value'):
  318. queryset = queryset.filter(
  319. banned_value__startswith=criteria.get('value').lower())
  320. if criteria.get('state') == 'valid':
  321. queryset = queryset.filter(is_valid=True)
  322. if criteria.get('state') == 'expired':
  323. queryset = queryset.filter(is_valid=False)
  324. return queryset
  325. """
  326. Warning levels
  327. """
  328. class WarningLevelForm(forms.ModelForm):
  329. name = forms.CharField(label=_("Level name"), max_length=255)
  330. description = forms.CharField(
  331. label=_("Description"), required=False, max_length=1000,
  332. help_text=_("Optional message description displayed to users with "
  333. "this warning level."),
  334. widget=forms.Textarea(attrs={'rows': 3}),
  335. error_messages={
  336. 'max_length': _("Description can't be longer "
  337. "than 1000 characters.")
  338. })
  339. length_in_minutes = forms.IntegerField(
  340. label=_("Length in minutes"), min_value=0,
  341. help_text=_("Enter number of minutes since this warning level was "
  342. "imposed on member until it's reduced, or 0 to make "
  343. "this warning level permanent."))
  344. restricts_posting_replies = forms.TypedChoiceField(
  345. label=_("Posting replies"),
  346. coerce=int, choices=RESTRICTIONS_CHOICES)
  347. restricts_posting_threads = forms.TypedChoiceField(
  348. label=_("Posting threads"),
  349. coerce=int, choices=RESTRICTIONS_CHOICES)
  350. class Meta:
  351. model = WarningLevel
  352. fields = [
  353. 'name',
  354. 'description',
  355. 'length_in_minutes',
  356. 'restricts_posting_replies',
  357. 'restricts_posting_threads',
  358. ]