When writing tests for Django Rest Framework, how are hyperlinked relations supposed to be created?
I'm trying to create a Foo object with a related Bar object, but I'm not sure how I can most efficiently create that relation.
# models.py
import uuid
from django.db import models
class Foo(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
bar = models.ForeignKey('Bar')
class Bar(models.Model):
BarUUID = models.UUIDField(primary_key=True, default=uuid.uuid4
# serializers.py
from rest_framework import serializers
from myapp.models import Bar, Foo
class FooSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Foo
fields = ('url', 'id', 'bar')
class BarSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Bar
fields = ('url', 'BarUUID')
# views.py
class FooViewSet(viewsets.ModelViewSet):
queryset = Foo.objects.all()
serializer_class = FooSerializer
class BarViewSet(viewsets.ModelViewSet):
queryset = Bar.objects.all()
serializer_class = BarSerializer
# urls.py
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'foos', views.FooViewSet)
router.register(r'bars', views.BarViewSet)
# tests.py
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
class FooTests(APITestCase):
def test_create_foo(self):
bar = Bar.objects.create()
url = reverse('foo-list')
data = {
'bar': # how do I get the URL?
}
response = self.client.post(url, data, format='json')
You can use the serializer to get it.
Like this:
from django.test.client import RequestFactory
from your_app.serializers import BarSerializer
context = {'request': RequestFactory().get('/')}
bar_serializer = BarSerializer(bar, context=context)
bar_serializer.data['url']
Straight forward way, without serializer:
url = 'http://testserver' + reverse('bar-detail', kwargs={'pk': bar.pk})
Related
I have 2 models named Recipe and Step..
I have serialized both to make an API for GET request.. I want to know is there a way to create for POST request so that I can send both the data (steps and recipe) in the same request?
models.py:
from django.db import models
class Recipe(models.Model):
title = models.CharField( max_length=50)
uuid = models.CharField( max_length=100)
def __str__(self):
return f'{self.uuid}'
class Step(models.Model):
step = models.CharField(max_length=300)
uuid = models.ForeignKey(Recipe, on_delete=models.CASCADE)
def __str__(self):
return f'{self.step} - {self.uuid}'
serializers.py:
from rest_framework import serializers
from .models import *
class RecipeSerializer(serializers.ModelSerializer):
class Meta:
model = Recipe
fields = ['title', 'uuid']
class StepSerializer(serializers.ModelSerializer):
class Meta:
model = Step
fields = ['step', 'uuid']
views.py:
from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import *
from .models import *
#api_view(['GET'])
def apiOverview(request):
api_urls = {
'List':'/recipe-list/',
'Detail View':'/recipe-detail/<str:pk>/',
'Create':'/recipe-create/',
'Update':'/recipe-update/<str:pk>/',
'Delete':'/recipe-delete/<str:pk>/',
'Steps' : '/steps/<str:pk>'
}
return Response(api_urls)
#api_view(['GET'])
def recipeList(request):
recipes = Recipe.objects.all()
serializer = RecipeSerializer(recipes, many=True)
return Response(serializer.data)
#api_view(['GET'])
def recipeDetail(request, pk):
recipe = Recipe.objects.get(uuid=pk)
recipe_serializer = RecipeSerializer(recipe, many=False)
steps = Step.objects.filter(uuid=pk)
steps_serializer = StepSerializer(steps, many=True)
return Response({
'recipe' : recipe_serializer.data,
'steps' : steps_serializer.data
})
How can I create a view for POST and handle both the models?
Try:
from rest_framework import generics
from .models import *
class StepAndRecipe(generics.CreateAPIView):
queryset = Step.objects.all()
queryset = Recipe.objects.all()
serializer_class = StepSerializer
serializer_class = RecipeSerializer
Add in urls.py:
from django.urls import path
from .views import StepAndRecipe
urlpatterns = [
path('steprecipepost', StepAndRecipe.as_view(), name='steps_recipes')
This will only work with POST! And one more thing: take care with the raw data and the HTML form, maybe theses get a little confused since you are using two models in the same view.
I am using Django 2.2, MongoDb with Djongo. I am facing issues in POST and PATCH API. I am facing three kinds of issues.
When performing a POST operation to create a new entry, API fails with error: Array items must be Model instances.
What is the correct way to refer an instance of Screenplay class in POST API. Is the id of the parent class sufficient?
How to perform a update to a specific field in Scene model including a text field in comments?
Following is the code snippet.
Sample POST API data
{
"title": "intro1",
"screenplay": "49423c83-0078-4de1-901c-f9176b51fd33",
"comments": [
{
"text": "hello world",
"author": "director"
}
]
}
models.py
import uuid
from djongo import models
class Screenplay(models.Model):
id = models.UUIDField(primary_key = True, default = uuid.uuid4,editable = False)
title = models.CharField(max_length=100)
def __str__(self):
return self.title
class Comment(models.Model):
text = models.TextField();
author = models.TextField();
def __str__(self):
return self.author +self.text
class Scene(models.Model):
id = models.UUIDField(primary_key = True, default = uuid.uuid4,editable = False)
title = models.CharField(max_length=100)
screenplay = models.ForeignKey(Screenplay, related_name='scenes', on_delete=models.CASCADE)
comments = models.ArrayModelField(
model_container = Comment,
);
def __str__(self):
return self.title
serializers.py
from rest_framework import serializers
from planning.models import Scene, Comment
class ScreenplaySerializer(serializers.ModelSerializer):
class Meta:
model = Screenplay
fields = ('id', 'title')
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Comment
fields = ('text', 'author')
class SceneSerializer(serializers.HyperlinkedModelSerializer):
comments = CommentSerializer();
class Meta:
model = Scene
fields = ('id', 'title', 'comments')
viewsets.py
from planning.models import Screenplay, Scene, Comment
from .serializers import ScreenplaySerializer, SceneSerializer, CommentSerializer
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
from rest_framework import generics
from rest_framework.generics import RetrieveUpdateDestroyAPIView
class ScreenplayViewSet(viewsets.ModelViewSet):
queryset = Screenplay.objects.all()
serializer_class = ScreenplaySerializer
class SceneViewSet(viewsets.ModelViewSet):
queryset = Scene.objects.all()
serializer_class = SceneSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
I suggest you read the documentation on Writable nested representations, it will help to dissipate your doubts.
This question seems to be asked before but none of the answers I came across solve my issue.
I'm getting the following error when I try running the server with python manage.py runserver:
django.core.exceptions.ImproperlyConfigured: The included URLconf 'tutorial.urls' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.
The error goes away if I change models.py so that my Item class does not extend models.Model.
These are the relevant files:
models.py
from django.db import models
from django.contrib.auth.models import User
class Item(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
price = models.DecimalField(decimal_places=2)
def __str__(self):
return self.name
serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User, Group
from .models import Item
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('url', 'name', 'owner', 'price')
views.py
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from .serializers import UserSerializer, GroupSerializer, ItemSerializer
from .models import Item
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Group.objects.all()
serializer_class = GroupSerializer
class ItemViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows items to be viewed or edited.
"""
queryset = Item.objects.all()
serializer_class = ItemSerializer
urls.py
from django.urls import include, path
from rest_framework import routers
from tutorial.quickstart import views
router = routers.DefaultRouter()
router.register('users', views.UserViewSet)
router.register('groups', views.GroupViewSet)
router.register('items', views.ItemViewSet)
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Overview
I'm setting up a new Django application with Django REST Framework (DRF), and this is my first time using the HyperlinkedModelSerializer for the API endpoint.
I've overridden the get_queryset() method on the ModelViewSet, so I've also the basename argument to the application router and explicitly defined the url attribute in the serializer as suggested here. This fixed issues that I was having with the model's own url attribute.
However, I'm getting the following error message when trying to serialize a ForeignKey field of the same class as the parent model. It fails with the following message:
Could not resolve URL for hyperlinked relationship using view name "employee-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
Is there something special in the serializer I need to do to support using recursive model relationships like this?
Example code
# app/models.py
from django.db import models
class AbstractBase(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Employee(AbstractBase):
name = models.CharField(max_length=254)
manager = models.ForeignKey('self', related_name='direct_reports',
on_delete=models.SET_NULL, blank=True, null=True)
...
def __str__(self):
return str(self.name)
# app/views.py
from rest_framework import viewsets
from rest_framework.pagination import PageNumberPagination
from app import models
from app import serializers
# pagination defaults
class StandardResultsSetPagination(PageNumberPagination):
page_size = 25
page_size_query_param = 'page_size'
max_page_size = 1000
class EmployeeViewSet(viewsets.ModelViewSet):
pagination_class = StandardResultsSetPagination
serializer_class = serializers.EmployeeSerializer
http_method_names = ['options', 'get']
def get_queryset(self):
params = self.request.query_params
queryset = models.Employee.objects.all()
# apply url query filters...
return queryset
# app/serializers.py
from app import models
from rest_framework import serializers
class EmployeeSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
read_only=True, view_name='employees-detail')
manager = serializers.HyperlinkedRelatedField(
read_only=True, view_name='employees-detail')
class Meta:
model = models.Employee
fields = ('url', 'name', 'manager')
# project/urls.py
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include('app.urls')),
]
# app/urls.py
from django.conf.urls import url, include
from rest_framework import routers
from app import views
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'employees', views.EmployeeViewSet, basename='employees')
urlpatterns = [
url(r'^', include(router.urls)),
]
I have no idea why this worked, but changing the inherited serializer from serializers.HyperlinkedModelSerializer to serializers.ModelSerializer and removing the overridden url fixed things perfectly.
New serializer looks like this:
# app/serializers.py
from app import models
from rest_framework import serializers
class EmployeeSerializer(serializers.ModelSerializer):
manager = serializers.HyperlinkedRelatedField(
read_only=True, view_name='employees-detail')
class Meta:
model = models.Employee
fields = ('url', 'name', 'manager')
I am currently using the default CRUD operations provided by django-rest-framework. It works well with normal models but one of my model has many-many relation with another tag model. Here is the code for models
class ActivityType(models.Model):
title = models.CharField(max_length=200)
slug = models.CharField(max_length=250,unique=True)
def __unicode__(self):
return self.slug
class Activity(models.Model):
owner = models.ForeignKey('auth.user')
title = models.CharField(max_length=200)
slug = models.CharField(max_length=250,unique=True)
description = models.TextField()
tags = models.ManyToManyField(ActivityType)
created = models.DateTimeField(auto_now_add=True, blank=True)
def __unicode__(self):
return self.slug
What i want to know is what is the best method to integrate DRF with the same, if possible without writing all CRUD operations from scratch.
In your serializers.py
from rest_framework import serializers
from rest_framework import generics
from models import Activity
from models import ActivityType
class ActivityTypeSerializer(serializers.ModelSerializer):
class Meta:
model = ActivityType
fields = ('id', 'title', 'slug')
class ActivitySerializer(serializers.ModelSerializer):
tags = ActivityTypeSerializer(many=True, read_only=True)
class Meta:
model = Activity
fields = ('id', 'owner', 'title', 'slug', 'description', 'tags', 'created')
in your views.py
from rest_framework import viewsets
from serializers import ActivitySerializer
from serializers import ActivityTypeSerializer
from models import Activity
from models import ActivityType
class ActivityViewSet(viewsets.ModelViewSet):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
class ActivityTypeViewSet(viewsets.ModelViewSet):
queryset = ActivityType.objects.all()
serializer_class = ActivityTypeSerializer
and in your urls.py
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework import routers, serializers, viewsets
from rest_framework import generics
from rest_framework import viewsets, routers
from your_app.views import ActivityTypeViewSet
from your_app.views import ActivityViewSet
router = routers.DefaultRouter()
router.register(r'activitytypes', ActivityTypeViewSet)
router.register(r'activities', ActivityViewSet)
Also make sure the restframework urls are included as described in docs
urlpatterns = patterns('',
# your other urls
url(r'^api/$', include('rest_framework.urls', namespace='rest_framework')),
url(r'api/accounts/', include('rest_framework.urls', namespace='rest_framework')),
)