Browse Source

#865: rename user.extra to user.profile_fields, move db field from json to hstore so its searchable in admin

Rafał Pitoń 8 years ago
parent
commit
7ddcf57b8e

+ 1 - 0
misago/project_template/project_name/settings.py

@@ -169,6 +169,7 @@ INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
+    'django.contrib.postgres',
     'django.contrib.humanize',
     'django.contrib.sessions',
     'django.contrib.messages',

+ 5 - 0
misago/templates/misago/admin/users/list.html

@@ -118,6 +118,11 @@
   </div>
 </div>
 <div class="row">
+  <div class="col-md-12">
+    {% form_row search_form.profilefields %}
+  </div>
+</div>
+<div class="row">
   <div class="col-md-4">
     {% form_row search_form.inactive %}
   </div>

+ 8 - 3
misago/users/forms/admin.py

@@ -10,6 +10,7 @@ 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, Rank
+from misago.users.profilefields import profilefields
 from misago.users.validators import validate_email, validate_username
 
 
@@ -188,12 +189,11 @@ class EditUserForm(UserBaseForm):
         ]
 
     def __init__(self, *args, **kwargs):
-        self.profilefields = kwargs.pop('profilefields')
         self._profile_fields_groups = []
 
         super(EditUserForm, self).__init__(*args, **kwargs)
 
-        self.profilefields.update_admin_form(self)
+        profilefields.update_admin_form(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 self.profilefields.clean_admin_form(self, data)
+        return profilefields.clean_admin_form(self, data)
 
 
 def UserFormFactory(FormType, instance):
@@ -306,6 +306,7 @@ def EditUserFormFactory(FormType, instance, add_is_active_fields=False, add_admi
 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"))
@@ -332,6 +333,10 @@ class SearchUsersFormBase(forms.Form):
         if criteria.get('is_staff'):
             queryset = queryset.filter(is_staff=True)
 
+        if criteria.get('profilefields', '').strip():
+            queryset = profilefields.admin_search(
+                criteria.get('profilefields').strip(), queryset)
+
         return queryset
 
 

+ 5 - 3
misago/users/migrations/0010_user_extra.py → misago/users/migrations/0010_user_profile_fields.py

@@ -2,7 +2,8 @@
 # Generated by Django 1.11.1 on 2017-06-03 22:15
 from __future__ import unicode_literals
 
-import django.contrib.postgres.fields.jsonb
+import django.contrib.postgres.fields
+from django.contrib.postgres.operations import HStoreExtension
 from django.db import migrations
 
 
@@ -13,9 +14,10 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
+        HStoreExtension(),
         migrations.AddField(
             model_name='user',
-            name='extra',
-            field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
+            name='profile_fields',
+            field=django.contrib.postgres.fields.HStoreField(default=dict),
         ),
     ]

+ 2 - 2
misago/users/models/user.py

@@ -4,7 +4,7 @@ from django.contrib.auth.models import AnonymousUser as DjangoAnonymousUser
 from django.contrib.auth.models import UserManager as BaseUserManager
 from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
 from django.contrib.auth.password_validation import validate_password
-from django.contrib.postgres.fields import JSONField
+from django.contrib.postgres.fields import HStoreField, JSONField
 from django.core.mail import send_mail
 from django.db import IntegrityError, models, transaction
 from django.urls import reverse
@@ -264,7 +264,7 @@ class User(AbstractBaseUser, PermissionsMixin):
 
     last_posted_on = models.DateTimeField(null=True, blank=True)
 
-    extra = JSONField(default=dict)
+    profile_fields = HStoreField(default=dict)
 
     USERNAME_FIELD = 'slug'
     REQUIRED_FIELDS = ['email']

+ 19 - 2
misago/users/profilefields/__init__.py

@@ -75,9 +75,26 @@ class ProfileFields(object):
             data = field.clean_admin_form(form, data) or data
         return data
 
-    def admin_update_extra(self, user, cleaned_data):
+    def admin_update_profile_fields(self, user, cleaned_data):
         for field in self.fields_dict.values():
-            field.admin_update_extra(user, cleaned_data)
+            field.admin_update_profile_fields(user, cleaned_data)
+
+    def admin_search(self, criteria, queryset):
+        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)
+
+        return queryset
 
 
 profilefields = ProfileFields(settings.MISAGO_PROFILE_FIELDS)

+ 29 - 8
misago/users/profilefields/basefields.py

@@ -1,4 +1,6 @@
 from django import forms
+from django.db.models import Q
+from django.utils.six import text_type
 
 
 __all__ = [
@@ -36,17 +38,22 @@ class ProfileField(object):
     def clean_admin_form(self, form, data):
         return data
 
-    def admin_update_extra(self, user, cleaned_data):
+    def admin_update_profile_fields(self, user, cleaned_data):
         if self.readonly:
             return
 
-        user.extra[self.fieldname] = cleaned_data.get(self.fieldname)
+        user.profile_fields[self.fieldname] = cleaned_data.get(self.fieldname)
+
+    def admin_search(self, criteria, queryset):
+        return Q(**{
+            'profile_fields__{}__contains'.format(self.fieldname): criteria
+        })
 
 
 class ChoiceProfileField(ProfileField):
     choices = None
 
-    def get_choices(self, user):
+    def get_choices(self, user=None):
         if not self.choices:
             raise NotImplementedError(
                 "profile field ChoiceProfileField has to define "
@@ -58,19 +65,33 @@ class ChoiceProfileField(ProfileField):
         return forms.ChoiceField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
-            initial=user.extra.get(self.fieldname),
+            initial=user.profile_fields.get(self.fieldname),
             choices=self.get_choices(user),
             disabled=self.readonly,
             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
+
 
 class TextProfileField(ProfileField):
     def get_admin_field(self, user):
         return forms.CharField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
-            initial=user.extra.get(self.fieldname),
+            initial=user.profile_fields.get(self.fieldname),
             max_length=250,
             disabled=self.readonly,
             required=False,
@@ -82,7 +103,7 @@ class TextareaProfileField(ProfileField):
         return forms.CharField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
-            initial=user.extra.get(self.fieldname),
+            initial=user.profile_fields.get(self.fieldname),
             max_length=250,
             widget=forms.Textarea(
                 attrs={'rows': 4},
@@ -97,7 +118,7 @@ class SlugProfileField(ProfileField):
         return forms.SlugField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
-            initial=user.extra.get(self.fieldname),
+            initial=user.profile_fields.get(self.fieldname),
             max_length=250,
             disabled=self.readonly,
             required=False,
@@ -109,7 +130,7 @@ class UrlProfileField(ProfileField):
         return forms.URLField(
             label=self.get_label(user),
             help_text=self.get_help_text(user),
-            initial=user.extra.get(self.fieldname),
+            initial=user.profile_fields.get(self.fieldname),
             max_length=250,
             disabled=self.readonly,
             required=False,

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

@@ -264,17 +264,6 @@ 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,
-                profilefields=profilefields,
-            )
-        else:
-            return form(instance=target, profilefields=profilefields)
-
     def handle_form(self, form, request, target):
         target.username = target.old_username
         if target.username != form.cleaned_data.get('username'):
@@ -311,7 +300,7 @@ class EditUser(UserAdmin, generic.ModelFormView):
 
         set_user_signature(request, target, form.cleaned_data.get('signature'))
 
-        profilefields.admin_update_extra(target, form.cleaned_data)
+        profilefields.admin_update_profile_fields(target, form.cleaned_data)
 
         target.update_acl_key()
         target.save()