How can i merge equal fields and append different values to the fields in the returned response into one, independent of the amount of objects? When accessing the enpoint, i currently get the following response:
[
{
"colors": [
"Red",
"Orange",
],
"styles": [
"Rock"
],
"application": [
"Wall"
],
"material": [
"Mosaic"
]
},
{
"colors": [
"Yellow",
],
"styles": [
"Mosaic"
],
"application": [
"Wall"
],
"material": [
"Ceramic"
]
}
]
While want to achieve something like the snippet bellow, where unique values are appended and equal fields are merged:
[
{
"colors": [
"Red",
"Orange",
"Yellow"
],
"styles": [
"Rock"
"Mosaic"
],
"application": [
"Wall"
],
"material": [
"Mosaic"
"Ceramic"
]
},
]
My serializer is structured like this:
class ProductFiltersByCategorySerializer(serializers.ModelSerializer):
"""
A serializer to display available filters for a product lust
"""
colors = serializers.StringRelatedField(read_only=True, many=True)
styles = serializers.StringRelatedField(read_only=True, many=True)
application = serializers.StringRelatedField(read_only=True, many=True)
material = serializers.StringRelatedField(read_only=True, many=True)
class Meta:
model = Product
fields = (
'colors',
'styles',
'application',
'material'
)
My viewset is structured like this:
class ProductFiltersByCategory(generics.ListAPIView):
"""
This viewset takes the category parameter from the url and returns related product filters
"""
serializer_class = ProductFiltersByCategorySerializer
def get_queryset(self):
category = self.kwargs['category']
return Product.objects.filter(category__parent__name__iexact=category).distinct()
The fields colors, styles, application and material are ManytoMany relations to their own models from the Product model.5
Update 1: (Models)
class ProductSize(models.Model):
...
class ProductColor(models.Model):
...
class ProductStyle(models.Model):
...
class ProductApplication(models.Model):
...
class ProductMaterial(models.Model):
...
class Product(models.Model):
...
colors = models.ManyToManyField(
ProductColor,
related_name='product_color'
)
styles = models.ManyToManyField(
ProductStyle,
related_name='product_style'
)
application = models.ManyToManyField(
ProductApplication,
related_name='product_application'
)
material = models.ManyToManyField(
ProductMaterial,
related_name='product_material'
)
absorption = models.FloatField(
null=True,
blank=True
)
...
This is because your query returns two objects and each object has some related field. Serializers serialize each object separately so you should merge fields in your view that is responsible for the logic of the application, for this if your database is postgres you can use some code like this:
from django.contrib.postgres.aggregates import ArrayAgg
Product.objects.filter(category__parent__name__iexact=category).distinct().aggregate(colors_field=ArrayAgg('colors__name'))
or you can write separate query for each field.
Related
I have a Bookmark and a BookmarkCategory object. I'd like to be able to fetch JSON that looks like this:
GET -> localhost:8000/api/bookmarks
[
"python": {
"title": "Python",
"bookmarks": [
{
"title": "Python Documentation",
"url": "https://docs.python.org"
}
]
},
"javascript": {
"title": "Javascript",
"bookmarks": [
{
"title": "Python Documentation",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript"
}
]
}
]
Here's my models:
class BookmarkCategory(models.Model):
title = models.CharField(max_length=255)
class Bookmark(models.Model):
title = models.CharField(max_length=255)
url = models.CharField(max_length=255)
category = models.ManyToManyField(BookmarkCategory)
Here's how I would query all the BookmarkCategory objects:
from .models import BookmarkCategory
bookmarks = BookmarkCategory.objects.all()
The JSON doesn't have to look exactly like this. I just need to get all my BookmarkCategory objects along with all the related Bookmark objects so I can iterate over them after I make a GET request to fetch them.
You'll have to use select_related on "bookmark_set", to fetch them along. If you are using DRF, you need to create a nested serializer configuration for bookmarks. Here is an example without DRF:
categories = BookmarkCategory.objects.select_related("bookmark_set")
items = []
for category in categories:
items.append(
{
"title": category.title,
"bookmarks": category.bookmark_set.values_list("title", "url"),
}
)
I'm trying to save m2m relation in Product model on Django Oscar Commerce package, the model has an many to many relation through the ProductAttributeValues model.
Making Postan POST method query to url 127.0.0.1:8000/api/products/1/update/
serializers.py:
class ProductAttributeValueSerializer(OscarModelSerializer):
class Meta:
model = ProductAttributeValue
fields = '__all__'
class ProductSerializer(OscarModelSerializer):
attribute_valuess = ProductAttributeValueSerializer(many=True, read_only=False)
class Meta:
model = catalogue_models.Product
fields = '__all__'
The json content:
{
'title': 'Producto 4',
'is_public': True,
'description': 'descripciĆ³n',
'attribute_values': [
{'value_text': 'contenido', 'attribute': 1, 'product': 1},
{'value_text': 'contenido', 'attribute': 2, 'product': 1}
]
}
The postman return this json:
{
"attribute_values": [
{
"non_field_errors": [
"The fields attribute, product must make a unique set."
]
},
{
"non_field_errors": [
"The fields attribute, product must make a unique set."
]
}
]
}
Anybody could help me please ?
Thanks in advance.
I already have a general idea of how it should be done. The only issue that I face now is how to actually send the data. I don't want to create new Projects I just want to add them to the notifications. How do I pass the data, the actual JSON?
class NotificationsScheduleSerializer(ModelSerializer):
projects = ProjectSerializer(many=True) # Thats the Many2Many Field
user = HiddenField(default=CurrentUserDefault())
class Meta:
model = NotificationsSchedule
fields = [
"pk",
"projects",
"period",
"week_day",
"created_at",
"time",
"report_type",
"user",
]
def create(self, validated_data):
breakpoint() # I don't ever get "projects" in validated_data just Empty OrderedDict
projects_data = validated_data.pop("projects", [])
notification = NotificationsSchedule.objects.create(**validated_data)
return notification
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ["pk", "name"]
I want to be able to pass something like this.
{
"projects": [290, 289],
"period": "daily",
"week_day": 2,
"time": "16:02:00",
"report_type": "word_report"
}
But it expects dict instead.
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
You have to set read_only,
projects = ProjectSerializer(many=True, read_only=True)
And when creating Notifications ,
notification = NotificationsSchedule.objects.create(**validated_data)
notification.projects.add(*self.initial_data.get("projects"))
notification.save()
I'm trying to optimize the queries for my moderation system, build with Django and DRF.
I'm currently stuck with the duplicates retrieval: currently, I have something like
class AdminSerializer(ModelSerializer):
duplicates = SerializerMethodField()
def get_duplicates(self, item):
if item.allowed:
qs = []
else:
qs = Item.objects.filter(
allowed=True,
related_stuff__language=item.related_stuff.language
).annotate(
similarity=TrigramSimilarity('name', item.name)
).filter(similarity__gt=0.2).order_by('-similarity')[:10]
return AdminMinimalSerializer(qs, many=True).data
which works fine, but does at least one additional query for each item to display. In addition, if there are duplicates, I'll do additional queries to fill the AdminMinimalSerializer, which contains fields and related objects of the duplicated item. I can probably reduce the overhead by using a prefetch_related inside the serializer, but that doesn't prevent me from making several queries per item (assuming I have only one related item to prefetch in AdminMinimalSerializer, I'd still have ~2N + 1 queries: 1 for the items, N for the duplicates, N for the related items of the duplicates).
I've already looked at Subquery, but I can't retrieve an object, only an id, and this is not enough in my case. I tried to use it in both a Prefetch object and a .annotate.
I also tried something like Item.filter(allowed=False).prefetch(Prefetch("related_stuff__language__related_stuff_set__items", queryset=Items.filter..., to_attr="duplicates")), but the duplicates property is added to "related_stuff__language__related_stuff_set", so I can't really use it...
I'll welcome any idea ;)
Edit: the real code lives here. Toy example below:
# models.py
from django.db.models import Model, CharField, ForeignKey, CASCADE, BooleanField
class Book(Model):
title = CharField(max_length=250)
serie = ForeignKey(Serie, on_delete=CASCADE, related_name="books")
allowed = BooleanField(default=False)
class Serie(Model):
title = CharField(max_length=250)
language = ForeignKey(Language, on_delete=CASCADE, related_name="series")
class Language(Model):
name = CharField(max_length=100)
# serializers.py
from django.contrib.postgres.search import TrigramSimilarity
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from .models import Book, Language, Serie
class BookAdminSerializer(ModelSerializer):
class Meta:
model = Book
fields = ("id", "title", "serie", "duplicates", )
serie = SerieAdminAuxSerializer()
duplicates = SerializerMethodField()
def get_duplicates(self, book):
"""Retrieve duplicates for book"""
if book.allowed:
qs = []
else:
qs = (
Book.objects.filter(
allowed=True, serie__language=book.serie.language)
.annotate(similarity=TrigramSimilarity("title", book.title))
.filter(similarity__gt=0.2)
.order_by("-similarity")[:10]
)
return BookAdminMinimalSerializer(qs, many=True).data
class BookAdminMinimalSerializer(ModelSerializer):
class Meta:
model = Book
fields = ("id", "title", "serie")
serie = SerieAdminAuxSerializer()
class SerieAdminAuxSerializer(ModelSerializer):
class Meta:
model = Serie
fields = ("id", "language", "title")
language = LanguageSerializer()
class LanguageSerializer(ModelSerializer):
class Meta:
model = Language
fields = ('id', 'name')
I'm trying to find a way to prefetch related objects and duplicates so that I can get rid of the get_duplicates method in BookSerializer, with the N+1 queries it causes, and have only a duplicates field in my BookSerializer.
Regarding data, here would be an expected output:
[
{
"id": 2,
"title": "test2",
"serie": {
"id": 2,
"language": {
"id": 1,
"name": "English"
},
"title": "series title"
},
"duplicates": [
{
"id": 1,
"title": "test",
"serie": {
"id": 1,
"language": {
"id": 1,
"name": "English"
},
"title": "first series title"
}
}
]
},
{
"id": 3,
"title": "random",
"serie": {
"id": 3,
"language": {
"id": 1,
"name": "English"
},
"title": "random series title"
},
"duplicates": []
}
]
Due to the way my database is designed, images are not stored with the project.
This is because there is no set amount of images per product. Some may have 1 image, others may have 10.
I would like my API to return content nested within itself. Currently, my code simply repeats the entire object when additional images exist for the item.
I am using Django Rest Framework:
class ProductDetailView(APIView):
renderer_classes = (JSONRenderer, )
def get(self, request, *args, **kwargs):
filters = {}
for key, value in request.GET.items():
key = key.lower()
if key in productdetailmatch:
lookup, val = productdetailmatch[key](value.lower())
filters[lookup] = val
qset = (
Product.objects
.filter(**filters)
.values('pk', 'brand')
.annotate(
image=F('variation__image__image'),
price=F('variation__price__price'),
name=F('variation__name'),
)
)
return Response(qset)
Currently, an item with 3 images pointing to it will look like this:
[{
"name": "Amplitiue jet black",
"brand": "Allup",
"price": "$1248",
"vari": "917439",
"image": "url1",
},
{
"name": "Amplitiue jet black",
"brand": "Allup",
"price": "$1248",
"vari": "917439",
"image": "url",
},
{
"name": "Amplitiue jet black",
"brand": "Allup",
"price": "$1248",
"vari": "917439",
"image": "url",
},
]
Ideally, it should look like this, combining all the images within an array:
{
"name": "Amplitiue jet black",
"brand": "Allup",
"price": "$1248",
"vari": "917439",
"images": [
"url1",
"url2"
"url3"
],
}
You should use a ListApiView together with a ModelSerializer. Don't put the filtering in the get method, the Django class based view way is to use get_queryset for that.
from rest_framework import serializers, generics
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ("url",)
class ProductSerializer(serializers.ModelSerializer):
images = ImageSerializer(many=True)
class Meta:
model = Product
fields = ("name", "brand", "price", "vari", "images")
class ProductListView(generics.ListAPIView): # it is not a details view
serializer_class = ProductSerializer
def get_queryset(self):
filters = {}
for key, value in self.request.GET.items():
key = key.lower()
if key in productdetailmatch:
lookup, val = productdetailmatch[key](value.lower())
filters[lookup] = val
return Product.objects.prefetch_related("images").filter(**filters)
The image list in the JSON will be objects with one "url" element instead of just a list of urls, but this is more consistent with REST standards anyway.