Custom user model doesn't create objects - python

I have created a CustomUser model and it has a one to relationship with my two other models.
class User(AbstractUser):
is_learner = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
class Teacher(models.Model):
#teacher_name = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
class Learner(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
They do not show up when I try to create learner and teacher objects in their respective databases, as shown:
class LearnerSignUpForm(UserCreationForm):
email = forms.EmailField()
class Meta(UserCreationForm.Meta):
#User = CustomUser
model = CustomUser
fields = ["username", "email", "password1", "password2"]
#fields = UserCreationForm.Meta.fields + ("username", "email", "password1", "password2")
#transaction.atomic
def save(self, *args, **kwargs):
user = super().save(commit=False)
user.is_learner = True
user.save()
learner = Learner.objects.create(user=user)
return user
How do I get it to save in learner and teacher tables respectively?

Welcome to StackOverflow!
It is worth pointing that using model.objects.create(...) you're implicitly telling Django that you want to create the object with the specific values you give and save it. check the docs page here
If you wish to save an object for which has associated items, you can create a form and use a ModelChoiceField and provide the model and default choices using the choices kwarg. An example of a choices value would be YourUserModel.objects.all() However, I think in your case you may not want to give the user the freedome to make this choice, in which it would be correct to override the save() method of your form and create the intended logic.
Secondly, I have used Django for quite some time and never seen a Model.Meta class used in this way so forgive me if I'm mistaken, but I also think you need have your save() method directly on your LearnerSignupForm and not on the Meta class.
Thirdly, if you setup your forms correctly, using the correct types of fields, Django forms will deal with all the messy stuff for you and complain at you when you do something wrong (usually gracefully).
Lastly, I would highly recommend having a read through the docs page for creating new objects
Free code:
class LearnerSignUpForm(UserCreationForm):
email = forms.EmailField()
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ["username", "email", "password1", "password2"]
#transaction.atomic
def save(self, *args, **kwargs):
user = super().save(commit=False)
user.is_learner = True
# create the relationship between the user and learner
learner = user.learner_set.create(user=user)
# user.learner_set.add(learner) # usually this way with FK
user.save()
return user # should this return the Learner object?
One final bit of advice:
Try to make your code as readable as possible! There's no reason that code can't be functional and beautiful! And where possible, you should make you class names and variable names as appropriate as possible, I would for example maybe use Student instead of Learner

Related

Updating related objects django rest framework

In my django project, I have 2 relevant models:
class User(AbstractUser):
email = models.EmailField(
verbose_name='e-mailaddress',
max_length=255,
unique=True)
# other props not important
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
#other props not important
For both creating and retrieving, it is convenient to have User as a nested object in the serializer:
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = '__all__'
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.save()
# because of reasons, an empty profile is created on initial save. This works, trust me
d, _ = Profile.objects.update_or_create(user=user, defaults=validated_data)
d.save()
return d
HOWEVER. One action that I want to be possible as well is to update both properties of the Profile and the User at the same time. When doing this with my serializer, serializer.is_valid() fails as the provided email is already present in the database. This also indicates that, even if the validation passes (i.e. because of an updated email address), a new User object will be created and coupled to the Profile. So my question is:
How do I make the validation of a serializer check if the edit of a nested object is valid rather than checking if a new one can be created?
you can update the properties of the profile and its related user by doing something similar.
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = '__all__'
def update(self, instance, validated_data):
# update the profile-related fields here.
# the other properties which you didn't mention.
...
profile = instance
# get the profile object
...
instance.save()
user = validated_data.get('user')
# get the related user.
# update the user properties here, like:
user.email = 'UPDATED_EMAIL'
user.save() # you need to save the user as well as the profile!
return instance
I managed to find something of a workaround on my own. Using the source property of serializer fields, one is able to add certain properties from related objects to the serializer. In my case, I was able to add the following lines to the ProfileSerializer for the functionalities I need:
first_name = serializers.CharField(max_length=30, source='user.first_name', required=False)
last_name = serializers.CharField(max_length=150, source='user.last_name', required=False)
email = serializers.CharField(max_length=255, source='user.email')
There are more properties to User, but for my current requirements I don't need them and this works fine (though it would be tedious to add such a line for all properties if you need all attributes). It does require a custom implementation of both create and update functions, but I assume one would need those anyway. Personally I used:
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.save()
instance = ...
return instance
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
for k,v in user_data.items():
setattr(user, k, v)
user.save()
...
return instance
Note: django-rest automatically groups properties with a shared source object into a nested object: {'user': {'email': ..., 'first_name': ..., 'last_name': ...}, 'phone_number': ...} (where phone_number is a profile property)

Trying to extend AbstractUser to create multiple user types in Django

So I have been searching all around the internet for a full example of how to user AbstractUser when u have at least 2 different models. Didn't find anything conclusive.. at least that would work on latest version of Django (2.0.1).
I have 2 models, teacher and student, and registration needs to be different. Besides username, email, name and surname, I need for example, for the student, to upload a profile picture, email, phone, student_ID. And for teacher, bio, academic title and website. Did I start good ? What is the right approach ?
class Profile(AbstractUser):
photo = models.ImageField(upload_to='students_images')
email = models.EmailField()
phone = models.CharField(max_length=15, )
class Student(Profile):
student_ID = models.CharField(unique=True, max_length=14,
validators=[RegexValidator(regex='^.{14}$',
message='The ID needs to be 14 characters long.')])
def __str__(self):
return self.name
class Teacher(Profile):
academic_title = models.CharField(max_length=30)
bio = models.TextField()
website = models.URLField(help_text="E.g.: https://www.example.com", blank=True)
Your goals can be accomplished using a 'Profile' pattern. You don't necessarily need to use a custom user model for this. But you need to have a single common model to for authentication; you can use the builtin django user for this or a custom class... Your Student and Teacher models should be OnetoOne relationships. This is the recommended solution per the documentation.
If you wish to store information related to User, you can use a OneToOneField to a model containing the fields for additional information. This one-to-one model is often called a profile model, as it might store non-auth related information about a site user.
In your case, you may do something like this:
class StudentProfile(models.Model):
user = models.OneToOneField('User', related_name='student_profile')
# additional fields for students
class TeacherProfile(models.Model):
user = models.OneToOneField('User', related_name='teacher_profile')
# additional fields for teachers
Then you can create your registration forms based on these profile models.
class StudentResistrationForm(forms.ModelForm):
class Meta:
model = StudentProfile
fields = (...)
class TeacherRegistrationForm(forms.ModelForm):
class Meta:
model = TeacherProfile
fields = (...)
You can create the user instance to which the profile is related to at the same time you create the profile. You might do this with formsets, for example.
add
class Meta:
abstract = True
to profile model
and change AbstractUser to models.Model

ValueError: Lookup failed for model referenced by field

I have made Custom User model in my Django project. Here it is:
class CustomUser(User):
avatar = models.ImageField(upload_to='avatars')
about_myself = models.TextField(max_length=300)
USERNAME_FIELD = 'username'
def __str__(self):
return self.username
def is_author(self):
return 'blog.change_post' and 'blog.add_post' in self.get_all_permissions()
And after it, I changed all Foreign Keys of user to new CustomUser model. It works OK. But I make one new migration and django cause error, when I want to migrate it:
ValueError: Lookup failed for model referenced by field blog.Comment.author: main.CustomUser
My blog.Comment model:
class Comment(models.Model):
content = models.TextField()
author = models.ForeignKey(CustomUser)
date_create = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Post)
What should I do?
Thanks!
Judging from the code you posted, you might be might be better served by extending the user model rather than replacing it. This pattern is usually called a profile model and works via a one-to-one relationship with User.
Profiles provides application specific fields and behaviors, while allowing User to go about it's usual business unchanged. It doesn't require you to muck around with rewriting auth or even necessarily change your foreign keys.
Here's an example of your code written as a profile:
class Profile(models.Model):
# Link to user :
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='avatars')
about_myself = models.TextField(max_length=300)
def __str__(self):
return self.user.username
def is_author(self):
return 'blog.change_post' and 'blog.add_post' in self.user.get_all_permissions()
Comment model:
class Comment(models.Model):
content = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL)
date_create = models.DateTimeField(auto_now_add=True)
post = models.ForeignKey(Post)
# How to access the profile:
def check_author(self):
self.author.profile.is_author()
You'll also want to add a signal to create a new profile when a user is registered:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_new_user(sender, created, instance, **kwargs):
if created:
profile = Profile(user=instance)
profile.save()
Django docs on extending users.
If a profile approach doesn't work for you, try inheriting from AbstractUser or AbstractBaseUser instead of User. The abstract models provide the same basic functionality as User and are the preferred technique for recent Django versions.
There are a handful of additional steps however, check out the docs on creating custom users for a run down.

Django register extended user

I'm trying to create and manage a custom user in django.
I saw there are two possibilities, and i've chosen to extend (not create a new auth).
Models
models.py
class Artists(models.Model):
user = models.OneToOneField(User)
artist_image = models.ImageField(null=True, blank=True, upload_to="/artist_image/")
def __str__(self):
return 'Profil de {0}'.format(self.username)
def get_absolute_url(self):
return reverse('artist-details', kwargs={'pk': self.pk})
As i read in doc, I just make a OneToOne field with the User class of django auth models, so I can access method and properties, such as username, email, on my own user (here Artists).
form.py
class CreateArtistForm(UserCreationForm):
class Meta:
model = User
fields = ('username', 'email')
def save(self, commit=True):
user = super(CreateArtistForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
Here I extend UserCreationForm to prepare a form a little different (I want to have email field on my register form).
But here is my question : I first tried with
class Meta:
model = Artists
fields = ('user.username', 'user.email')
But I the error fields unknown in model Artist.
So I tried just with username and email and same error.
So I changed the model = Artists to User, and it works fine.
But now how i register my Artist Object when the user is saved?
Do I have to make something like (in save()):
artist = Artists()
artist.user = user
artist.save()
Or override create_user()?
I'm quite lost here and i'm looking docs and questions not able to find something because most of example people define their own auth.
Thanks in advance
Besta
edit : i'm using django 1.8.2 and python 3.4

Django Admin: Ordering of ForeignKey and ManyToManyField relations referencing User

I have an application that makes use of Django's UserProfile to extend the built-in Django User model. Looks a bit like:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
# Local Stuff
image_url_s = models.CharField(max_length=128, blank=True)
image_url_m = models.CharField(max_length=128, blank=True)
# Admin
class Admin: pass
I have added a new class to my model:
class Team(models.Model):
name = models.CharField(max_length=128)
manager = models.ForeignKey(User, related_name='manager')
members = models.ManyToManyField(User, blank=True)
And it is registered into the Admin:
class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'manager')
admin.site.register(Team, TeamAdmin)
Alas, in the admin inteface, when I go to select a manager from the drop-down box, or set team members via the multi-select field, they are ordered by the User numeric ID. For the life of me, I can not figure out how to get these sorted.
I have a similar class with:
class Meta:
ordering = ['name']
That works great! But I don't "own" the User class, and when I try this trick in UserAdmin:
class Meta:
ordering = ['username']
I get:
django.core.management.base.CommandError: One or more models did not validate:
events.userprofile: "ordering" refers to "username", a field that doesn't exist.
user.username doesn't work either. I could specify, like image_url_s if I wanted to . . . how can I tell the admin to sort my lists of users by username? Thanks!
This
class Meta:
ordering = ['username']
should be
ordering = ['user__username']
if it's in your UserProfile admin class. That'll stop the exception, but I don't think it helps you.
Ordering the User model as you describe is quite tricky, but see http://code.djangoproject.com/ticket/6089#comment:8 for a solution.
One way would be to define a custom form to use for your Team model in the admin, and override the manager field to use a queryset with the correct ordering:
from django import forms
class TeamForm(forms.ModelForm):
manager = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Team
class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'manager')
form = TeamForm
This might be dangerous for some reason, but this can be done in one line in your project's models.py file:
User._meta.ordering=["username"]
For me, the only working solution was to use Proxy Model. As stated in the documentation, you can create own proxy models for even built-in models and customize anything like in regular models:
class OrderedUser(User):
class Meta:
proxy = True
ordering = ["username"]
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
After that, in your model just change Foreign Key to:
user = models.OneToOneField(OrderedUser, unique=True)
or even more suitable
user = models.OneToOneField(OrderedUser, unique = True, parent_link = True)

Categories