now i'm studying DRF and have to do project with photo albums. One of my tasks is to create custom #action "patch" method, using model field "title", but i can't understand how to add fields for search in custom methods. We can see them in base methods, like "get", "patch" and "put", but i can't find any info about how to add them to custom actions.
If anyone knows how to do it, please, tell me.
My model:
class PhotoAlbum(models.Model):
title = models.CharField(verbose_name='Название альбома', max_length=50, null=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name='Автор')
created_at = models.DateTimeField(verbose_name='Дата создания', editable=False,
default=datetime.datetime.today())
class Meta:
verbose_name = 'Фотоальбом'
verbose_name_plural = 'Фотоальбомы'
def __str__(self):
return f'{self.title} Автор: {self.created_by} Дата: {self.created_at}'
My view:
def photo_album_view(request):
photo_albums = PhotoAlbum.objects.all()
context = {
'photo_albums': photo_albums,
}
return render(request, 'photo_album.html', context=context)
My viewset:
class AlbumFilter(django_filters.FilterSet):
title = django_filters.Filter(field_name='title')
class PhotoAlbumViewSet(viewsets.ModelViewSet):
queryset = PhotoAlbum.objects.all()
filterset_class = AlbumFilter
serializer_class = PhotoAlbumSerializer
pagination_class = ResultPagination
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
search_fields = ['title', ]
ordering_fields = ['created_at', ]
To answer the above
say you have a viewset class
// all imports
class AbcApi(viewsets.ModelViewset):
serializer_class = random
permission_classses = [IsAuthenticated]
search_fields = ["a_field", "b_field"]
filter_backends = [....]
#custom action
#action(detail=False)
def custom_action(self, *args, **kwargs):
""" to answer your question """
qset_ = **self.filter_queryset**(self.queryset())
#the filter bold automatically involves the class filters in the custom method
the answer is to use self.filter_queryset(..pass the get_queryset()) here as seen
If you are using ModelViewSet, I believe you are looking for one of these custom methods:
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
These functions allow you to add custom functionalities to your update method. Reference
Brief example:
def partial_update(self, request, pk=None):
serializer = UserPostSerializer(user, data=request.data, partial=True)
if serializer.is_valid():
try:
serializer.save()
except ValueError:
return Response({"detail": "Serializer not valid."}, status=400)
return Response({"detail": "User updated."})
else:
return Response(serializer.errors)
Related
I have a model which has specific many to many fields to the user model. Now, to prevent information leaking, I do not want to return the whole related field though the rest framework. But, I want to create some kind of computed field, such that it return True if the requesting user is in the related field, and False otherwise. Is there a way to make this work?
For example, as it stands now, the rest framework will list the users for "user_like" and
"user_bookmark", which I dont want to happen, hence I want to exclude them from the serialized. But I want to have a field, say, named is_liked, which will be true if request.user is in user_like, and false otherwise.
My current setup:
model
class Post(models.Model):
title = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(upload_to='dream_photos')
description = models.TextField(max_length=500)
date_added = models.DateTimeField(auto_now_add=True)
user_like = models.ManyToManyField(User, related_name='likes', blank=True)
user_bookmark = models.ManyToManyField(
User, related_name='bookmarks', blank=True)
total_likes = models.PositiveIntegerField(db_index=True, default=0)
tags = TaggableManager()
serialiser
class PostSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()
class Meta:
model = Dream
fields = ('title','user', 'image','description','date_added', 'tags', 'total_likes' )
views
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.prefetch_related('user').all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]
#action(detail=False, methods=['get'], url_path='current-profile', url_name='current-profile')
def current_user_posts(self, request):
# I expected this to add the extra field I required
# But it does not seem to work as expected
queryset = self.get_queryset().filter(user=request.user).annotate(
bookmark=(request.user in "user_bookmark"))
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Expected behavior when requesting:
{
"id": 1,
"tags": [
"test"
],
"title": "Tets",
"image": "http://127.0.0.1:8000/media/post_photos/photo1648638314.jpeg",
"description": "TEst",
"date_added": "2022-05-20T17:47:55.739431Z",
"total_likes": 0,
"user": 1,
"like": true, // true if current user is in user_like, false otherwise
"bookmark": true // true if current user is in user_bookmark, false otherwise
}
Actual behavior
TypeError: 'in ' requires string as left operand, not SimpleLazyObject
Edit 1:
The answer from here seems to help to resolve the error. Unfortunately, the annotated field does not seem to be returned by the serializer
the edited view:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.prefetch_related('user').all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]
#action(detail=False, methods=['get'], url_path='current-profile', url_name='current-profile')
def current_user_posts(self, request):
queryset = self.get_queryset().filter(user=request.user).annotate(
bookmark=Exists(Post.user_bookmark.through.objects.filter(
post_id=OuterRef('pk'), user_id=request.user.id))
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
What you can do is add your custom fields to the serializer with SerializerMethodField and pass the request.user via get_serializer_context in your view. For example:
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.prefetch_related('user').all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticated]
#action(detail=False, methods=['get'], url_path='current-profile', url_name='current-profile')
def current_user_posts(self, request):
queryset = self.get_queryset().filter(user=request.user)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_serializer_context(self):
context = super(PostViewSet, self).get_serializer_context()
context.update({"request": self.request})
return context
This allows you to sent the request via the context which can be used by the serializer. Now in your serializer you can add this two new fields:
class PostSerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()
bookmark = serializers.SerializerMethodField()
like = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('title','user', 'image','description','date_added', 'tags', 'total_likes', 'bookmark', 'like')
def get_like(self, obj):
return self.context['request'].user in obj.user_like.all()
def get_bookmark(self, obj):
return self.context['request'].user in obj.user_bookmark.all()
I have method in my model that I want to be callable in the API.
model:
class Booking(models.Model):
PENDING = 'PN'
ACCEPTED = 'AC'
DENIED = 'DN'
BOOKING_STATUS_CHOICES = [
(PENDING, 'Pending'),
(ACCEPTED, 'Accepted'),
(DENIED, 'Denied')
]
createdDate = models.DateTimeField(auto_now_add=True)
comments = models.CharField(max_length=120)
location = models.TextField()
date = models.DateTimeField()
operator = models.ForeignKey("Business", on_delete=models.CASCADE)
status = models.CharField(max_length=2, choices=BOOKING_STATUS_CHOICES,default=PENDING)
def __str__(self):
return self.comments
def acceptBooking(self):
self.status = self.ACCEPTED
def denyBooking(self):
self.status = self.DENIED
serializer:
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = ('createdDate', 'comments', 'location', 'date', 'operator', 'status')
views:
class BookingView(viewsets.ModelViewSet):
serializer_class = BookingSerializer
queryset = Booking.objects.all()
filter_backends = [filters.SearchFilter]
search_fields = ['createdDate', 'comments', 'location', 'date', 'operator']
I would like to call acceptBooking or denyBooking. What's the best practice for achieving this?
Thanks!
One option is to add extra actions to your view. So someone can accept or deny a booking by doing one of these HTTP requests:
PUT /bookings/1/accept/
PUT /bookings/1/deny/
You can achieve this with something like:
# models.py
class Booking(models.Model):
...
def accept(self):
self.status = self.ACCEPTED
self.save()
def deny(self):
self.status = self.DENIED
self.save()
# views.py
from rest_framework.decorators import action
from rest_framework.response import Response
class BookingView(viewsets.ModelViewSet):
...
#action(detail=True, methods=['put', 'patch'])
def accept(self, request, pk=None):
booking = self.get_object()
booking.accept()
return Response({'status': booking.status})
#action(detail=True, methods=['put', 'patch'])
def deny(self, request, pk=None):
booking = self.get_object()
booking.deny()
return Response({'status': booking.status})
It looks like your only problem is that you are missing the self.save() from the model function. The serializer will automatically save the statuses for you without the functions needed.
If that's not what your looking for and you want to call the functions then as #Ram mentioned, look at how to call the model fields. For you this would be:
class BookingView(viewsets.ModelViewSet):
serializer_class = BookingSerializer
queryset = Booking.objects.all()
filter_backends = [filters.SearchFilter]
search_fields = ['createdDate', 'comments', 'location', 'date', 'operator']
def perform_update(self, serializer):
serializer.save()
if <accept_check>:
serializer.instance.acceptBooking()
if <deny_check>:
serializer.instance.denyBooking()
Also you must save the changes on model functions like:
def acceptBooking(self):
self.status = self.ACCEPTED
self.save()
def denyBooking(self):
self.status = self.DENIED
self.save()
Please note functions should use underscores, so acceptBooking should be accept_booking.
If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.
For example fields email I need set to lowercase and fields nda I need set as null
models.py
class Vendors(models.Model):
nda = models.DateField(blank=True, null=True)
parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)
def clean(self):
if self.nda == "":
self.nda = None
class VendorContacts(models.Model):
....
vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
email = models.CharField(max_length=80, blank=True, null=True, unique=True)
def clean(self):
if self.email:
self.email = self.email.lower()
serializer.py
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
...
'email',)
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
As I learned from the documentation
Django Rest Framework serializers do not call the Model.clean when
validating model serializers
In dealing with this problem, I found two ways to solve it.
1. using the custom method at serializer. For my case, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
def validate(self, attrs):
instance = Vendors(**attrs)
instance.clean()
return attrs
Using full_clean() method. For me, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors(**validated_data)
vendor.full_clean()
vendor.save()
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.
In my case I had the same problem but with validation feature
I used the way below and it works for me (not excludes the way found above):
class CustomViewClass(APIView):
def post(self, request, format=None):
prepared_data_variable = 'some data in needed format'
serializer = CustomSerializer(data=request.data)
if serializer.is_valid(self):
serializer.validated_data['field_name'] = prepared_data_variable
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable
For DRF you can change your serializer before save as below...
First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.
if serializer.is_valid():
serializer.object.user_id = 15 # For example
serializer.save()
UPD!
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
data = request.data
if data['nda'] == '':
data['nda'] = None
for contact in data['contacts']:
if contact['email']:
print(contact['email'])
contact['email'] = contact['email'].lower()
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.
Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.
I want to hide specific fields of a model on the list display at persons/ and show all the fields on the detail display persons/jane
I am relatively new to the rest framework and the documentation feels like so hard to grasp.
Here's what I am trying to accomplish.
I have a simple Person model,
# model
class Person(models.Model):
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
nickname = models.CharField(max_length=20)
slug = models.SlugField()
address = models.TextField(max_length=300, blank=True)
and the serializer class
# serializers
class PersonListSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ('nickname', 'slug')
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ('first_name', 'last_name', 'nickname', 'slug', 'address')
and the viewsets.
# view sets (api.py)
class PersonListViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonListSerializer
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
at the url persons I want to dispaly list of persons, just with fields nickname and slug and at the url persons/[slug] I want to display all the fields of the model.
my router configurations,
router = routers.DefaultRouter()
router.register(r'persons', api.PersonListViewSet)
router.register(r'persons/{slug}', api.PersonViewSet)
I guess the second configuration is wrong, How can I achieve what I am trying to do?
update:
the output to persons/slug is {"detail":"Not found."} but it works for person/pk
Thank you
For anyone else stumbling across this, I found overriding get_serializer_class on the viewset and defining a serializer per action was the DRY-est option (keeping a single viewset but allowing for dynamic serializer choice):
class MyViewset(viewsets.ModelViewSet):
serializer_class = serializers.ListSerializer
permission_classes = [permissions.IsAdminUser]
renderer_classes = (renderers.AdminRenderer,)
queryset = models.MyModel.objects.all().order_by('-updated')
def __init__(self, *args, **kwargs):
super(MyViewset, self).__init__(*args, **kwargs)
self.serializer_action_classes = {
'list':serializers.AdminListSerializer,
'create':serializers.AdminCreateSerializer,
'retrieve':serializers.AdminRetrieveSerializer,
'update':serializers.AdminUpdateSerializer,
'partial_update':serializers.AdminUpdateSerializer,
'destroy':serializers.AdminRetrieveSerializer,
}
def get_serializer_class(self, *args, **kwargs):
"""Instantiate the list of serializers per action from class attribute (must be defined)."""
kwargs['partial'] = True
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(MyViewset, self).get_serializer_class()
Hope this helps someone else.
You can override the 'get_fields' method your serializer class and to add something like that:
def get_fields(self, *args, **kwargs):
fields = super().get_fields(*args, **kwargs)
request = self.context.get('request')
if request is not None and not request.parser_context.get('kwargs'):
fields.pop('your_field', None)
return fields
In this case when you get detail-view there is 'kwargs': {'pk': 404} and when you get list-view there is 'kwargs': {}
I wrote an extension called drf-action-serializer (pypi) that adds a serializer called ModelActionSerializer that allows you to define fields/exclude/extra_kwargs on a per-action basis (while still having the normal fields/exclude/extra_kwargs to fall back on).
The implementation is nice because you don't have to override your ViewSet get_serializer method because you're only using a single serializer. The relevant change is that in the get_fields and get_extra_kwargs methods of the serializer, it inspects the view action and if that action is present in the Meta.action_fields dictionary, then it uses that configuration rather than the Meta.fields property.
In your example, you would do this:
from action_serializer import ModelActionSerializer
class PersonSerializer(ModelActionSerializer):
class Meta:
model = Person
fields = ('first_name', 'last_name', 'nickname', 'slug', 'address')
action_fields = {
'list': {'fields': ('nickname', 'slug')}
}
Your ViewSet would look something like:
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
And your router would look normal, too:
router = routers.DefaultRouter()
router.register(r'persons', api.PersonViewSet)
Implementation
If you're curious how I implemented this:
I added a helper method called get_action_config which gets the current view action and returns that entry in the action_fields dict:
def get_action_config(self):
"""
Return the configuration in the `Meta.action_fields` dictionary for this
view's action.
"""
view = getattr(self, 'context', {}).get('view', None)
action = getattr(view, 'action', None)
action_fields = getattr(self.Meta, 'action_fields', {})
I changed get_field_names of ModelSerializer:
From:
fields = getattr(self.Meta, 'fields', None)
exclude = getattr(self.Meta, 'exclude', None)
To:
action_config = self.get_action_config()
if action_config:
fields = action_config.get('fields', None)
exclude = action_config.get('exclude', None)
else:
fields = getattr(self.Meta, 'fields', None)
exclude = getattr(self.Meta, 'exclude', None)
Finally, I changed the get_extra_kwargs method:
From:
extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {}))
To:
action_config = self.get_action_config()
if action_config:
extra_kwargs = copy.deepcopy(action_config.get('extra_kwargs', {}))
else:
extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {}))
If you want to change what fields are displayed in the List vs Detail view, the only thing you can do is change the Serializer used. There's no field that I know of that lets you specify which fields of the Serializer gets used.
The field selection on you serializers should be working, but I don't know what might be happening exactly. I have two solutions you can try:
1 Try to change the way you declare you serializer object
#If you aren't using Response:
from rest_framework.response import Response
class PersonListViewSet(viewsets.ModelViewSet):
def get(self, request):
queryset = Person.objects.all()
serializer_class = PersonListSerializer(queryset, many=True) #It may change the things
return Response(serializer_class.data)
class PersonViewSet(viewsets.ModelViewSet):
def get(self, request, pk): #specify the method is cool
queryset = Person.objects.all()
serializer_class = PersonSerializer(queryset, many=True) #Here as well
#return Response(serializer_class.data)
2 The second way around would change your serializers
This is not the most normal way, since the field selector should be working but you can try:
class PersonListSerializer(serializers.ModelSerializer):
nickname = serializers.SerializerMethodField() #Will get the attribute my the var name
slug = serializers.SerializerMethodField()
class Meta:
model = Person
def get_nickname(self, person):
#This kind of method should be like get_<fieldYouWantToGet>()
return person.nickname
def get_slug(self, person):
#This kind of method should be like get_<fieldYouWantToGet>()
return person.slug
I hope it helps. Try to see the APIview class for building your view too.
Somehow close:
If you just want to skip fields in the serilaizer
class UserSerializer(serializers.ModelSerializer):
user_messages = serializers.SerializerMethodField()
def get_user_messages(self, obj):
if self.context.get('request').user != obj:
# do somthing here check any value from the request:
# skip others msg
return
# continue with your code
return SystemMessageController.objects.filter(user=obj, read=False)
I rewrite ModelViewSet list function to modify serializer_class.Meta.fields attribute, code like this:
class ArticleBaseViewSet(BaseViewSet):
def list(self, request, *args, **kwargs):
exclude = ["content"]
self.serializer_class.Meta.fields = [f.name for f in self.serializer_class.Meta.model._meta.fields if f.name not in exclude]
queryset = self.filter_queryset(self.get_queryset()).filter(is_show=True, is_check=True)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class BannerArticleViewSet(ArticleBaseViewSet):
queryset = BannerArticle.objects.filter(is_show=True, is_check=True).all()
serializer_class = BannerArticleSerializer
permission_classes = (permissions.AllowAny,)
But it looks not stable, so i will not use it, just share to figure out the best way
My solution.
class BaseSerializerMixin(_ModelSerializer):
class Meta:
exclude: tuple[str, ...] = ()
exclude_in_list: tuple[str, ...] = ()
model: Type[_models.Model]
def get_action(self) -> Optional[str]:
if 'request' not in self.context:
return None
return self.context['request'].parser_context['view'].action
def get_fields(self):
fields = super().get_fields()
if self.get_action() == 'list':
[fields.pop(i) for i in list(fields) if i in self.Meta.exclude_in_list]
return fields
I think it should be like this:
router.register(r'persons/?P<slug>/', api.PersonViewSet)
and you should include a line like this:
lookup_field='slug'
in your serializer class. Like this:
class PersonSerializer(serializers.ModelSerializer):
lookup_field='slug'
class Meta:
model = Person
fields = ('first_name', 'last_name', 'nickname', 'slug', 'address')
I am beginner to Django and currently, I can construct model like this.
models.py
class Car(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(max_digits=5, decimal_places=2)
photo = models.ImageField(upload_to='cars')
serializers.py
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('id','name','price', 'photo')
views.py
class CarView(APIView):
permission_classes = ()
def get(self, request):
car = Car.objects.all()
serializer = CarSerializer(car)
return Response(serializer.data)
For photo, it doesn't show full URL. How can I show full URL?
Django is not providing an absolute URL to the image stored in a models.ImageField (at least if you don't include the domain name in the MEDIA_URL; including the domain is not recommended, except of you are hosting your media files on a different server (e.g. aws)).
However, you can modify your serializer to return the absolute URL of your photo by using a custom serializers.SerializerMethodField. In this case, your serializer needs to be changed as follows:
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.SerializerMethodField()
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
def get_photo_url(self, car):
request = self.context.get('request')
photo_url = car.photo.url
return request.build_absolute_uri(photo_url)
Also make sure that you have set Django's MEDIA_ROOTand MEDIA_URL parameters and that you can access a photo via your browser http://localhost:8000/path/to/your/image.jpg.
As piling pointed out, you need to add the request while initialising the serializer in your views.py:
def my_view(request):
…
car_serializer = CarSerializer(car, context={"request": request})
car_serializer.data
For future visitors, there is no need to add another field to the serializer if the view method already returns a serialized object. The only thing required is to add the context since it is needed to generate hyperlinks, as stated in the drf documentation
#list_route()
def my_view(self, request):
qs = Object.objects.all()
return Response(MySerializer(qs, many=True, context={'request': request}).data)
Serializer class
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.ImageField(max_length=None, use_url=True, allow_null=True, required=False)
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
View
class CarView(APIView):
def get(self, request, *args, **kwargs):
queryset = Car.objects.all()
serializer = CarSerializer(queryset, many=True, context={"request":request})
return Response(serializer.data, status=status.HTTP_200_OK)
It's better to use this code, due to the above code doesn't check the image is null able or not.
class CarSerializer(serializers.ModelSerializer):
photo_url = serializers.SerializerMethodField()
class Meta:
model = Car
fields = ('id','name','price', 'photo_url')
def get_photo_url(self, car):
request = self.context.get('request')
if photo and hasattr(photo, 'url'):
photo_url = car.photo.url
return request.build_absolute_uri(photo_url)
else:
return None
serializers.py
class BannerSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField()
def get_image(self, obj):
return self.context['request'].build_absolute_uri( obj.image.url)
views.py
banner = Banner.objects.all()
banner_data = BannerSerializer(banner,many=True, context={'request': request})
data = banner_data.data
return Response({"data":data})
I read the implement of the Serializer, and find the simplest way is to extends ImageField:
from django.db import models
class ImageField(models.ImageField):
def value_to_string(self, obj): # obj is Model instance, in this case, obj is 'Class'
return obj.fig.url # not return self.url
class Class(models.Model):
name = models.CharField(max_length=50)
intro = models.CharField(max_length=200)
# fig = models.ImageField(upload_to="classes")
fig = ImageField(upload_to="classes")
def __str__(self):
return repr(self,"name")