Просмотр исходного кода

wip #865: profile fields python api cleanup

Rafał Pitoń 8 лет назад
Родитель
Сommit
a903a45fe2

+ 6 - 17
misago/users/api/userendpoints/editdetails.py

@@ -18,8 +18,8 @@ def get_form_description(request, user):
     for group in profilefields.get_fields_groups():
         group_fields = []
         for field in group['fields']:
-            if field.can_edit(request, user):
-                group_fields.append(field.get_edit_field_json(request, user))
+            if field.is_editable(request, user):
+                group_fields.append(field.get_form_field_json(request, user))
         if group_fields:
             groups.append({
                 'name': group['name'],
@@ -32,18 +32,17 @@ def get_form_description(request, user):
 def submit_form(request, user):
     fields = []
     for field in profilefields.get_fields():
-        if field.can_edit(request, user):
+        if field.is_editable(request, user):
             fields.append(field)
 
     form = DetailsForm(
         request.data,
         request=request,
         user=user,
-        profilefields=fields,
     )
 
     if form.is_valid():
-        user.profile_fields = form.cleaned_data
+        profilefields.update_user_profile_fields(user, form)
         user.save(update_fields=['profile_fields'])
 
         return Response(serialize_profilefields_data(request, profilefields, user))
@@ -55,21 +54,11 @@ class DetailsForm(forms.Form):
     def __init__(self, *args, **kwargs):
         self.request = kwargs.pop('request')
         self.user = kwargs.pop('user')
-        self.profilefields = kwargs.pop('profilefields')
 
         super(DetailsForm, self).__init__(*args, **kwargs)
 
-        for field in self.profilefields:
-            self.fields[field.fieldname] = field.get_field_for_validation(
-                self.request, self.user)
+        profilefields.add_fields_to_form(self.request, self.user, self)
 
     def clean(self):
         data = super(DetailsForm, self).clean()
-        for field in self.profilefields:
-            if field.fieldname in data:
-                try:
-                    data[field.fieldname] = field.clean_field(
-                        self.request, self.user, data[field.fieldname])
-                except forms.ValidationError as e:
-                    self.add_error(field.fieldname, e)
-        return data
+        return profilefields.clean_form(self.request, self.user, self, data)

+ 4 - 4
misago/users/forms/admin.py

@@ -189,11 +189,11 @@ class EditUserForm(UserBaseForm):
         ]
 
     def __init__(self, *args, **kwargs):
-        self._profile_fields_groups = []
+        self.request = kwargs.pop('request')
 
         super(EditUserForm, self).__init__(*args, **kwargs)
 
-        profilefields.update_admin_form(self)
+        profilefields.add_fields_to_admin_form(self.request, self.instance, self)
 
     def get_profile_fields_groups(self):
         profile_fields_groups = []
@@ -226,7 +226,7 @@ class EditUserForm(UserBaseForm):
 
     def clean(self):
         data = super(EditUserForm, self).clean()
-        return profilefields.clean_admin_form(self, data)
+        return profilefields.clean_form(self.request, self.instance, self, data)
 
 
 def UserFormFactory(FormType, instance):
@@ -334,7 +334,7 @@ class SearchUsersFormBase(forms.Form):
             queryset = queryset.filter(is_staff=True)
 
         if criteria.get('profilefields', '').strip():
-            queryset = profilefields.admin_search(
+            queryset = profilefields.search_users(
                 criteria.get('profilefields').strip(), queryset)
 
         return queryset

+ 62 - 38
misago/users/profilefields/__init__.py

@@ -1,3 +1,4 @@
+from django.core.exceptions import ValidationError
 from django.utils.module_loading import import_string
 from django.utils.translation import ugettext as _
 
@@ -53,10 +54,16 @@ class ProfileFields(object):
 
         self.is_loaded = True
 
-    def update_admin_form(self, form):
+    def get_fields(self):
+        if not self.is_loaded:
+            self.load()
+        return self.fields_dict.values()
+
+    def get_fields_groups(self):
         if not self.is_loaded:
             self.load()
 
+        groups = []
         for group in self.fields_groups:
             group_dict = {
                 'name': _(group['name']),
@@ -65,49 +72,29 @@ class ProfileFields(object):
 
             for field_path in group['fields']:
                 field = self.fields_dict[field_path]
-                admin_field = field.get_admin_field(form.instance)
-                if admin_field:
-                    form.fields[field.fieldname] = admin_field
-                    group_dict['fields'].append(field.fieldname)
-
-            form._profile_fields_groups.append(group_dict)
-
-    def clean_admin_form(self, form, data):
-        for field in self.fields_dict.values():
-            data = field.clean_admin_form(form, data) or data
-        return data
+                group_dict['fields'].append(field)
 
-    def admin_update_profile_fields(self, user, cleaned_data):
-        for field in self.fields_dict.values():
-            field.admin_update_profile_fields(user, cleaned_data)
+            if group_dict['fields']:
+                groups.append(group_dict)
+        return groups
 
-    def admin_search(self, criteria, queryset):
+    def add_fields_to_form(self, request, user, form):
         if not self.is_loaded:
             self.load()
 
-        q_obj = None
-        for field in self.fields_dict.values():
-            q = field.admin_search(criteria, queryset)
-            if q:
-                if q_obj:
-                    q_obj = q_obj | q
-                else:
-                    q_obj = q
-        if q_obj:
-            return queryset.filter(q_obj)
+        form._profile_fields = []
 
-        return queryset
+        for field in self.get_fields():
+            if not field.is_editable(request, user):
+                continue
 
-    def get_fields(self):
-        if not self.is_loaded:
-            self.load()
-        return self.fields_dict.values()
+            form._profile_fields.append(field.fieldname)
+            form.fields[field.fieldname] = field.get_form_field(request, user)
 
-    def get_fields_groups(self):
-        if not self.is_loaded:
-            self.load()
+    def add_fields_to_admin_form(self, request, user, form):
+        self.add_fields_to_form(request, user, form)
 
-        groups = []
+        form._profile_fields_groups = []
         for group in self.fields_groups:
             group_dict = {
                 'name': _(group['name']),
@@ -116,11 +103,48 @@ class ProfileFields(object):
 
             for field_path in group['fields']:
                 field = self.fields_dict[field_path]
-                group_dict['fields'].append(field)
+                if field.fieldname in form._profile_fields:
+                    group_dict['fields'].append(field.fieldname)
 
             if group_dict['fields']:
-                groups.append(group_dict)
-        return groups
+                form._profile_fields_groups.append(group_dict)
+
+    def clean_form(self, request, user, form, cleaned_data):
+        for field in self.get_fields():
+            if field.fieldname not in cleaned_data:
+                continue
+
+            try:
+                cleaned_data[field.fieldname] = field.clean(
+                    request, user, cleaned_data[field.fieldname])
+            except ValidationError as e:
+                form.add_error(field.fieldname, e)
+
+        return cleaned_data
+
+    def update_user_profile_fields(self, user, form):
+        cleaned_profile_fields = {}
+        for fieldname in form._profile_fields:
+            if fieldname in form.cleaned_data:
+                cleaned_profile_fields[fieldname] = form.cleaned_data[fieldname]
+        user.profile_fields = cleaned_profile_fields
+
+    def search_users(self, criteria, queryset):
+        if not self.is_loaded:
+            self.load()
+
+        q_obj = None
+        for field in self.fields_dict.values():
+            q = field.search_users(criteria, queryset)
+            if q:
+                if q_obj:
+                    q_obj = q_obj | q
+                else:
+                    q_obj = q
+        if q_obj:
+            return queryset.filter(q_obj)
+
+        return queryset
 
 
 profilefields = ProfileFields(settings.MISAGO_PROFILE_FIELDS)

+ 70 - 105
misago/users/profilefields/basefields.py

@@ -24,6 +24,9 @@ class ProfileField(object):
     help_text = None
     readonly = False
 
+    def is_editable(self, request, user):
+        return not self.readonly
+
     def get_label(self, user):
         if not self.label:
             raise NotImplementedError(
@@ -35,29 +38,34 @@ class ProfileField(object):
     def get_help_text(self, user):
         return self.help_text
 
-    def get_admin_field(self, user):
+    def get_form_field(self, request, user):
         return None
 
-    def clean_admin_form(self, form, data):
-        return data
+    def get_form_field_json(self, request, user):
+        input_json = self.get_input_json(request, user)
+        if not input_json:
+            return None
 
-    def admin_update_profile_fields(self, user, cleaned_data):
-        if self.readonly:
-            return
+        return {
+            'fieldname': self.fieldname,
+            'label': self.get_label(user),
+            'help_text': self.get_help_text(user),
+            'initial': user.profile_fields.get(self.fieldname, ''),
+            'input': input_json,
+        }
 
-        user.profile_fields[self.fieldname] = cleaned_data.get(self.fieldname)
+    def get_input_json(self, request, user):
+        return None
 
-    def admin_search(self, criteria, queryset):
-        return Q(**{
-            'profile_fields__{}__contains'.format(self.fieldname): criteria
-        })
+    def clean(self, request, user, data):
+        return data
 
-    def get_data(self, request, user):
-        field_data = user.profile_fields.get(self.fieldname, '')
-        if not len(field_data):
+    def get_display_data(self, request, user):
+        value = user.profile_fields.get(self.fieldname, '')
+        if not len(value):
             return None
 
-        data = self.get_display_data(request, user, field_data)
+        data = self.get_value_display_data(request, user, value)
         if not data:
             return None
 
@@ -68,33 +76,15 @@ class ProfileField(object):
 
         return data
 
-    def get_display_data(self, request, user, data):
-        return {
-            'text': data
-        }
-
-    def can_edit(self, request, user):
-        return not self.readonly
-
-    def get_edit_field_json(self, request, user):
-        return {
-            'fieldname': self.fieldname,
-            'label': self.get_label(user),
-            'help_text': self.get_help_text(user),
-            'initial': user.profile_fields.get(self.fieldname, ''),
-            'input': self.get_edit_field_input_attrs(request, user)
-        }
-
-    def get_edit_field_input_attrs(self, request, user):
+    def get_value_display_data(self, request, user, value):
         return {
-            'type': 'text',
+            'text': value
         }
 
-    def get_field_for_validation(self, request, user):
-        return forms.CharField(max_length=250, required=False)
-
-    def clean_field(self, request, user, data):
-        return data
+    def search_users(self, criteria, queryset):
+        return Q(**{
+            'profile_fields__{}__contains'.format(self.fieldname): criteria
+        })
 
 
 class ChoiceProfileField(ProfileField):
@@ -108,7 +98,7 @@ class ChoiceProfileField(ProfileField):
             )
         return self.choices
 
-    def get_admin_field(self, user):
+    def get_form_field(self, request, user):
         return forms.ChoiceField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
@@ -118,29 +108,7 @@ class ChoiceProfileField(ProfileField):
             required=False,
         )
 
-    def admin_search(self, criteria, queryset):
-        """custom search implementation for choice fields"""
-        q_obj = Q(**{
-            'profile_fields__{}__contains'.format(self.fieldname): criteria
-        })
-
-        for key, choice in self.get_choices():
-            if key and criteria.lower() in text_type(choice).lower():
-                q_obj = q_obj | Q(**{
-                    'profile_fields__{}'.format(self.fieldname): key
-                })
-
-        return q_obj
-
-    def get_display_data(self, request, user, data):
-        for key, name in self.get_choices():
-            if key == data:
-                return {
-                    'text': text_type(name),
-                }
-        return None
-
-    def get_edit_field_input_attrs(self, request, user):
+    def get_input_json(self, request, user):
         choices = []
         for key, choice in self.get_choices():
             choices.append({
@@ -153,12 +121,31 @@ class ChoiceProfileField(ProfileField):
             'choices': choices,
         }
 
-    def get_field_for_validation(self, request, user):
-        return forms.ChoiceField(choices=self.get_choices(user), required=False)
+    def get_value_display_data(self, request, user, value):
+        for key, name in self.get_choices():
+            if key == value:
+                return {
+                    'text': text_type(name),
+                }
+        return None
+
+    def search_users(self, criteria, queryset):
+        """custom search implementation for choice fields"""
+        q_obj = Q(**{
+            'profile_fields__{}__contains'.format(self.fieldname): criteria
+        })
+
+        for key, choice in self.get_choices():
+            if key and criteria.lower() in text_type(choice).lower():
+                q_obj = q_obj | Q(**{
+                    'profile_fields__{}'.format(self.fieldname): key
+                })
+
+        return q_obj
 
 
 class TextProfileField(ProfileField):
-    def get_admin_field(self, user):
+    def get_form_field(self, request, user):
         return forms.CharField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
@@ -168,9 +155,14 @@ class TextProfileField(ProfileField):
             required=False,
         )
 
+    def get_input_json(self, request, user):
+        return {
+            'type': 'text',
+        }
+
 
 class TextareaProfileField(ProfileField):
-    def get_admin_field(self, user):
+    def get_form_field(self, request, user):
         return forms.CharField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
@@ -183,50 +175,26 @@ class TextareaProfileField(ProfileField):
             required=False,
         )
 
-    def get_display_data(self, request, user, data):
-        return {
-            'html': html.linebreaks(html.escape(data)),
-        }
-
-    def get_edit_field_input_attrs(self, request, user):
+    def get_input_json(self, request, user):
         return {
             'type': 'textarea',
         }
 
-    def get_field_for_validation(self, request, user):
-        return forms.CharField(max_length=500, required=False)
-
-
-class UrlifiedTextareaProfileField(TextareaProfileField):
-    def get_display_data(self, request, user, data):
+    def get_value_display_data(self, request, user, value):
         return {
-            'html': format_plaintext_for_html(data),
+            'html': html.linebreaks(html.escape(value)),
         }
 
 
-class SlugProfileField(ProfileField):
-    def get_admin_field(self, user):
-        return forms.SlugField(
-            label=self.get_label(user),
-            help_text=self.get_help_text(user),
-            initial=user.profile_fields.get(self.fieldname),
-            max_length=250,
-            disabled=self.readonly,
-            required=False,
-        )
-
-    def get_display_data(self, request, user, data):
+class UrlifiedTextareaProfileField(TextareaProfileField):
+    def get_value_display_data(self, request, user, value):
         return {
-            'text': data,
-            'url': data,
+            'html': format_plaintext_for_html(value),
         }
 
-    def get_field_for_validation(self, request, user):
-        return forms.SlugField(max_length=250, required=False)
 
-
-class UrlProfileField(ProfileField):
-    def get_admin_field(self, user):
+class UrlProfileField(TextProfileField):
+    def get_form_field(self, request, user):
         return forms.URLField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
@@ -236,11 +204,8 @@ class UrlProfileField(ProfileField):
             required=False,
         )
 
-    def get_display_data(self, request, user, data):
+    def get_value_display_data(self, request, user, value):
         return {
-            'text': data,
-            'url': data,
+            'text': value,
+            'url': value,
         }
-
-    def get_field_for_validation(self, request, user):
-        return forms.URLField(max_length=250, required=False)

+ 10 - 4
misago/users/profilefields/default.py

@@ -1,6 +1,9 @@
 from __future__ import unicode_literals
 
-from django.utils.translation import ugettext_lazy as _
+import re
+
+from django.forms import ValidationError
+from django.utils.translation import ugettext, ugettext_lazy as _
 
 from . import basefields
 
@@ -47,11 +50,14 @@ class TwitterHandleField(basefields.TextProfileField):
     label = _("Twitter handle")
     help_text = _('Without leading "@" sign.')
 
-    def get_display_data(self, request, user, data):
+    def get_value_display_data(self, request, user, data):
         return {
             'text': '@{}'.format(data),
             'url': 'https://twitter.com/{}'.format(data),
         }
 
-    def clean_field(self, request, user, data):
-        return data.lstrip('@').replace('/', '').replace('\\', '')
+    def clean(self, request, user, data):
+        data = data.lstrip('@')
+        if data and not re.search('^[A-Za-z0-9_]+$', data):
+            raise ValidationError(ugettext("This is not a valid twitter handle."))
+        return data

+ 2 - 2
misago/users/profilefields/serializers.py

@@ -14,10 +14,10 @@ def serialize_profilefields_data(request, profilefields, user):
     for group in profilefields.get_fields_groups():
         group_fields = []
         for field in group['fields']:
-            display_data = field.get_data(request, user)
+            display_data = field.get_display_data(request, user)
             if display_data:
                 group_fields.append(display_data)
-        if can_edit and field.can_edit(request, user):
+        if can_edit and field.is_editable(request, user):
             has_editable_fields = True
         if group_fields:
             data['groups'].append({

+ 12 - 1
misago/users/views/admin/users.py

@@ -264,6 +264,17 @@ class EditUser(UserAdmin, generic.ModelFormView):
         target.old_is_avatar_locked = target.is_avatar_locked
         return super(EditUser, self).real_dispatch(request, target)
 
+    def initialize_form(self, form, request, target):
+        if request.method == 'POST':
+            return form(
+                request.POST,
+                request.FILES,
+                instance=target,
+                request=request,
+            )
+        else:
+            return form(instance=target, request=request)
+
     def handle_form(self, form, request, target):
         target.username = target.old_username
         if target.username != form.cleaned_data.get('username'):
@@ -300,7 +311,7 @@ class EditUser(UserAdmin, generic.ModelFormView):
 
         set_user_signature(request, target, form.cleaned_data.get('signature'))
 
-        profilefields.admin_update_profile_fields(target, form.cleaned_data)
+        profilefields.update_user_profile_fields(target, form)
 
         target.update_acl_key()
         target.save()