Django rest-framework: Spring Boot like model serialization - python

I am looking forward to build a "mother of REST" rest api with the django REST framework. Therefore I like to have all relationships represented as urls. Using Spring Boot this is pretty easy, but not so for django REST I think. Please correct me if I am wrong.
I have to models:
class User(AbstractBaseUser):
username = models.CharField(max_length=25, unique=True)
email = models.EmailField()
created_at = models.DateTimeField(auto_now_add=True)
class Group(models.Model):
admin = models.ForeignKey(User, related_name='groups', on_delete=models.PROTECT)
shared_user = models.ManyToManyField(User, related_name='shared_groups')
name = models.CharField(max_length=25)
description = models.TextField()
created_at = models.DateField(auto_now=True)
Now I like the model relation ships to be serialized like the following schema (for shortness I only show the User model as json):
{
"id":1,
"username":"user_1",
"email":"user_1#test.de",
"created_at":"2019-03-08T08:42:34.766951Z",
"_links":{
"self":"http://127.0.0.1:8000/users/1/",
"groups":"http://127.0.0.1:8000/users/1/groups"
}
}
I know I can get the self url by passing it to the fields, but I like to have it in the _links section. I tried using the serializers.SerializerMethodFieldto add the extra information. But I have no idea to generate the urls for representing the objects.
Can anyone help?
By the way the serializer:
class UserSerializer(serializers.HyperlinkedModelSerializer):
_links = serializers.SerializerMethodField('representations')
class Meta:
model = User
fields = ('id', 'username', 'email', 'created_at', 'password', '_links')
extra_kwargs = {
'password': {'write_only': True},
'groups': {'read_only': True}
}
def create(self, validated_data):
user = User(
email=validated_data['email'],
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
def representations(self, obj):
_links = dict()
_links['self'] = 'link to user'
_links['groups'] = 'hers should be the link to the groups'
return _links

Related

Combine models to get cohesive data

I'm writing app in witch I store data in separate models. Now I need to combine this data to use it.
The problem.
I have three models:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=50, blank=True)
...
class Contacts(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user")
contact_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="contact_user")
class UserPhoto(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
url = models.CharField(max_length=220)
How can I get the current user contacts with their names and pictures like this (serialized)
{
{
"contact_user":"1",
"first_name":"Mark ",
"url":first picture that corresponds to contact_user id
},
{
"contact_user":"2",
"first_name":"The Rock",
"url":first picture that corresponds to contact_user id
}
}
Now I'm quering the Contacts model to get all contacts_user id's that he has connection to.
class MatchesSerializer(serializers.ModelSerializer):
class Meta:
model = Contacts
fields = '__all__'
depth = 1
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactsSerializer
def get_queryset(self):
return Contacts.objects.filter(user__id=self.request.user.id)
The thing you need to is to serialize the Contacts queryset to include the related User and UserPhoto objects for each contact.
Try to create a custom serializer for the Contacts model so:
class ContactSerializer(serializers.ModelSerializer):
contact_user = serializers.SerializerMethodField()
def get_contact_user(self, obj):
user = obj.contact_user
photo = user.userphoto_set.first()
return {
"id": user.id,
"first_name": user.first_name,
"url": photo.url if photo else None
}
class Meta:
model = Contacts
fields = ("contact_user",)
Then, modify the ContactViewSet to use this newly created serializer so:
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
def get_queryset(self):
return Contacts.objects.filter(user__id=self.request.user.id)
Note: Generally, Django models don't require s to be added as suffix since it is by default added, so it is better to modify it as Contact from Contacts.

Trouble combining CustomUser and Profile into single endpoint in django

I have a CustomUser model and two separate Models for profile two types of User. I'm trying to combine the attribute of CustomUser and one of the Profile into single endpoint from which the user can see/update/delete the user/profile. For instance there are 2 types of users, doctor & patient. so if the user is doc then the endpoint will return the attributes of CustomUser+DoctorProfile and same for the Patient CustomUser+PatientProfile. Below is the code. I will explain the issue in the code base with comments. I will enormously appreciate any suggestion. One thing to mention is that I split the models.py file into 3 different folder and imported all of them into __init__.py of models folder.
CustomUser 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
DoctorProfile Model:
class DoctorProfile(models.Model):
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 required attributes from DoctorProfile model"""
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number', 'gender', 'city', 'country', )
class DoctorProfileSerializer(serializers.ModelSerializer):
"""Above Serializer is used in a new attribute profile. So that I can combine the CustomUser and DoctorProfile."""
profile = DoctorProfileFields(source='*')
"""
if I use source in the above line the serializer returns the json in the
expected format while I use get method, otherwise it return error saying profile
is not an attribute of CustomUser. but for put method the json payload is getting
received in a wrong formation. attributes of nested Profile object is getting
combined in the same level of Custom user,
{"name": "jon", "avatar": null, "doctor_type": "anything"}
but it has to receive like this
{"name": "jon", "avatar": null, "profile": {"doctor_type": "anything}}
"""
class Meta:
model = User
fields = ('name', 'avatar', 'profile', )
#transaction.atomic
def update(self, instance, validated_data):
ModelClass = self.Meta.model
"""print("=======validated_data=========: ", validated_data). I found in this
line that the payload is wrong"""
profile = validated_data.pop('profile', {})
"""print("=======profile=========: ", profile) profile is not in validated data
that's why 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
On the other hand if I don't use source the error is same for both get and put method.
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)
After using source the get method returns in the expected format:
{
"name": "Maruf updated again",
"avatar": null,
"profile": {
"doctor_type": null,
"date_of_birth": null,
"registration_number": null,
"city": null,
"country": null
}
}
But the problem is with the Put method.
Another note: with using source and not overriding the update method in the serializer, only the CustomUser attributes is getting updated.
my objective is to get both CustomUser+Profile in the same endpoint.
And when updating, the CustomUser and the Profile will be updated in their own table but through the same endpoint.

How to use same serializer class for get and post method in Django?

I have been trying, but fail miserably, to use only a single serializer class for both post and get api call. I have a Person model which has some fields. What I need is I want to create a person object using post API with certain fields and however needs to show some other fields while doing get request. For eg:
My model:
class Person(models.Model):
name = models.CharField(max_length=255,blank=True)
email = models.EmailField(unique=True)
phone = models.CharField(max_length=16)
address = models.CharField(max_length=255,blank=True)
roll = models.IntegerField()
subject = models.CharField(max_length=255,blank=True)
college_name = models.CharField(max_length=255,blank=True)
Now my serializer will look like this.
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ['id','email','name','phone','roll','subject','college_name', 'address']
For eg, I have a view that handles both post and get request (probably using APIView), then what I need to do is, to create a person object I only using name, phone, and email. So for post API call, I only need three fields whereas while calling the get API I only need to display the fields name, roll, subject, and college_name not others.
In this situation, how can I handle using the same serializer class??
You can set fields to be read-only (for GET requests) with the read_only_fields list and write-only (for POST, PUT, PATCH) with the extra_kwargs dictionary, both inside the Serializer's Meta class:
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ['id', 'email', 'name', 'phone', 'roll', 'subject', 'college_name']
read_only_fields = ['roll', 'subject', 'college_name']
extra_kwargs = {
'email': {'write_only': True},
'phone': {'write_only': True},
}
First in the model, you need to add the attribute null=True into the non-required fields.
class Person(models.Model):
name = models.CharField(max_length=255,blank=True)
email = models.EmailField(unique=True)
phone = models.CharField(max_length=16)
# here I added `null=True`
address = models.CharField(max_length=255,blank=True, null = True)
roll = models.IntegerField(null = True)
subject = models.CharField(max_length=255,blank=True, null = True)
college_name = models.CharField(max_length=255,blank=True, null = True)
And in the serializer, you need to add the extra_kwargs property to set the read-only fields.
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ['id', 'email', 'name', 'phone', 'roll', 'subject', 'college_name']
extra_kwargs = {
'roll': {'read_only': True},
'subject': {'read_only': True},
'college_name': {'read_only': True},
}

I cant join two models on Django rest api and get a specific output

Hello guys I am new in Django REST API. I want your help.
I am trying to join my two models : User and blogs to get specific api output like this:
{
"blog_id": 1,
"title": "first blog",
"description": "hola",
"image": "/images/phone.jpg",
"create_at": "2021-04-08T14:24:51.122272Z",
"update_at": "2021-04-08T14:37:00.287746Z",
"user": 1,
"user_name": "superuser",
"first_name": "Dannis",
"email": "superuser#test.com"
}
Here is the models.py
class Blog(models.Model):
blog_id = models.AutoField(primary_key=True, editable=False)
title = models.CharField(max_length=128,null=False,blank=False)
description = models.TextField(null=True,blank=True)
image=models.ImageField(null=True,blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
create_at = models.DateTimeField(auto_now_add=True, editable=False)
update_at = models.DateTimeField(auto_now=True,editable=False)
def __str__(self):
return f'{self.user.username} {self.title} {self.create_at} {self.update_at}'
class UserActive(models.Model):
user_active_id = models.AutoField(primary_key=True, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, editable=False)
last_active = models.DateTimeField(auto_now_add=True, editable=False)
Here is the views.py
#api_view(['GET'])
def get_blogs(request):
blogs = Blog.objects.all()
serializer = BlogSerializers(blogs, many=True)
return Response(serializer.data)
#api_view(['GET'])
def get_users(request):
user = User.objects.all()
serializer = UserSerializer(user, many=True)
return Response(serializer.data)
here is the serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'is_staff', ]
class BlogSerializers(serializers.ModelSerializer):
class Meta:
model = Blog
fields = '__all__'
Please Help me out. I will be so grateful
You need to tell the BlogSerializer to use the UserSerializer for the user field:
class BlogSerializers(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Blog
fields = '__all__'

Django Rest - Serializer: must be a instance on create

I'm trying to create create a nested serializer using the Django Rest framework. The relationship is Profile X User but when i use Profile.objects.create(user=profile, **user_data) i get ValueError: Cannot assign "<Profile: Profile object (7)>": "Profile.user" must be a "User" instance..
This should be some rookie misunderstanding of models relationship definitions or the serializer declaration itself but I can't find anything around the docs. If someone can point me a direction I'll be gracefull.
models.py
class User(models.Model):
name = models.CharField(max_length=100, blank=False)
email = models.CharField(max_length=100, blank=True, default='')
password = models.CharField(max_length=100, blank=True, default='')
timestamp = models.DateTimeField(default= timezone.now)
class Meta:
ordering = ['timestamp']
class Profile(models.Model):
# choices [...]
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
profile_type = models.CharField(max_length=2,choices=PROFILE_CHOICES,default=TEAMMEMBER)
authentication_token = models.CharField(max_length=100, null=True)
avatar_url = models.CharField(max_length=100, default='')
permissions = models.CharField(max_length=100, null=True)
timestamp = models.DateTimeField(default= timezone.now)
class Meta:
ordering = ['timestamp']
serializer.py
class UserSerlializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['name', 'email', 'password']
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
profile = Profile.objects.create(**validated_data)
Profile.objects.create(user=profile, **user_data)
return Profile
POST
{
"profile_type" : "ST",
"user": {
"name" : "test",
"email" : "test#test.com",
"password" : "123456"
}
}
You are creating instances in wrong way. Change your create(...) method as,
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
user_instance = User.objects.create(**user_data)
profile_instance = Profile.objects.create(user=user_instance, **validated_data)
return profile_instance
Profile.user should beUser instance, but you are assigning Profile instance.
Change your create method to this:
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
profile = Profile.objects.create(**validated_data)
user = User.objects.create(**user_data) # 1. creating user
profile.user = user # 2. assigning user
profile.save() # 3. saving profile after adding user
return profile # returning Profile instance.
inherit your user model from django contrib auth module also, and make a one to one relation with profile
from django.contrib.auth.models import User

Categories

Resources