I've gotten this far with UserProfiles on Django 1.4.5. It works, but now I would like to be able to add a value to my custom field in the admin when creating the user. The way this works, I need to create the new user, then go back and add the UserProfile field.
Where and what do I need to do to add the UserProfile field upon User creation?
### admin.py
class UserProfileInline(admin.StackedInline):
model = UserProfile
can_delete = False
verbose_name_plural = 'profile'
# Define a new User admin
class UserAdmin(UserAdmin):
inlines = (UserProfileInline, )
# Something here?
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
### models.py
class UserProfile(models.Model):
"""Create additional fields for users.
Site ID
"""
# This field is required
user = models.OneToOneField(User)
# Additional fields to add.
site_id = models.IntegerField(null=True, blank=True, verbose_name='Site ID')
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
# Or maybe here?
post_save.connect(create_user_profile, sender=User)
It turns out I was actually running into this issue. This post solved it.
Django - UserProfile m2m field in admin - error
Related
How can I make a Django User email unique when a user is signing up?
forms.py
class SignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ("username", "email", "password1", "password2")
def save(self, commit=True):
user = super(SignUpForm, self).save(commit=False)
user.email = self.cleaned_data["email"]
if commit:
user.save()
return user
I'm using the from django.contrib.auth.models User.
Do I need to override the User in the model. Currently the model doesn't make a reference to User.
views.py
class SignUp(generic.CreateView):
form_class = SignUpForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
The best answer is to use CustomUser by subclassing the AbstractUser and put the unique email address there. For example:
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
email = models.EmailField(unique=True)
and update the settings with AUTH_USER_MODEL="app.CustomUser".
But if its not necessary for you to store the unique email in Database or maybe not use it as username field, then you can update the form's clean method to put a validation. For example:
from django.core.exceptions import ValidationError
class YourForm(UserCreationForm):
def clean(self):
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise ValidationError("Email exists")
return self.cleaned_data
Update
If you are in mid project, then you can follow the documentation on how to change migration, in short which is to:
Backup you DB
Create a custom user model identical to auth.User, call it User (so many-to-many tables keep the same name) and set db_table='auth_user' (so it uses the same table)
Delete all Migrations File(except for __init__.py)
Delete all entry from table django_migrations
Create all migrations file using python manage.py makemigrations
Run fake migrations by python manage.py migrate --fake
Unset db_table, make other changes to the custom model, generate migrations, apply them
But if you are just starting, then delete the DB and migrations files in migration directory except for __init__.py. Then create a new DB, create new set of migrations by python manage.py makemigrations and apply migrations by python manage.py migrate.
And for references in other models, you can reference them to settings.AUTH_USER_MODEL to avoid any future problems. For example:
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
It will automatically reference to the current User Model.
Here is a working code
Use the below code snippets in any of your models.py
models.py
from django.contrib.auth.models import User
User._meta.get_field('email')._unique = True
django version : 3.0.2
Reference : Django auth.user with unique email
Working Code for Django 3.1
models.py
from django.contrib.auth.models import User
User._meta.get_field('email')._unique = True
SETTINGS.PY
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend'
]
There is a great example of this in Django's docs - https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#a-full-example.
You have to declare the email field in your AbstractBaseUser model as unique=True.
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
Easy way:
you can user signal
Example
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.forms import ValidationError
#receiver(pre_save, sender=User)
def check_email(sender, instance, **kwargs):
email = instance.email
if sender.objects.filter(email=email).exclude(username=instance.username).exists():
raise ValidationError('Email Already Exists')
You might be interested in:
django-user-unique-email
Reusable User model with required unique email field and mid-project support.
It defines custom User model reusing of the original table (auth_user) if exists. If needed (when added to existing project), it recreates history of applied migrations in the correct order.
I'll appreciate any feedback.
A better way of doing then using AbstractBaseUser
#forms.py
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
from django.contrib.auth.form import UserCreationForm
from some_app.validators import validate_email
def validate_email(value):
if User.objects.filter(email = value).exists():
raise ValidationError((f"{value} is taken."),params = {'value':value})
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField(validators = [validate_email])
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
In case of use CustomUser model inherit from AbstractBaseUser you can override the full_clean() method to validate unique constraints on the model fields you specified unique=True. This is safer than form (i.e. FormClass) validation.
Example:
from django.contrib.auth.models import AbstractBaseUser
from django.db import models
class CustomUser(AbstractBaseUser):
email = models.EmailField(unique=True)
# ...
def full_clean(self, **kwargs):
"""
Call clean_fields(), clean(), and validate_unique() on the model.
Raise a ValidationError for any errors that occur.
"""
super().full_clean()
Note: Tested on Django 3.1
Improvement for solution with form validation
Instead of raising a ValidationError, it would be better to use the add_error method so that all errors of the forms are sent, and not only the one raised by ValidationError.
class SignUpForm(UserCreationForm):
email = forms.EmailField(max_length=254, help_text='Required. Inform a valid email address.')
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2', )
def clean(self):
cleaned_data = super().clean()
email = cleaned_data.get('email')
if User.objects.filter(email=email).exists():
msg = 'A user with that email already exists.'
self.add_error('email', msg)
return self.cleaned_data
You can edit model in meta as follow
Note: This will not update the original model
class SignUpForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
model._meta.get_field('email')._unique = True
fields = ("username", "email", "password1", "password2")
I am using the authtools plugin to manage the user and its profile in django, but when I create the user, it does not create its profile, I have to go to the admin part of the site and create it manually.
I separated the applications into account and profile.
This is the profiles model:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True)
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
email_verified = models.BooleanField("Email verified", default=True)
This is the signal.py, that is inside of the profiles application:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_handler(sender, instance, created, **kwargs):
if not created:
return
profile = models.Profile(user=instance)
profile.save()
logger.info('New user profile for {} created'.format(instance))
This is the admin.py of the account app:
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline]
list_display = ('is_active', 'email', 'name', 'permalink',
'is_superuser', 'is_staff',)
# 'View on site' didn't work since the original User model needs to
# have get_absolute_url defined. So showing on the list display
# was a workaround.
def permalink(self, obj):
url = reverse("profiles:show",
kwargs={"slug": obj.profile.slug})
# Unicode hex b6 is the Pilcrow sign
return format_html('{}'.format(url, '\xb6'))
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
admin.site.register(Profile)
When I signup a user, both the user and the profile objects are created, only they are not linked. Why is that?
Thank you
Use this below your profile model in models.py. I hope you are generating slug by another slugify signal.
def user_created_receiver(sender, instance, created, *args, **kwargs):
if created:
Profile.objects.get_or_create(user = instance)
post_save.connect(user_created_receiver, sender = User)
I have the following models:
class UserProfile(models.Model):
user = models.OneToOneField(User)
class Property(models.Model):
user = models.ForeignKey(User)
I would like to create a TabularInline displaying every Property connected to a particular UserProfile on its Django admin page. The problem here is, of course, that Property does not have a ForeignKey directly to UserProfile, so I cannot simply write
class PropertyTabularInline(admin.TabularInline):
model = Property
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
How can I easily do what I want?
You can overwrite the User admin page to display both the Profile and the Property models.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from myapp.models import *
class ProfileInline(admin.TabularInline):
model = Profile
class PropertyInline(admin.TabularInline):
model = Property
class UserAdmin(UserAdmin):
inlines = (ProfileInline, PropertyInline,)
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
You can also remove any unwanted/unused User properties from being displayed (e.g. Groups or Permissions)
more here: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#extending-the-existing-user-model
and here:
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#a-full-example
class PropertyTabularInline(admin.TabularInline):
model = Property
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'user':
# implement your method to get userprofile object from request here.
user_profile = self.get_object(kwargs['request'], UserProfile)
kwargs["queryset"] = Property.objects.filter(user=user_profile)
return super(PropertyInLine, self).formfield_for_dbfield(field, **kwargs)
once this is done, you can add this inline to user UserProfileAdmin like:
class UserProfileAdmin(admin.ModelAdmin):
inlines = (PropertyTabularInline,)
Haven't tested it, but that should work.
It is achievable by making one change in your models.
Instead of creating OneToOne relationship from UserProfile to User, subclass User creating UserProfile. Code should look like that:
class UserProfile(User):
# some other fields, no relation to User model
class Property(models.Model):
user = models.ForeignKey(User)
That will result in creating UserProfile model that have hidden OneToOne relation to User model, it won't duplicate user model.
After doing that change, your code will work. There are some changes under the hood, like UserProfile no longer have it's own ID, you can access fields from User inside UserProfile and it's hard to swap User model using settings.AUTH_USER_MODEL (that will require creating some custom function returning proper type and changing migration by hand) but if this is not a problem for you, it may be good solution.
I'm using a custom sign up form with django-allauth.
settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'project.userprofile.form.UserSignupForm'
form.py
from django import forms
from models import UserProfile
class UserSignupForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('mobile_number',)
models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
mobile_number = models.CharField(max_length=30, blank=True, null=True)
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
The User and the UserProfile objects are created, however the UserProfile isn't associated with any User object. It's late and I'm probably missing something silly, right?
UPDATE: As Kevin pointed out, the solution was to add the save method in the form.py. This is how it looks now:
from django import forms
from django.contrib.auth.models import User
from models import UserProfile
class UserSignupForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('mobile_number',)
def save(self, user):
profile = UserProfile(user=user)
profile.mobile_number = self.cleaned_data['mobile_number']
profile.save()
The documentation says:
[ACCOUNT_SIGNUP_FORM_CLASS] should implement a ‘save’ method, accepting the newly signed up user as its only parameter.
It looks like you haven't provided such a method, so the user never gets connected to the profile. And I think you're not seeing an error because ModelForm has a save(commit=True) method that happens to match this signature, even though it doesn't do what you want.
I have django custom user model MyUser with one extra field:
# models.py
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
age = models.PositiveIntegerField(_("age"))
# settings.py
AUTH_USER_MODEL = "web.MyUser"
I also have according to these instructions custom all-auth Signup form class:
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = MyUser
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
After submitting SignupForm (field for property MyUser.age is rendered corectly), I get this error:
IntegrityError at /accounts/signup/
(1048, "Column 'age' cannot be null")
What is the proper way to store Custom user model?
django-allauth: 0.12.0; django: 1.5.1; Python 2.7.2
Though it is a bit late but in case it helps someone.
You need to create your own Custom AccountAdapter by subclassing DefaultAccountAdapter and setting the
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=False)
user.age = form.cleaned_data.get('age')
user.save()
and you also need to define the following in settings:
ACCOUNT_ADAPTER = 'api.adapter.UserAccountAdapter'
This is also useful, if you have a custom SignupForm to create other models during user registration and you need to make an atomic transaction that would prevent any data from saving to the database unless all of them succeed.
The DefaultAdapter for django-allauth saves the user, so if you have an error in the save method of your custom SignupForm the user would still be persisted to the database.
So for anyone facing this issue, your CustomAdpater would look like this
class UserAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
"""
This is called when saving user via allauth registration.
We override this to set additional data on user object.
"""
# Do not persist the user yet so we pass commit=False
# (last argument)
user = super(UserAccountAdapter, self).save_user(request, user, form, commit=commit)
user.age = form.cleaned_data.get('age')
# user.save() This would be called later in your custom SignupForm
Then you can decorate your custom SignupForm's with #transaction.atomic
#transaction.atomic
def save(self, request, user):
user.save() #save the user object first so you can use it for relationships
...
Side note
With Django 1.5 custom user model, the best practice is to use the get_user_model function:
from django.contrib.auth import get_user_model
# forms.py
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
age = forms.IntegerField(max_value=100)
class Meta:
model = get_user_model() # use this function for swapping user model
def save(self, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.age = self.cleaned_data['age']
user.save()
# settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'web.forms.SignupForm'
Maybe it's not related, but I thought it would be worth noticing.
i think you should define fields property in class Meta in SignupForm and set list of fields that contains age, like this :
class SignupForm(forms.Form):
...
class Meta:
model = MyUser
fields = ['first_name', 'last_name', 'age']
and if it's not worked, look at
this