Need to post many items onetime -many titles- in a titles list from mobile app to the backend, the problem is when use create method within the for loop it gets no record in database & that Type Error -Direct assignment to the forward side of a many-to-many set is prohibited. Use container.set() instead.- when try to use the set() method it's not showing any right case!!!!, the problem is when to use create in order to made many create objects in many-to-many relationship. please guide.
models.py
class Container(models.Model):
name = models.CharField(max_length=200, blank=False)
capacity = models.IntegerField( null=False ,blank=False)
class Item(models.Model):
title = models.CharField(max_length=200, blank=False)
container = models.ManyToManyField(container)
serializers.py
class ContainerSerializer(serializers.ModelSerializer):
class Meta:
model = Container
fields = '__all__'
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'
Request
{
"container": {
"name": "container1",
"capacity": 10,
},
"Items":[
{"title":"tl1",},
{"title":"tl2",},
{"title":"tl3",}
]
}
view.py
#api_view(['POST'])
def additems(request):
data = request.data
container = Container.objects.create(
name = data['name']
capacity = data['capacity']
)
container.save()
for i in Items:
item = Item.objects.create(
container = container ,
title = i['title'] ,
)
item.save()
serializer = ContainerSerializer(container, many=False)
return response(serializer.data)
TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use container.set() instead.
Why getting this error ?!!! please guide ...
The error is occurring because you are attempting to create and assign a Container object to a ManyToManyField on the Item model using the create() method within a for loop. This will result in a new container being created for each iteration of the loop, which is not what you want. Additionally, when attempting to add Item objects to a many-to-many relationship with a Container object, you should use the add() method, not the set() method. You can refactor your view like this
#api_view(['POST'])
def additems(request):
data = request.data
# Create the container
container = Container.objects.create(
name=data['container']['name'],
capacity=data['container']['capacity']
)
# Create the items and add them to the container
items = data['Items']
for item_data in items:
item = Item.objects.create(title=item_data['title'])
container.item_set.add(item)
serializer = ContainerSerializer(container)
return Response(serializer.data)
In the ContainerSerializer, you need to specify the ItemSerializer as a nested serializer in order to properly serialize the Item objects that are associated with the Container through the many-to-many relationship.As shown below:
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True, read_only=True)
class Meta:
model = Container
fields = ['id', 'name', 'capacity', 'items']
Related
I have an model which is for mapping book(item) to categories(tag),
it shows like this in the django admin page.
id item_uid tag_uid
407 Food Recipe
but in django swagger page, when I try to GET this mapping api with ID 407, it returned like this:
"id": 407,
"item_uid": "http://127.0.0.1:8000/items/237/";
"tag_uid": "http://127.0.0.1:8000/tags/361/"
as you can see, it mapped together correctly, but the response body showed the object url and it's object id, which is not readable for human users. I wonder that if there is anyway to make them like this:
"id": 407,
"item_uid": "Food";
"tag_uid": "Recipe"
edit: codes,
#models.py
class Map_item_tag(models.Model):
item_uid = models.ForeignKey(items, on_delete=models.CASCADE, verbose_name='Item UID')
tag_uid = models.ForeignKey(tags, on_delete=models.CASCADE, verbose_name='Tag UID')
#admin.py
#admin.register(Map_item_tag)
class map_item_tag_admin(ImportExportModelAdmin):
resource_class = map_item_tag_Resource
readonly_fields = ('id',)
list_display = ['id','item_uid','tag_uid']
#serializers.py
class Map_item_tag_Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
#views.py
class Map_item_tag_ViewSet(viewsets.ModelViewSet):
queryset = Map_item_tag.objects.all().order_by('item_uid')
serializer_class = Map_item_tag_Serializer
parser_classes = (FormParser, MultiPartParser)
permission_classes = [permissions.IsAuthenticated]
thank you for answering!
It seems you are using a HyperlinkedModelSerializer instead of a regular ModelSerializer
Try changing the serializer class to a ModelSerializer:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [] # list of fields you want to include in your Item serializer
class Map_item_tag_Serializer(serializers.ModelSerializer):
item_uid = ItemSerializer()
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
In addition, I would advise you to use CamelCase notation for all your classes. For example: instead of using Map_item_tag_Serializer, change the name to MapItemTagSerializer. The same goes for all your other classes.
I would also avoid using using the _uuid suffix when using ForeignKey relationships. In the MapItemTag model, the ForeignKey relationship inherently means that the field will point to an object Item of Tag object. Hence, no need to specify the _uuid part again.
For example, the following changes would make the model a lot more readable:
class MapItemTag(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, verbose_name='map_item')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, verbose_name='map_tag')
Many time we access data via serializer directory according to relationship defined in models in Django(1.11.10). How can i set a filter like fetch-only is_active=1.
class DaasJobsSerializer(serializers.ModelSerializer):
class Meta:
model = DaasJobs
fields = '__all__'
class DaasScheduleSerializer(serializers.ModelSerializer):
jobs = DaasJobsSerializer(read_only=True,many=True)
class Meta:
model = DaasSchedule
fields = '__all__'
Here i just want to set a filter to fetch only those Jobs which db field is_active=1 in this line like that DaasJobsSerializer(read_only=True,many=True, filter={"is_active":1}) how to do something like this ??
Currently it is giving me all the data without checking is_active,
and i dont want to create serializerMethodField for that.. because all methods written earlier.. i am just setting a is_active field later in the tables in db.
If you want to do it via serializers you can try overriding the ListSerializer and passing it as a custom list_serializer_class.
class IsActiveListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(is_active=1)
return super().to_representation(data)
In your serializer:
class DaasJobsSerializer(serializers.ModelSerializer):
class Meta:
model = DaasJobs
fields = '__all__'
list_serializer_class = IsActiveListSerializer # import it here
Of course this is a specific use-case, you could make a more generalized version of the ListSerializer to:
class FilteredListSerializer(serializers.ListSerializer):
filter_kwargs = {}
def to_representation(self, data):
if not self.filter_kwargs or not isinstance(self.filter_kwargs, dict):
raise TypeError(_('Invalid Attribute Type: `filter_kwargs` must be a of type `dict`.'))
data = data.filter(**self.filter_kwargs)
return super().to_representation(data)
And then you could sub-class that to make other specific ListSerializers such as:
class IsActiveListSerializer(FilteredListSerializer):
filter_kwargs = {'is_active': 1}
and many others...
I have a very simple ( with a first look) problem. Case - A product can be sold in a several places(shops), and every product can be represented in a single shop with a different categories and sub categories ( That is why category linked via ForeignKey with Assortment twice).
So here is My Assortment model, with several FKs.
class Assortment(models.Model):
category = models.ForeignKey('category.Category', null=True, blank=True, default=None,related_name='assortment_child')
parent_category = models.ForeignKey('category.Category', null=True, blank=True, default=None,related_name='assortment_parent')
product = models.ForeignKey(Product)
shop = models.ForeignKey(Shop)
View, based on rest_framework.generics.ListAPIView
class InstitutionTreeCategories(generics.ListAPIView):
"""Resource to get shop's tree of categories."""
serializer_class = serializers.InstitutionCategoriesSerializer
def get_queryset(self):
shop = self.get_shop()
return Category.objects.filter(assortment_parent__shop=shop).distinct()
And finally, serializers
class CategoryListSerializer(serializers.ModelSerializer):
class Meta:
"""Meta class."""
model = Category
fields = ('id', 'name', 'image')
class CategoriesTreeSerializer(CategoryListSerializer):
# childs = CategoryListSerializer(many=True, source='assortment_child__parent_category')
childs = serializers.SerializerMethodField()
class Meta(CategoryListSerializer.Meta):
"""Meta class."""
fields = ('id', 'name', 'image', 'childs')
def get_childs(self, obj):
qs = Category.objects.filter(assortment_child__parent_category=obj.id).distinct()
return CategoryListSerializer(qs, many=True, context=self.context).data
And i need to show Category Tree for a one single shop with my API.
But the problem is - If I use serializer.SerializerMethodField - it works, but too many queries (for every parent category). I tried to avoid it using 'source' option with my 'CategoryListSerializer' by I can't make it. Every time, I get - 'Category' object has no attribute assortment_child__parent_category. In a shell model i've tried
In [8]: cat.assortment_parent.values('category').distinct()
Out[8]: (0.003) SELECT DISTINCT "marketplace_assortment"."category_id" FROM "marketplace_assortment" WHERE "marketplace_assortment"."parent_category_id" = 4 LIMIT 21; args=(4,)
<AssortmentQuerySet [{'category': 3}]>
So - category object has this attributes, of course it does, i used it a get_childs method. So question is - how i can use it with serializer.ModelSerializer and it's source option? ( Of course using select_related method with queryset, to avoid excess queries).
by source option you should use . in instead of __:
childs = CategoryListSerializer(many=True, source='assortment_child.parent_category')
but still you will has many queries, to fix it you should use prefetch-related
def get_queryset(self):
shop = self.get_shop()
qs = Category.objects.filter(assortment_parent__shop=shop).all()
return qs.prefetch_related('assortment_child').distinct()
more detail you can read in the how-can-i-optimize-queries-django-rest-framework
I had the similar problem and the best solution I have found is to do some manual processing in order to receive desired tree representation.
So firstly we fetch all Assortment for shop and then build the tree manually.
Let's look at the example.
def get_categories_tree(assortments, context):
assortments = assortments.select_related('category', 'parent_category')
parent_categories_dict = OrderedDict()
for assortment in assortments:
parent = assortment.parent_category
# Each parent category will appear in parent_categories_dict only once
# and it will accumulate list of child categories
if parent not in parent_categories_dict:
parent_data = CategoryListSerializer(instance=parent, context=context).data
parent_categories_dict[parent] = parent_data
parent_categories_dict[parent]['childs'] = []
child = assortment.category
child_data = CategoryListSerializer(instance=child, context=context).data
parent_categories_dict[parent]['childs'].append(child_data)
# convert to list as we don't need the keys already - they were used only for matching
parent_categories_list = list(parent_categories_dict.values())
return parent_categories_list
class InstitutionTreeCategories(generics.ListAPIView):
def list(self, request, *args, **kwargs):
shop = self.get_shop()
assortments = Assortment.objects.filter(shop=shop)
context = self.get_serializer_context()
categories_tree = get_categories_tree(assortments, context)
return Response(categories_tree)
All in single DB query.
The problem here is that there is no explicit relation between category and parent_category. If you define ManyToManyField in Category using Assortment as through intermediate model, you will get an access which Django can understand, so you would just use attribute childs on Category for example. However this will still return all children (the same would happen if your source example works) categories, ignoring shop, so some clever Prefetch would have to be done to achieve correct results. But I believe manual "join" is simpler.
you need to use prefetch_related along with serializer method field
serializer:
class CategoriesTreeSerializer(CategoryListSerializer):
children = serializers.SerializerMethodField()
class Meta(CategoryListSerializer.Meta):
fields = (
'id',
'name',
'image',
'children'
)
def get_children(self, obj):
children = set()
for assortment in obj.assortment_parent.all():
children.add(assortment.category)
serializer = CategoryListSerializer(list(children), many=True)
return serializer.data
your get queryset method:
def get_queryset(self):
shop = self.get_shop()
return (Category.objects.filter(assortment_parent__shop=shop)
.prefetch_related(Prefetch('assortment_parent', queryset=Assortment.objects.all().select_related('category')
.distinct())
TL;DR: What could be the reason the incoming data for one of my serializers does not get processed?
I'm working on a serializer for a nested relationship. The serializer should get a list of UUIDs, so that I can make many to many relationships. Here is the model:
class Order(
UniversallyUniqueIdentifiable,
SoftDeletableModel,
TimeStampedModel,
models.Model
):
menu_item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
custom_choice_items = models.ManyToManyField(CustomChoiceItem, blank=True)
price = models.ForeignKey(MenuItemPrice, on_delete=models.CASCADE)
amount = models.PositiveSmallIntegerField(
validators=[MinValueValidator(MINIMUM_ORDER_AMOUNT)]
)
Here is the data (my post body) with which I hit the route in my tests:
data = {
"checkin_uuid": self.checkin.uuid,
"custom_choice_items": [],
"menu_item": self.menu_item.uuid,
"price": self.menu_item_price.uuid,
"amount": ORDER_AMOUNT,
}
response = self.client.post(self.order_consumer_create_url, self.data)
Note that the empty list for custom_choice_items does not change anything. Even if I fill it with values the same error occurs. And last but not least here are the serializers:
class CustomChoiceItemUUIDSerializer(serializers.ModelSerializer):
"""Serializer just for the uuids, which is used when creating orders."""
class Meta:
model = CustomChoiceItem
fields = ["uuid"]
....
# The serializer that does not work
class OrderSerializer(serializers.ModelSerializer):
menu_item = serializers.UUIDField(source="menu_item.uuid")
custom_choice_items = CustomChoiceItemUUIDSerializer()
price = serializers.UUIDField(source="price.uuid")
wish = serializers.CharField(required=False)
class Meta:
model = Order
fields = [
"uuid",
"menu_item",
"custom_choice_items",
"price",
"amount",
"wish",
]
The problem is now, that when I leave out many=True, I get the error:
{'custom_choice_items': [ErrorDetail(string='This field is required.', code='required')]}
And If I set many=True I just simply don't get any data. By that I mean e.g. the value of validated_data["custom_choice_items"] in the serializers create() method is just empty.
What goes wrong here?
I even checked that the data is in the request self.context["request"].data includes a key custom_choice_items the way I pass the data to this view!
EDIT: Here is the data I pass to custom_choice_items:
data = {
“checkin_uuid”: self.checkin.uuid,
“custom_choice_items”: [{“uuid”: custom_choice_item.uuid}],
“menu_item”: self.menu_item.uuid,
“price”: self.menu_item_price.uuid,
“amount”: ORDER_AMOUNT,
}
self.client.credentials(HTTP_AUTHORIZATION=“Token ” + self.token.key)
response = self.client.post(self.order_consumer_create_url, data)
When you post data using the test api client, if the data contains nested structure you should use format=json, like this:
response = self.client.post(self.order_consumer_create_url, data, format='json')
Did you override .create method in the serializer? Something like this should work:
from django.db import transaction
class OrderSerializer(serializers.ModelSerializer):
# your fields and Meta class here
#transaction.atomic
def create(self, validated_data):
custom_choice_items = validated_data.pop('custom_choice_items')
order = super().create(validated_data)
order.custom_choice_items.add(*custom_choice_items)
return order
By the way you don't really need to define CustomChoiceItemUUIDSerializer if is just the primary key of that.
I am attempting to fetch nested objects but not quite sure how to achieve this. My model is as shown:
class Brand(models.Model)
name = models.CharField(max_length=128)
class Size(models.Model)
name = models.CharField(max_length=128)
class Brand_Size(models.Model)
brand = models.ForeignKey(Brand)
size = models.ForeignKey(Size)
class Brand_Size_Location(models.Model)
location = models.ForeignKey(Location)
brand_size = models.ForeignKey(Brand_Size)
I filter objects in Brand_Size_Location by location which can occur 1..x. I want my serializer to output the results in terms of the model Brand (BrandSerializer). Since my resultset can be 1..x and furthermore the occurrence of Brand can be duplicates i would like to eliminate these aswell at the same time.
You should be able to do this fairly easily by including a serializer field in your BrandSerializer:
class BrandSerializer(serializers.ModelSerializer):
brand_sizes = BrandSizeSerializer(
source='brand_size_set',
read_only=True
)
class Meta:
model = Brand
fields = (
'id',
...add more fields you want here
'brand_sizes')
You can simlarly create the brand size serializer to nest the locations
Filtering on this relationship occurs in the view and will need a custom filter. Here's a basic example using a ListView:
class BrandFilter(django_filters.FilterSet):
location = django_filters.ModelMultipleChoiceFilter(
queryset=Brand_Size_Location.objects.all(),
name='brand_size__brand_size_location__location'
)
location_name = django_filters.CharFilter(
name='brand_size__brand_size_location__location__name'
)
class Meta:
model = Brand
fields = [
'location',
'location_name'
]
class BrandList(LoginRequiredMixin, generics.ListCreateAPIView):
model = Brand
serializer_class = BrandSerializer
filter_class = BrandFilter
You can then use query parameters to filter on the URL like:
http://somehost/api/brands/?location=42
which uses a PK to filter, or with the location name (assuming you have a name field in the Location model):
http://somehost/api/brands/?location_name=Atlantis