Filtering many2many django rest framework - django

I have an entity model, and i want to list all the instances of Entity based on the id of a category instance in it. So basically i'd like to list all the Entity instances with category id of 1 with /specialists/category/1 but for some reason i get
Not Found: /api/v1/specialists/categories/1
Even though the code looks alright.
class Entity(models.Model):
entity_category = models.ManyToManyField(Category)
class SpecialistSerializer(serializers.ModelSerializer):
entity_category = SpecialistCategorySerializer(read_only=True, many=True)
class Meta:
model = Entity
fields = (....., entity_category)
class SpecialistsPerCategory(generics.ListAPIView):
serializer_class = SpecialistSerializer
def get_queryset(self):
category_id = self.kwargs['pk']
return Entity.objects.filter(entity_category=category_id, lookup_type='in')
path('specialists/category/<int:pk>', SpecialistsPerCategory.as_view()),
Do you have any idea what's wrong?

You have category in path, but plural categories in url.
Also I think that will not work, because SpecialistSerializer have model of Entity, but you want to get pk of Category in SpecialistsPerCategory that is based on that serializer.

Related

Accessing parent model instance within model admin to provide custom queryset

I want to provide a custom queryset within a model admin class that inherits from TabluarInline, but I want to provide this queryset by calling a method of current instance of the model object.
I have two models. One for tracks belonging to an album, and one for the Album itself. Some tracks can be hidden and I have a method in Album to return only the visible tracks.
class Track(models.Model):
name = models.CharField()
length = models.IntegerField()
album = ForeignKey(Album)
hidden = BooleanField()
class Album(models.Model):
name = models.CharField()
def get_visible_tracks_queryset(self):
return self.track_set.filter(hidden=False)
And I have a tracks inline admin which is included on the django admin page for an album. I want to re-use the get_visible_tracks_queryset to define the queryset for this inline admin, I don't want to repeat the logic again. I can't figure out how to do it. I could do something like the following, however I'm using a simplified example here, I actually have more complex logic and I don't want to be repeating the logic in multiple places.
class TracksInlineAdmin(admin.TabularInline):
fields = ("name", "length")
model = Track
def get_queryset(self, request):
qs = super(TracksInlineAdmin, self).get_queryset(request)
return qs.filter(hidden=False)
Ideally I could do something like:
class TracksInlineAdmin(admin.TabularInline):
fields = ("name", "length")
model = Track
def get_queryset(self, request, parent_model_instance):
return parent_model_instance.get_visible_tracks_queryset()
Any thoughts on how to achieve this?
The cleanest way is to define a custom QuerySet class for your model in which you can define any complex filters for re-use in various places:
class Track(models.Model):
# fields defined here
objects = TrackManager()
class TrackManager(models.Manager):
def get_queryset(self):
return TrackQuerySet(self.model, using=self._db)
class TrackQuerySet(models.QuerySet):
def visible(self):
return self.filter(hidden=False)
Now, anywhere in code, when you have a queryset of tracks (e.g. Track.objects.filter(name="my movie")) you can add .visible() to filter further. Also on a related set:
album.track_set.all().visible()

Django's prefetch_related and select_related on more complex relationships in Admin

I have a somewhat complex relationship between multiple models. A simplified example:
class Country(models.Model):
name = models.CharField([...])
[...]
def __ str__(self):
return f'{self.name}'
class Region(models.Model):
country = models.ForeignKey(Country)
name = models.CharField([...])
[...]
def __ str__(self):
return f'{self.name}'
class CityManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('region', 'region__country')
class City(models.Model):
name = models.CharField([...])
region = models.ForeignKey(Region)
objects = CityManager()
def __str__(self):
return f'{self.region.country} - {self.region} - {self.name}'
Hence when I want to display some kind of list of cities (e.g. list all cities in Germany), I have to use select_related to be even remotely efficient otherwise I query for Country each time the __str__ is called. This is not the problem.
The problem is that when I have unrelated group of models and I want to FK to City, such as:
class Tour(models.Model):
[...]
class TourItem(models.Model):
tour = models.ForeignKey(Tour)
city = models.ForeignKey(City)
[...]
Tour would represent a planned tour for some music band; and TourItem would be a specific tour in a given city. I have a simple admin interface for this, so that TourItem is an inline field for the Tour (ie. so multiple tour items can be edited/added simultaneously). The problem is that now there are multiple queries firing for same Country when looking up the City FK and I'm not sure how to solve it. I tried what follows, but it did not work as expected:
class TourManager(models.Manager):
def get_queryset(self):
return super().get_queryset().prefetch_related('touritem_set__city', 'touritem_set__city__region', 'touritem_set__city__region__country')
And neither did this work:
class TourItemManager(models.Manager):
def get_queryset(self):
return super().get_queryset().select_related('city', 'city__region', 'city__region__country')
How can I adjust the managers/models so that when I load Tour's admin there will not be additional queries fired for Country?
You can use ModelAdmin select_related to select related tables
list_select_related = ('author', 'category')
If that is not fully helpful and you still want to do override try with following in your custom Manager
def get_queryset(self, request):
return super(TourItemManager,self).queryset(request).select_related('city', 'city__region', 'city__region__country')

How can I create model instance via Serializer without creating models from nested serialziers?

I have model with many links into it:
class Travel(BaseAbstractModel):
tags = models.ManyToManyField(
Tag,
related_name='travels',
)
owner = models.ForeignKey(
'users.TravelUser',
related_name='travel_owner'
)
payment = models.ForeignKey(
Payment,
related_name='travels',
)
country = models.ForeignKey(
Country,
related_name='travels,
)
........
Many of these models have only two fields with unique name and image.
I create serializer for each of these models and put them in TravelSerializer
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
Based on docs I override create() and update.
The problem is, when I sent JSON data, Django create each model from nested serializers. But I want to create only Travel instance. Also I want receive and respond serialized object not only pk field.
UPDATE
I solved this problem, put code in the answer. Now I can receive and respond with Serializer data without creating object.
But I think the DRF provides more elegant approach then I do. It is my first project with DRF, maybe I miss something and there's an easier solution.
I decide override to_internal_value() put it in custom serailizer and inherit all nested serializers from it:
class NestedRelatedSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
try:
pk = data['pk']
except (TypeError, KeyError):
# parse pk from request JSON
raise serializers.ValidationError({'_error': 'object must provide pk!'})
return pk
Get all pk from it and save in create and updated methods:
def update(self, instance, validated_data):
# If don't get instance from db, m2m field won't update immediately
# I don't understand why
instance = Travel.objects.get(pk=instance.pk)
instance.payment_id = validated_data.get('payment', instance.payment_id)
instance.country_id = validated_data.get('country', instance.country_id)
# update m2m links
instance.tags.clear()
instance.tags.add(*validated_data.get('tags'))
instance.save()
return instance
I'm not exactly sure I understand what you want to do, but could setting read_only_fields is the Meta class be what you need ?
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
class Meta:
read_only_fields = ('tags',)
See this section in the docs.

Denormalizing models in tastypie

What i'm trying to do is to add a query result from a model to a modelresource, as you can see in this block of code:
def dehydrate(self, bundle):
bundle.data['image'] = place_image.image.get(place=1).get(cardinality=0)
I want to add a field to PlaceResource that will contain the image from place_site model where place=1 and cardinality=0. But im recieving an error:
The 'image' attribute can only be accessed from place_image instances
So, my question is: Is it impossible to use the query result from another model in a tastypie modelresource? Im sorry for my bad english, please correct me if something's wrong. Thanks for your time.
There's the complete code:
MODELS.py:
class place(models.Model):
idPlace = models.AutoField(primary_key=True)
Name = models.CharField(max_length=70)
class place_image(models.Model):
idImage = models.AutoField(primary_key=True)
place = models.ForeignKey(place,
to_field='idPlace')
image = ThumbnailerImageField(upload_to="place_images/", blank=True)
cardinality = models.IntegerField()
API.py
from models import place
from models import place_image
class PlaceResource(ModelResource):
class Meta:
queryset = place.objects.all()
resource_name = 'place'
filtering = {"name": ALL}
allowed_methods = ['get']
def dehydrate(self, bundle):
bundle.data['image'] = place_image.image.get(place=1).get(cardinality=0)
return bundle
class PlaceImageResource(ModelResource):
place = fields.ForeignKey(PlaceResource, 'place')
class Meta:
queryset = place_image.objects.all()
resource_name = 'placeimage'
filtering = {"place": ALL_WITH_RELATIONS}
allowed_methods = ['get']
The error you are getting is caused by the fact that you are accessing the image attribute of a model class, not instance.
The object that is being dehydrated in the dehydrate method is stored in obj attribute of the bundle parameter. Also, you are trying to filter place_image models to only those with place=1 and cardinality=0 by accessing the image attribute of place_image model class. Such filtering won't work as image is not a ModelManager instance. You should use objects attribute instead. Furthermore, get() method returns an actual model instance thus a subsequent call to get() will raise AtributeError as your place_image model instances have no attribute get.
So, all in all, your dehydrate should look like this:
def dehydrate(self, bundle):
bundle.data['image'] = place_image.objects.get(place_id=1, cardinality=0).image
return bundle
Notice that this code requires the place_image with desired values to exist, otherwise a place_image.DoesNotExist will be thrown.
There is also some redundancy in your models:
idPlace and idImage can be removed, as django by default creates an AutoField that is a primary key called id when no other primary key fields are defined
place_image.place field has a redundant to_field parameter, as by default ForeignKey points to a primary key field

django rest nested relation in post/put

I am new in django rest api developement. I have two models one is category and another is subcategories.
Here is my models
class Category(models.Model):
title = models.Charfield()
brief = models.TextField()
subcategories = model.ManyToManyField('Subcategory', blank=True)
My serializer class
class CategorySerializer(serializers.ModelSerializer):
title= serializer.Charfield()
subcategories = Relatedfield(many=True)
Now in view
def post(self, request, format = None):
data=request.DATA
serialize= CategorySerializer(data=request.DATA)
if serializer.valid():
serializer.save()
How to save nested data like {'title':"test",'subscategories':[{'description':'bla bla bla'},{'description':'test test'}]} in post method.
I have read this in documentation
Note: Nested serializers are only suitable for read-only
representations, as there are cases where they would have ambiguous or
non-obvious behavior if used when updating instances. For read-write
representations you should always use a flat representation, by using
one of the RelatedField subclasses.
Please let me suggest which is right way or solution to do nested relation post/put in django rest.
Have you tried creating a SubCategorySerializer and adding this as a field on CategorySerializer?
class SubcategorySerializer(serializers.ModelSerializer):
class Meta:
model = Subcategory
class CategorySerializer(serializers.ModelSerializer):
subcategories = SubcategorySerializer(many=True)
Docs: http://django-rest-framework.org/api-guide/relations.html#nested-relationships