How to enable POST,PUT for django rest framework viewset - django

I have simple viewset like this
in views.py
class MyFileViewSet(viewsets.ViewSet):
http_method_names = ['post','get','put']
def list(self, request):
queryset = MyFile.objects.all()
serializer = MyFileSerializer(queryset, many=True)
return Response(serializer.data)
models.py
from xml.dom.minidom import DOMImplementation
from django.db import models
class MyModel(models.Model):
name = models.CharField(verbose_name='NAME', max_length=30)
file = models.FileField(upload_to='file/%Y/%m/%d')
is_transfer_finish = models.BooleanField(default=False)
created_at = models.DateTimeField(verbose_name='created', auto_now_add=True)
Now I can see the list of MyModel.
However I want to set post and push for this model,
I set
http_method_names = ['post','get','put']
But currently, there is only 'GET' on html.
I want to enable POST or PUT to create new entry.
Is it possible? how can I make this?

Related

Need Serialize a custom filed AVG notes for my API in Django

models.py
from django.db import models
from django.db.models import Avg
from users.models import UserProfile
from subjects.models import Subject
class Note(models.Model):
id_user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='user_note')
id_subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='subject_note')
exam_note = models.IntegerField()
#property
def average_note(self):
if hasattr(self, '_average_note'):
return self._average_note
return Note.objects.aggregate(Avg('exam_note'))
Thats my Note models and i need to calculate the notes average to serialize it and send in request resonse
views.py
class AvgNoteViewSet(viewsets.ModelViewSet):
serializer_class = AvgSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (NotePermissions,)
def get_queryset(self):
return Note.objects.all().annotate(_average_note=Avg('exam_note'))
And that is my get_queryset redefined method to calculate de average notes, but im just having a list of notes for result of that query, like that: resques response
serializers.py
class AvgSerializer(serializers.ModelSerializer):
"""
Serializes a notes AVG
"""
average_note = serializers.SerializerMethodField()
def get_average_note(self, obj):
return obj.average_note
class Meta:
model = Note
fields = ['average_note']
And this is my serializer
My intencion is try to get an avergate from the exam_notes from the logged user, so i understand that i need to try to group by user_id ant then agregate the average. This is my postgresql table from when im querying: notes_table
I based my code from How to calculate average of some field in Django models and send it to rest API?, but im trying to get some result like as running the following query:
SELECT avg(exam_note)
FROM public.notes_note x
group by id_user_id
To add a GROUP BY functionality, you’ll simply add values to your get_queryset method like this:
class AvgNoteViewSet(viewsets.ModelViewSet):
serializer_class = AvgSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (NotePermissions,)
def get_queryset(self):
return Note.objects.all().values('id_user').annotate(_average_note=Avg('exam_note'))

How to have a custom endpoint for a model's field in Django?

I want to have an endpoint for every image I want to add tags in.
Suppose I have:
#models.py
class ImageTag(models.Model):
name = models.CharField()
description = models.CharField()
class Image(models.Model):
image_id = models.CharField(unique=True)
image_tags = models.ManyToManyField(ImageTag, blank=True)
...
#serializers.py
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = '__all__'
#views.py
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
lookup_field = 'image_id'
...
#urls.py
router.register(r'images', ImageViewSet, basename='image')
I want to POST and DELETE image_tags in an endpoint such as:
localhost:8000/my_app/images/IMG_123/image_tags
where: IMG_123 is an Image
I think I will need a separate serializer and viewset for that. But more importantly I want to know how will I add such endpoint in the router in urls.py
I am looking for something like this:
router.register(r'<image>/image_tags', ImageTagViewSet, basename='image_tag')
NOTE: I was able to change Image endpoint using its image_id (instead of ID) because of lookup_field in ImageViewSet, thus can be:
localhost:8000/my_app/images/IMG_123/
you can create custom endpoint for a viewset whit defining extra actions
you will create a method with action decorator and the router of this viewset will create url from that method name for you for example:
from rest_framework.decorators import action
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
lookup_field = 'image_id'
#action(detail=True, methods=['post'])
def image_tags(self, request, *args, **kwargs):
image_instance = self.get_object() # image that it's id has been passed by url
# you can now filter and get your ImageTags and serialize it with a serializer
this will create an endpoint like you want it
localhost:8000/my_app/images/IMG_123/image_tags/
views.py
class ImageViewSet(viewsets.ModelViewSet):
image_id = self.kwargs['id']
queryset = Image.objects.get(image_id=image_id)
serializer_class = ImageSerializer
lookup_field = 'image_id'
...
urls.py
router.register(r'(?P<id>[a-zA-Z0-9_]+)/image_tags$', ImageTagViewSet, basename='image_tag')

Django update modelform with field constraints

Having the following Model:
class Book(models.Model):
name = models.CharField()
author = models.CharField()
date = models.DateField()
class Meta:
unique_together = ('name', 'author')
class BookSerializerWrite(serializers.ModelSerializer):
class Meta:
model = Book
class BookView(ApiView):
def put(self, request, *args, **kwargs):
serializer = BookSerializerWrite(data=request.data)
if serializer.is_valid():
serializer.save()
The view above does not work as the serializer.is_valid() is False.
The message is:
'The fields name, author must make a unique set'
Which is the constraint of the model.
How do I update the model?
I would rather not override the serializer's validation method.
I also cannot access the validated_data for an update as in
https://www.django-rest-framework.org/api-guide/serializers/#saving-instances
as this is empty due to the fact that the form does not validate.
Is there a builtin solution?
You can achieve it using UpdateAPIview
serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('name', 'author', 'date')
views.py
from rest_framework.generics import UpdateAPIview
from .serializers import BookSerializer
class BookUpdateView(UpdateAPIView):
serializer_class = BookSerializer
urls.py
from django.urls import path
from . import views
url_patterns = [
path('api/book/<int:pk>/update/', views.BookUpdateView.as_view(), name="book_update"),
]
Now, post your data to above url. It should work.
Reference: https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py

drf mongoengine serializer imagefield none

I used Django restframework and mongoengine.
Here is my model and serializer.
[model.py]
class Attachment(EmbeddedDocument):
attachment_id = SequenceField()
path = StringField()
path_small = StringField()
class Book(Document):
book_id = SequenceField()
user_id = LongField(required=True)
attachments = ListField(EmbeddedDocumentField(Attachment))
created_at = DateTimeField(default=datetime.now().replace(microsecond=0))
updated_at = DateTimeField(default=datetime.now().replace(microsecond=0))
[serializer.py]
from rest_framework_mongoengine.serializers import DocumentSerializer
from rest_framework.serializers import ImageField
from books.models.mongo import Book
class BookSerializer(DocumentSerializer):
image = ImageField()
class Meta:
model = Appeal
fields = (
'book_id',
'image',
)
Work flow like this.
Upload image to s3
Get s3 path
Save s3 path to attachments field in models.py.
So do not defined attachments to ImageField() in models.py.
Just set image = ImageField() in serializer to validate it is correct image.
But when I validate with serializer.is_valid(), image get None.
[views.py]
class BookList(GenericAPIView):
serializer_class = BookSerializer
queryset = ''
def post(self, request: Request) -> Union[Response, NoReturn]:
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
print(serializer.data)
appeal = CreateAppealInteractor().execute(request=serializer.data)
return Response(status=status.HTTP_200_OK)
As you know that after serializer.is_valid(), I printed serializer.data.
But it throw None like this -> {'book_id': 1, 'image': None}
Is there any error in my code?
Thanks.
Problem is solved in chat room. Just use validated_data.
class BookList(GenericAPIView):
serializer_class = BookSerializer
queryset = ''
def post(self, request: Request) -> Union[Response, NoReturn]:
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
appeal = CreateAppealInteractor().execute(request=serializer.validated_data)
return Response(status=status.HTTP_200_OK)

Django Rest Framework - "Got a `TypeError` when calling `Note.objects.create()'"

When i am trying to POST using the browsable API of DRF, i get the following error:
Got a TypeError when calling Note.objects.create(). This may be
because you have a writable field on the serializer class that is not
a valid argument to Note.objects.create(). You may need to make the
field read-only, or override the NoteSerializer.create() method to
handle this correctly.
I don't know what is generating this error or how to overcome it. Literally spent hours google searching or changing the code. Can someone explain how to overcome this error? (Please note i am new to Django and DRF!)
Here is the models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
import uuid
class Stock(models.Model):
'''
Model representing the stock info.
'''
user = models.ForeignKey(User)
book_code = models.CharField(max_length=14, null=True, blank=True)
def __str__(self):
return self.book_code
class Note(models.Model):
'''
Model representing the stock note.
'''
user = models.ForeignKey(User)
note = models.TextField(max_length=560)
stock = models.ForeignKey(Stock, related_name='notes')
date_note_created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.note
This is the views.py:
from rest_framework import generics
from stocknoter.models import Stock, Note
from api.serializers import StockSerializer, NoteSerializer
# Create your views here.
class StockList(generics.ListCreateAPIView):
serializer_class = StockSerializer
def get_queryset(self):
user = self.request.user
return Stock.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
class NoteList(generics.ListCreateAPIView):
serializer_class = NoteSerializer
def get_queryset(self):
user = self.request.user
return Note.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(data=self.request.data)
def perform_update(self, serializer):
serializer.save(data=self.request.data)
This is the serializers.py:
from stocknoter.models import Stock, Note
from rest_framework import serializers
class StockSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
notes = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Stock
fields = ('id', 'user', 'book_code', 'notes')
class NoteSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Note
fields = ('user', 'note', 'stock')
I think this is because you are providing keyword argument data to save method. This is unnecessary. save() dont have such argument. Try just this:
def perform_create(self, serializer):
serializer.save()
take a print() of serializer_data in the view.py
to see what you actually sending to model and create.
then in your serializer.py override the create method..
for example:
def post(self, request):
print(serializer_data.validated_data)
def create(self, validated_data):
del validated_data['c']
return X.objects.create(a=validated_data['a'],
b=validated_data['b'],
)