Django Rest Framework - image url on reverse foreignkey - django

I am trying to access the url of an ImageField on a model related through a reverse-ForeignKey. I have attempted various possible options based on examples in the docs, but have had no luck. Any help would be appreciated.
models.py
class Car(models.Model):
name = models.CharField(... )
#property
def default_image(self):
... ...
return image # <=== returns from the CarImage model
class CarImage(models.Model):
car = models.ForeignKey(Car) # <=== no related_name set, but technically we could use carimage_set
image = models.ImageField(... ...)
serializers.py (attempt)
class CarSerializer(serializers.ModelSerializer):
... ...
image = fields.SerializerMethodField('get_image')
class Meta:
mode = Car
def get_image(self, obj):
return '%s' % obj.default_image.url
exception
'SortedDictWithMetadata' object has no attribute 'default_image'

The new DRF 2.3 seems to be helpful with reverse relationships and has solved my issues.
DRF 2.3 Announcement
For example, in REST framework 2.2, reverse relationships needed to be included explicitly on a serializer class.
class BlogSerializer(serializers.ModelSerializer):
comments = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Blog
fields = ('id', 'title', 'created', 'comments')
As of 2.3, you can simply include the field name, and the appropriate serializer field will automatically be used for the relationship.
class BlogSerializer(serializers.ModelSerializer):
"""Don't need to specify the 'comments' field explicitly anymore."""
class Meta:
model = Blog
fields = ('id', 'title', 'created', 'comments')

Related

How to get the related objects in the Django rest API retrieve view?

Here I have two models and have Many-to-one relation . In the ListPackageGallery class I want to list all the images uploaded to some package.
How can I query the images of some particular package here?
I am very new to django rest.So am I going the right way by using the generics API view for such cases ?
class Package(models.Model):
name = models.CharField(max_length=255,unique=True)
slug = AutoSlugField(populate_from='name')
package_desc = models.TextField()
class PackageGallery(models.Model):
package = models.ForeignKey(Package, on_delete=models.CASCADE,related_name='gallery')
image = models.ImageField(upload_to='package_gallery')
serializers.py
class PackageGallerySerializer(serializers.ModelSerializer):
class Meta:
model = PackageGallery
fields = '__all__'
class PackageGalleryDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Package
fields = '__all__'
views.py
class CreatePackageGallery(generics.CreateAPIView):
serializer_class = PackageGallerySerializer
queryset = PackageGallery.objects.all()
class ListAllGallery(generics.ListAPIView):
serializer_class = PackageGallerySerializer
queryset = PackageGallery.objects.all()
class ListPackageGallery(generics.RetrieveAPIView):
serializer_class = PackageGalleryDetailSerializer
lookup_field = 'slug'
def get_queryset(self):
return self.gallery.all() #i got stuck here
urls.py
path('create/gallery/',CreatePackageGallery.as_view(),name='create_package_gallery'),
path('list/all/gallery/',ListAllGallery.as_view(),name='list_all_gallery'),
path('list/<slug>/gallery/',ListPackageGallery.as_view(),name='list_package_gallery'),
Django Version: 2.2.7 Exception Type: AttributeError Exception Value:
'ListPackageGallery' object has no attribute 'gallery'
You can use a SerializerMethodField to return the list of associated images:
class PackageGalleryDetailSerializer(serializers.ModelSerializer):
images = serializers.SerializerMethodField()
class Meta:
model = Package
fields = '__all__'
def get_images(self, package):
return [
# Change this to meet your need e.g. `gallery.image.url`
gallery.image.name
for gallery in package.gallery.all()
]
Also, the related_name from PackageGallery to Package should be named e.g. galleries to have a better notion of the relationship as it's a many-to-one.
And you can drop the get_queryset method from ListPackageGallery as we don't need any filtering.

Add Serializer on Reverse Relationship - Django Rest Framework

I have a Cart model and a CartItem model. The CartItem model has a ForeignKey to the Cart model.
Using Django Rest Framework I have a view where the API user can display the Cart, and obviously then I want to include the CartItem in the respone.
I set up my Serializer like this:
class CartSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
cartitem_set = CartItemSerializer(read_only=True)
class Meta:
model = Cart
depth = 1
fields = (
'id',
'user',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
My problem is the second line, cartitem_set = CartItemSerializer(read_only=True).
I get AttributeErrors saying 'RelatedManager' object has no attribute 'product'. ('product' is a field in the CartItem model. If I exclude product from the CartItemSerializer I just get a new AttributeError with the next field and so on. No matter if I only leave 1 or all fields in the Serializer, I will get a error.
My guess is that for some reason Django REST Framework does not support adding Serializers to reverse relationships like this. Am I wrong? How should I do this?
PS
The reason why I want to use the CartItemSerializer() is because I want to have control of what is displayed in the response.
Ahmed Hosny was correct in his answer. It required the many parameter to be set to True to work.
So final version of the CartSerializer looked like this:
class CartSerializer(serializers.ModelSerializer):
cartitem_set = CartItemSerializer(read_only=True, many=True) # many=True is required
class Meta:
model = Cart
depth = 1
fields = (
'id',
'date_created',
'voucher',
'carrier',
'currency',
'cartitem_set',
)
It's important to define a related name in your models, and to use that related name in the serializer relationship:
class Cart(models.Model):
name = models.CharField(max_length=500)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cart_items')
items = models.IntegerField()
Then in your serializer definition you use those exact names:
class CartSerializer(serializers.ModelSerializer):
cart_items = CartItemSerializer(read_only=True)
class Meta:
model = Cart
fields = ('name', 'cart_items',)
It would be wise to share your whole code, that is model and serializers classes. However, perhaps this can help debug your error,
My serializer classes
class CartItemSerializer(serializers.ModelSerializer):
class Meta:
model = CartItem
fields = ('id')
class CartSerializer(serializers.ModelSerializer):
#take note of the spelling of the defined var
_cartItems = CartItemSerializer()
class Meta:
model = Cart
fields = ('id','_cartItems')
Now for the Models
class CartItem(models.Model):
_cartItems = models.ForeignKey(Subject, on_delete=models.PROTECT)
#Protect Forbids the deletion of the referenced object. To delete it you will have to delete all objects that reference it manually. SQL equivalent: RESTRICT.
class Meta:
ordering = ('id',)
class Cart(models.Model):
class Meta:
ordering = ('id',)
For a detailed overview of relationships in django-rest-framework, please refer their official documentation

Django - Feeding only part of Foreignkey in models

i'm new to django and as a exercise I want to make "home expenses".
I've already did simple model and form :
(from models)
#models.py
#these are the type (groceries/clothes/etc. )
class TypWydatku(models.Model):
typ_wydatku = models.CharField(max_length=25)
data_wpisu=models.DateField(auto_now_add=True)
#these are the actual input with dates/prices/etc
class Wpisy(models.Model):
data_zakupu=models.DateField(default=timezone.now())
data_wpisu=models.DateField(auto_now=True)
typ_wydatku=models.ForeignKey(TypWydatku)
kwota=models.FloatField('kwota')
uwagi=models.CharField(max_length=255, blank=True)
But now I would like to add another model, which will describe expenses connected to my car more specific. So I add another TypWydatku - Moto with id=3
Next step is to create new model with extra fields (mileage/ fuel tanked):
#models.py
(...)
class WpisyMoto(models.Model):
wpis=models.ForeignKey(Wpisy)
przebieg=models.IntegerField()
uwagi=models.CharField(max_length=200)
litry=models.FloatField()
#and more
I have the sipmlest forms as one can have rigth now :
# forms.py
class TypWydatkuForm(ModelForm):
class Meta:
model = TypWydatku
fields = '__all__'
class WpisyForm(ModelForm):
class Meta:
model = Wpisy
fields = '__all__'
class WpisyMotoForm(ModelForm):
class Meta:
model = WpisyMoto
fields = '__all__'
I would like to have choice field 'wpis' in the template, where i want to see onlythose which have 'typ_wydatku'=3. How should I do it ?
From the docs for the ModelChoiceField field (which is the form field type Django will use to represent the ForeignKey field), this can be achieved by setting the form field's queryset attribute:
class WpisyMotoForm(ModelForm):
class Meta:
model = WpisyMoto
fields = '__all__'
def __init__(self, *args, **kwargs):
super(WpisyMotoForm, self).__init__(*args, **kwargs)
self.fields["wpis"].queryset = Wpisy.objects.filter(typ_wydatku__pk=3)

How do you filter a nested serializer in Django Rest Framework?

In Django Rest Framework, how do you filter a serializer when it's nested in another serializer?
My filters are imposed in the DRF viewsets, but when you call a serializer from inside another serializer, the viewset of the nested serializer never gets called, so the nested results appear unfiltered.
I have tried adding a filter on originating viewset, but it doesn't seem to filter the nested results because the nested results get called as a separate pre-fretched query. (The nested serializer is a reverse lookup, you see.)
Is it possible to add a get_queryset() override in the nested serializer itself (moving it out of the viewset), to add the filter there? I've tried that, too, with no luck.
This is what I tried, but it doesn't even seem to get called:
class QuestionnaireSerializer(serializers.ModelSerializer):
edition = EditionSerializer(read_only=True)
company = serializers.StringRelatedField(read_only=True)
class Meta:
model = Questionnaire
def get_queryset(self):
query = super(QuestionnaireSerializer, self).get_queryset(instance)
if not self.request.user.is_staff:
query = query.filter(user=self.request.user, edition__hide=False)
return query
You can subclass the ListSerializer and overwrite the to_representation method.
By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.
subclass ListSerializer, overwriting to_representation and then calling super
add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer
Here is the relevant code for your sample.
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(user=self.context['request'].user, edition__hide=False)
return super(FilteredListSerializer, self).to_representation(data)
class EditionSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = Edition
class QuestionnaireSerializer(serializers.ModelSerializer):
edition = EditionSerializer(read_only=True)
company = serializers.StringRelatedField(read_only=True)
class Meta:
model = Questionnaire
While all the above answers work, I find the use of Django's Prefetch object the easiest way of all.
Say a Restaurant obj has a lot of MenuItems, some of which are is_removed == True, and you only want those that are not removed.
In RestaurantViewSet, do something like
from django.db.models import Prefetch
queryset = Restaurant.objects.prefetch_related(
Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items')
)
In RestaurantSerializer, do something like
class RestaurantSerializer(serializers.ModelSerializer):
menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)
Tested many solutions from SO and other places.
Found only one working solution for Django 2.0 + DRF 3.7.7.
Define a method in model which has nested class. Craft a filter that will fit your needs.
class Channel(models.Model):
name = models.CharField(max_length=40)
number = models.IntegerField(unique=True)
active = models.BooleanField(default=True)
def current_epg(self):
return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6]
class Epg(models.Model):
start = models.DateTimeField()
end = models.DateTimeField(db_index=True)
title = models.CharField(max_length=300)
description = models.CharField(max_length=800)
channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)
.
class EpgSerializer(serializers.ModelSerializer):
class Meta:
model = Epg
fields = ('channel', 'start', 'end', 'title', 'description',)
class ChannelSerializer(serializers.ModelSerializer):
onair = EpgSerializer(many=True, read_only=True, source="current_epg")
class Meta:
model = Channel
fields = ('number', 'name', 'onair',)
Pay attention to source="current_epg" and you'll get the point.
I find it easier, and more straight forward, to use a SerializerMethodField on the serializer field you want to filter.
So you would do something like this.
class CarTypesSerializer(serializers.ModelSerializer):
class Meta:
model = CarType
fields = '__all__'
class CarSerializer(serializers.ModelSerializer):
car_types = serializers.SerializerMethodField()
class Meta:
model = Car
fields = '__all__'
def get_car_types(self, instance):
# Filter using the Car model instance and the CarType's related_name
# (which in this case defaults to car_types_set)
car_types_instances = instance.car_types_set.filter(brand="Toyota")
return CarTypesSerializer(car_types_instances, many=True).data
This saves you from having to create many overrides of the serializers.ListSerializer if you need different filtering criteria for different serializers.
It also has the extra benefit of seeing exactly what the filter does within the serializer instead of diving into a subclass definition.
Of course the downside is if you have a serializer with many nested objects that all need to be filtered in some way. It could cause the serializer code to greatly increase. It's up to you how you would like to filter.
Hope this helps!
When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer
This method takes the target of the field as the value argument, and
should return the representation that should be used to serialize the
target. The value argument will typically be a model instance.
Below is the example of the nested serializer
class UserSerializer(serializers.ModelSerializer):
""" Here many=True is passed, So a ListSerializer instance will be
created"""
system = SystemSerializer(many=True, read_only=True)
class Meta:
model = UserProfile
fields = ('system', 'name')
class FilteredListSerializer(serializers.ListSerializer):
"""Serializer to filter the active system, which is a boolen field in
System Model. The value argument to to_representation() method is
the model instance"""
def to_representation(self, data):
data = data.filter(system_active=True)
return super(FilteredListSerializer, self).to_representation(data)
class SystemSerializer(serializers.ModelSerializer):
mac_id = serializers.CharField(source='id')
system_name = serializers.CharField(source='name')
serial_number = serializers.CharField(source='serial')
class Meta:
model = System
list_serializer_class = FilteredListSerializer
fields = (
'mac_id', 'serial_number', 'system_name', 'system_active',
)
In view:
class SystemView(viewsets.GenericViewSet, viewsets.ViewSet):
def retrieve(self, request, email=None):
data = get_object_or_404(UserProfile.objects.all(), email=email)
serializer = UserSerializer(data)
return Response(serializer.data)
The following worked for me, from self.context['view'], You can get the filter params inside the serializer and use it however you want.
class ShipmentDocumentSerializer(serializers.ModelSerializer):
class Meta:
model = Document
fields = ['id', 'created_date', 'consignment', 'document', 'org', 'title' ]
class ShipmentDocumentTypeSerializer(serializers.ModelSerializer):
documents = serializers.SerializerMethodField()
class Meta:
model = DocumentType
fields = ['id', 'type', 'documents']
def get_documents(self, instance):
consignment_id=self.context['view'].kwargs['consignment_id']
queryset = Document.objects.filter(consignment__id=consignment_id)
return ShipmentDocumentSerializer(queryset, many=True).data

Restricted set of nested fields in a django REST framework ModelSerializer

Consider the following serializer
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('id', 'account')
depth = 1
The field account refers to a ForeignKey in MyModel and I want to expose some of the Account fields with this serializer but not all of them.
How do I specify that only account.name and account.email should be serialized?
You can do this by creating your own serializer to use as the nested serializer.
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('name', 'email', )
You are better off with creating specialized serializers instead of relying on Django REST Framework to create them for you. By default, serializers that are automatically created contain all fields defined on the model.
class MyModelSerializer(serializers.ModelSerializer):
account = AccountSerializer()
class Meta:
model = MyModel
fields = ('id', 'account', )
You can find out more about nested serializers in the Django REST Framework documentation.