add custom attributes support to Django user model
- support added to profile, registration and admin form - new field type can be added to authentic2/attribute_kinds.py - only two types now: string and siret (french enterprise identifier)
This commit is contained in:
parent
7de5f17574
commit
b9c0f7e213
|
@ -3,51 +3,69 @@ from copy import deepcopy
|
|||
from django.contrib import admin
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.admin import GroupAdmin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.admin import GroupAdmin, UserAdmin
|
||||
from django.contrib.auth.models import Group, User
|
||||
|
||||
from .nonce.models import Nonce
|
||||
from . import forms
|
||||
from . import forms, models, admin_forms
|
||||
|
||||
if settings.DEBUG:
|
||||
class NonceModelAdmin(admin.ModelAdmin):
|
||||
list_display = ("value", "context", "not_on_or_after")
|
||||
admin.site.register(Nonce, NonceModelAdmin)
|
||||
class AttributeValueAdmin(admin.ModelAdmin):
|
||||
list_display = ('content_type', 'owner', 'attribute',
|
||||
'content')
|
||||
admin.site.register(models.AttributeValue, AttributeValueAdmin)
|
||||
admin.site.register(models.FederatedId)
|
||||
admin.site.register(models.LogoutUrl)
|
||||
admin.site.register(models.AuthenticationEvent)
|
||||
admin.site.register(models.UserExternalId)
|
||||
|
||||
if settings.AUTH_USER_MODEL == 'authentic2.User':
|
||||
import models
|
||||
import admin_forms
|
||||
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
class AuthenticUserAdmin(UserAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
(_('Personal info'), {'fields': ('first_name', 'last_name',
|
||||
'email', 'nickname', 'url', 'company', 'phone',
|
||||
'postal_address')}),
|
||||
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
|
||||
'groups')}),
|
||||
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
|
||||
class AuthenticUserAdmin(UserAdmin):
|
||||
fieldsets = (
|
||||
(None, {'fields': ('username', 'password')}),
|
||||
(_('Personal info'), {'fields': ('first_name', 'last_name',)}),
|
||||
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
|
||||
'groups')}),
|
||||
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
|
||||
)
|
||||
form = admin_forms.UserChangeForm
|
||||
add_form = admin_forms.UserCreationForm
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'classes': ('wide',),
|
||||
'fields': ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')}
|
||||
),
|
||||
)
|
||||
form = admin_forms.UserChangeForm
|
||||
add_form = admin_forms.UserCreationForm
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'classes': ('wide',),
|
||||
'fields': ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')}
|
||||
),
|
||||
)
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
fieldsets = deepcopy(super(AuthenticUserAdmin, self).get_fieldsets(request, obj))
|
||||
if obj:
|
||||
if not request.user.is_superuser:
|
||||
fieldsets[2][1]['fields'] = filter(lambda x: x !=
|
||||
'is_superuser', fieldsets[2][1]['fields'])
|
||||
return fieldsets
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
fieldsets = deepcopy(super(AuthenticUserAdmin, self).get_fieldsets(request, obj))
|
||||
if obj:
|
||||
if not request.user.is_superuser:
|
||||
fieldsets[2][1]['fields'] = filter(lambda x: x !=
|
||||
'is_superuser', fieldsets[2][1]['fields'])
|
||||
qs = models.Attribute.objects.all()
|
||||
insertion_idx = 2
|
||||
else:
|
||||
qs = models.Attribute.objects.filter(required=True)
|
||||
insertion_idx = 1
|
||||
if qs.exists():
|
||||
fieldsets = list(fieldsets)
|
||||
fieldsets.insert(insertion_idx,
|
||||
(_('Attributes'), {'fields': [at.name for at in qs]}))
|
||||
return fieldsets
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, AuthenticUserAdmin)
|
||||
|
||||
class AttributeAdmin(admin.ModelAdmin):
|
||||
list_display = ('label', 'kind', 'required',
|
||||
'asked_on_registration', 'user_editable',
|
||||
'user_visible')
|
||||
|
||||
admin.site.register(models.Attribute, AttributeAdmin)
|
||||
|
||||
admin.site.register(models.User, AuthenticUserAdmin)
|
||||
|
||||
class A2GroupAdmin(GroupAdmin):
|
||||
form = forms.GroupAdminForm
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
from django import forms
|
||||
|
||||
|
||||
from django.contrib.auth.forms import UserChangeForm as AuthUserChangeForm, UserCreationForm as AuthUserCreationForm
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.auth.forms import (UserChangeForm as
|
||||
AuthUserChangeForm, UserCreationForm as
|
||||
AuthUserCreationForm)
|
||||
|
||||
from authentic2.compat import get_user_model
|
||||
|
||||
from . import forms
|
||||
|
||||
class UserChangeForm(forms.UserAttributeFormMixin,
|
||||
AuthUserChangeForm):
|
||||
|
||||
class UserChangeForm(AuthUserChangeForm):
|
||||
class Meta(AuthUserChangeForm.Meta):
|
||||
model = get_user_model()
|
||||
|
||||
class UserCreationForm(forms.UserAttributeFormMixin,
|
||||
AuthUserCreationForm):
|
||||
|
||||
class UserCreationForm(AuthUserCreationForm):
|
||||
class Meta(AuthUserCreationForm.Meta):
|
||||
model = get_user_model()
|
||||
|
||||
|
||||
def clean_username(self):
|
||||
# Since User.username is unique, this check is redundant,
|
||||
# but it sets a nicer error message than the ORM. See #13147.
|
||||
|
@ -26,4 +28,4 @@ class UserCreationForm(AuthUserCreationForm):
|
|||
User._default_manager.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
return username
|
||||
raise forms.ValidationError(self.error_messages['duplicate_username'])
|
||||
raise ValidationError(self.error_messages['duplicate_username'])
|
||||
|
|
|
@ -43,7 +43,8 @@ class CustomIndexDashboard(Dashboard):
|
|||
self.children.append(modules.ModelList(
|
||||
_('Users and groups'),
|
||||
models=('authentic2.models.User',
|
||||
'django.contrib.auth.models.*'),
|
||||
'django.contrib.auth.models.*',
|
||||
'authentic2.models.Attribute'),
|
||||
))
|
||||
self.children.append(modules.ModelList(
|
||||
_('SAML2'),
|
||||
|
|
|
@ -1,16 +1,55 @@
|
|||
from django import forms
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from authentic2.compat import get_user_model
|
||||
|
||||
from . import models
|
||||
|
||||
auth_models.User.USER_PROFILE = ('first_name', 'last_name', 'email')
|
||||
User = get_user_model()
|
||||
all_field_names = [field.name for field in User._meta.fields]
|
||||
field_names = getattr(User, 'USER_PROFILE', all_field_names)
|
||||
|
||||
__USER_FORM_CLASS = None
|
||||
|
||||
class UserProfileForm(forms.ModelForm):
|
||||
class UserAttributeFormMixin(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UserAttributeFormMixin, self).__init__(*args, **kwargs)
|
||||
self.attributes = self.get_attributes()
|
||||
initial = {}
|
||||
if 'instance' in kwargs:
|
||||
content_type = ContentType.objects.get_for_model(self.instance)
|
||||
for av in models.AttributeValue.objects.filter(
|
||||
content_type=content_type,
|
||||
object_id=self.instance.pk):
|
||||
initial[av.attribute.name] = av.to_python()
|
||||
for attribute in self.attributes:
|
||||
iv = initial.get(attribute.name)
|
||||
attribute.contribute_to_form(self, initial=iv)
|
||||
|
||||
def get_attributes(self):
|
||||
return models.Attribute.objects.all()
|
||||
|
||||
def save_attributes(self):
|
||||
for attribute in self.attributes:
|
||||
attribute.set_value(self.instance,
|
||||
self.cleaned_data[attribute.name])
|
||||
|
||||
def save(self, commit=True):
|
||||
result = super(UserAttributeFormMixin, self).save(commit=commit)
|
||||
if commit:
|
||||
self.save_attributes()
|
||||
else:
|
||||
old = self.save_m2m
|
||||
def save_m2m(*args, **kwargs):
|
||||
old(*args, **kwargs)
|
||||
self.save_attributes()
|
||||
self.save_m2m = save_m2m
|
||||
return result
|
||||
|
||||
class UserProfileForm(UserAttributeFormMixin, forms.ModelForm):
|
||||
error_css_class = 'form-field-error'
|
||||
required_css_class = 'form-field-required'
|
||||
|
||||
|
@ -20,6 +59,10 @@ class UserProfileForm(forms.ModelForm):
|
|||
if field in self.fields:
|
||||
self.fields[field].required = True
|
||||
|
||||
def get_attributes(self):
|
||||
qs = super(UserProfileForm, self).get_attributes()
|
||||
qs = qs.filter(user_visible=True, user_editable=True)
|
||||
return qs
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
|
|
@ -71,3 +71,8 @@ class FederatedIdManager(managers.PassThroughManager \
|
|||
'id_format': id_format,
|
||||
'id_value': id_value})
|
||||
|
||||
class AttributeValueQuerySet(QuerySet):
|
||||
pass
|
||||
|
||||
AttributeValueManager = managers.PassThroughManager \
|
||||
.for_queryset_class(AttributeValueQuerySet)
|
||||
|
|
|
@ -4,6 +4,8 @@ import urlparse
|
|||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils import timezone
|
||||
from django.utils.http import urlquote
|
||||
from django.conf import settings
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
from django.core.mail import send_mail
|
||||
|
@ -11,8 +13,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin,
|
||||
BaseUserManager, SiteProfileNotAvailable)
|
||||
from django.contrib.auth import load_backend
|
||||
from django.utils.http import urlquote
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from model_utils.managers import QueryManager
|
||||
|
||||
from . import attribute_kinds
|
||||
|
||||
|
||||
try:
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
|
@ -248,3 +254,82 @@ class FederatedId(models.Model):
|
|||
id_value = models.TextField()
|
||||
|
||||
objects = managers.FederatedIdManager()
|
||||
|
||||
class Attribute(models.Model):
|
||||
label = models.CharField(verbose_name=_('label'), max_length=63,
|
||||
unique=True)
|
||||
description = models.TextField(verbose_name=_('description'), blank=True)
|
||||
name = models.SlugField(verbose_name=_('name'), max_length=256,
|
||||
unique=True)
|
||||
required = models.BooleanField(
|
||||
verbose_name=_('required'),
|
||||
blank=True)
|
||||
asked_on_registration = models.BooleanField(
|
||||
verbose_name=_('asked on registration'),
|
||||
blank=True)
|
||||
user_editable = models.BooleanField(
|
||||
verbose_name=_('user editable'),
|
||||
blank=True)
|
||||
user_visible = models.BooleanField(
|
||||
verbose_name=_('user visible'),
|
||||
blank=True)
|
||||
multiple = models.BooleanField(
|
||||
verbose_name=_('multiple'),
|
||||
blank=True)
|
||||
kind = models.CharField(max_length=16,
|
||||
verbose_name=_('kind'),
|
||||
choices=attribute_kinds.get_choices())
|
||||
|
||||
objects = models.Manager()
|
||||
registration_attributes = QueryManager(asked_on_registration=True)
|
||||
user_attributes = QueryManager(user_editable=True)
|
||||
|
||||
def get_form_field(self, **kwargs):
|
||||
kwargs['label'] = self.label
|
||||
kwargs['required'] = self.required
|
||||
if self.description:
|
||||
kwargs['help_text'] = self.description
|
||||
return attribute_kinds.get_form_field(self.kind, **kwargs)
|
||||
|
||||
def get_kind(self):
|
||||
return attribute_kinds.get_kind(self.kind)
|
||||
|
||||
def contribute_to_form(self, form, **kwargs):
|
||||
form.fields[self.name] = self.get_form_field(**kwargs)
|
||||
|
||||
def set_value(self, owner, value):
|
||||
serialize = self.get_kind()['serialize']
|
||||
content = serialize(value)
|
||||
av, created = AttributeValue.objects.get_or_create(
|
||||
content_type=ContentType.objects.get_for_model(owner),
|
||||
object_id=owner.pk,
|
||||
attribute=self,
|
||||
defaults={'content': content})
|
||||
if not created:
|
||||
av.content = content
|
||||
av.save()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.label
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('attribute definition')
|
||||
verbose_name_plural = _('attribute definitions')
|
||||
|
||||
class AttributeValue(models.Model):
|
||||
content_type = models.ForeignKey('contenttypes.ContentType')
|
||||
object_id = models.PositiveIntegerField()
|
||||
owner = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
attribute = models.ForeignKey('Attribute',
|
||||
verbose_name=_('attribute'))
|
||||
|
||||
content = models.TextField(verbose_name=_('content'))
|
||||
|
||||
def to_python(self):
|
||||
deserialize = self.attribute.get_kind()['deserialize']
|
||||
return deserialize(self.content)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('attribute value')
|
||||
verbose_name_plural = _('attribute values')
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django import forms
|
||||
from django.forms import Form, CharField, PasswordInput
|
||||
|
||||
|
||||
from .. import app_settings, compat
|
||||
from .. import app_settings, compat, forms
|
||||
|
||||
|
||||
class RegistrationForm(forms.Form):
|
||||
class RegistrationForm(forms.UserAttributeFormMixin, Form):
|
||||
error_css_class = 'form-field-error'
|
||||
required_css_class = 'form-field-required'
|
||||
|
||||
password1 = forms.CharField(widget=forms.PasswordInput,
|
||||
label=_("Password"))
|
||||
password2 = forms.CharField(widget=forms.PasswordInput,
|
||||
label=_("Password (again)"))
|
||||
password1 = CharField(widget=PasswordInput, label=_("Password"))
|
||||
password2 = CharField(widget=PasswordInput, label=_("Password (again)"))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -47,7 +46,7 @@ class RegistrationForm(forms.Form):
|
|||
User = compat.get_user_model()
|
||||
existing = User.objects.filter(username__iexact=self.cleaned_data['username'])
|
||||
if existing.exists():
|
||||
raise forms.ValidationError(_("A user with that username already exists."))
|
||||
raise ValidationError(_("A user with that username already exists."))
|
||||
else:
|
||||
return self.cleaned_data['username']
|
||||
|
||||
|
@ -58,7 +57,7 @@ class RegistrationForm(forms.Form):
|
|||
User = compat.get_user_model()
|
||||
if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE:
|
||||
if User.objects.filter(email__iexact=self.cleaned_data['email']):
|
||||
raise forms.ValidationError(_('This email address is already in '
|
||||
raise ValidationError(_('This email address is already in '
|
||||
'use. Please supply a different email address.'))
|
||||
return self.cleaned_data['email']
|
||||
|
||||
|
@ -71,5 +70,5 @@ class RegistrationForm(forms.Form):
|
|||
"""
|
||||
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
|
||||
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
|
||||
raise forms.ValidationError(_("The two password fields didn't match."))
|
||||
raise ValidationError(_("The two password fields didn't match."))
|
||||
return self.cleaned_data
|
||||
|
|
|
@ -42,6 +42,11 @@ class RegistrationView(BaseRegistrationView):
|
|||
new_user.clean()
|
||||
new_user.set_password(cleaned_data['password1'])
|
||||
new_user.save()
|
||||
attributes = models.Attribute.objects.filter(
|
||||
asked_on_registration=True)
|
||||
if attributes:
|
||||
for attribute in attributes:
|
||||
attribute.set_value(new_user, cleaned_data[attribute.name])
|
||||
registration_profile = RegistrationProfile.objects.create_profile(new_user)
|
||||
registration_profile.send_activation_email(site)
|
||||
|
||||
|
|
Loading…
Reference in New Issue