My models:
class UserProfile(models.Model):
TYPES_CHOICES = (
(0, _(u'teacher')),
(1, _(u'student')),
)
user = models.ForeignKey(User, unique=True)
type = models.SmallIntegerField(default=0, choices=TYPES_CHOICES, db_index=True)
cities = models.ManyToManyField(City)
class City(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50)
In admin.py:
admin.site.unregister(User)
class UserProfileInline(admin.StackedInline):
model = UserProfile
class UserProfileAdmin(UserAdmin):
inlines = [UserProfileInline]
admin.site.register(User, UserProfileAdmin)
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
"""Create a matching profile whenever a user object is created."""
if created:
profile, new = UserProfile.objects.get_or_create(user=instance)
But when I add new user and select a city I get that error: IntegrityError at /admin/auth/user/add/
(1062, "Duplicate entry '3' for key 'user_id'")
What is wrong with my code? If I don't select any city - user is added properly. Some way, user is being added to UserProfile more than once.
I had this same issue recently. It actually makes perfect sense when you think about it. When you save a form with inlines in the admin, it saves the main model first, and then proceeds to save each inline. When it saves the model, your post_save signal is fired off and a UserProfile is created to match, but now it's time to save the inlines. The UserProfile inline is considered new, because it didn't exist previously (has no pk value), so it tries to save as an entirely new and different UserProfile and you get that integrity error for violating the unique constraint. The solution is simple. Just override UserProfile.save:
def save(self, *args, **kwargs):
if not self.pk:
try:
p = UserProfile.objects.get(user=self.user)
self.pk = p.pk
except UserProfile.DoesNotExist:
pass
super(UserProfile, self).save(*args, **kwargs)
Essentially, this just checks if there's an existing UserProfile for the user in question. If so, it sets this UserProfile's pk to that one's so that Django does an update instead of a create.
Related
I'm creating a django app for an education company, and I created a custom user model and two classes (Student and Teacher) that will inherit from the User model through a one-to-one relationship.
I'm trying to avoid the situation where a teacher and a student both use the same user object. Thus, I have a user_type char field in my User object, and the idea is when a user is set to a teacher, the field will be updated. Then if I try to make the user a student, it should throw an error.
I'm trying to do the check in the clean function - after the clean() function is called in the save() function, the user_type seems to be updated, but when I actually test, it seems user_type is returning a blank string. I'm wondering if someone could point me the right direction.
class User(AbstractUser):
user_type = models.CharField(max_length=20, choices=USER_TYPES, blank=True)
class Teacher(TimeStampedModel):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def clean(self):
if self.user.user_type and self.user.user_type !='teacher':
raise ValidationError('Some error')
else:
self.user.user_type = 'teacher'
def save(self, *args, **kwargs):
self.clean()
#this prints 'teacher'
print(self.user.user_type)
super().save(*args, **kwargs)
tests.py
#this test fails
def test_should_set_user_type_automatically_to_teacher(self):
user = User.objects.create(username='tangbj', first_name='Dan')
Teacher.objects.create(user=user)
teacher = Teacher.objects.get(user__username='tangbj')
print(teacher.user.user_type)
self.assertEqual(teacher.user.user_type, 'teacher')
You are saving the Teacher instance, not the User instance. When you assign the user_type, you should do a save(..) to the User instance. Records aren't magically updated.
Said in another way; you are modifying an instance attribute, but you are not saving the instance.
I`m tryng to add user from Admin interface, using a UserProfile, but show me this error:
IntegrityError at /admin/auth/user/add/
(1062, "Duplicate entry '7' for key 'user_id'")
Here is my UserProfile class:
class UserProfile(models.Model):
# This field is required.
user = models.OneToOneField(User)
# Other fields here
cliente = models.ForeignKey(cliente, null=True, blank=True)
setor = models.CharField(verbose_name=u'Setor',
max_length=1,
default='C',
choices=Setor_CHOICE)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
I try to use this:
post_save.connect(create_user_profile, sender=User, dispatch_uid='user_id')
but same error.
How can I fix this?
Thanks
It seems like it is trying to create a second instance of UserProfile connected to the same User. This can happen if post_save signal gets fired twice.
As this answer points out, this can happen as a result of the order of model imports.
EDIT I reformulated the question here
I want to create several custom user models extending django.contrib.auth.models.User, with the following features:
some specific fields
a nice admin, i.e., for each model, an admin form that I can customize easily (eg. show/hide some fields, both from the parent django.contrib.auth.models.User and the child model).
I almost managed to do it with the code below, but I still have an issue: the password is cleared every time I want to modify an instance of MyUser1 or MyUser2 from the admin.
Is it the best way to do it? If so, how can I fix this cleared password issue?
models.py
from django.db import models
from django.contrib.auth.models import User
class MyUser1(User):
#add more fields specific to MyUser1
class MyUser2(User):
#add more fields specific to MyUser2
admin.py
class MyUser1AdminForm(forms.ModelForm):
class Meta:
model = MyUser1
def __init__(self, *args, **kwargs):
super(MyUser1AdminForm, self).__init__(*args, **kwargs)
self.fields['password'].widget = forms.PasswordInput()
def save(self, commit=True):
user = super(MyUser1AdminForm, self).save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user
class MyUser1Admin(admin.ModelAdmin):
form = MyUser1AdminForm
admin.site.register(MyUser1, MyUser1Admin)
# same for MyUser2
if you whant for user to enter pass - add new field for pass and check it before commit
class MyUser1AdminForm(forms.ModelForm):
check_pass = forms.CharField(label="Password",widget = forms.PasswordInput(), required=True)
class Meta:
model = MyUser1
exclude = ('password',)
def save(self, commit=True):
user = super(MyUser1AdminForm, self).save(commit=False)
if commit and user.check_password(self.cleaned_data["check_pass"]):
user.save()
return user
I have a UserAdmin, and I defined a UserProfileInline like this:
from ...
from django.contrib.auth.admin import UserAdmin as UserAdmin_
class UserProfileInLine(admin.StackedInline):
model = UserProfile
max_num = 1
can_delete = False
verbose_name = 'Profile'
verbose_name_plural = 'Profile'
class UserAdmin(UserAdmin_):
inlines = [UserProfileInLine]
My UserProfile model has some required fields.
What I want is to force the user not only to enter the username & repeat password, but also to enter at least the required fields so that the UserProfile instance is created and associated to the User that is being added.
If I enter anything in any field of UserProfileInline when creating the user, it validates the form without problem, but if I don't touch any field, it just creates the User and nothing happens with the UserProfile.
Any thoughts?
Check recent answer Extending the user profile in Django. Admin creation of users , you need to set the empty_permitted attribute of the form of the inline , to be False. Just like
class UserProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args, **kwargs)
if self.instance.pk is None:
self.empty_permitted = False # Here
class Meta:
model = UserProfile
class UserProfileInline(admin.StackedInline):
form = UserProfileForm
Another possible solution could be to create your own Formset (that inherits from BaseInlineFormSet), like suggested in this link.
It could be something like that:
class UserProfileFormset(BaseInlineFormSet):
def clean(self):
for error in self.errors:
if error:
return
completed = 0
for cleaned_data in self.cleaned_data:
# form has data and we aren't deleting it.
if cleaned_data and not cleaned_data.get('DELETE', False):
completed += 1
if completed < 1:
raise forms.ValidationError('You must create a User Profile.')
Then specify that formset in the InlineModelAdmin:
class UserProfileInline(admin.StackedInline):
formset = UserProfileFormset
....
The good thing about this second option is that if the UserProfile model doesn't require any field to be filled, it will still ask for you to enter any data in at least one field. The first mode doesn't.
I've changed the django-registration code. I am inserting data in UserProfile and Business model during signup.
Data is saving in UserProfile Model.
#TODO: saving contact and address field data into UserProfile
user_profile = new_user.get_profile()
user_profile.user = new_user
user_profile.contact, user_profile.address = contact, kwargs['address']
user_profile.save()
Following code does not work.Getting this error.
'Business' instance needs to have a primary key value before a many-to-many relationship can be used.
#TODO: saving business field data into Business Model
user_business = Business()
user_business.owner = new_user
user_business.name = business
user_business.save()
thanks
UPDATE
class Business(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
owner = models.ManyToManyField(User)
created = models.DateTimeField(editable=False, default=datetime.now)
modified = models.DateTimeField(editable=False, default=datetime.now)
class Meta:
ordering = ['name']
def save(self):
self.modified = datetime.now()
if not self.slug:
self.slug = slugify(self.name, instance=self)
super(Business, self).save()
Try updating your custom code to :
def save(self, *args, **kwargs):
self.modified = datetime.now()
if not self.slug:
self.slug = slugify(self.name)
super(Business, self).save(*args, **kwargs)
UPDATE
#no_access I think there is a problem in the process of assigning the User instance to the ManyToManyField in Business. I suspect that the ManyToManyField field isn't getting the reference to the User instance that is being created. The intermediate table of ManyToManyField field needs a proper User object to reference to. So, I think here lies the problem.