Django Rest Framework to receive specific product by field - api

I am currently making an API to return JSON for a product.
Currently using Django rest framework, I have successfully implemented the API to view all products via
path/api/products
to show a JSON of all products:
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"url": "http://127.0.0.1:8000/api/products/1/",
"id": 1,
"brand": "Mars",
"name": "Barres",
"barcode": 5000159366168,
"category": "Snacks, Snacks sucrés, Cacao et dérivés, Confiseries, Barres, Confiseries chocolatées, Barres chocolatées, Barres chocolatées au caramel",
"allergens": "gluten,milk,soybeans",
"weight": 540.0,
"quantity": 1,
"footprint": 2.28655779803366e-06,
"perishable": false
},
{
"url": "http://127.0.0.1:8000/api/products/2/",
"id": 2,
"brand": "Twix",
"name": "Twix",
"barcode": 5000159366267,
"category": "Snacks, Snacks sucrés, Confiseries, Barres",
"allergens": "gluten,nuts",
"weight": 600.0,
"quantity": 1,
"footprint": 0.0,
"perishable": false
},
{
"url": "http://127.0.0.1:8000/api/products/3/",
"id": 3,
"brand": "Twix",
"name": "Twix salted caramel",
"barcode": 5000159528955,
"category": "Biscuits et gâteaux, Biscuit chocolat",
"allergens": "caramel, choclate, wheat",
"weight": 46.0,
"quantity": 1,
"footprint": 0.0,
"perishable": false
}
]
However, I would like to be able to receive the JSON object of only one product by barcode:
for example
path/api/products/5000159366168
or
path/api/products/?barcode=5000159366168
to return only the product that matches the barcode:
{
"url": "http://127.0.0.1:8000/api/products/1/",
"id": 1,
"brand": "Mars",
"name": "Barres",
"barcode": 5000159366168,
"category": "Snacks, Snacks sucrés, Cacao et dérivés, Confiseries, Barres, Confiseries chocolatées, Barres chocolatées, Barres chocolatées au caramel",
"allergens": "gluten,milk,soybeans",
"weight": 540.0,
"quantity": 1,
"footprint": 2.28655779803366e-06,
"perishable": false
}
Here is my code:
my views.py:
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
my serializers.py:
from app1.models import Product
from rest_framework import serializers
class ProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Product
fields = ["url","id", "brand", "name", "barcode","category","allergens", "weight", "quantity", "footprint","perishable"]
my urls.py:
router = routers.DefaultRouter()
router.register(r'products', views.ProductViewSet)
urlpatterns = [
...
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

You need to override get_queryset() method to filter your data with your barcode or any other filter you want.
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
def get_queryset(self):
queryset = super().get_queryset()
barcode= self.request.query_params.get('barcode', None)
if barcode:
queryset = queryset.filter(barcode=barcode)
return queryset
Notice: How I added if condition for barcode that only if barcode variable have some data in it, filter it otherwise simply return the normal queryset.

With help from Asim Ejaz
Here is my working View.py class
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
def get_queryset(self):
super(ProductViewSet, self).get_queryset()
barcode = self.request.query_params.get('barcode', None)
print("Barcode = ", barcode)
print(self)
queryset = Product.objects.all()
if barcode:
queryset = Product.objects.filter(barcode=barcode)
return queryset
Notice: barcode = self.request
as request is not previously defined
If you too run into this error, ensure the request is typed in properly, in this case it should be, http://127.0.0.1:8000/api/products/?barcode=5000159366168

Related

Get the value from the response based on a condition and store it to a variable

I would like to get the value from the response based on a condition and store it to a variable.
In the below JSON, I would like to store the value when the name matches to something I prefer. Is there a way to achieve this using Karate API?
{
"results": [
{
"name": "Sample1",
"email": "sample1#text.com",
"id": "U-123"
},
{
"name": "Sample2",
"email": "sample2#text.com",
"id": "U-456"
},
{
"name": "Sample3",
"email": "sample3#text.com",
"id": "U-789"
}
]
}
So after reading the comment, my interpretation is to "find" the id where name is Sample2. Easy, just use a filter() operation, refer the docs: https://github.com/karatelabs/karate#jsonpath-filters
Instead of using a filter, I'm using the JS Array find() method as a very concise example below:
* def response =
"""
{ "results": [
{ "name": "Sample1", "email": "sample1#text.com", "id": "U-123" },
{ "name": "Sample2", "email": "sample2#text.com", "id": "U-456" },
{ "name": "Sample3", "email": "sample3#text.com", "id": "U-789" }
]
}
"""
* def id = response.results.find(x => x.name == 'Sample2').id
* match id == 'U-456'
Take some time to understand how it works. Talk to someone who knows JS if needed.

Getting testing error " Test data contained a dictionary value for key 'category'"

Writing test for a little API. Test for GET method working, but for create an error is being called. What could be the problem? i may guess the wrong data format is using.
class CoursesTest(APITestCase):
def setUp(self):
self.course_url = reverse('course-group')
User.objects.create(username='test111', password='123456')
def test_courses_post(self):
data = {
"name": "Blasssbla",
"description": "blabla",
"logo": "img",
"category": {
"name": "Baling",
"imgpath": "img"
},
"contacts": [
{
"status": 1
}
],
"branches": [
{
"latitude": "2131ssss2321",
"longitude": "12321321",
"address": "Osssssh"
}
]
}
self.response = self.client.post(self.course_url, data)
self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)
Error:
AssertionError: Test data contained a dictionary value for key 'category', but multipart uploads do not support nested data. You may want to consider using format='json' in this test case.
If your test data is JSON, you should add format="json" to self.client.post.
class CoursesTest(APITestCase):
def setUp(self):
self.course_url = reverse('course-group')
User.objects.create(username='test111', password='123456')
def test_courses_post(self):
data = {
"name": "Blasssbla",
"description": "blabla",
"logo": "img",
"category": {
"name": "Baling",
"imgpath": "img"
},
"contacts": [
{
"status": 1
}
],
"branches": [
{
"latitude": "2131ssss2321",
"longitude": "12321321",
"address": "Osssssh"
}
]
}
self.response = self.client.post(self.course_url, data, format="json")
self.assertEqual(self.response.status_code, status.HTTP_201_CREATED)

Serializing MPTT Tree with Django REST Framework

I searched for a similar question without success.. So, i am working on a website in which i am using django-mptt to organize categories. My Model looks like:
class Category(MPTTModel):
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, related_name='children')
name = models.CharField(max_length=90)
slug = models.SlugField(unique=True)
_full_slug_separator = '/'
#property
def url(self):
names = [category.name for category in self.get_ancestors(include_self=True)]
return self._full_slug_separator.join(names)
I defined the CategorySerializer as bellow:
class CategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ('name', 'url', 'children')
def get_children(self, obj):
return CategorySerializer(obj.get_children(), many=True).data
# views.py
class CategoryList(generics.ListAPIView):
queryset = Category.objects.root_nodes()
serializer_class = CategorySerializer
The question is how can i:
1. have the 'url' data included in leaf nodes only.
2. have the 'children' data included in non leaf nodes only.
Here is an example of the output I am looking for
[
{
"title":"root node",
"children":[
{
"title":"leaf node",
"url":"link"
},
{
"title":"non leaf node",
"children":[
{
"title":"leaf node",
"url":"link"
}
]
},
{
"title":"non leaf node",
"children":[
{
"title":"non leaf node",
"children":[
{
"title":"leaf node",
"url":"link"
}
]
}
}
]
},
{
"title":"root node",
"url":"link"
}
]
Also i want to know if there is a good way for generating the 'url' to reduce queries
And thanks for any help in advance.
I ran into the same problem and found this question. But there were no answers. So, I am posting how I managed to do it for anyone who may need it in the future. This is most likely not the best solution but it works.
My models.py:
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
class Category(MPTTModel):
name = models.CharField(max_length=100, null=False, blank=False, verbose_name=_("category name"), help_text=_("format: required, max_length=100"))
slug = models.SlugField(max_length=150, null=False, blank=False, editable=False, verbose_name=_("category url"), help_text=_("format: required, letters, numbers, underscore or hyphen"))
parent = TreeForeignKey("self", on_delete=models.SET_NULL, related_name="children", null=True, blank=True, verbose_name=_("parent category"), help_text=_("format: not required"))
class MPTTMeta:
order_insertion_by = ['name']
class Meta:
verbose_name = _('product category')
verbose_name_plural = _('product categories')
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def __str__(self):
return self.name
My serializers.py:
from rest_framework import serializers
class SubCategorySerializer(serializers.ModelSerializer):
"""Serializer for lowest level category that has no children."""
parent = serializers.SerializerMethodField(source='get_parent')
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'parent', ]
def get_parent(self, obj):
if obj.parent:
return obj.parent.name
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for category."""
parent = serializers.SerializerMethodField(source='get_parent')
children = serializers.SerializerMethodField(source='get_children')
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'parent', 'children', ]
def get_parent(self, obj):
if obj.parent:
return obj.parent.name
def get_children(self, obj):
if obj.children.exists():
children = [child for child in obj.children.all()]
children_with_children = [child for child in children if child.children.exists()]
children_without_children = [child for child in children if not child.children.exists()]
if children_with_children:
return CategorySerializer(children_with_children, many=True).data
if children_without_children:
return SubCategorySerializer(children_without_children, many=True).data
This way children is included in non-leaf nodes only.
Here is an example of how the data looks:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 4,
"name": "Clothes",
"slug": "clothes",
"parent": null,
"children": [
{
"id": 5,
"name": "Men's Clothes",
"slug": "mens-clothes",
"parent": "Clothes",
"children": [
{
"id": 7,
"name": "Men's Jeans",
"slug": "mens-jeans",
"parent": "Men's Clothes"
}
]
},
{
"id": 6,
"name": "Women's Clothes",
"slug": "womens-clothes",
"parent": "Clothes",
"children": [
{
"id": 8,
"name": "Women's Jeans",
"slug": "womens-jeans",
"parent": "Women's Clothes"
}
]
}
]
},
{
"id": 1,
"name": "Technology",
"slug": "technology",
"parent": null,
"children": [
{
"id": 3,
"name": "Laptop",
"slug": "laptop",
"parent": "Technology"
},
{
"id": 2,
"name": "Mobile Phone",
"slug": "mobile-phone",
"parent": "Technology"
}
]
}
]
}

DRF: how to join many-to-many field into a string in json

models.py
class SalesPerson(models.Model):
name = models.CharField(max_length=50)
office = models.ForeignKey(Location, on_delete=models.SET_NULL, null=True)
class Project(models.Model):
title = models.CharField(max_length=50)
leader = models.ForeignKey(SalesPerson, on_delete=models.SET_NULL, null=True,related_name='leader')
location = models.ForeignKey(Location, on_delete=models.SET_NULL, null=True)
product = models.ManyToManyField(Product)
sales_person = models.ManyToManyField(SalesPerson)
serializers.py
class ProjectSerializer(serializers.ModelSerializer):
leader_name= serializers.ReadOnlyField(source='leader.name')
location_name= serializers.ReadOnlyField(source='location.name')
product = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
sales_person = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
class Meta:
model = Project
fields = ('id', 'title', 'leader_name', 'location_name', 'product', 'sales_person')
class SPSerializer(serializers.ModelSerializer):
projects = ProjectSerializer(many=True, read_only=True, source='project_set')
office_name= serializers.ReadOnlyField(source='office.city')
class Meta:
model = SalesPerson
fields = ('id', 'name', 'office_name', 'email', 'projects')
result:
{
"id": 2,
"name": "Angela",
"office_name": "NSW Sydney",
"email": "angela#angela.com",
"projects": [
{
"id": 1,
"title": "Mall Orchid",
"leader_name": "Boris",
"product": [
"Split wall mounted"
],
"sales_person": [
"Angela",
"Boris",
"David"
],
},
{
"id": 6,
"title": "Mall Petunia",
"leader_name": "Boris",
"product": [
"Split duct"
],
"sales_person": [
"Angela",
"Boris",
"David"
],
},
]
},
I am going to consume the json using react native
I know how to iterate over "projects"
However, I want to avoid iterating over "sales_person" to make rendering the array simpler
So I am sure I have to make the sales_person into a string but after googling for many hours today, I can't find an answer
I am hoping to do this in Django Rest Framework and not in Expo React Native, if possible
So in short, I want to make the result like this:
{
"id": 2,
"name": "Angela",
"office_name": "NSW Sydney",
"email": "angela#angela.com",
"projects": [
{
"id": 1,
"title": "Mall Orchid",
"leader_name": "Boris",
"product": [
"Split wall mounted"
],
"sales_person": "Angela", "Boris", "David",
},
{
"id": 6,
"title": "Mall Petunia",
"leader_name": "Boris",
"product": [
"Split duct"
],
"sales_person": "Angela", "Boris", "David",
},
]
},
Thank you so much for your help.
I've found the answer
I am writing it for my own future reference
class ProjectSerializer(serializers.ModelSerializer):
leader_name= serializers.ReadOnlyField(source='leader.name')
location_name= serializers.ReadOnlyField(source='location.city')
product = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
sales_person = serializers.SerializerMethodField('get_sales_person')
class Meta:
model = Project
fields = ('id', 'title', 'leader_name', 'location_name', 'product', 'sales_person')
def get_sales_person(self, obj):
return ', '.join([sales_person.name for sales_person in obj.sales_person.all()])

Karate DSL : How to check if the JSON object is optional, but if that object comes the keys inside that object should be of correct schema?

I want to validate a JSON schema where an object in a nested JSON response is optional but if the object comes the keys in that object should also come.
Sample Response :-
{
"id" : "1234",
"targetPhoneNumber" : "1234",
"paid" : { //optional
"units" : "asd", //mandatory if the paid object is coming
"amount" : 12.00 //mandatory if the paid object is coming
},
"date" : "2019",
"boolean" : false,
"transactionId" : "1234"
}
I need schema checks for these cases.
1) If the paid object is coming it should be a JSON object and should contains units as string and amount as mandatory.
2) If the paid object is not coming still the schema validation should pass.
Combining 'self' validation expressions and karate.match(actual, expected) API gives some way to achieve this,
This should work,
* def schema =
"""
{
"boolean": "#boolean",
"date": "#string",
"id": "#string",
"paid": "##object? karate.match(_,{\"amount\": \"#number\",\"units\": \"#string\"}).pass",
"targetPhoneNumber": "#string",
"transactionId": "#string"
}
"""
If you don't want to add your subSchema inline / if your subschema is too large you can try this
* def passSchema =
"""
{
"amount": "#number",
"units": "#string"
}
"""
* def schema =
"""
{
"boolean": "#boolean",
"date": "#string",
"id": "#string",
"paid": "##object? karate.match(_,passSchema).pass",
"targetPhoneNumber": "#string",
"transactionId": "#string"
}
"""
Simple JSON schema:
{
"boolean": "#boolean",
"date": "#string",
"id": "#string",
"paid": "##object? karate.match(_,{\"amount\": \"#number\",\"units\": \"#string\"}).pass",
"targetPhoneNumber": "#string",
"transactionId": "#string"
}
It's possible to do this with conditionals:
* def outerMatcher =
"""
{
"id" : "#string",
"targetPhoneNumber" : "#string",
"paid" : "##object",
"date" : "#string",
"boolean" : "#boolean",
"transactionId" : "#string"
}
"""
* def paidMatcher =
"""
{
"units": "#string",
"amount": "#number"
}
"""
* def innerMatcher = karate.match(response.paid, "#object").pass ? paidMatcher : "#notpresent"
* match response == outerMatcher
* match response.paid == innerMatcher