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

Very early implementation of user model

Rafał Pitoń 11 лет назад
Родитель
Сommit
866bc3dcf3

+ 2 - 2
misago/core/migrations/0002_db_settings.py

@@ -16,8 +16,8 @@ class Migration(DataMigration):
             {
                 'key': 'basic',
                 'name': _("Basic forum settings"),
-                'description': _("Those settings control most basic properties of "
-                                 "your forum like it's name or description."),
+                'description': _("Those settings control most basic properties "
+                                 "of your forum like it's name or description."),
                 'settings': (
                     {
                         'setting': 'forum_name',

+ 94 - 0
misago/users/migrations/0001_initial.py

@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'User'
+        db.create_table(u'users_user', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('password', self.gf('django.db.models.fields.CharField')(max_length=128)),
+            ('last_login', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
+            ('is_superuser', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('username', self.gf('django.db.models.fields.CharField')(max_length=30)),
+            ('username_slug', self.gf('django.db.models.fields.CharField')(unique=True, max_length=30)),
+            ('email', self.gf('django.db.models.fields.EmailField')(max_length=255, db_index=True)),
+            ('email_hash', self.gf('django.db.models.fields.CharField')(unique=True, max_length=32)),
+            ('joined_on', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
+            ('is_staff', self.gf('django.db.models.fields.BooleanField')(default=False, db_index=True)),
+        ))
+        db.send_create_signal('users', ['User'])
+
+        # Adding M2M table for field groups on 'User'
+        m2m_table_name = db.shorten_name(u'users_user_groups')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('user', models.ForeignKey(orm['users.user'], null=False)),
+            ('group', models.ForeignKey(orm[u'auth.group'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['user_id', 'group_id'])
+
+        # Adding M2M table for field user_permissions on 'User'
+        m2m_table_name = db.shorten_name(u'users_user_user_permissions')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('user', models.ForeignKey(orm['users.user'], null=False)),
+            ('permission', models.ForeignKey(orm[u'auth.permission'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['user_id', 'permission_id'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'User'
+        db.delete_table(u'users_user')
+
+        # Removing M2M table for field groups on 'User'
+        db.delete_table(db.shorten_name(u'users_user_groups'))
+
+        # Removing M2M table for field user_permissions on 'User'
+        db.delete_table(db.shorten_name(u'users_user_user_permissions'))
+
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'users.user': {
+            'Meta': {'object_name': 'User'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'db_index': 'True'}),
+            'email_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'joined_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
+            'username_slug': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        }
+    }
+
+    complete_apps = ['users']

+ 177 - 0
misago/users/migrations/0002_db_settings.py

@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+from misago.conf.migrationutils import with_conf_models, migrate_settings_group
+from misago.core.migrationutils import ugettext_lazy as _
+
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        "Write your forwards methods here."
+        migrate_settings_group(
+            orm,
+            {
+                'key': 'users',
+                'name': _("Users"),
+                'settings': (
+                    {
+                        'setting': 'account_activation',
+                        'name': _("New accounts activation"),
+                        'legend': _("New accounts"),
+                        'value': 'none',
+                        'form_field': 'select',
+                        'field_extra': {
+                            'choices': (
+                                ('none', _("No activation required")),
+                                ('user', _("Activation Token sent to User")),
+                                ('admin', _("Activation by Administrator")),
+                                ('block', _("Don't allow new registrations"))
+                            )
+                        },
+                    },
+                    {
+                        'setting': 'default_timezone',
+                        'name': _("Default timezone"),
+                        'description': _("Default timezone for newly "
+                                         "registered accouts as well as "
+                                         "unsigned users."),
+                        'value': 'utc',
+                        'form_field': 'select',
+                        'field_extra': {
+                            'choices': '#TZ#',
+                        },
+                    },
+                    {
+                        'setting': 'username_length_min',
+                        'name': _("Minimal allowed username length"),
+                        'legend': _("User names"),
+                        'python_type': 'int',
+                        'value': 3,
+                        'field_extra': {
+                            'min_value': 2,
+                            'max_value': 255,
+                        },
+                    },
+                    {
+                        'setting': 'username_length_max',
+                        'name': _("Maximal allowed username length"),
+                        'python_type': 'int',
+                        'value': 14,
+                        'field_extra': {
+                            'min_value': 2,
+                            'max_value': 255,
+                        },
+                    },
+                    {
+                        'setting': 'password_length_min',
+                        'name': _("Minimum user password length"),
+                        'legend': _("Passwords"),
+                        'python_type': 'int',
+                        'value': 3,
+                        'field_extra': {
+                            'min_value': 2,
+                            'max_value': 255,
+                        },
+                    },
+                    {
+                        'setting': 'password_complexity',
+                        'name': _("Complexity"),
+                        'python_type': 'list',
+                        'value': [],
+                        'form_field': 'checkbox',
+                        'field_extra': {
+                            'choices': (
+                                ('case', _("Require mixed Case")),
+                                ('digits', _("Require digits")),
+                                ('special', _("Require special characters"))
+                            ),
+                        },
+                    },
+                    {
+                        'setting': 'avatars_types',
+                        'name': _("Available avatar types"),
+                        'legend': _("Avatars"),
+                        'python_type': 'list',
+                        'value': ['gravatar', 'upload'],
+                        'form_field': 'checkbox',
+                        'field_extra': {
+                            'choices': (
+                                ('gravatar', _("Gravatar")),
+                                ('upload', _("Uploaded avatar")),
+                                ('gallery', _("Avatars gallery"))
+                            ),
+                        },
+                    },
+                    {
+                        'setting': 'default_avatar',
+                        'name': _("Default avatar"),
+                        'value': 'gravatar',
+                        'form_field': 'select',
+                        'field_extra': {
+                            'choices': (
+                                ('gravatar', _("Gravatar")),
+                                ('gallery', _("Random avatar from gallery")),
+                            ),
+                        },
+                    },
+                    {
+                        'setting': 'upload_limit',
+                        'name': _("Maximum size of uploaded avatar"),
+                        'description': _("Enter maximum allowed file size "
+                                         "(in KB) for avatar uploads"),
+                        'python_type': 'int',
+                        'value': 128,
+                        'field_extra': {
+                            'min_value': 0,
+                        },
+                    },
+                    {
+                        'setting': 'subscribe_start',
+                        'name': _("Subscribe to started threads"),
+                        'legend': _("Default subscriptions settings"),
+                        'python_type': 'int',
+                        'value': 2,
+                        'form_field': 'select',
+                        'field_extra': {
+                            'choices': (
+                                (0, _("Don't watch")),
+                                (1, _("Put on watched threads list")),
+                                (2, _("Put on watched threads list and "
+                                      "e-mail user when somebody replies")),
+                            ),
+                        },
+                    },
+                    {
+                        'setting': 'subscribe_reply',
+                        'name': _("Subscribe to replied threads"),
+                        'python_type': 'int',
+                        'value': 2,
+                        'form_field': 'select',
+                        'field_extra': {
+                            'choices': (
+                                (0, _("Don't watch")),
+                                (1, _("Put on watched threads list")),
+                                (2, _("Put on watched threads list and "
+                                      "e-mail user when somebody replies")),
+                            ),
+                        },
+                    },
+                )
+            },
+        )
+
+
+    def backwards(self, orm):
+        "Write your backwards methods here."
+
+    models = with_conf_models('0001_initial')
+
+    complete_apps = ['core']
+    symmetrical = True
+
+    depends_on = (
+        ("conf", "0001_initial"),
+    )

+ 0 - 0
misago/users/migrations/__init__.py


+ 9 - 4
misago/users/models/usermodel.py

@@ -14,8 +14,8 @@ class UserManager(BaseUserManager):
             raise ValueError(_("User must have an email address."))
 
         now = timezone.now()
-        user = self.model(is_staff=False, is_active=True, is_superuser=False,
-                          last_login=now, joined_on=now, **extra_fields)
+        user = self.model(is_staff=False, is_superuser=False, last_login=now,
+                          joined_on=now, **extra_fields)
 
         user.set_username(username)
         user.set_email(email)
@@ -26,14 +26,19 @@ class UserManager(BaseUserManager):
     def create_superuser(self, username, email, password):
         user = self.create_user(username, email, password=password)
         user.is_staff = True
-        user.is_active = True
         user.is_superuser = True
         user.save(using=self._db)
         return user
 
+    def get_by_username(self, username):
+        return self.get(username_slug=slugify(username))
+
+    def get_by_email(self, email):
+        return self.get(email_hash=hash_email(email))
+
     def get_by_username_or_email(self, login):
         queryset = models.Q(username_slug=slugify(login))
-        queryset = queryset | models.Q(email_hash=hash_email(new_email))
+        queryset = queryset | models.Q(email_hash=hash_email(login))
         return self.get(queryset)
 
 

+ 40 - 1
misago/users/tests/test_validators.py

@@ -1,13 +1,32 @@
 #-*- coding: utf-8 -*-
+from django.contrib.auth import get_user_model
 from django.core.exceptions import ValidationError
 from django.test import TestCase
+from misago.conf import settings
 from misago.users.validators import (validate_username_available,
                                      validate_username_content,
                                      validate_username_length,
                                      validate_username)
 
 
-class ValidateContentTests(TestCase):
+class ValidateUsernameAvailableTests(TestCase):
+    def setUp(self):
+        User = get_user_model()
+        self.test_user = User.objects.create_user('EricTheFish',
+                                                  'eric@test.com',
+                                                  'pass123')
+
+    def test_valid_names(self):
+        """validate_username_available allows available names"""
+        validate_username_available('BobBoberson')
+
+    def test_invalid_names(self):
+        """validate_username_available disallows unvailable names"""
+        with self.assertRaises(ValidationError):
+            validate_username_available(self.test_user.username)
+
+
+class ValidateUsernameContentTests(TestCase):
     def test_valid_names(self):
         """validate_username_content allows valid names"""
         validate_username_content('123')
@@ -26,3 +45,23 @@ class ValidateContentTests(TestCase):
             validate_username_content(u'Rafał')
         with self.assertRaises(ValidationError):
             validate_username_content(u'初音 ミク')
+
+
+class ValidateUsernameLengthTests(TestCase):
+    def test_valid_names(self):
+        """validate_username_length allows valid names"""
+        validate_username_length('a' * settings.username_length_min)
+        validate_username_length('a' * settings.username_length_max)
+
+    def test_invalid_names(self):
+        """validate_username_length disallows invalid names"""
+        with self.assertRaises(ValidationError):
+            validate_username_length('a' * (settings.username_length_min - 1))
+        with self.assertRaises(ValidationError):
+            validate_username_length('a' * (settings.username_length_max + 1))
+
+
+class ValidateUsernameTests(TestCase):
+    def test_validate_username(self):
+        """validate_username has no crashes"""
+        validate_username('LeBob')

+ 39 - 5
misago/users/validators.py

@@ -1,27 +1,61 @@
 import re
 from django.core.exceptions import ValidationError
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ungettext, ugettext_lazy as _
 from django.contrib.auth import get_user_model
+from misago.conf import settings
 
 
-username_regex = re.compile(r'^[0-9A-Z]+$', re.IGNORECASE)
+USERNAME_REGEX = re.compile(r'^[0-9a-z]+$', re.IGNORECASE)
 
 
 def validate_username_available(value):
     User = get_user_model()
 
+    try:
+        User.objects.get_by_username(value)
+    except User.DoesNotExist:
+        pass
+    else:
+        raise ValidationError(_("This username is not available."))
+
 
 def validate_username_content(value):
-    if not username_regex.match(value):
+    if not USERNAME_REGEX.match(value):
         raise ValidationError(
             _("Username can only contain latin alphabet letters and digits."))
 
 
 def validate_username_length(value):
-    pass
+    if len(value) < settings.username_length_min:
+        message = ungettext(
+            'Username must be at least one character long.',
+            'Username must be at least %(count)d characters long.',
+            settings.username_length_min)
+        message = message % {'count': settings.username_length_min}
+        raise ValidationError(message)
+
+    if len(value) > settings.username_length_max:
+        message = ungettext(
+            "Username cannot be longer than one characters.",
+            "Username cannot be longer than %(count)d characters.",
+            settings.username_length_max)
+        message = message % {'count': settings.username_length_max}
+        raise ValidationError(message)
 
 
 def validate_username(value):
-    validate_username_available(value)
+    """Shortcut function that does complete validation of username"""
     validate_username_content(value)
     validate_username_length(value)
+    validate_username_available(value)
+
+
+def validate_email_available(value):
+    User = get_user_model()
+
+    try:
+        User.objects.get_by_email(value)
+    except User.DoesNotExist:
+        pass
+    else:
+        raise ValidationError(_("This e-mail address is not available."))