Confused about reverse URL in testing mode - django-rest-framework

In my test file, there is one class with :
INGREDIENTS_URL = reverse("recipe:ingredient-list")
def setUp(self):
self.client = APIClient()
def test_Auth_required(self):
res = self.client.get(INGREDIENTS_URL)
I have one recipe app, but there are no "recipe-list" anywhere else.
Ingredient endpoint is defined with a router in the view.py :
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('ingredients', views.IngredientViewSet)
And the serializer.py
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = ['id', 'name']
read_only_fields = ['id']
So I wonder how the reverse is working in this case
EDIT 1
I guess the reverse("recipe:ingredient-list") it related with viewsets.ModelViewSet and would inherit of my views.py :
class IngredientViewSet(mixins.ListModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):

Related

Django REST error when serializing uploaded image

I'm getting a serializer error:
"Upload a valid image. The file you uploaded was either not an image
or a corrupted image"
When trying to serialize an uploaded image
My code:
Models:
class Post(models.Model):
text = models.TextField(max_length=10000)
class Image(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
image = models.ImageField(blank=False, null=False, upload_to='test')
View:
class CreateNewPostAPIView(generics.CreateAPIView):
serializer_class = serializers.NewPostSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
if self.request.data.get('image', None):
context['image'] = self.request.data.pop('image', None)
return context
Serializers:
class NewPostSerializer(serializers.ModelSerializer):
def create(self, validated_data):
post = Post.objects.create(**validated_data)
image = self.context['image'][0] # working with only one image for now
# image is of type <class'django.core.files.uploadedfile.InMemoryUploadedFile'>
serializer = ImageSerializer(data={'post': post.pk, 'image': image})
is_valid = serializer.is_valid() # it's always False!
return post
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('post', 'image')
I already tried doing that without a serializer and it's working with this code:
class NewPostSerializer(serializers.ModelSerializer):
def create(self, validated_data):
post = Post.objects.create(**validated_data)
image = self.context['image']
Image.objects.create(post=post.pk, image=image)
return post
But I need to do it using a serializer.
The easy way for this would be like:
Models:
from django.db import models
# Create your models here.
class Post(models.Model):
text = models.TextField()
def __str__(self):
return self.text[:50]
class Image(models.Model):
post = models.ForeignKey(
Post, on_delete=models.CASCADE,
related_name='images', editable=False
)
image = models.ImageField(upload_to='images/')
def __str__(self):
return self.image.name
def delete(self, *args, **kwargs):
self.image.delete()
super().delete(*args, **kwargs)
Serializer:
from rest_framework import serializers
from .models import Post, Image
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = "__all__"
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = "__all__"
class PostWithImageSerializer(serializers.ModelSerializer):
image = serializers.ImageField(write_only=True)
# This is for the GET request or the response of the POST request
# We can also work with a separate serializer for such cases
images = ImageSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = "__all__"
def create(self, validated_data):
image_data = validated_data.pop('image')
post = Post.objects.create(**validated_data)
Image.objects.create(post=post, image=image_data)
return post
Views as:
from rest_framework.response import Response
from rest_framework import status
from .serializers import *
# Create your views here.
class AddPostWithAnImageView(APIView):
serializer_class = PostWithImageSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
For testing purposes, please use Postman where you can upload images with so much ease. The request form should only require the text and the image fields.
Thanks to #pKiran, who gave me an idea, now I have a working code for saving one or more images. It's, also, quite concise and readable. There's quite a lot going on here with so little of code. Django REST is quite confusing.
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = ('image', 'thumb')
class NewPostSerializer(serializers.ModelSerializer):
image = serializers.ListField(child=serializers.ImageField(), write_only=True, required=False)
def create(self, validated_data):
images = validated_data.pop('image', None)
post = Post.objects.create(**validated_data)
if images:
img_models = [Image(post=post,
image=image, thumb=make_thumb(image))
for image in images]
Image.objects.bulk_create(img_models)
return post

How represent a many to many relationship with django rest framework using Hyperlinking

I have two models in a 'many to many' relationship Video and Genre. I want to represent them in my API using the Django Rest Framework and with Hyperlinking. I have tried using the the nested relationships solution that I have found over the internet but it brings me an error for the list viewss eg genres/ and videos/ as described below.
models.py
class VideoGenre(models.Model):
video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='genres')
genres = models.ForeignKey(Genre, on_delete=models.CASCADE, related_name='videos')
class Meta:
unique_together = ['video', 'genre']
class Video(models.Model):
genres = models.ManyToManyField(Genre, related_name="videos", blank=True, through='VideoGenre')
title = serializers.CharField(default=serializers.CurrentUserDefault())
class Genre(models.Model):
name = models.CharField(max_length=20)
descr = models.CharField(max_length=255)
serializers.py
class GenreSerializer(serializers.HyperlinkedModelSerializer):
videos = serializers.HyperlinkedIdentityField(many=True, view_name="video-detail")
class Meta:
model = ku_api_models.Genre
fields = "__all__"
class VideoSerializer(serializers.HyperlinkedModelSerializer):
genres = GenreSerializer(source='genre_set', many=True, read_only=True)
class Meta:
model = ku_api_models.Video
fields = "__all__"
class VideoGenreSerializer(serializers.HyperlinkedModelSerializer):
video = serializers.ReadOnlyField(source='video.id')
genre = serializers.ReadOnlyField(source='genre.id')
class Meta:
model = ku_api_models.VideoGenre
fields = "__all__"
unique_together = ['video', 'genre']
views.py
class VideoDetailView(RetrieveUpdateDestroyAPIView):
parser_classes = (MultiPartParser,)
serializer_class = VideoSerializer
queryset = Video.objects.all()
def perform_update(self, serializer):
serializer.save(owner=self.request.user)
class VideoListView(ListCreateAPIView):
parser_classes = (MultiPartParser,)
serializer_class = VideoSerializer
queryset = Video.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class GenreListView(ListCreateAPIView):
serializer_class = GenreSerializer
queryset = Genre.objects.all()
class GenreDetailtView(RetrieveUpdateDestroyAPIView):
serializer_class = GenreSerializer
queryset = Genre.objects.all()
urls.py
path('videos/', views.VideoListView.as_view(), name='video-list'),
path('videos/<int:pk>', views.VideoDetailView.as_view(), name='video-detail'),
path('genres/', views.GenreListView.as_view(), name="genre-list"),
path('genres/<int:pk>', views.GenreDetailtView.as_view(), name="genre-detail"),
I am getting below exception on the browsable API renderer
Exception Type: ProgrammingError at /api/genres/
Exception Value: relation "ku_api_videogenre" does not exist
LINE 1: ..._video"."owner_id" FROM "ku_api_video" INNER JOIN "ku_api_vi...
I have looked into these below links as referred above
django rest framework serializing many to many field
many to many relationship through an intermidiate model
serializing many to many field
... and tried them but getting the same/almost the same, only different is the relation name that "does not exist"
What am I missing? How to make it right? Thanks
I have found a way to this:
Changes will be in the serializers.py
serializers.py
class VideoSerializer(serializers.HyperlinkedModelSerializer):
genres = serializers.HyperlinkedRelatedField(many=True, read_only=False, view_name='genre-detail', queryset=ku_api_models.Genre.objects.all())
class Meta:
model = ku_api_models.Video
#fields = ('url', 'id', 'datetime_added', 'title', 'video_file', 'owner')
fields = "__all__"
def create(self, validated_data):
genres = validated_data.pop('genres', [])
video_instance = ku_api_models.Video.objects.create(**validated_data)
video_instance.genres.set(genres)
return video_instance
def update(self, instance, validated_data):
genres = validated_data.pop('genres', [])
instance.genres.set(genres)
instance.save()
return instance
class VideoGenreSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ku_api_models.VideoGenre
fields = "__all__"
class GenreSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ku_api_models.Genre
fields = "__all__"
Source: Docs: intermediary-manytomany

Django REST foreign key issue

Trying to implement and test my own serializer, I have the following issue:
Searched for many question including this error message, I didn't manage to find any solution to my problem.
DRF Foreign key misusage?
ValueError: Cannot assign "'effcad53-bc45-41fa-be43-4f22c0376eb5'": "Product.related_workspace" must be a "Workspace" instance.
The Workspace class:
class Workspace(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_login = models.ForeignKey(
Login,
on_delete=models.PROTECT,
)
description = models.CharField(
max_length=150,
)
def __str__(self):
login = Login.objects.get(pk=self.related_login_id)
return f'{login.username} ({self.description})'
class Meta:
db_table = 'workspaces'
The Product class:
class Product(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_workspace = models.ForeignKey(
Workspace,
on_delete=models.PROTECT,
)
code = models.CharField(
max_length=50,
)
description = models.CharField(
max_length=150,
)
class Meta:
db_table = 'products'
unique_together = ('related_workspace', 'code',)
The ProductSerializer class:
class ProductSerializer(serializers.Serializer):
id = serializers.UUIDField(read_only=True)
related_workspace = serializers.UUIDField()
code = serializers.CharField()
description = serializers.CharField()
def create(self, validated_data):
return Product.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.related_workspace = validated_data.get('related_workspace', instance.related_workspace)
instance.code = validated_data.get('code', instance.code)
instance.description = validated_data.get('description', instance.description)
instance.save()
return instance
The script I'm using to test my serializer :
PROJECT_NAME = 'api'
def main():
#
from api.applications.product.models import Product
from api.applications.product.serializers import ProductSerializer
#
# Create a product
#
code = 'P001'
description = f'Product {code}'
#
# This is a valid workspace id!
#
related_workspace = 'effcad53-bc45-41fa-be43-4f22c0376eb5'
#
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
product.save()
#
serializer = ProductSerializer(product)
print('serializer.data:', serializer.data)
if __name__ == '__main__':
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '%s.settings' % PROJECT_NAME)
import django
django.setup()
main()
Any advice of what I'm missing ?
First thing, when you are testing anything in Django its easiest to write test classes in a test module and use manage.py to run them. All that setup logic you are doing is already in manage.py.
python manage.py test my_app.tests.my_test_class
In your serializer you are setting a foreign key to related_workspace = serializers.UUIDField(), instead you want to nest your serializers (docs) so it pulls out the Workspace data, serializes, saves, and returns a Workspace object. You should have two serializers and they should look something like this, I am going to use ModelSerializer.
from rest_framework import serializers
# Workspace Serializer
class WorkspaceSerializer(serializers.ModelSerializer):
class Meta:
model = Workspace
fields = "__all__"
class ProductSerializer(serializers.ModelSerializer):
related_workspace = WorkspaceSerializer()
class Meta:
model = Product
fields = ["id", "related_workspace", "code", "description"]
def create(self, validated_data):
workspace_data = validated_data.pop('related_workspace')
workspace_serializer = WorkspaceSerializer(data=workspace_data)
if workspace_serializer.is_valid(reaise_exception=True):
workspace = workspace_serializer.save()
product = Product.objects.create(related_workspace = workspace, **validated_data)
return product
We want to remove the workspace data, then feed it to its own serializer, if that data is valid, we save the serializer and we get a Workspace object in return. Then we can create our Product object with that returned workspace object.
Searching deeper, my mistake was in the way I was creating my Product instance...
In my test script, I just updated:
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
To:
product = Product(
code=code,
description=description,
related_workspace=Workspace.objects.get(pk=related_workspace)
)

DRF docs - getting serializers dynamically

I have a viewset which uses different serializers for different HTTP methods (and possibly even for different users). DRF docs says this:
To be compatible with this behaviour methods (such as get_serializer or
get_serializer_class etc.) which inspect self.request or, particularly,
self.request.user may need to be adjusted to handle this case.
Can anyone provide me an example of the "adjustment to handle this case"?
Say I have a viewset which is using serializer A for get method for admins, serializer B for get method for all other users and serializer C for all other methods. How to specify this so that the documentation understands this? I am using the builtin one.
My urls.py:
router = DefaultRouter()
router.register('test', SampleViewset, base_name="test")
urlpatterns = [
path('admin/', admin.site.urls),
path('docs/', include_docs_urls(title='My API title', public=True)),
path('', include(router.urls)),
]
Minimal example
models.py
class SampleModel(models.Model):
name = models.CharField(max_length=120)
age = models.IntegerField()
address = models.CharField(max_length=100)
serializers.py
class SampleSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = SampleModel
class SampleSerializerCreate(serializers.ModelSerializer):
age = serializers.IntegerField(max_value=20)
class Meta:
fields = '__all__'
model = SampleModel
views.py
class SampleViewset(ModelViewSet):
serializer_class = SampleSerializer
queryset = SampleModel.objects.all()
def get_serializer_class(self):
if self.action=='create':
return SampleSerializerCreate
return SampleSerializer
Here I used a logic that, while creation of SampleModel, the age attribute should not exceed a max_value of 20.
Update-1
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'sample', SampleViewset)
urlpatterns = [
path('docs/', include_docs_urls(title='My API title', public=True, patterns=router.urls)),
] + router.urls

django rest framework many to many serialize and apiview

I am creating APIView for book list and add, delete.
Is this serialization the right way?
I want to show a list of books, but I do not know what to put in '?'.
member/models.py
class MyUser(AbstractBaseUser, PermissionsMixin):
username = models.EmailField(unique=True, max_length=256)
nickname = models.CharField(max_length=256)
is_staff = models.BooleanField(default=False)
created_date = models.DateTimeField(auto_now_add=True)
mybook = models.ManyToManyField(Book)
book/models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=256)
author = models.CharField(max_length=200)
def __str__(self):
return self.title
book/seralizers.py
from rest_framework import serializers
from book.models import Book
class MyBookSerializer(serializers.ModelSerializer):
mybook = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Book
fields = (
'title',
'author',
)
apiview.py
class MyBook(APIView):
def get(self, request):
mybook = Book.objects.filter(myuser=?)
serializer = MyBookSerializer(mybook)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request):
pass
def delete(self, request):
pass
If you want create a CRUD for model, depends on a request.user you can simply use ModelViewSet:
from rest_framework import viewsets
class MyBookViewSet(viewsets.ModelViewSet):
serializer_class = MyBookSerializer
def get_queryset(self, request):
return Book.objects.filter(myuser=self.request.user)
That's all for view. You only need to specify router for a viewset.
urls.py:
from rest_framework import routers
router = routers.DefaultRouter()
router.register('books', MyBookViewSet)
urlpatterns = []
urlpatterns += router.urls
Include this usl file in a root urls and you are done.
You also need to specify related_name for m2m field if you want to use myuser name:
mybook = models.ManyToManyField(Book, related_name='myuser')
And you don't need this field in a serializer:
mybook = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
viewsets docs
routers docs

Resources