How to filter records based on nested data using django rest framework - django-rest-framework

I have this json response from my customer api based on django rest framework.
When I hit api
http://localhost:8000/api/customers
I receive following response
[
{
'name': 'Daniel',
'group': 'BK',
'user_id': 102,
'user_details': {
'username': 'dan1',
'active': true,
}
},
{
'name': 'John',
'group': 'BK',
'user_id': 103,
'user_details': {
'username': 'john1',
'active': true,
}
}
]
Now I need to filter record whose username=john1, how do I do that?
I have tried using this in my customer viewset by defining filter backend
filter_fields = ('user_details__username',)
and tried hitting the api as
http://localhost:8000/api/customers?user_details__username=john1
but it gives error as
'Meta.fields' contains fields that are not defined on this FilterSet:
user_details__username
Its happening because user_details is not the field of my customer serializer, its basically SerializerMethodField which manipulates user information to display under customer api.
Here is my customer serializer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
user_details = serializers.SerializerMethodField('get_serialized_target_object')
class Meta:
model = Customer
fields = '__all__'
def get_serialized_target_object(self, obj):
usr_id = obj.user_id
if usr_id:
instance = User.objects.filter(pk=usr_id)
if instance:
instance = instance[0]
return UserSerializer(instance=instance).data
else:
return None
and here is my viewset
class CustomerViewSet(viewsets.ModelViewSet):
queryset = Customer.objects.all()
serializer_class = CustomerSerializer
filter_fields = ('user_details__username',)
Please help me how do I filter my record from customer api with username=john1

You should not use SerializerMethodField.
Try the serializer below:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'active',)
class CustomerSerializer(serializers.ModelSerializer):
user_details = UserSerializer(many=True)
class Meta:
model = Customer
fields = '__all__'
Then you can define your field as filter_fields = ('user_details__username',)

Related

IntegrityError at /api/order/create NOT NULL constraint failed: store_order.product_id

I'm trying to create Order via Api request with related keys, product and user id's. But getting an error that they cannot be null, although they're not null.
model.py
class Order(models.Model):
id = models.AutoField(primary_key=True)
product = models.ForeignKey(Product, related_name='product', on_delete=models.CASCADE, default = None)
user = models.ForeignKey(User, related_name='user', on_delete=models.CASCADE, default = None)
orderPrice = models.IntegerField(default=0)
status = models.CharField(max_length=255, default='Принят на обработку')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
serializer.py
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id', 'product', 'user', 'status', 'orderPrice']
depth = 1
view.py
#api_view(['POST'])
def OrderCreateView(request):
order_serializer = OrderSerializer(data=request.data)
if order_serializer.is_valid(raise_exception=True):
order_serializer.save()
return JsonResponse(order_serializer.data, safe=False)
and finally my api request
{
"product": 1,
"user": 3,
"status": "Принят на обработку",
"orderPrice": 30000
}
This code worked sometime but i did couple changes and now it getting an error. I tried to remigrate all my models, and requested like that "user_id", "product_id". But keeps getting the error
Hi
the problem of your code is your serializer because you mention a depth of 1 and you send a JSON in your POST functiona JSON in coherent.
Your json should contain all the attributes of your foreign keys like this:
{
"product": {
...attributes of product
},
"user": {
...attributes of user
},
"status": "Принят на обработку",
"orderPrice": 30000
}
You just have to declare the product field as nullable, because null is False by delfault:
class Order(models.Model):
[...]
product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, blank=True)
[...]
I also removed related_name, as to retrieve the order of a product, you had to call product.product. Now you retrieve it using product.order.

How to get multiple records in the main model based on an array from a nested serializer?

I have next code:
models.py
class tbox(models.Model):
name = models.CharField("box", max_length=15)
status = models.CharField("status box", max_length=50)
class tbox_move(models.Model):
tbox = models.ForeignKey('api.tbox',related_name='tbox_moves', on_delete=models.DO_NOTHING)
discription = models.TextField(null=True, blank=True)
def __str__(self):
return '%d: %s' % (self.order, self.title)
serializer.py
from rest_framework import serializers
from .models import tbox, tbox_move
class tboxSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False, write_only=False)
class Meta:
model = tbox
fields = ['id', 'name', 'status']
def update(self, instance, validated_data):
print("Updade in serializer TBOX")
class tbox_moveSerializer(serializers.ModelSerializer):
tbox_moves = tboxSerializer(many=True)
class Meta:
model = tbox_move
fields = ['tbox_moves', 'discription']
def create(self, validated_data):
tboxs_data = validated_data.pop('tbox_moves')
for tbox_data in tboxs_data:
tbox_id = tbox_data.get("id", None)
if tbox_id is None:
print("requare BOX ID")
id_tbox = tbox.objects.filter(id=tbox_id).update(**tbox_data)
tbox_m = tbox_move.objects.create(tbox=tbox_id, **validated_data)
return tbox_m
I send next request POST json:
{
"tbox_moves" : [
{"id" : 3, "id_test" : 1, "name" : "name_box1", "status": "block"},
{"id" : 1, "id_test" : 1, "name" : "name_box2", "status": "block"}
],
"description": "collective data"
}
I want to get the following result.
Create multiple records in the model equal to the number in the nested serializer
Example tbox_move model:
ID
discription
tbox_id
10
collective data
3
11
collective data
1
I want update status in the nested model tbox from enable to block
How this create?

Nested write serializer validated_date is empty

Followed the best practive create nested objects with serializers, however I still receive empty nested validate_data.
Serializers:
class WriteOrganisationSiteSerializer(serializers.ModelSerializer):
"""Organisation Site serializer class for post methods."""
site = WriteAPSiteSerializer()
class Meta:
model = models.OrganisationSite
fields = ("organisation", "site")
def create(self, validated_data):
from fadat.sites.models import APSite
site_data = validated_data.pop("site")
activeplaces_site_id = site_data.pop("activeplaces_site_id")
site, created = APSite.objects.get_or_create(
activeplaces_site_id=activeplaces_site_id, defaults=site_data
)
organisation_site = models.OrganisationSite.objects.create(
site=site, **validated_data
)
return organisation_site
class WriteAPSiteSerializer(serializers.Serializer):
"""Active Places Site serializer class for post methods."""
class Meta:
model = models.APSite
fields = (
"activeplaces_site_id",
"site_name",
"dependent_thoroughfare",
"thoroughfare_name",
"double_dependent_locality",
"dependent_locality",
"post_town",
"postcode",
"easting",
"northing",
"longitude",
"latitude",
)
The view
class OrganisationSitesView(APIView):
"""Organisation Sites API view."""
def post(self, request, **kwargs):
user = request.user
ser = serializers.WriteOrganisationSiteSerializer(data=request.data)
ser.is_valid(raise_exception=True)
obj = ser.save()
ser = serializers.ReadOrganisationSiteSerializer(obj)
return Response(ser.data, status=201)
Running the following test (or via browser a ajax request, same result)
def test_add_organisation_site(self):
user = User.objects.create(email="newbie#dat.example.com")
organisation_type = OrganisationType.objects.create(name="Club")
organisation = Organisation.active_objects.create(
name="Club", organisation_type=organisation_type
)
site = {
"activeplaces_site_id": 1200341,
"site_name": "CITY OF LONDON SCHOOL",
"dependent_thoroughfare": "",
"thoroughfare_name": "QUEEN VICTORIA STREET",
"double_dependent_locality": "",
"dependent_locality": "",
"post_town": "LONDON",
"postcode": "EC4V 3AL",
"easting": 531990,
"northing": 180834,
"longitude": -0.099387,
"latitude": 51.511025,
}
body = {
"organisation": organisation.id,
"site": site,
}
self.authenticate(user)
url = reverse("api:inspections:organisation-sites")
res = self.client.post(url, json.dumps(body), content_type="application/json; charset=utf-8")
self.assertEqual(res.status_code, 201)
Receiving the following headers in my views
{'organisation': 1, 'site': {'activeplaces_site_id': 1200341, 'site_name': 'CITY OF LONDON SCHOOL', 'dependent_thoroughfare': '', 'thoroughfare_name': 'QUEEN VICTORIA STREET', 'double_dependent_locality': '', 'dependent_locality': '', 'post_town': 'LONDON', 'postcode': 'EC4V 3AL', 'easting': 531990, 'northing': 180834, 'longitude': -0.099387, 'latitude': 51.511025}}
In the view the request.data show me
{'Cookie': '', 'Content-Length': '368', 'Content-Type': 'application/json; charset=utf-8', 'Authorization': 'Token 0081d8a36d90f1d922a2a7df494afe127a220495'}
Still the serialized doesn't validate nested fields and returns
{'organisation': <Organisation: Club (Club)>, 'site': OrderedDict()}
Right, here's what I see that needs fixing: the conventional way to design a post()method is as follows:
def post(self, request, **kwargs):
serializer = serializers.WriteOrganisationSiteSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Although your case is in many ways equivalent, it's obviously not 100% correct. Perhaps it has to do with the fact that you're using one serializer to create the new instance, and another to show the result, when that's not needed: you can set up which fields are read_only, which ones are write_only, and those that are both read and write, as the documentation explains. I suggest you follow the schema I laid out above to guide you, and define in WriteOrganisationSiteSerializer how to show the data to the end user. Do let me know if you have any problems.

In the Django Rest Framework, how do you add ManyToMany related objects?

Here's my code:
Models
class Recipe(models.Model):
name = models.CharField(max_length=50, unique=True)
ingredient = models.ManyToManyField(Ingredient)
class Ingredient(models.Model):
name = models.CharField(max_length=50, unique=True)
View
class RecipeDetailAPIView(RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminOrReadOnly,)
serializer_class = RecipeSerializer
queryset = Recipe.objects.all()
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def perform_update(self, serializer):
serializer.save(updated_by_user=self.request.user)
Serializers
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = [
'id',
'name',
]
class RecipeSerializer(serializers.ModelSerializer):
ingredient = IngredientSerializer(many=True, read_only=False)
class Meta:
model = Recipe
fields = [
'id',
'name',
'ingredient',
]
I'm starting with the following Recipe object:
{
"id": 91
"name": "Potato Salad"
"ingredient": [
{
"id": 5,
"name": "Potato"
}
]
}
Now, I am attempting to update that object by putting the following JSON object to the IngredientSerializer:
{
"id": 91
"name": "Potato Salad"
"ingredient": [
{
"id": 5,
"name": "Potato"
},
{
"id": 6,
"name": "Mayo"
}
]
}
What I want is it to recognize that the relationship to Potato already exists and skip over that, but add a relationship to the Mayo object. Note that the Mayo object already exists in Ingredients, but is not yet tied to the Potato Salad object.
What actually happens is the Serializer tries to create a new Ingredient object and fails because "ingredient with this name already exists."
How do I accomplish this?
DRF does not have any automatic "write" behavior for nested serializers, precisely because it does not know things like how to go about updates in the scenario you mentioned. Therefore, you need to write your own update method in your RecipeSerializer.
class IngredientSerializer(serializers.ModelSerializer):
def validate_name(self, value):
# manually validate
pass
class Meta:
model = Ingredient
fields = ['id', 'name']
extra_kwargs = {
'name': {'validators': []}, # remove uniqueness validation
}
class RecipeSerializer(serializers.ModelSerializer):
ingredient = IngredientSerializer(many=True, read_only=False)
def update(self, instance, validated_data):
ingredients = validated_data.pop('ingredient')
# ... logic to save ingredients for this recipe instance
return instance
class Meta:
model = Recipe
fields = ['id', 'name', 'ingredient']
Relevant DRF documentation:
Saving Instances
Writable Nested Serializer
Updating nested serializers
Update:
If DRF validation fails for the uniqueness constraint in the name field, you should try removing validators for that field.
Alternative solution: Only use full serializer as read only field
You can change you RecipeSerializer to the following:
class RecipeSerializer(serializers.ModelSerializer):
ingredient_details = IngredientSerializer(many=True, read_only=True, source='ingredient')
class Meta:
model = Recipe
fields = ['id', 'name', 'ingredient', 'ingredient_details']
And that's it. No need to override update or anything. You'll get the detailed representation when you get a recipe, and you can just PUT with the ingredient ids when updating. So your json when updating will look something like this:
{
"id": 91
"name": "Potato Salad"
"ingredient": [5, 6]
}

Django Rest Framework - can't created object with existing FK reference

I have a serializer class in DRF like so:
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
created_by = UserSerializer() # created_by is an FK to User model
class Meta:
model = Project
fields = ('id', 'title', 'created_by')
My views:
class ProjectList(generics.ListCreateAPIView):
model = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
serializer = ProjectSerializer(data={
"id": 12,
"title": "FooBar's Project",
"created_by": {
"id": 1,
"username": "foobar",
"first_name": "foo",
"last_name": "bar",
},
})
if serializer.is_valid():
serializer.save()
else:
print serializer.errors
return Response(serializer.data, status=status.HTTP_201_CREATED)
This works almost as expected except that DRF complains that:
{'created_by': [{'username': [u'User with this Username already exists.']}]}
What I want is that the Project is created with reference to an existing user. What am I doing wrong here?
Note: I am tracking trunk for DRF, not from the cheeseshop.
If you want to assign an existing user, not creating one, nested serializer is not the way. As you want to just add the relation to the user, you will need to take a look at relations.
The PrimaryKeyRelatedField is way to go and since this field is going to be writable you will also need to pass a queryset attribute.
created_by = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
See the documentation to check the additional options that can be passed to this field:
http://django-rest-framework.org/api-guide/relations#primarykeyrelatedfield
Then your example post could look like this:
def post(self, request, format=None):
user = User.objects.get(pk=8)
serializer = ProjectSerializer(data={
"id": 12,
"title": "FooBar's Project",
"created_by": user.pk,
})
(Posted on behalf of the question author).
This is the working solution:
# serializers.py
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
...
created_by = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all()
)
class Meta:
model = Project
fields = ('id', 'title', 'created_by')
# views.py
class ProjectList(generics.ListCreateAPIView):
model = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)

Resources