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" <[email protected]> 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 [email protected]. > To post to this group, send email to [email protected]. > 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 [email protected]. To post to this group, send email to [email protected]. 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.

