Browse Source

logging and management commands

Rafał Pitoń 8 years ago
parent
commit
771405be9c

+ 26 - 2
docs/ProfileFields.md

@@ -511,6 +511,30 @@ Field inheriting from `TextProfileField` that allows user to enter their Twitter
 
 ## Obtaining list of profile fields keys existing in database
 
-## Removing profile fields from database
+Misago comes with special `listusedprofilefields` command that lists fields existing in database, by their `fieldname`:
 
-## Using logging for controling profile fields usage
+```
+$ python manage.py listusedprofilefields
+bio:      5
+fullname: 5
+location: 2
+```
+
+Above result means that 5 users `profile_fields` has `bio` and `fullname` keys and two users have `location` key. This tool doesn't filter off empty values, meaning that those key exist, but may be empty strings.
+
+
+## Deleting profile field from database
+
+If you want to delete key from users `profile_fields`, you may do it with `deleteprofilefield` commant. For example, to delete all information set in `website` field, you may do this:
+
+```
+$ python manage.py deleteprofilefield website
+"website" profile field has been deleted from 132 users.
+```
+
+Likewise, this field deletes keys and does no checking if those are user entered values or empty strings.
+
+
+## Using logging for controling profile fields usage
+
+Whenever user's `profile_fields` value is edited either in admin control panel or via "edit details" form, such event, new values and old values are logged by `misago.users.ProfileFields` logger.

+ 44 - 0
misago/users/management/commands/deleteprofilefield.py

@@ -0,0 +1,44 @@
+from __future__ import unicode_literals
+
+from django.contrib.auth import get_user_model
+from django.core.management.base import CommandError, BaseCommand
+
+
+UserModel = get_user_model()
+
+
+class Command(BaseCommand):
+    help = (
+        "Deletes specified profile field from database."
+    )
+
+    def add_arguments(self, parser):
+        parser.add_argument(
+            'fieldname',
+            help="field to delete",
+            nargs='?',
+        )
+
+    def handle(self, *args, **options):
+        fieldname = options['fieldname']
+        if not fieldname:
+            self.stderr.write("Specify fieldname to delete.")
+            return
+
+        deleted = 0
+
+        queryset = UserModel.objects.filter(
+            profile_fields__has_keys=[fieldname],
+        )
+
+        for user in queryset.iterator():
+            if fieldname in user.profile_fields.keys():
+                user.profile_fields.pop(fieldname)
+                user.save(update_fields=['profile_fields'])
+                deleted += 1
+
+        self.stdout.write(
+            '"{}" profile field has been deleted from {} users.'.format(
+                fieldname, deleted
+            )
+        )

+ 29 - 0
misago/users/management/commands/listusedprofilefields.py

@@ -0,0 +1,29 @@
+from __future__ import unicode_literals
+
+from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand
+
+
+UserModel = get_user_model()
+
+
+class Command(BaseCommand):
+    help = (
+        "Lists all profile fields in use."
+    )
+
+    def handle(self, *args, **options):
+        keys = {}
+
+        for user in UserModel.objects.all().iterator():
+            for key in user.profile_fields.keys():
+                keys.setdefault(key, 0)
+                keys[key] += 1
+
+        if keys:
+            max_len = max([len(k) for k in keys.keys()])
+            for key in sorted(keys.keys()):
+                space = ' ' * (max_len + 1 - len(key))
+                self.stdout.write("{}:{}{}".format(key, space, keys[key]))
+        else:
+            self.stdout.write("No profile fields are currently in use.")

+ 22 - 3
misago/users/profilefields/__init__.py

@@ -1,3 +1,8 @@
+from __future__ import unicode_literals
+
+import copy
+import logging
+
 from django.core.exceptions import ValidationError
 from django.utils.module_loading import import_string
 from django.utils.translation import ugettext as _
@@ -8,6 +13,9 @@ from .basefields import *
 from .serializers import serialize_profilefields_data
 
 
+logger = logging.getLogger('misago.users.ProfileFields')
+
+
 class ProfileFields(object):
     def __init__(self, fields_groups):
         self.is_loaded = False
@@ -123,11 +131,22 @@ class ProfileFields(object):
         return cleaned_data
 
     def update_user_profile_fields(self, user, form):
-        cleaned_profile_fields = {}
+        old_fields = copy.copy(user.profile_fields or {})
+
+        new_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
+                new_fields[fieldname] = form.cleaned_data[fieldname]
+        user.profile_fields = new_fields
+
+        if old_fields != new_fields:
+            logger.info(
+                "Changed {}'s (ID: {}) profile fields".format(user.username, user.pk),
+                extra={
+                    'old_fields': old_fields,
+                    'new_fields': new_fields,
+                }
+            )
 
     def search_users(self, criteria, queryset):
         if not self.is_loaded:

+ 42 - 0
misago/users/tests/test_deleteprofilefield.py

@@ -0,0 +1,42 @@
+from django.contrib.auth import get_user_model
+from django.core.management import call_command
+from django.test import TestCase
+from django.utils.six import StringIO
+
+from misago.users.management.commands import deleteprofilefield
+
+
+UserModel = get_user_model()
+
+
+class DeleteProfileFieldTests(TestCase):
+    def test_no_fieldname(self):
+        """utility has no showstoppers when no fieldname is given"""
+        out = StringIO()
+        call_command(deleteprofilefield.Command(), stderr=out)
+        command_output = out.getvalue().splitlines()[0].strip()
+
+        self.assertEqual(command_output, "Specify fieldname to delete.")
+
+    def test_no_fields_set(self):
+        """utility has no showstoppers when no fields are set"""
+        out = StringIO()
+        call_command(deleteprofilefield.Command(), 'gender', stdout=out)
+        command_output = out.getvalue().splitlines()[0].strip()
+
+        self.assertEqual(command_output, '"gender" profile field has been deleted from 0 users.')
+
+    def test_delete_fields_(self):
+        """utility has no showstoppers when no fields are set"""
+        user = UserModel.objects.create_user('Bob', 'bob@bob.com', 'pass123')
+        user.profile_fields = {'gender': 'male', 'bio': "Yup!"}
+        user.save()
+
+        out = StringIO()
+        call_command(deleteprofilefield.Command(), 'gender', stdout=out)
+        command_output = out.getvalue().splitlines()[0].strip()
+
+        self.assertEqual(command_output, '"gender" profile field has been deleted from 1 users.')
+
+        user = UserModel.objects.get(pk=user.pk)
+        self.assertEqual(user.profile_fields, {'bio': "Yup!"})

+ 45 - 0
misago/users/tests/test_listusedprofilefields.py

@@ -0,0 +1,45 @@
+from django.contrib.auth import get_user_model
+from django.core.management import call_command
+from django.test import TestCase
+from django.utils.six import StringIO
+
+from misago.users.management.commands import listusedprofilefields
+
+
+UserModel = get_user_model()
+
+
+class ListUsedProfileFieldsTests(TestCase):
+    def test_no_fields_set(self):
+        """utility has no showstoppers when no fields are set"""
+        UserModel.objects.create_user('Bob', 'bob@bob.com', 'pass123')
+
+        out = StringIO()
+        call_command(listusedprofilefields.Command(), stdout=out)
+        command_output = out.getvalue().splitlines()[0].strip()
+
+        self.assertEqual(command_output, "No profile fields are currently in use.")
+
+    def test_fields_set(self):
+        """utility lists number of users that have different fields set"""
+        user = UserModel.objects.create_user('Bob', 'bob@bob.com', 'pass123')
+        user.profile_fields = {'gender': 'male', 'bio': "Yup!"}
+        user.save()
+
+        user = UserModel.objects.create_user('Bob2', 'bob2@bob.com', 'pass123')
+        user.profile_fields = {'gender': 'male'}
+        user.save()
+
+        user = UserModel.objects.create_user('Bob3', 'bob3@bob.com', 'pass123')
+        user.profile_fields = {'location': ""}
+        user.save()
+
+        out = StringIO()
+        call_command(listusedprofilefields.Command(), stdout=out)
+        command_output = [l.strip() for l in out.getvalue().strip().splitlines()]
+
+        self.assertEqual(command_output, [
+            "bio:      1",
+            "gender:   2",
+            "location: 1",
+        ])