Wow, nice. You are probably right in not inheriting from PermissionMixin and AbstractBaseUser and just re-implementing the needed functionality. I ended up doing the same thing for my custom user and auth backend as well. Not as convenient, but hey, it works right? ;-)
Bravo. -James On Jan 20, 2015 3:17 PM, "Erik Cederstrand" <erik+li...@cederstrand.dk> wrote: > Ok, here's a stripped-down solution. > > I ended up creating a new SchoolUser user model with a OneToOne relation > to my LegacyUser, to keep the LegacyUser model uncluttered. The SchoolUser > implements all methods from AbstractBaseUser and PermissionsMixin but > doesn't inherit from them, because I don't want the model fields that they > contain. > > I also kept the SchoolUser independent from the standard Django User (i.e. > no AUTH_USER_MODEL='SchoolUser' in settings.py), so I can still create > superuser accounts for myself and my colleagues, that are not connected to > a school user. > > Here's the code: > > > settings.py: > [...] > > AUTHENTICATION_BACKENDS = ( > 'django.contrib.auth.backends.ModelBackend', > 'school_auth.backends.SchoolModelBackend', > ) > > > > school_auth/backend.py: > from django.contrib.auth.backends import ModelBackend > from .models import SchoolUser, LegacyUser > > class SchoolModelBackend(object): > def authenticate(self, school_id=None, username=None, password=None, > **kwargs): > if LegacyUser.validate(school=school_id, username=username, > password=password): > # Password hash validation > try: > school_user = SchoolUser.objects.get(user__school=school_id, > user__name=username) > except SchoolUser.DoesNotExist: > school_user = > SchoolUser.objects.create_user(school_id=school_id, username=username) > # Annotate the user object with the path of the backend. > school_user.backend = "%s.%s" % (self.__class__.__module__, > self.__class__.__name__) > return school_user > # > # if LDAP.validate(school=school_id, username=username, > password=password): > # pass > # if WAYF.validate(school=school_id, username=username, > password=password): > # pass > return None > > def get_group_permissions(self, user_obj, obj=None): > raise NotImplementedError() > > def get_all_permissions(self, user_obj, obj=None): > raise NotImplementedError() > > def has_perm(self, user_obj, perm, obj=None): > if not user_obj.is_active: > return False > return perm in self.get_all_permissions(user_obj, obj) > > def has_module_perms(self, user_obj, app_label): > if not user_obj.is_active: > return False > for perm in self.get_all_permissions(user_obj): > if perm[:perm.index('.')] == app_label: > return True > return False > > def get_user(self, user_id): > try: > return SchoolUser.objects.get(pk=user_id) > except SchoolUser.DoesNotExist: > return None > > > > school_auth/forms.py: > from django.contrib.auth.forms import AuthenticationForm, > PasswordResetForm, PasswordChangeForm > from django import forms > from django.utils.translation import ugettext_lazy as _ > from django.utils.text import capfirst > from .models import LegacyUser, School, SchoolUser > from .backends import SchoolModelBackend > > class SchoolAuthenticationForm(AuthenticationForm): > school = forms.ModelChoiceField(queryset=School.objects.active(), > empty_label=_('Please select a school')) > username = forms.CharField(max_length=40) > password = forms.CharField(label=_("Password"), > widget=forms.PasswordInput) > > class Meta: > model = LegacyUser > > fields = ['school', 'name', 'password'] > > def __init__(self, request=None, *args, **kwargs): > """ > The 'request' parameter is set for custom auth use by subclasses. > The form data comes in via the standard 'data' kwarg. > """ > self.request = request > self.user_cache = None > super().__init__(*args, **kwargs) > > # Set the label for the "username" field. > self.username_field = > LegacyUser._meta.get_field(SchoolUser.USERNAME_FIELD) > if self.fields['username'].label is None: > self.fields['username'].label = > capfirst(self.username_field.verbose_name) > > def clean(self): > school = self.cleaned_data.get('school') > username = self.cleaned_data.get('username') > password = self.cleaned_data.get('password') > > if school and username and password: > self.user_cache = > SchoolModelBackend().authenticate(school_id=school.pk, username=username, > > password=password) > if self.user_cache is None: > raise forms.ValidationError( > self.error_messages['invalid_login'], > code='invalid_login', > params={'username': self.username_field.verbose_name}, > ) > else: > self.confirm_login_allowed(self.user_cache) > > return self.cleaned_data > > > > school_auth/models.py: > from django.contrib.auth.models import PermissionsMixin, BaseUserManager, > AbstractBaseUser > from django.db import models > from django.utils.translation import ugettext_lazy as _ > from django.utils import timezone > from django.utils.crypto import salted_hmac > from ..legacy.models import LegacyUser, School > > class SchoolUserManager(BaseUserManager): > > def create_user(self, school_id, username): > user = LegacyUser.objects.get(school=school_id, name=username) > school_user = self.model(user=user) > school_user.save() > return school_user > > > class SchoolUser(models.Model): > """ > Custom User model. We don't inherit AbstractBaseUser because we don't > want the password field. > """ > USERNAME_FIELD = 'name' > > user = models.OneToOneField(User, null=False) > last_login = models.DateTimeField(_('last login'), default=timezone.now) > > objects = SchoolUserManager() > REQUIRED_FIELDS = [] > > @property > def school(self): > return self.user.school > > def __str__(self): > return self.get_username() > > def get_username(self): > return self.user.name > > @property > def is_active(self): > return self.user.active > > @property > def is_staff(self): > return self.user.is_admin() > > def natural_key(self): > return self.user.school_id, self.user.name > > @staticmethod > def is_anonymous(): > return False > > @staticmethod > def is_authenticated(): > return True > > def set_password(self, raw_password): > self.user.set_password(raw_password) > > def check_password(self, raw_password): > return self.user.validate_password(raw_password) > > def set_unusable_password(self): > pass > > @staticmethod > def has_usable_password(): > return True > > def get_full_name(self): > return self.user.name > > def get_short_name(self): > return self.user.name > > def get_session_auth_hash(self): > """ > Returns an HMAC of the password field. > """ > key_salt = > "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" > return salted_hmac(key_salt, self.user.password).hexdigest() > > def email_user(self, subject, message, from_email=None, **kwargs): > """ > Sends an email to this User. > """ > from django.core.mail import send_mail > send_mail(subject, message, from_email, [self.email], **kwargs) > > @property > def is_superuser(self): > # This can never be a Django superuser > return False > > def get_group_permissions(self, obj=None): > raise NotImplementedError() > > def get_all_permissions(self, obj=None): > raise NotImplementedError() > > def has_perm(self, perm, obj=None): > [...] # My custom per-app permissions > > def has_perms(self, perm_list, obj=None): > for perm in perm_list: > if not self.has_perm(perm, obj): > return False > return True > > def has_module_perms(self, app_label): > [...] # My custom per-module permissions > > > > school_auth/urls.py: > from django.conf.urls import url > from django.contrib.auth.views import login, logout > from .forms import SchoolAuthenticationForm > > urlpatterns = [ > url(r'^login/$', login, kwargs={'authentication_form': > SchoolAuthenticationForm, 'template_name': 'school_auth/login.html'}, > name='school_auth.login'), > url(r'^logout/$', logout, name='school_auth.logout'), > ] > > > Thanks, > > Erik > > -- > You received this message because you are subscribed to the Google Groups > "Django users" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to django-users+unsubscr...@googlegroups.com. > To post to this group, send email to django-users@googlegroups.com. > Visit this group at http://groups.google.com/group/django-users. > To view this discussion on the web visit > https://groups.google.com/d/msgid/django-users/BC66EBBA-A032-42B1-8DB0-F2CFFEE48BA6%40cederstrand.dk > <https://groups.google.com/d/msgid/django-users/BC66EBBA-A032-42B1-8DB0-F2CFFEE48BA6%40cederstrand.dk?utm_medium=email&utm_source=footer> > . > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "Django users" group. To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscr...@googlegroups.com. To post to this group, send email to django-users@googlegroups.com. Visit this group at http://groups.google.com/group/django-users. To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CA%2Be%2BciXf_KgDQDY6DdGGja6%3D%2BON8CaLBbbBnkJDb92BzH6ZYrg%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.