AttributeError with non-model Field serialization - django

I have a model:
class Company(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
name = models.CharField(max_length=30, blank=True)
balance = models.DecimalField(max_digits=10, decimal_places=2, default=0)
phone_number = PhoneNumberField(null=True, blank=True)
active = models.BooleanField(default=False)
And I need to serialize creation of User and Model. Idea is to ask User company name while doing registration, so I have:
class CreateUserSerializer(serializers.ModelSerializer):
company_name = serializers.CharField(required=True)
class Meta:
model = User
fields = ('id', 'company_name', 'username', 'email', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
username = validated_data['username']
email = validated_data['email']
password = validated_data['password']
company_name = validated_data['company_name']
user = User.objects.create(username=username, email=email, password=password)
Company.objects.create(user=user, name=company_name)
return user
And I get error:
Got AttributeError when attempting to get a value for field
company_name on serializer CreateUserSerializer. The serializer
field might be named incorrectly and not match any attribute or key on
the User instance. Original exception text was: 'User' object has no
attribute 'company_name'.
Anyway objects Company and User are created as I can see it in my admin panel.
What am I doing wrong and how can I fix it?

You can use source field's argument in this case:
class CreateUserSerializer(serializers.ModelSerializer):
company_name = serializers.CharField(required=True, source='company.name')

Related

how to Combine UserProfile and the User model that are connected through OneToOne relation into a single endpoint?

I have a custom user class and a profile class. Profile class has a OneToOne relation with the custom User. the Serializer is having User as Meta model with adding Profile model in a new field profile extended to the fields tuple. but When I try to get the detail view it returns an error saying Profile field is not an attribute of CustomUser.
I would appreciate if you go over the code that I added below and help me through this.
The User model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
class Types(models.TextChoices):
DOCTOR = "DOCTOR", "Doctor"
PATIENT = "PATIENT", "Patient"
# what type of user
type = models.CharField(_("Type"), max_length=50, choices=Types.choices, null=True, blank=False)
avatar = models.ImageField(upload_to="avatars/", null=True, blank=True)
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomBaseUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'type'] #email is required by default
def get_full_name(self):
return self.name
def __str__(self):
return self.email
The Profile Model:
class DoctorProfile(models.Model):
"""Model for Doctors profile"""
class DoctorType(models.TextChoices):
"""Doctor will choose profession category from enum"""
PSYCHIATRIST = "PSYCHIATRIST", "Psychiatrist"
PSYCHOLOGIST = "PSYCHOLOGIST", "Psychologist"
DERMATOLOGIST = "DERMATOLOGIST", "Dermatologist"
SEXUAL_HEALTH = "SEXUAL HEALTH", "Sexual health"
GYNECOLOGIST = "GYNECOLOGIST", "Gynecologist"
INTERNAL_MEDICINE = "INTERNAL MEDICINE", "Internal medicine"
DEVELOPMENTAL_THERAPIST = "DEVELOPMENTAL THERAPIST", "Developmental therapist"
owner = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='doctor_profile')
doctor_type = models.CharField(
_("Profession Type"),
max_length=70,
choices=DoctorType.choices,
null=True,
blank=False)
title = models.IntegerField(_('Title'), default=1, choices=TITLES)
date_of_birth = models.DateField(null=True, blank=False)
gender = models.IntegerField(_('Gender'), default=1, choices=GENDERS)
registration_number = models.IntegerField(_('Registration Number'), null=True, blank=False)
city = models.CharField(_('City'), max_length=255, null=True, blank=True)
country = models.CharField(_('Country'), max_length=255, null=True, blank=True)
def __str__(self):
return f'profile-{self.id}-{self.title} {self.owner.get_full_name()}'
Serializer:
class DoctorProfileFields(serializers.ModelSerializer):
"""To get the fields from the DoctorProfile. it will be used in the DoctorProfileSerializer"""
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number', 'gender', 'city', 'country', )
class DoctorProfileSerializer(serializers.ModelSerializer):
"""retrieve, update and delete profile"""
profile = DoctorProfileFields()
class Meta:
model = User
fields = ('name', 'avatar', 'profile', )
#transaction.atomic
def update(self, instance, validated_data):
ModelClass = self.Meta.model
profile = validated_data.pop('profile', {})
ModelClass.objects.filter(id=instance.id).update(**validated_data)
if profile:
DoctorProfile.objects.filter(owner=instance).update(**profile)
new_instance = ModelClass.objects.get(id = instance.id)
return new_instance
View:
class DoctorProfileAPIView(generics.RetrieveUpdateDestroyAPIView):
"""To get the doctor profile fields and update and delete"""
serializer_class = DoctorProfileSerializer
queryset = User.objects.all()
def get_object(self):
return get_object_or_404(User, id=self.request.user.id, is_active=True)
What I want is a json response in the detail view like below:
{
"name": the name,
"avatar": avatar,
"profile": {
"doctor_type": "PSYCHIATRIST",
"title": 1,
"date_of_birth": 11-11-1990,
"registration_number": 21547,
}
}
Can Anybody guide me through this..? Or is there any other design approach that meets my objective. My objective is to have the user info + profile info combined in a single endpoint as a whole Profile in the frontend from which the user will see/edit profile.
First of all move the foreign key OneToOne in the CustomUser model, add:
owner = models.OneToOneField('DoctorProfile', on_delete=models.CASCADE, related_name='doctor_profile')
and delete from DoctorProfile:
owner = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name='doctor_profile')
Make all migrations, and now you have to set new data in the db.
In the serializers you are using Nested relationships correctly, so add the attribute many set to False:
profile = DoctorProfileFields(many=False)
Edit
If you cant edit the structure of your models, you can work with SerializerMethodField (not tested):
class DoctorProfileSerializer(serializers.ModelSerializer):
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number')
class CustomDoctorProfileSerializer(serializers.Serializer):
name = serializers.SerializerMethodField()
avatar = serializers.SerializerMethodField()
profile = DoctorProfileSerializer(many=False)
def get_name(self, obj)
return obj.doctor_profile.name
def get_avatar(self, obj)
return obj.doctor_profile.avatar

how to add current active user as foreign key to the create post model in djangorestframework?

how to add current active user as foreign key to the create post model in djangorestframework ?
models:
class DoctorProfile(AbstractBaseUser, PermissionsMixin):
id=models.AutoField(primary_key=True)
name = models.CharField(_('name'), max_length=50, blank=True)
mobile = models.CharField(_('mobile'), unique=True, max_length=10, blank=False)
email = models.EmailField(_('email address'), blank=True)
password = models.CharField(_('password'),max_length=25,blank=False)
otp = models.IntegerField(null=True, blank=True)
class Doctor_clinic(models.Model):
clinic_id = models.AutoField(primary_key=True)
doc_profile = models.ForeignKey(DoctorProfile,on_delete=models.CASCADE)
clinic_name = models.CharField(max_length=150)
clinic_address = models.CharField(max_length=150)
City = models.CharField(max_length=50)
state = models.CharField(max_length=50)
pincode = models.IntegerField()
#how to get the forign key in serializers
I wrote in this way, is this correct/relevent?
class UserSerializer(serializers.ModelSerializer):
# mobile = serializers.RegexField("[0-9]{10}",min_length=10,max_length=10)
password = serializers.CharField(write_only=True)
email=serializers.EmailField(max_length=155,min_length=3,required=True)
name=serializers.CharField(max_length=55,min_length=3,required=True)
class Meta:
model = DoctorProfile
fields = ("name", "email", "password", "mobile","otp")
class ClinicSerializer(serializers.ModelSerializer):
class Meta:
model = Doctor_clinic
fields =('clinic_name','clinic_address', 'City', 'state', 'pincode','doc_profile')
views:
class ClinicRegistrationView(generics.ListCreateAPIView):
serializer_class = ClinicSerializer
queryset = Doctor_clinic.objects.all()
permission_classes = (IsAuthenticated,)
When serializing relations you need to define a seperate field depending on the representation you want, for example write your serializer like this:
class ClinicSerializer(serializers.ModelSerializer):
doc_profile = serializers.StringRelatedField()
class Meta:
model = Doctor_clinic
fields =('clinic_name','clinic_address', 'City', 'state', 'pincode','doc_profile')
permissions.py
use permission classes
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.doc_profile == request.user
views.py
def perform_create(self, serializer):
return serializer.save(doc_profile=self.request.user)

How to update Foreign key field using nested serializer in django

I am facing one issue for updating models using django serializer.
Here is my models:
class User(models.Model):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class UserProfile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
designation = models.CharField(max_length=200, blank=True)
contact_number = models.CharField(max_length=20, blank=True)
team = models.CharField(max_length=200, blank=True)
manager = models.CharField(max_length=200, blank=True)
joining_date = models.DateField(default=datetime.now)
I need to create a serializer for editing profile details of the current user. In this User details like designation, contact_number , team , manager, joining_date are in UserProfile model and te first_name and last_name are in User model .... At one edit both the models needs to get update
This is my serializer.
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ('id', 'first_name','last_name')
class UserProfileSerializer(ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = ('id', 'designation', 'contact_number', 'team', 'manager',
'joining_date','user')
def update(self, instance, validated_data):
user = validated_data.get('user')
instance.user.first_name = user.get('first_name')
instance.user.save()
return instance
I am getting an error {
"user": [
"This field is required."
]
}
Change user field to DictField. In this way, it will not be treated as foreignkey.
In to_representation, serialize user object and you will get data in format in which you want to get.
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ('id', 'first_name','last_name')
class UserProfileSerializer(ModelSerializer):
user = serializer.DictField(required=False, write_only=True, default={})
def to_representation(self, instance):
data = super().to_representation(instance)
data.update({'user': UserSerializer(instance.user).data})
return data
class Meta:
model = UserProfile
fields = ('id', 'designation', 'contact_number', 'team', 'manager',
'joining_date','user')
def update(self, instance, validated_data):
user = validated_data.get('user')
instance.user.first_name = user.get('first_name')
instance.user.save()
return instance

Django Rest Framework - OneToOne reverse relation

I have a custom User model and the User Profile model.
class User(AbstractUser):
"""Custom User authentication class to use email as username"""
username = None
email = models.EmailField(verbose_name='email', max_length=255, unique=True,
error_messages={
'unique': _(
"A user is already registered with this email address"),
}, )
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
def __str__(self):
return self.email
class UserProfile(models.Model):
user = models.OneToOneField(User, to_field='email', on_delete=models.CASCADE)
emp_id = models.CharField(max_length=20, blank=False, default='0', null=False)
department = models.CharField(max_length=50, blank=True, default='', null=True)
I am trying to write a serializer that combines both these models are produces a nested JSON.
for example:
{
"email":"user#gmail.com",
"is_active":true,
"profile":
{
"emp_id":2,
"department":2
}
}
This is what I tried to do
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id', 'user', 'emp_id', 'department')
class UserPairSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer(read_only=True)
class Meta:
model = User
fields = ('id', 'email', 'is_active', 'profile')
But for some reason, there is neither the field profile in my response nor am I getting any errors.
I tried following this docs: https://www.django-rest-framework.org/api-guide/relations/
What is the issue and how do I solve this?
As per the documentation implicitly refering to this, 'reverse' queries are done using the name of the Model, lowercased (in this case user.userprofile).
So you have two options:
Either you specify a custom related_name on the user field on the UserProfile model.
class UserProfile(models.Model):
user = models.OneToOneField(User, to_field='email', on_delete=models.CASCADE, related_name='profile')
Or, you specify a source argument on your nested serializer (see documentation):
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id', 'user', 'emp_id', 'department')
class UserPairSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer(read_only=True, source='userprofile')
class Meta:
model = User
fields = ('id', 'email', 'is_active', 'profile')

Forms - Foreign Key - ModelChoiceField - NOT SAVING

I have 2 accounts, Instructor & Student which both extend a CustomUser model. Instructors have an email field. Students have an instructor key that they select from a drop-down menu.
I am able to populate the ModelChoiceField with instructor's emails, no problem. However, I am now unable to save the selected instructor's email into the foreign key appropriately. I think the error lies in how I save the instructor_id, specifically this line,
student.instructor_id = Instructor.objects.get(email=self.cleaned_data["instructor_id"])
, but I'm not sure what the problem is. Please help!
forms.py
class StudentSignUpForm(UserCreationForm):
instructor_id = forms.ModelChoiceField(queryset=Instructor.objects.all())
class Meta(UserCreationForm):
model = CustomUser
fields = ('username', 'inGameName', 'instructor_id')
help_texts = {
'username': 'Required',
'inGameName': 'Required; A name by which you can be identified',
'instructor_id': 'Optional; Provided by your professor',
}
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.is_student = True
student = Student.objects.create(user=user)
student.instructor_id = Instructor.objects.get(email=self.cleaned_data["instructor_id"])
student.save()
user.save()
return user
class InstructorSignUpForm(UserCreationForm):
email = forms.EmailField(label='Your Email', help_text='Required')
class Meta(UserCreationForm.Meta):
model = CustomUser
fields = ('username', 'inGameName', 'email')
help_texts = {
'username': 'Required',
'inGameName': 'Required; A name by which you can be identified',
}
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.is_instructor = True
instructor = Instructor.objects.create(user=user)
instructor.email = self.cleaned_data["email"]
instructor.save()
user.save()
return user
models.py
from django.contrib.auth.models import AbstractUser, UserManager as AbstractUserManager
from django.db import models
class UserManager(AbstractUserManager):
pass
class CustomUser(AbstractUser):
objects = UserManager()
is_student = models.BooleanField(default=False)
is_instructor = models.BooleanField(default=False)
username = models.CharField(max_length=40, primary_key=True, default='')
inGameName = models.CharField("In-Game Name", max_length=40, default='')
USERNAME_FIELD = 'username'
class Instructor(models.Model):
user = models.OneToOneField(CustomUser, default='USER', on_delete=models.CASCADE)
email = models.EmailField(max_length=254, unique=True, default='')
def __str__(self):
return self.email
class Student(models.Model):
user = models.OneToOneField(CustomUser, default='USER', on_delete=models.CASCADE)
instructor_id = models.ForeignKey(Instructor, to_field='email', on_delete=models.CASCADE, default='')