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')
Related
I have two models: the first is named Card and has a foreign key (named owner) to the second model (User). I'm trying to write a view that lists all the Cards owned by a User, the client selects the User by changing the URL (not query parameters).
For example, if I make a GET request to /cards/by-user-id/42/, I should get all the Cards owned by the User whose id is 42.
From what I understood, it is possible to achieve this by using an URL pattern like this : path('cards/by-user-id/<int:user_id>', my_view.as_view()) and then in the viewset I can use self.kwargs['user_id'] to get the id and then filter the data. However, I can't find how to do it using routers (more appropriated for djangorestframework). I tried with <int:user_id> in the prefix but it does not pick it up, it just considers it as a normal string, so not only the URL is incorrect (I have to type literally /cards/by-user-id/<int:user_id>/) but also kwargs doesn't contain 'user_id'.
Here is what I tried:
models.py:
from django.contrib.auth.models import User
from django.db import models
class Card(models.Model):
owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
serializers.py:
from rest_framework import serializers
from . import models
class CardSerializer(serializers.ModelSerializer):
owner = serializers.StringRelatedField()
class Meta:
model = models.Card
fields = ['owner', 'id']
views.py:
from rest_framework import viewsets
from . import models, serializers
class ListCardsPerUser(viewsets.ModelViewSet):
serializer_class = serializers.CardListSerializer
def get_queryset(self):
models.CardList.objects.filter(owner__id=self.kwargs['user_id'])
urls.py:
from django.urls import path, include
from rest_framework import routers
from . import views
router = routers.SimpleRouter()
router.register(r'cards/by-user-id/<int:user_id>/', views.ListCardsPerUser, basename='cards_list')
urlpatterns = [
path('', include(router.urls)),
]
I'm starting to wonder if it is even possible with routers...
Django version: 4.1 and DRF: 3.14
you should just edit your user serializer
class CardASerializer(serializers.ModelSerializer):
class Meta:
model = Card
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
cards = CardASerializer(many=True)
class Meta:
model = User
fields = "__all__"
Note that the cards is related_name
class Card(models.Model):
owner = models.ForeignKey(User, null=True,related_name="cards", on_delete=models.SET_NULL)
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'))
]
I am having my models.py file defined as below:-
from django.db import models
from django.contrib.auth.models import User
class Custom_User(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
mobile = models.CharField(max_length=20)
REGISTRATION_CHOICES = (
('Learner', 'Learner'),
('Trainer', 'Trainer'),
)
primary_registration_type = models.CharField(max_length=15, choices=REGISTRATION_CHOICES)
def __str__(self):
return self.user.email
As you can see that my Custom_User model uses Django's User model as its foreign Key.
For the above model I have defined my serialziers.py file like this:-
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import *
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email')
class Custom_UserSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Custom_User
fields = ('__all__')
Now I am using this serializers in my viewsets like below:-
from django.contrib.auth.models import User
from rest_framework import viewsets
from .serializers import *
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
class Custom_UserViewSet(viewsets.ModelViewSet):
queryset = Custom_User.objects.all()
serializer_class = Custom_UserSerializer
class TrainerViewSet(viewsets.ModelViewSet):
queryset = Custom_User.objects.filter(primary_registration_type="Trainer")
serializer_class = Custom_UserSerializer
class LearnerViewSet(viewsets.ModelViewSet):
queryset = Custom_User.objects.filter(primary_registration_type="Learner")
serializer_class = Custom_UserSerializer
And Finally inside my urls.py file I register them as below:-
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'users', api_mailing_list_views.UserViewSet)
router.register(r'custom_users', api_mailing_list_views.Custom_UserViewSet)
router.register(r'trainers', api_mailing_list_views.TrainerViewSet)
router.register(r'learners', api_mailing_list_views.LearnerViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('', mailing_list_views.index, name='index'),
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
As I was expecting the urls list to be like below in my browser:
{
"users": "http://localhost:8080/api/users/",
"custom_users": "http://localhost:8080/api/custom_users/",
"trainers": "http://localhost:8080/api/trainers/",
"learners": "http://localhost:8080/api/learners/"
}
But what i get instead is a list of urls like this:-
{
"users": "http://localhost:8080/api/users/",
"custom_users": "http://localhost:8080/api/trainers/",
"trainers": "http://localhost:8080/api/trainers/",
"learners": "http://localhost:8080/api/trainers/"
}
However I am not getting any errors or if I visit the following url:-
http://localhost:8080/api/learners/
which is not showing up in the urls list I still get the filtered list of learners in JSON format.
Thanks for the help in advance.
you need to provide basename during router register as all of them actually from same custom_user model.
router = routers.DefaultRouter()
router.register(r'users', api_mailing_list_views.UserViewSet, basename='users')
router.register(r'custom_users', api_mailing_list_views.Custom_UserViewSet, basename='custom_user')
router.register(r'trainers', api_mailing_list_views.TrainerViewSet, basename='trainers')
router.register(r'learners', api_mailing_list_views.LearnerViewSet, basename='learners')
Django-rest-framework's router tries to identify the viewset by its model/queryset, since both viewsets use the same model things most likely get mixed up.
From the documentation:
If unset the basename will be automatically generated based on the queryset attribute of the viewset, if it has one. Note that if the viewset does not include a queryset attribute then you must set basename when registering the viewset.
Try providing a basename to the router:
router.register(r'custom_users', api_mailing_list_views.Custom_UserViewSet, basename='custom_users')
router.register(r'trainers', api_mailing_list_views.TrainerViewSet, basename='trainers')
router.register(r'learners', api_mailing_list_views.LearnerViewSet, basename='learners')
I am new to Django Rest Framework and am struggling to get my serialisations to work correctly for a foreignkey relationship between two models. I have tried to reduce my setup down to be as simple as possible but I still can't understand how it is supposed to work. I am trying to use HyperlinkedModelSerializer so (from the docs) 'that it uses hyperlinks to represent relationships'. When I try to visit the url for either the list or detail view for {model X} on the test server I get:
'Could not resolve URL for hyperlinked relationship using view name
"{model Y}-detail". You may have failed to include the related model
in your API, or incorrectly configured the lookup_field attribute on
this field.'
What am I doing wrong?
My models:
from django.db import models
class Project(models.Model):
name = models.CharField(max_length=50)
description = models.TextField()
class ProjectPhoto(models.Model):
project = models.ForeignKey(
Project, related_name='photos', on_delete=models.CASCADE
)
image = models.ImageField()
caption = models.CharField(max_length=100)
date_added = models.DateTimeField(auto_now_add=True)
My serializers
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Project
fields = ('name', 'description', 'photos')
class ProjectPhotoSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ProjectPhoto
fields = ('image', 'caption', 'date_added', 'project'))
My views:
from rest_framework import viewsets
from projects.models import Project, ProjectPhoto
from projects.serializers import ProjectSerializer, ProjectPhotoSerializer
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all().order_by('name')
serializer_class = ProjectSerializer
class ProjectPhotoViewSet(viewsets.ModelViewSet):
queryset = ProjectPhoto.objects.all().order_by('date_added')
serializer_class = ProjectPhotoSerializer
EDIT:
My urls:
from django.conf.urls import url, include
from rest_framework import routers
from projects import views
router = routers.DefaultRouter()
router.register(r'^projects', views.ProjectViewSet)
router.register(r'^project-photos', views.ProjectPhotoViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
these are then added in my main app urls.py file. I don't think this is problem as if I change the serializer to ModelSerializer then everything works fine.
I think your problem is in your urls.py file, see the code and picture
rest/urls.py file
from django.conf.urls import url, include
from rest_framework import routers
from .views import ProjectViewSet, ProjectPhotoViewSet
router = routers.SimpleRouter()
router.register(r'project', ProjectViewSet)
router.register(r'project-photo', ProjectPhotoViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
Principal urls.py file:
from django.conf.urls import url, include
from django.contrib import admin
from rest import urls as urls_rest
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^rest/', include(urls_rest)),
]
and other option, try to use this code in your serializers.py file:
from rest_framework import serializers
from .models import Project, ProjectPhoto
class ProjectPhotoSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectPhoto
fields = ('image', 'caption', 'date_added', 'project')
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('name', 'description', 'photos')
depth = 2
You have 3 options to use serializers (see picture below)
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')),
)