DRF: use to_representation to hide fields - django-rest-framework

In my app, I want to hide absolute_url in the detail view and content in the list view. I always use different serializers and conditionals in view to achieve this goal. I wanted to know if it is a bad practice to use to_representation here.
serializers.py
class PostSerializer(serializers.ModelSerializer):
absolute_url = serializers.SerializerMethodField()
author = serializers.SlugRelatedField(slug_field="full_name", read_only=True)
category = serializers.SlugRelatedField(
queryset=Category.objects.all(),
slug_field="id",
)
def get_absolute_url(self, obj):
request = self.context.get("request")
return request.build_absolute_uri(obj.slug)
def to_representation(self, instance):
rep = super().to_representation(instance)
request = self.context.get("request")
if request.parser_context.get("kwargs").get("slug"):
rep.pop("absolute_url", None)
else:
rep.pop("content", None)
rep["category"] = CategorySerializer(instance.category).data["name"]
return rep
def create(self, validated_data):
validated_data["author"] = User.objects.get(
id=self.context.get("request").user.id
)
return super().create(validated_data)
class Meta:
model = Post
fields = [
"id",
"author",
"category",
"title",
"slug",
"content",
"status",
"image",
"absolute_url",
"created_at",
"updated_at",
"published_at",
]
read_only_fields = ["slug"]
views.py
class PostViewSet(ModelViewSet):
queryset = Post.objects.select_related("author", "category").all()
serializer_class = PostSerializer
lookup_field = "slug"

Related

How to retrieve multiple slugs as url parameters with Django REST Framework DRF RetrieveAPIView?

I am new to DRF. I am very interested in application of generic views. I want to rewrite the following code with generic DRF view RetrieveAPIView:
views.py
class ProductDetail(APIView):
def get_object(self, category_slug, product_slug):
try:
return Product.objects.filter(category__slug=category_slug).get(slug=product_slug)
except Product.DoesNotExist:
raise Http404
def get(self, request, category_slug, product_slug, format=None):
product = self.get_object(category_slug, product_slug)
serializer = ProductSerializer(product)
return Response(serializer.data)
urls.py
urlpatterns = [
path('products/<slug:category_slug>/<slug:product_slug>/',
ProductDetail.as_view())
]
models.py
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField()
class Meta:
ordering = ('name',)
def __str__(self):
return self.name
def get_absolute_url(self):
return f'/{self.slug}/'
class Product(models.Model):
category = models.ForeignKey(
Category, related_name='products', on_delete=models.CASCADE)
name = models.CharField(max_length=255)
slug = models.SlugField()
description = models.TextField(blank=True, null=True)
price = models.DecimalField(max_digits=6, decimal_places=2)
image = models.ImageField(upload_to='uploads/', blank=True, null=True)
thumbnail = models.ImageField(upload_to='uploads/', blank=True, null=True)
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-date_added',)
def __str__(self):
return self.name
def get_absolute_url(self):
return f'{self.category.slug}/{self.slug}/'
How can I use RetrieveAPIView instead of above APIView?
I tried to add a custom mixin, did not help:
class MultipleFieldLookupMixin:
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
multi_filter = {field: self.kwargs[field] for field in self.lookup_fields}
obj = get_object_or_404(queryset, **multi_filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
class ProductDetail(MultipleFieldLookupMixin, RetrieveAPIView):
queryset = Product.objects.filter(category__slug=category_slug).get(slug=product_slug)
serializer = ProductSerializer
lookup_fields = ['category_slug', 'product_slug']
Does not work.

rest_framework access objects of ManyToManyField in intermediate model

I have a patient model. Each patient can have several "Medical Records" where choices come from models like "BodyPart" or "Medicin":
class Patient(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Examination(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class BodyPart(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Medicin(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class MedicalRecord(models.Model):
patient = models.ForeignKey('Patient', on_delete=models.CASCADE,)
examination = models.ForeignKey('Examination', on_delete=models.CASCADE)
part_of_body = models.ForeignKey('BodyPart', on_delete=models.CASCADE)
medicin = models.ManyToManyField('Medicin')
def __str__(self):
return self.patient.name
Using Inlines we can nicely add Records to each Patient:
class MedicalRecordAdmin(admin.TabularInline):
model = MedicalRecord
extra = 1
class PatientAdmin(admin.ModelAdmin):
model = Patient
inlines = [MedicalRecordAdmin,]
...
admin.site.register(Patient, PatientAdmin)
...
Where I'm failing is (already tried trough, serializers.StringRelatedField(many=True), etc.), how can I serialize these relations from the side of the Patient Model?
# view
class PatientViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = PatientSerializer
queryset = Patient.objects.all()
# serializer
class PatientSerializer(serializers.ModelSerializer):
class Meta:
model = Patient
fields = '__all__'
depth = 3
ends in:
[
{
"id": 1,
"name": "Bert"
}
]
## But I would expect something like:
[
{
"id": 1,
"name": "Bert",
"medical_records":[
[
{
"medicine": ["a","b","c"],
"body_part": "leg",
"examination": "x-ray"
}
],
[
{
"medicine": ["e","f","g"],
"body_part": "head",
"examination": "surgery"
}
],
]
}
]
You have to use a nested serializer and use source fields to achieve the desired result.
Nested relationships- DRF documentation
Somewhat like this :
class Patient(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Examination(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class BodyPart(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Medicin(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class MedicalRecord(models.Model):
patient = models.ForeignKey('Patient', on_delete=models.CASCADE, related_name='patient_med')
examination = models.ForeignKey('Examination', on_delete=models.CASCADE, related_name='examination_med')
part_of_body = models.ForeignKey('BodyPart', on_delete=models.CASCADE, relayted_name='part_of_body_med')
medicin = models.ManyToManyField('Medicin', related_name='medicin_med')
class MedicalRecordSerializer(serializers.ModelSerializer):
body_part = serializers.CharField(source='part_of_body.name', read_only=True)
examination = serializers.CharField(source='examination.name', read_only=True)
medicine = serializers.ListField(source='medicine.name', read_only=True)
class Meta:
model = MedicalRecord
fields = ('body_part', 'examination', 'medicine')
class PatientSerializer(serializers.ModelSerializer):
patient_med = MedicalRecordSerializer(many=True, read_only=True)
class Meta:
model = Patient
fields = '__all__'
depth = 3

Serializer to create user and profile in Django Rest Framework

I'm trying to serializer to create a user and your profile at the same time.
My code:
serializers
class UserProfileSerializer(
serializers.ModelSerializer
):
profile = ProfileSerializer(required=True)
def create(self, validated_data):
user = super(UserProfileSerializer, self).create(validated_data)
user.save()
profile_data = validated_data.pop('profile')
return user
class Meta:
model = User
exclude = ("groups", "user_permissions", )
class UserSerializer(
serializers.ModelSerializer
):
class Meta:
model = User
exclude = ("groups", "user_permissions", )
view
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
model = User
def get_serializer_class(self):
if self.request.method == "POST":
return UserProfileSerializer
return UserProfileSerializer
My problem is the user... When I trying to create a user/profile I get this error:
{
"profile": {
"user": [
"This field must be unique."
]
}
}

Django Rest Framework Relationships Serialization

I am trying to get serialized data for the below model with foreign keys but in the output i am only getting the ids instead of the fields of the model associated with hyperlink.
I have tried getting all the fields of the Skills model using the commented lines in the profile serializer but no luck
models.py
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
first_name = models.CharField(max_length=20, null=True)
last_name = models.CharField(max_length=20, null=True)
def __str__(self):
return 'Profile for user {}'.format(self.user.username)
class Skill(models.Model):
skill = models.CharField(max_length=20, null=True)
level = models.CharField(max_length=20, null=True)
user = models.ForeignKey(Profile, null=True,
on_delete=models.PROTECT, related_name='skills')
def __str__(self):
return '%s: %s: %s' % (self.user, self.skill, self.level)
serializer.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
# skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
# skills = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='skill-detail')
# skills = serializers.StringRelatedField(many=True)
# skills = serializers.HyperlinkedIdentityField( view_name = "skill-list", lookup_field = "user")
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
# user = serializers.ReadOnlyField(source='user.username')
# pdb.set_trace()
model = Profile
fields = ['id', 'user', 'url', 'skills']
views.py
class ProfileList(generics.ListCreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class SkillList(generics.ListCreateAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
class SkillDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
urls.py
path('profiles/', views.ProfileList.as_view(), name='profile-list'),
path('profiles/<int:pk>/', views.ProfileDetail.as_view(), name='profile-detail'),
path('skills/', views.SkillList.as_view(), name='profile-list'),
path('skills/<int:pk>/', views.SkillDetail.as_view(), name='skill-list'),
Output: http://127.0.0.1:8000/authenticator/profiles/
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
57,
55
],
]
Expected output:
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
{
"user": 6,
"skill": "ABC",
"level": "Beginner"
},
{
"user": 6,
"skill": "DEF",
"level": "Intermediate"
},
]
]
This should normally work with the following in serializer.py:
class ProfileSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ['id', 'user', 'url', 'skills']
So basically uncomment the line in which the ProfileSerializer is told to serialize the skills with your SkillsSerializers, such that the entire skill objects are included in the result instead of its IDs.
You can use the concept of nested serializer for the solution.
Create two serializer ProfileReadSerializer and ProfileWriteSerializer
serializers.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileReadSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True)
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
class ProfileWriteSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True)
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
In views you simply can use ModelViewSet to make things easy and use get_serializer_class
views.py
from rest_framework.viewsets import ModelViewSet
class ProfileVewSet(ModelViewSet):
queryset = Profile.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST' or self.request.method == 'PUT' or self.request.method == 'PATCH':
return ProfileWriteSerializer
else:
return ProfileReadSerializer
And at last if you are using ModelViewSet you need to change the urls.py
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('profile', views.ProfileViewSet, base_name='profile')
urlpatterns = [
path('', include(router.urls))
]
I think this will solve your problem. Please have a look.

django-rest-framework CRUD many models in same view

I would like to be able to CRUD many models in the same API View.
In this case there are User and UserProfile Models in the same API View.
My Serializers
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('expected_calories',)
class UserSerializer(serializers.HyperlinkedModelSerializer):
expected_calories = serializers.
class Meta:
model = User
fields = ('url', 'username', 'email', 'first_name', 'last_name', 'expected_calories')
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Then my views
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
GET Api Call to
/api/v1/user/2/
{
"url": "http://localhost:8000/api/v1/user/2/",
"username": "jsalchichon",
"email": "jsalchichon#example.com",
"first_name": "John",
"last_name": "Salchichon",
"expected_calories": 0
}
But when I try to update the expected_calories field to 2500, The field is not updated.
Is this the right way to do this? What else should I do to be able to update the expected_calories of the UserProfile Model?
Thanks to you in advance.
I just did this:
class UserSerializer(serializers.HyperlinkedModelSerializer):
expected_calories = serializers.Field(source='userprofile.expected_calories')
class Meta:
model = User
fields = ('url', 'username', 'email', 'first_name', 'last_name', 'expected_calories')
def validate_expected_calories(self, attrs, source):
expected_calories = self.data.get('expected_calories')
if not isinstance(expected_calories, int):
raise serializers.ValidationError("expected_calories must be an integer")
attrs['expected_calories'] = expected_calories or self.object.userprofile.expected_calories
return attrs
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def post_save(self, obj, created=False):
super(UserViewSet, self).post_save(obj, created)
expected_calories = self.request.DATA.get('expected_calories')
if isinstance(expected_calories, int):
obj.userprofile.expected_calories = self.request.DATA.get('expected_calories',
obj.userprofile.expected_calories)
obj.userprofile.save()
Not sure if it's the right way to handle this but it works.

Resources