123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715 |
- from django import forms
- from django.db.models import Q
- from django.contrib.auth import get_user_model
- from django.contrib.auth.password_validation import validate_password
- from django.utils.translation import ugettext_lazy as _
- from django.utils.translation import ungettext
- from misago.acl.models import Role
- from misago.conf import settings
- from misago.core import threadstore
- from misago.core.forms import IsoDateTimeField, YesNoSwitch
- from misago.core.validators import validate_sluggable
- from misago.users.models import Ban, DataDownload, Rank
- from misago.users.profilefields import profilefields
- from misago.users.utils import hash_email
- from misago.users.validators import validate_email, validate_username
- UserModel = get_user_model()
- class UserBaseForm(forms.ModelForm):
- username = forms.CharField(label=_("Username"))
- title = forms.CharField(label=_("Custom title"), required=False)
- email = forms.EmailField(label=_("E-mail address"))
- class Meta:
- model = UserModel
- fields = ['username', 'email', 'title']
- def clean_username(self):
- data = self.cleaned_data['username']
- validate_username(data, exclude=self.instance)
- return data
- def clean_email(self):
- data = self.cleaned_data['email']
- validate_email(data, exclude=self.instance)
- return data
- def clean_new_password(self):
- data = self.cleaned_data['new_password']
- if data:
- validate_password(data, user=self.instance)
- return data
- def clean_roles(self):
- data = self.cleaned_data['roles']
- for role in data:
- if role.special_role == 'authenticated':
- break
- else:
- message = _('All registered members must have "Member" role.')
- raise forms.ValidationError(message)
- return data
- class NewUserForm(UserBaseForm):
- new_password = forms.CharField(
- label=_("Password"),
- strip=False,
- widget=forms.PasswordInput,
- )
- class Meta:
- model = UserModel
- fields = ['username', 'email', 'title']
- class EditUserForm(UserBaseForm):
- IS_STAFF_LABEL = _("Is administrator")
- IS_STAFF_HELP_TEXT = _(
- "Designates whether the user can log into admin sites. "
- "If Django admin site is enabled, this user will need "
- "additional permissions assigned within it to admin "
- "Django modules."
- )
- IS_SUPERUSER_LABEL = _("Is superuser")
- IS_SUPERUSER_HELP_TEXT = _(
- "Only administrators can access admin sites. "
- "In addition to admin site access, superadmins "
- "can also change other members admin levels."
- )
- IS_ACTIVE_LABEL = _('Is active')
- IS_ACTIVE_HELP_TEXT = _(
- "Designates whether this user should be treated as active. "
- "Turning this off is non-destructible way to remove user accounts."
- )
- IS_ACTIVE_STAFF_MESSAGE_LABEL = _("Staff message")
- IS_ACTIVE_STAFF_MESSAGE_HELP_TEXT = _(
- "Optional message for forum team members explaining "
- "why user's account has been disabled."
- )
- new_password = forms.CharField(
- label=_("Change password to"),
- strip=False,
- widget=forms.PasswordInput,
- required=False,
- )
- is_avatar_locked = YesNoSwitch(
- label=_("Lock avatar"),
- help_text=_(
- "Setting this to yes will stop user from changing "
- "his/her avatar, and will reset his/her avatar to "
- "procedurally generated one."
- )
- )
- avatar_lock_user_message = forms.CharField(
- label=_("User message"),
- help_text=_(
- "Optional message for user explaining "
- "why he/she is banned form changing avatar."
- ),
- widget=forms.Textarea(attrs={'rows': 3}),
- required=False
- )
- avatar_lock_staff_message = forms.CharField(
- label=_("Staff message"),
- help_text=_(
- "Optional message for forum team members explaining "
- "why user is banned form changing avatar."
- ),
- widget=forms.Textarea(attrs={'rows': 3}),
- required=False
- )
- signature = forms.CharField(
- label=_("Signature contents"),
- widget=forms.Textarea(attrs={'rows': 3}),
- required=False,
- )
- is_signature_locked = YesNoSwitch(
- label=_("Lock signature"),
- help_text=_(
- "Setting this to yes will stop user from "
- "making changes to his/her signature."
- )
- )
- signature_lock_user_message = forms.CharField(
- label=_("User message"),
- help_text=_("Optional message to user explaining why his/hers signature is locked."),
- widget=forms.Textarea(attrs={'rows': 3}),
- required=False
- )
- signature_lock_staff_message = forms.CharField(
- label=_("Staff message"),
- help_text=_("Optional message to team members explaining why user signature is locked."),
- widget=forms.Textarea(attrs={'rows': 3}),
- required=False
- )
- is_hiding_presence = YesNoSwitch(label=_("Hides presence"))
- limits_private_thread_invites_to = forms.TypedChoiceField(
- label=_("Who can add user to private threads"),
- coerce=int,
- choices=UserModel.LIMIT_INVITES_TO_CHOICES
- )
- subscribe_to_started_threads = forms.TypedChoiceField(
- label=_("Started threads"), coerce=int, choices=UserModel.SUBSCRIBE_CHOICES
- )
- subscribe_to_replied_threads = forms.TypedChoiceField(
- label=_("Replid threads"), coerce=int, choices=UserModel.SUBSCRIBE_CHOICES
- )
- class Meta:
- model = UserModel
- fields = [
- 'username',
- 'email',
- 'title',
- 'is_avatar_locked',
- 'avatar_lock_user_message',
- 'avatar_lock_staff_message',
- 'signature',
- 'is_signature_locked',
- 'is_hiding_presence',
- 'limits_private_thread_invites_to',
- 'signature_lock_user_message',
- 'signature_lock_staff_message',
- 'subscribe_to_started_threads',
- 'subscribe_to_replied_threads',
- ]
- def __init__(self, *args, **kwargs):
- self.request = kwargs.pop('request')
- super().__init__(*args, **kwargs)
- profilefields.add_fields_to_admin_form(self.request, self.instance, self)
- def get_profile_fields_groups(self):
- profile_fields_groups = []
- for group in self._profile_fields_groups:
- fields_group = {
- 'name': group['name'],
- 'fields': [],
- }
- for fieldname in group['fields']:
- fields_group['fields'].append(self[fieldname])
- profile_fields_groups.append(fields_group)
- return profile_fields_groups
- def clean_signature(self):
- data = self.cleaned_data['signature']
- length_limit = settings.signature_length_max
- if len(data) > length_limit:
- raise forms.ValidationError(
- ungettext(
- "Signature can't be longer than %(limit)s character.",
- "Signature can't be longer than %(limit)s characters.",
- length_limit,
- ) % {'limit': length_limit}
- )
- return data
- def clean(self):
- data = super().clean()
- return profilefields.clean_form(self.request, self.instance, self, data)
- def UserFormFactory(FormType, instance):
- extra_fields = {}
- extra_fields['rank'] = forms.ModelChoiceField(
- label=_("Rank"),
- help_text=_(
- "Ranks are used to group and distinguish users. They are "
- "also used to add permissions to groups of users."
- ),
- queryset=Rank.objects.order_by('name'),
- initial=instance.rank
- )
- roles = Role.objects.order_by('name')
- extra_fields['roles'] = forms.ModelMultipleChoiceField(
- label=_("Roles"),
- help_text=_('Individual roles of this user. All users must have "member" role.'),
- queryset=roles,
- initial=instance.roles.all() if instance.pk else None,
- widget=forms.CheckboxSelectMultiple
- )
- return type('UserFormFinal', (FormType, ), extra_fields)
- def StaffFlagUserFormFactory(FormType, instance):
- staff_fields = {
- 'is_staff': YesNoSwitch(
- label=EditUserForm.IS_STAFF_LABEL,
- help_text=EditUserForm.IS_STAFF_HELP_TEXT,
- initial=instance.is_staff
- ),
- 'is_superuser': YesNoSwitch(
- label=EditUserForm.IS_SUPERUSER_LABEL,
- help_text=EditUserForm.IS_SUPERUSER_HELP_TEXT,
- initial=instance.is_superuser
- ),
- }
- return type('StaffUserForm', (FormType, ), staff_fields)
- def UserIsActiveFormFactory(FormType, instance):
- is_active_fields = {
- 'is_active': YesNoSwitch(
- label=EditUserForm.IS_ACTIVE_LABEL,
- help_text=EditUserForm.IS_ACTIVE_HELP_TEXT,
- initial=instance.is_active
- ),
- 'is_active_staff_message': forms.CharField(
- label=EditUserForm.IS_ACTIVE_STAFF_MESSAGE_LABEL,
- help_text=EditUserForm.IS_ACTIVE_STAFF_MESSAGE_HELP_TEXT,
- initial=instance.is_active_staff_message,
- widget=forms.Textarea(attrs={'rows': 3}),
- required=False
- ),
- }
- return type('UserIsActiveForm', (FormType, ), is_active_fields)
- def EditUserFormFactory(FormType, instance, add_is_active_fields=False, add_admin_fields=False):
- FormType = UserFormFactory(FormType, instance)
- if add_is_active_fields:
- FormType = UserIsActiveFormFactory(FormType, instance)
- if add_admin_fields:
- FormType = StaffFlagUserFormFactory(FormType, instance)
- return FormType
- class SearchUsersFormBase(forms.Form):
- username = forms.CharField(label=_("Username starts with"), required=False)
- email = forms.CharField(label=_("E-mail starts with"), required=False)
- profilefields = forms.CharField(label=_("Profile fields contain"), required=False)
- inactive = YesNoSwitch(label=_("Inactive only"))
- disabled = YesNoSwitch(label=_("Disabled only"))
- is_staff = YesNoSwitch(label=_("Admins only"))
- is_deleting_account = YesNoSwitch(label=_("Deleting their accounts"))
- def filter_queryset(self, criteria, queryset):
- if criteria.get('username'):
- queryset = queryset.filter(slug__startswith=criteria.get('username').lower())
- if criteria.get('email'):
- queryset = queryset.filter(email__istartswith=criteria.get('email'))
- if criteria.get('rank'):
- queryset = queryset.filter(rank_id=criteria.get('rank'))
- if criteria.get('role'):
- queryset = queryset.filter(roles__id=criteria.get('role'))
- if criteria.get('inactive'):
- queryset = queryset.filter(requires_activation__gt=0)
- if criteria.get('disabled'):
- queryset = queryset.filter(is_active=False)
- if criteria.get('is_staff'):
- queryset = queryset.filter(is_staff=True)
- if criteria.get('is_deleting_account'):
- queryset = queryset.filter(is_deleting_account=True)
- if criteria.get('profilefields', '').strip():
- queryset = profilefields.search_users(
- criteria.get('profilefields').strip(), queryset)
- return queryset
- def SearchUsersForm(*args, **kwargs):
- """
- Factory that uses cache for ranks and roles,
- and makes those ranks and roles typed choice fields that play nice
- with passing values via GET
- """
- ranks_choices = threadstore.get('misago_admin_ranks_choices', 'nada')
- if ranks_choices == 'nada':
- ranks_choices = [('', _("All ranks"))]
- for rank in Rank.objects.order_by('name').iterator():
- ranks_choices.append((rank.pk, rank.name))
- threadstore.set('misago_admin_ranks_choices', ranks_choices)
- roles_choices = threadstore.get('misago_admin_roles_choices', 'nada')
- if roles_choices == 'nada':
- roles_choices = [('', _("All roles"))]
- for role in Role.objects.order_by('name').iterator():
- roles_choices.append((role.pk, role.name))
- threadstore.set('misago_admin_roles_choices', roles_choices)
- extra_fields = {
- 'rank': forms.TypedChoiceField(
- label=_("Has rank"),
- coerce=int,
- required=False,
- choices=ranks_choices,
- ),
- 'role': forms.TypedChoiceField(
- label=_("Has role"),
- coerce=int,
- required=False,
- choices=roles_choices,
- )
- }
- FinalForm = type('SearchUsersFormFinal', (SearchUsersFormBase, ), extra_fields)
- return FinalForm(*args, **kwargs)
- class RankForm(forms.ModelForm):
- name = forms.CharField(
- label=_("Name"),
- validators=[validate_sluggable()],
- help_text=_(
- 'Short and descriptive name of all users with this rank. '
- '"The Team" or "Game Masters" are good examples.'
- )
- )
- title = forms.CharField(
- label=_("User title"),
- required=False,
- help_text=_(
- 'Optional, singular version of rank name displayed by user names. '
- 'For example "GM" or "Dev".'
- )
- )
- description = forms.CharField(
- label=_("Description"),
- max_length=2048,
- required=False,
- widget=forms.Textarea(attrs={'rows': 3}),
- help_text=_(
- "Optional description explaining function or status of "
- "members distincted with this rank."
- )
- )
- roles = forms.ModelMultipleChoiceField(
- label=_("User roles"),
- widget=forms.CheckboxSelectMultiple,
- queryset=Role.objects.order_by('name'),
- required=False,
- help_text=_("Rank can give additional roles to users with it.")
- )
- css_class = forms.CharField(
- label=_("CSS class"),
- required=False,
- help_text=_("Optional css class added to content belonging to this rank owner.")
- )
- is_tab = forms.BooleanField(
- label=_("Give rank dedicated tab on users list"),
- required=False,
- help_text=_(
- "Selecting this option will make users with this rank easily discoverable "
- "by others through dedicated page on forum users list."
- )
- )
- class Meta:
- model = Rank
- fields = [
- 'name',
- 'description',
- 'css_class',
- 'title',
- 'roles',
- 'is_tab',
- ]
- def clean_name(self):
- data = self.cleaned_data['name']
- self.instance.set_name(data)
- unique_qs = Rank.objects.filter(slug=self.instance.slug)
- if self.instance.pk:
- unique_qs = unique_qs.exclude(pk=self.instance.pk)
- if unique_qs.exists():
- raise forms.ValidationError(_("This name collides with other rank."))
- return data
- class BanUsersForm(forms.Form):
- ban_type = forms.MultipleChoiceField(
- label=_("Values to ban"),
- widget=forms.CheckboxSelectMultiple,
- choices=[]
- )
- user_message = forms.CharField(
- label=_("User message"),
- required=False,
- max_length=1000,
- help_text=_("Optional message displayed to users instead of default one."),
- widget=forms.Textarea(attrs={'rows': 3}),
- error_messages={
- 'max_length': _("Message can't be longer than 1000 characters."),
- }
- )
- staff_message = forms.CharField(
- label=_("Team message"),
- required=False,
- max_length=1000,
- help_text=_("Optional ban message for moderators and administrators."),
- widget=forms.Textarea(attrs={'rows': 3}),
- error_messages={
- 'max_length': _("Message can't be longer than 1000 characters."),
- }
- )
- expires_on = IsoDateTimeField(
- label=_("Expires on"),
- required=False,
- help_text=_("Leave this field empty for set bans to never expire.")
- )
- def __init__(self, *args, **kwargs):
- users = kwargs.pop('users')
- super().__init__(*args, **kwargs)
- self.fields['ban_type'].choices = [
- ('usernames', _('Usernames')),
- ('emails', _('E-mails')),
- ('domains', _('E-mail domains')),
- ]
- enable_ip_bans = list(filter(None, [u.joined_from_ip for u in users]))
- if enable_ip_bans:
- self.fields['ban_type'].choices += [
- ('ip', _('IP addresses')),
- ('ip_first', _('First segment of IP addresses')),
- ('ip_two', _('First two segments of IP addresses')),
- ]
- class BanForm(forms.ModelForm):
- check_type = forms.TypedChoiceField(
- label=_("Check type"),
- coerce=int,
- choices=Ban.CHOICES,
- )
- registration_only = YesNoSwitch(
- label=_("Restrict this ban to registrations"),
- help_text=_(
- "Changing this to yes will make this ban check be only performed on registration "
- "step. This is good if you want to block certain registrations like ones from "
- "recently comprimised e-mail providers, without harming existing users."
- ),
- )
- banned_value = forms.CharField(
- label=_("Banned value"),
- max_length=250,
- help_text=_(
- 'This value is case-insensitive and accepts asterisk (*) '
- 'for rought matches. For example, making IP ban for value '
- '"83.*" will ban all IP addresses beginning with "83.".'
- ),
- error_messages={
- 'max_length': _("Banned value can't be longer than 250 characters."),
- }
- )
- user_message = forms.CharField(
- label=_("User message"),
- required=False,
- max_length=1000,
- help_text=_("Optional message displayed to user instead of default one."),
- widget=forms.Textarea(attrs={'rows': 3}),
- error_messages={
- 'max_length': _("Message can't be longer than 1000 characters."),
- }
- )
- staff_message = forms.CharField(
- label=_("Team message"),
- required=False,
- max_length=1000,
- help_text=_("Optional ban message for moderators and administrators."),
- widget=forms.Textarea(attrs={'rows': 3}),
- error_messages={
- 'max_length': _("Message can't be longer than 1000 characters."),
- }
- )
- expires_on = IsoDateTimeField(
- label=_("Expires on"),
- required=False,
- help_text=_("Leave this field empty for this ban to never expire.")
- )
- class Meta:
- model = Ban
- fields = [
- 'check_type',
- 'registration_only',
- 'banned_value',
- 'user_message',
- 'staff_message',
- 'expires_on',
- ]
- def clean_banned_value(self):
- data = self.cleaned_data['banned_value']
- while '**' in data:
- data = data.replace('**', '*')
- if data == '*':
- raise forms.ValidationError(_("Banned value is too vague."))
- return data
- class SearchBansForm(forms.Form):
- check_type = forms.ChoiceField(
- label=_("Type"),
- required=False,
- choices=[
- ('', _('All bans')),
- ('names', _('Usernames')),
- ('emails', _('E-mails')),
- ('ips', _('IPs')),
- ],
- )
- value = forms.CharField(label=_("Banned value begins with"), required=False)
- registration_only = forms.ChoiceField(
- label=_("Registration only"),
- required=False,
- choices=[
- ('', _('Any')),
- ('only', _('Yes')),
- ('exclude', _('No')),
- ]
- )
- state = forms.ChoiceField(
- label=_("State"),
- required=False,
- choices=[
- ('', _('Any')),
- ('used', _('Active')),
- ('unused', _('Expired')),
- ]
- )
- def filter_queryset(self, search_criteria, queryset):
- criteria = search_criteria
- if criteria.get('check_type') == 'names':
- queryset = queryset.filter(check_type=0)
- if criteria.get('check_type') == 'emails':
- queryset = queryset.filter(check_type=1)
- if criteria.get('check_type') == 'ips':
- queryset = queryset.filter(check_type=2)
- if criteria.get('value'):
- queryset = queryset.filter(banned_value__startswith=criteria.get('value').lower())
- if criteria.get('state') == 'used':
- queryset = queryset.filter(is_checked=True)
- if criteria.get('state') == 'unused':
- queryset = queryset.filter(is_checked=False)
- if criteria.get('registration_only') == 'only':
- queryset = queryset.filter(registration_only=True)
- if criteria.get('registration_only') == 'exclude':
- queryset = queryset.filter(registration_only=False)
- return queryset
- class RequestDataDownloadsForm(forms.Form):
- user_identifiers = forms.CharField(
- label=_("Usernames or emails"),
- help_text=_(
- "Enter every item in new line. Duplicates will be ignored. "
- "This field is case insensitive. Depending on site configuration and amount of data "
- "to archive it may take up to few days for requests to complete. E-mail "
- "will notification will be sent to every user once their download is ready."
- ),
- widget=forms.Textarea,
- )
- def clean_user_identifiers(self):
- user_identifiers = self.cleaned_data['user_identifiers'].lower().splitlines()
- user_identifiers = list(filter(bool, user_identifiers))
- user_identifiers = list(set(user_identifiers))
-
- if len(user_identifiers) > 20:
- raise forms.ValidationError(
- _(
- "You may not enter more than 20 items at single time "
- "(You have entered %(show_value)s)."
- ) % {'show_value': len(user_identifiers)}
- )
-
- return user_identifiers
- def clean(self):
- data = super().clean()
- if data.get('user_identifiers'):
- username_match = Q(slug__in=data['user_identifiers'])
- email_match = Q(email_hash__in=map(hash_email, data['user_identifiers']))
- data['users'] = list(UserModel.objects.filter(username_match | email_match))
- if len(data['users']) != len(data['user_identifiers']):
- raise forms.ValidationError(_("One or more specified users could not be found."))
- return data
- class SearchDataDownloadsForm(forms.Form):
- status = forms.ChoiceField(
- label=_("Status"),
- required=False,
- choices=DataDownload.STATUS_CHOICES,
- )
- user = forms.CharField(
- label=_("User"),
- required=False,
- )
- requested_by = forms.CharField(
- label=_("Requested by"),
- required=False,
- )
- def filter_queryset(self, search_criteria, queryset):
- criteria = search_criteria
- if criteria.get('status') is not None:
- queryset = queryset.filter(status=criteria['status'])
- if criteria.get('user'):
- queryset = queryset.filter(user__slug__istartswith=criteria['user'])
- if criteria.get('requested_by'):
- queryset = queryset.filter(requester__slug__istartswith=criteria['requested_by'])
- return queryset
|