override create in serializer - django

Im overriding the create method in serializer but it seems it is not getting into this function when i do a POST request:
My serializer:
class ElsUserSerializer(serializers.ModelSerializer):
class Meta:
model = ElsUser
fields = ['id', 'first_name', 'last_name', "email"]
read_only_fields = ['id']
class RequisiteItemSerializser(serializers.ModelSerializer):
created_by = ElsUserSerializer()
class Meta:
model = RequisiteItem
fields = ['id', 'enrollable', 'completed_within_days', 'completed_within_type','date_completed_by', 'created_by' ]
read_only_fields = ['id']
class RequisiteSerializer(serializers.ModelSerializer):
created_by = ElsUserSerializer()
items = RequisiteItemSerializser(many=True)
class Meta:
model = Requisite
fields = ('id', 'name', 'description', 'items', 'enrolments', 'created_by', 'timestamp')
read_only_fields = ['id']
depth = 1
def create(self, validated_data):
print("SELF INTITIAL DATA", self.initial_data)
helper = RequisiteHelper()
requisite = helper.create(self.initial_data,self.context['request'])
print("LOGGES IN USER", self.context['request'].user.username)
return requisite
I have moved the methods to a separate helper class:
My helper class:
class RequisiteHelper:
def create(self, initial_data, context):
name = initial_data['name']
description = initial_data['description']
items = initial_data['items']
enrolments = initial_data['enrolments']
requisite = Requisite.objects.create(name=name, description=description, created_by=context.user)
self.create_items(requisite, items, context)
self.create_enrolments(requisite, enrolments)
requisite.save()
return requisite
def create_items(self, requisite, items, context):
for item in items:
requisite_item = RequisiteItem()
enrollable = Enrollable.objects.get(id=item.enrollable.id)
requisite_item.enrollable = enrollable
requisite_item.created_by = context.user
requisite_item.completed_within_days = item.completed_within_days
cw_type = CompletedWithinType.objects.get(short_name = item.completed_within_type)
requisite_item.completed_within_type = cw_type
requisite_item.save()
requisite.items.add(requisite_item)
def create_enrolments(self, requisite, enrolments):
for enrolment in enrolments:
requisite.add(enrolment)
When i create a Requisite by post method,it keeps showing created_by is required. Im saving created_by automatically as logged in user by overrriding the create method. The print statements are also not displaying. What could i be doing wrong?
In views.py i have:
class ListCreateRequisite(generics.ListCreateAPIView):
serializer_class = RequisiteSerializer
queryset = Requisite.objects.all()

You create method is not calling because created_by model field validation of require-field is catch before it when serailizer.is_valid() called. You need to assign that value, before calling serializer or in run time or even you can change created_by requirements in model defination. That will allow you to by pass this catch.

Related

DRF Invalid pk object does not exist for foreign key on POST create

I have two classes related to each other.
One class I have made the primary key a char field so I can easily reference to it or create it to match the id of the actual object (all objects will have this unique name).
from django.db import models
class Ven(models.Model):
id = models.CharField(max_length=80, primary_key=True)
statusOn = models.BooleanField(default=True)
class Device(models.Model):
device_id = models.CharField(max_length=80)
ven_id = models.ForeignKey(VEN, related_name='devices', on_delete=models.SET_NULL, null=True)
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
class VENSerializer(serializers.ModelSerializer):
class Meta:
model = VEN
fields = ['id', 'statusOn', 'devices']
class DeviceList(generics.ListCreateAPIView):
logger.info("DeviceList: view")
# permission_classes = [permissions.IsAuthenticated]
queryset = Device.objects.all()
serializer_class = DeviceSerializer
however when I try to run my test:
class DevicesTestCase(TestCase):
def setUp(self):
self.factory = Client()
def test_create_device(self):
device = {
"ven_id": "TEST_VEN_1",
"device_id": "TEST_DEVICE_1",
"statusOn": True,
}
response = self.factory.post('/devices/', data=device)
self.assertEqual(response.status_code, 201)
my response returns 400 and states:
b'{"ven_id":["Invalid pk \\"TEST_VEN_1\\" - object does not exist."]}'
I'm trying to write a custom create in my serializer to create the ven if it does not exist but it's not being called. Data is being validated else where. My breakpoint in my view set's perform_create also does not fire.
I don't want to write a bunch of workaround code for something that should be straightforward. I know I'm missing/messing something up somewhere.
I think you need to customize the create method in the DeviceSerializer.
class DeviceSerializer(serializers.ModelSerializer):
ven_id = serializers.CharField()
status_on = serializers.BooleanField(write_only = True)
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
def create(self, validated_data):
ven_id = validated_data.pop('ven_id')
status_on = validated_data.pop('status_on')
try:
ven = Ven.objects.get(id = ven_id)
except Ven.DoesNotExist:
ven = Ven.objects.create(id = ven_id, statusOn = status_on)
new_device = Device.objects.create(ven_id = ven, **validated_data)
return new_device

How to get a request user query without hitting DB each time in django?

I built some Blog API but I've got an issue.
In the serializer, there is a method field that returns request user has liked the post or not. The problem is this field hits the user DB each time a post returns, which means if 1000 posts are returned, user DB would be hit 1000 times.
How can I avoid this? The first idea was to assign the request user to something like a global variable, but I don't know how to.
this is serializer
class DashboardSerializer(serializers.ModelSerializer):
image = serializers.ImageField(allow_null=True, use_url=True)
likes_count = serializers.SerializerMethodField(read_only=True)
tags = TagSerializer(many=True, read_only=True)
user_has_liked = serializers.SerializerMethodField(read_only=True)
owner = UserField(read_only=True)
comments = CommentsField(read_only=True, many=True, source='two_latest_comments')
comments_count = serializers.SerializerMethodField()
class Meta:
model = Blog
fields = ['id', 'title', 'owner', 'likes_count', 'user_has_liked',
'image', 'video', 'tags', 'get_elapsed_time_after_created',
'comments', 'comments_count']
ordering = ['created_at']
def get_likes_count(self, instance):
return instance.likes.count()
def get_user_has_liked(self, instance):
request = self.context.get('request')
***return instance.likes.filter(pk=request.user.pk).exists()***
*******request.user.pk hits the DB.*********
def get_comments_count(self, instance):
return instance.comments.count()
Thank you in advance.
Use .annotate and Exists subqyery on the queryset that you are passing to the serializer, and then adjust the fields:
serializer call:
queryset = ... # your queryset
user_commented = Comment.objects.filter(
blog_id=OuterRef("pk"),
user_id=request.user.pk,
)
data = DashboardSerializer(instance=queryset.annotate(
likes_count=Count("likes"),
comments_count=Count("comments"),
user_has_liked=Exists(user_commented),
)
serializer:
class DashboardSerializer(serializers.ModelSerializer):
image = serializers.ImageField(allow_null=True, use_url=True)
likes_count = serializers.IntegerField(read_only=True)
tags = TagSerializer(many=True, read_only=True)
user_has_liked = serializers.BooleanField(read_only=True)
owner = UserField(read_only=True)
comments = CommentsField(read_only=True, many=True, source='two_latest_comments')
comments_count = serializers.IntegerField(read_only=True)
class Meta:
model = Blog
fields = ['id', 'title', 'owner', 'likes_count', 'user_has_liked',
'image', 'video', 'tags', 'get_elapsed_time_after_created',
'comments', 'comments_count']
ordering = ['created_at']

django rest framework : nested model get not working. 'str' object has no attribute 'values'

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
Saving is working perfectly. But now it is showing the following error when I call the GET method.
AttributeError at /api/v1/customer 'str' object has no attribute 'values'
Request Method: GET
bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
bcusomer/serializers.py
class CustomerDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = BCustomer
fields = ('city', 'phone')
class CustomerSerializer(serializers.ModelSerializer):
customer_details = CustomerDetailsSerializer()
class Meta:
model = get_user_model()
fields = ('id','first_name', 'email', 'customer_details')
def create(self, validated_data):
request = self.context.get('request')
customer_details_data = validated_data.pop('customer_details')
customer_user = get_user_model().objects.create(**validated_data)
BCustomer.objects.create(customer=customer_user, user=request.user, **customer_details_data)
customer_user.customer_details = customer_details_data
return customer_user
class CustomerListSerializer(serializers.ModelSerializer):
model = get_user_model()
fields = '__all__'
class Meta:
model = get_user_model()
fields = '__all__'
bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
def get_queryset(self):
queryset = BCustomer.objects.all()
return queryset
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
return CustomerListSerializer
return CustomerSerializer
bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
Data post parameter format
{
"first_name":"Myname",
"email":"testemail#gmail.com",
"customer_details": {
"city":"citys",
"phone":"04722874567",
}
}
You should remove model and fields from CustomListSerializer
class CustomerListSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = '__all__'
customer_details = CustomerDetailsSerializer()
You need to set the source argument to point to the user model's customer. Most probably:
customer_details = CustomerDetailsSerializer(source='customer')
(or maybe source='bcustomer', not sure if it reversed the field name or class name).
On a side not, you should not need the ListSerializer at all. The list method will call the serializer with the many=True argument on CustomerSerializer which will create the ListSerializer appropriately.

Django Rest Framework Ordering Filter, order by nested list length

I'm using OrderingFilter globally through settings.py and it works great.
Now I would like to order on the size of a nested list from a ManyToManyField. Is that possible with the default OrderingFilter?
If not, is there a way I can do it with a custom filter, while keeping the query param ordering in the url (http://example.com/recipes/?ordering=). For the sake of consistency.
Oh and the ManyToManyField is a through table one.
These are my models.py:
class Recipe(models.Model):
name = models.CharField(max_length=255)
cook_time = models.FloatField()
ingredients = models.ManyToManyField(IngredientTag, through=Ingredient)
My serializers.py:
class IngredientTagSerializer(serializers.ModelSerializer):
class Meta:
model = IngredientTag
fields = ('id', 'label')
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ('amount', 'unit', 'ingredient_tag')
depth = 1
class RecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(source='ingredient_set', many=True)
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'cook_time')
read_only_fields = ('owner',)
depth = 2
And my views.py:
class RecipeViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows recipes to be viewed or edited.
"""
queryset = Recipe.objects.all().order_by()
serializer_class = RecipeSerializer
permission_classes = (DRYPermissions,)
ordering_fields = ('cook_time',) #Need ingredient count somewhere?
Thanks!
Try:
class RecipeSerializer(serializers.ModelSerializer):
ingredients = IngredientSerializer(source='ingredient_set', many=True)
ingredients_length = serializers.SerializerMethodField()
class Meta:
model = Recipe
fields = ('id', 'url', 'name', 'ingredients', 'cook_time')
read_only_fields = ('owner',)
depth = 2
def get_ingredients_length(self, obj):
return obj.ingredients.count()
Then order by ingredients_length
EDIT
In model.py, try this:
#property
def ingredient_length(self):
return self.ingredient_set.count()

Trouble POSTing multiple related objects at once to api created with django rest framework

I'm making a ToDo app but having difficulties getting the api to allow a user to create a new list with multiple items via one api call. Each list belongs to a specific "room".
I get 400 Bad Request. If I leave the 'todo_items' off the POST data it works fine to create the ToDoList object.
Also, if I remove "user" from the Meta fields attribute for the CreateToDoItemSerializer, it'll create both the ToDoList object and the ToDoItem objects, but the "content" for each ToDoItem will be an empty string. Inside the create method of NewToDoListSerializer, the validated_data is returning a list of empty OrderedDict() objects for the key "todo_items". I'm not sure what to make of that.
my models:
class Room(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
slug = models.SlugField(max_length=255, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="rooms")
class ToDoList(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, blank=True)
room = models.ForeignKey(Room, related_name="todo_lists")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="todo_lists")
class ToDoItem(models.Model):
todo_list = models.ForeignKey(ToDoList, related_name="todo_items")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="replies")
content = FroalaField(options={'placeholder': '''Just start writing...
Highlight any text to bring up the editor.'''})
my serializers
class CreateTodoItemSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=User.objects.all())
class Meta:
model = ToDoItem
fields = ['pk', 'user', 'content']
def create(self, validated_data):
reply = ToDo.objects.create(**validated_data)
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, read_only=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)
my viewset (the relevant bits):
class ToDoListViewSet(viewsets.ModelViewSet):
queryset = ToDoList.objects.all()
serializer_class = ToDoListSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated]
renderer_classes = (renderers.TemplateHTMLRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer)
template_name = "react_base.html"
lookup_field = "slug"
def create(self, request, **kwargs):
self.serializer_class = NewToDoListSerializer
return super(ToDoListViewSet, self).create(request, **kwargs)
def perform_create(self, serializer):
instance = serializer.save(user=self.request.user)
the data I'm POSTing:
todoListTitle, todoItemContent, moreTodoItemContent are all strings. this.props.room.pk is an integer. this.props.csrfmiddlewaretoken is the csrfmiddlewaretoken
var newToDoListData = {
"room": this.props.room.pk,
"title": todoListTitle,
"todo_items": [{"content": todoItemContent}, {"content": moreTodoItemContent}],
"csrfmiddlewaretoken": this.props.csrfmiddlewaretoken
};
You need to make the todo_items in your serializer required = false. I am not sure I understand what your second issue is.
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, required=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)