Trying to serialize data from multiple models - django

I am trying to serialize three django models in a section of my api, but it seems he doesn't like the way I do it .. Im following the documentation of https://www.django-rest-framework.org/api-guide/relations/#nested-relationships
I have tried to create 3 serializers one for each model and then put everything together in the fields of the last
serializers.py
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ['name', 'user']
class UserCompanySerializer(serializers.ModelSerializer):
class Meta:
model = UserCompany
fields = ['name']
class UserInfoSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer
companys = UserCompanySerializer(many=True)
class Meta:
model = CustomUser
fields = ['email', 'profile', 'companys']
I thought it would work but it returns the error:
ImproperlyConfigured at /user_info
Field name profile is not valid for model CustomUser.
models.py
class UserCompany(models.Model):
name = models.CharField(max_length=150, unique=True)
def __str__(self):
return self.name
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
is_active = models.BooleanField(default=False)
companys = models.ForeignKey(UserCompany, on_delete=models.CASCADE, null=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = CustomUserManager()
def __str__(self):
return self.email
class UserProfile(models.Model):
name = models.CharField(max_length=300, unique=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.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

retrieving the last instance of a model in another model's modelSerializer in django rest framework

I am creating rest APIs for a website in which users can purchase one of the provided subscriptions.
In this website there is a user-info API which returns the information about the logged in user which can be used to show their info on the website.
The problem is that, the mentioned API's serializer is a modelSerializer on the "User" model and the information that I want to return is the instance of "Subscription" model which the latest instance of "SubPurchase" model refers to.
These are my serializers, models and views.And I need to somehow return the user's current subscription's ID and name along with the user's information. If you have any further questions, ask me in the comments and I'll answer them.
# models.py
class User(AbstractBaseUser, PermissionsMixin):
userID = models.AutoField(primary_key=True)
username = models.CharField(max_length=100, unique=True, validators=[RegexValidator(regex="^(?=[a-z0-9._]{5,20}$)(?!.*[_.]{2})[^_.].*[^_.]$")])
email= models.EmailField(max_length=100, unique=True, validators=[EmailValidator()])
name = models.CharField(max_length=100)
isSuspended = models.BooleanField(default=False)
isAdmin = models.BooleanField(default=False)
emailActivation = models.BooleanField(default=False)
balance = models.IntegerField(default=0)
objects = UserManager()
USERNAME_FIELD = 'username'
class Subscription(models.Model):
subID = models.AutoField(primary_key=True)
nameOf = models.CharField(max_length=50)
price = models.PositiveIntegerField()
salePercentage = models.PositiveIntegerField(default=0)
saleExpiration = models.DateTimeField(default=datetime.datetime.now, blank=True)
def __str__(self):
return f"{self.nameOf}"
class SubPurchase(models.Model):
price = models.PositiveIntegerField()
dateOf = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
subscription = models.ForeignKey(Subscription, null=True, on_delete=models.SET_NULL)
def __str__(self):
return self.subscription
# serializers.py
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
# views.py
class UserInfoViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserInfoSerializer
def get_queryset(self):
uID = getattr(self.request.user,'userID')
return get_user_model().objects.filter(userID=uID)
def get_object(self):
uID = getattr(self.request.user,'userID')
return self.queryset.filter(userID=uID)
Again, I need to change the UserInfoSerializer in a way that would give me the user's current subscription's name, ID and expiration date which would be 30 days after the purchase date
If you are only interested in the returned data, you can override the function to_representation of your serializer and create a serializer for your related model. If I understood correctly, the current subscription of your user is the last one (if sorted by "dateOf"). So something like that could do the trick
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = ('nameOf', 'id', 'saleExpiration ')
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
def to_representation(self, instance):
data = super().to_representation(instance)
current_subs = instance.subpurchase_set.order_by('dateOf').last().subscription
data['current_subscription'] = SubscriptionSerializer(instance=current_subs).data
return data
you can use NestedSerializers to achieve what you are looking for
basically, nested serialization is a method in which you can return, create, put..., into a model from another model, it goes like this..
models.py
class User(AbstractBaseUser, PermissionsMixin):
....
#user model data
class SubPurchase(models.Model):
...
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
serializers.py
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields =["anyfield you wanna include"]
class SubPurchaseSerializer(serializers.ModelSerializer):
class Meta:
model = SubPurchase
fields =["anyfield you wanna include"]
class UserInfoSerializer(serializers.ModelSerializer):
subpurchace = SubPurchaseSerializer()
subscription= SubscriptionSerializer() #later included in the fields of this serializer
class Meta:
model = get_user_model()
fields = ('userID','subpurchace', 'subscription', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')
read_only_fields = ('userID', 'username','email', 'name', 'balance', 'emailActivation', 'isSuspended')

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 use serializer inside serializer?

Now this is my plan:
I want to be able to show followers for users,but i have a problem.I have to use nested serializers to show serializers which gives me this error:
followers_set= UserSerializer(source='followers',many=True)
NameError: name 'UserSerializer' is not defined
Now i am using this kind of modelling in my API:
Models.py
class User(AbstractUser,PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.CharField(max_length=255,unique=True)
username =models.CharField(max_length=80,unique=True,default='SOME STRING')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
class FollowUserModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
author = models.ForeignKey(User,on_delete=models.CASCADE, related_name='followers')
profile = models.ForeignKey(User,on_delete=models.CASCADE,null=True)
created = models.DateTimeField(auto_now=True)
And here are my serializers:
Serializers.py
class UserSerializer(serializers.ModelSerializer):
followers_set= UserSerializer(source='followers',many=True)
class Meta:
model = User
fields = ('id','email','username','followers_set')
How can i modify it in a way that it shows serializers inside serializer??
Use serializers.SerializerMethodField(...) as
class UserSerializer(serializers.ModelSerializer):
followers_set = serializers.SerializerMethodField()
def get_followers_set(self, user):
return UserSerializer(user.followers.all(), source='followers', many=True).data
class Meta:
model = User
fields = ('id', 'email', 'username', 'followers_set')

Not getting foreign key data in django api views

Currently I have a site, and I want the user to be able to view their liked articles. I want this to be included in the user api view that is already set up. I have tried the tracks = serializers.StringRelatedField(many=True)that is in the drf docs yet this didn't work. I have also tried the following:
from rest_framework import serializers
from articles.models import Article, CustomUser,FavoriteArticles
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('title', 'content')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = '__all__'
class FavoriteArticleSerializer(serializers.ModelSerializer):
class Meta:
model = FavoriteArticles
fields = '__all__'
class UserProfileSerializer(serializers.ModelSerializer):
fav_title = FavoriteArticleSerializer(read_only=False)
class Meta:
model = CustomUser
fields = 'username, git, email, fav_article, fav_title, homepage'
and my models:
from django.db import models
# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db.models.signals import post_save
from django.dispatch import receiver
import uuid
class ProgrammingLanguage(models.Model):
programming_language = models.CharField(max_length=120, null=False, primary_key=True, default="React")
def __str__(self):
return self.programming_language
class Article(models.Model):
title = models.CharField(max_length=25, primary_key=True)
content = models.TextField()
usedfor = models.TextField()
url=models.CharField(max_length=200, null=True)
article_programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE, related_name="article_programming_language", default="react")
score = models.IntegerField(max_length=5, null=0)
def __str__(self):
return self.title
class CustomUser(AbstractUser):
username = models.CharField(max_length=50, unique=True, primary_key=True)
git = models.CharField(max_length=200, null=True)
homepage = models.CharField(max_length=250, null=True)
user_programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE, related_name="most_used_programming_language", default="react")
def __str__(self):
return str(self.username)
class FavoriteArticles(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
fav_title = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='fav_title')
reasons_liked = models.CharField(max_length=120, null=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name="user", default="tom" )
def __unicode__(self):
return '%s: %s' % (self.fav_title, self.reasons_liked)
I think you misunderstood what related_name means. It specifies how you would access a model from its reverse relationship. So I'd recommend you remove it from fields in your FavoriteArticles model and use the default Django already provides (in this case favoritearticles_set):
class FavoriteArticles(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
fav_title = models.ForeignKey(Article, on_delete=models.CASCADE)
reasons_liked = models.CharField(max_length=120, null=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, default="tom")
def __unicode__(self):
return '%s: %s' % (self.fav_title, self.reasons_liked)
This way, you can access favorite articles of a user via my_user.favoritearticles_set.all(). Then, you can change your UserSerializer to include a liked_articles field which is populated from the favoritearticles_set reverse relationship to a user's FavoriteArticles using a source attribute:
class UserSerializer(serializers.ModelSerializer):
liked_articles = FavoriteArticleSerializer(source='favoritearticles_set', many=True, read_only=True)
class Meta:
model = CustomUser
# explicitly include other fields as required
fields = ('username', 'git', 'user_programming_language', 'liked_articles')
Note that we've made this a read_only field, so it will only get populated if you perform a GET request.