Skip to content

Commit

Permalink
Dj1.8: repl longerusername with custom user model
Browse files Browse the repository at this point in the history
The longerusername package is not really compatible with Django 1.7+ - it only
hooks into South migrations, and Django 1.7+ does not allow the "monkeypatch"
approach that longerusername uses to modify the Auth.User model.

Instead, do the proper thing and define a custom user model as per
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#specifying-a-custom-user-model

To make the transition easy:
* Create a model that inherits from Auth.AbstractUser, only modifying the username length
* Make the model use the auth_user DB table so it picks up existing data
* Call the model User so that links from other existing tables still work

For existing Django 1.4 databases, the model would just inherit the existing table structure.

For new Django 1.8 databases, the generated initial migrations would create the tables the same way.

Make the following considerations in designing and implementing the model:
* Register the model (accounts.User) with the admin class
  (django.contrib.auth.admin.UserAdmin) in accounts/admin.py, not
  accounts/models.py (follow conventions in django.contrib.auth and resolve
  compatibility issues raised in grnet#15).
* Mark the model as swappable.
* Make sure the migration also keeps the swappable property.
* Update imports across the DjNRO codebase to reflect that User is now defined in
  accounts.models, not django.contrib.auth.models - and if possible, in model
  definition, refer to the (swappable) user model through
  settings.AUTH_USER_MODEL.

Swappable models:

The accounts.User Meta class has the same property "swappable" to as is defined in auth.User:

class User(AbstractUser):
    ...

    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

The value of the property is the name of a setting from the Django project's
settings that defines which model class implements a particular feature (the
user model in this case).

With this feature on, only the model class where the name of the class matches
the value of the setting referenced by the property is active.

Without adding this property, and when changing settings.AUTH_USER_MODEL back
to auth.User (either explicitly or by removing the setting and letting it
default to this value), the accounts.User model class would clash with
auth.User - they'd be both adding foreign key relations with the same name to
related classes.
  • Loading branch information
vladimir-mencl-eresearch committed Mar 17, 2016
1 parent 3c341d4 commit 7acf827
Show file tree
Hide file tree
Showing 11 changed files with 38 additions and 14 deletions.
5 changes: 3 additions & 2 deletions accounts/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from django.contrib import admin
from accounts.models import UserProfile

from accounts.models import User, UserProfile
from django.contrib.auth.admin import UserAdmin

class UserPrAdmin(admin.ModelAdmin):
list_display = ('user', 'institution')

admin.site.register(UserProfile, UserPrAdmin)
admin.site.register(User, UserAdmin)
25 changes: 23 additions & 2 deletions accounts/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
from django.db import models
from django.contrib.auth.models import User
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.admin import UserAdmin
from django.contrib import admin
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from edumanage.models import Institution
import re


class User(AbstractUser):
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
db_table = 'auth_user';
verbose_name = _('user')
verbose_name_plural = _('users')

User._meta.get_field('username').max_length = 255
# try also updating the help_text
try:
help_text = User._meta.get_field('username').help_text._proxy____args[0]
if help_text != None:
User._meta.get_field('username').help_text = _(re.sub("[0-9]+ characters", "255 characters", help_text))
except:
pass

class UserProfile(models.Model):
user = models.OneToOneField(User)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
institution = models.ForeignKey(Institution)
is_social_active = models.BooleanField(default=False)

Expand Down
2 changes: 1 addition & 1 deletion accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from accounts.models import User
from django.views.decorators.cache import never_cache
from django import forms
from registration.models import RegistrationProfile
Expand Down
3 changes: 2 additions & 1 deletion djangobackends/ldapBackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import ldap

from django.contrib.auth.models import User, UserManager, Permission, Group
from django.contrib.auth.models import UserManager, Permission, Group
from accounts.models import User
from django.conf import settings

class ldapBackend:
Expand Down
3 changes: 2 additions & 1 deletion djangobackends/shibauthBackend.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- vim:encoding=utf-8:
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab

from django.contrib.auth.models import User, UserManager, Permission, Group
from django.contrib.auth.models import UserManager, Permission, Group
from accounts.models import User
from django.conf import settings

class shibauthBackend:
Expand Down
4 changes: 3 additions & 1 deletion djnro/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
('en', _('English')),
)

# Use a custom user model (as replacement for longerusername)
AUTH_USER_MODEL = 'accounts.User'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en'
Expand Down Expand Up @@ -150,7 +153,6 @@
)

INSTALLED_APPS = (
'longerusername',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
Expand Down
2 changes: 1 addition & 1 deletion edumanage/decorators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.template import RequestContext
from django.shortcuts import render
from django.utils.translation import ugettext as _
from django.contrib.auth.models import User
from accounts.models import User
from django import forms

from accounts.models import UserProfile
Expand Down
2 changes: 1 addition & 1 deletion edumanage/management/commands/contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from optparse import make_option
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from accounts.models import User


class Command(BaseCommand):
Expand Down
3 changes: 1 addition & 2 deletions edumanage/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from django.utils.text import capfirst
from django.core import exceptions
from django.conf import settings
from django.contrib.auth.models import User
from django import forms
from django.core.exceptions import ValidationError

Expand Down Expand Up @@ -582,7 +581,7 @@ class CatEnrollment(models.Model):
inst = models.ForeignKey(Institution)
url = models.CharField(max_length=255, blank=True, null=True, help_text="Set to ACTIVE if institution has CAT profiles")
cat_instance = models.CharField(max_length=50, choices=settings.CAT_INSTANCES)
applier = models.ForeignKey(User)
applier = models.ForeignKey(settings.AUTH_USER_MODEL)
ts = models.DateTimeField(auto_now=True)

class Meta:
Expand Down
2 changes: 1 addition & 1 deletion edumanage/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from django.views.decorators.cache import never_cache
from django.utils.translation import ugettext as _
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User
from accounts.models import User
from django.core.cache import cache
from django.contrib.auth import REDIRECT_FIELD_NAME

Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ argparse>=1.2.1
django-registration==2.0.4
django-tinymce==1.5.3
ipaddr==2.1.11
longerusername==0.4
python-social-auth==0.2.14
requests==2.7.0
wsgiref==0.1.2
Expand Down

0 comments on commit 7acf827

Please sign in to comment.