HyperlinkedRelatedField DRF does not work with a ViewSet - python

I have these models:
class ExamSheet (models.Model):
pass
class Exam(models.Model):
exam_sheet = models.ForeignKey('myapp.ExamSheet',
related_name='exams',
)
Serializer:
class ExamBaseSerializer(serializers.ModelSerializer):
exam_sheet = serializers.HyperlinkedRelatedField(queryset=ExamSheet.objects.all(), view_name='examsheet-detail')
class Meta:
model = Exam
fields = ('id', 'user', 'exam_sheet', )
read_only_fields = ('id', 'user',)
ViewSets:
class ExamViewSet(MultiSerializerViewSet):
queryset = Exam.objects.all()
class ExamSheetViewSet(MultiSerializerViewSet):
queryset = ExamSheet.objects.all()
Routes:
app_name = 'exams_api'
router = DefaultRouter()
router.register(r'exams', views.ExamViewSet)
router.register(r'exams_sheets', views.ExamSheetViewSet)
urlpatterns = []
urlpatterns += router.urls
Global app urls:
urlpatterns = [
path('api/', include('exams_api.urls')),
]
GenericViewSet:
class MultiSerializerViewSet(viewsets.ModelViewSet):
serializers = {
'default': None,
}
def get_serializer_class(self):
return self.serializers.get(self.action, self.serializers['default'])
But this throws me an error:
ImproperlyConfigured at /api/exams/
Could not resolve URL for hyperlinked relationship using view name
"examsheet-detail". You may have failed to include the related model
in your API, or incorrectly configured the lookup_field attribute on
this field.
How can I use HyperlinkedRelatedField to show a link to a related model in my serializer?

The view_name you set for the hyperlink field view_name='examsheet-detail, do you have a view named examsheet-detail in your urlconf(mostly urls.py) for that app? If not, after include(views.someview), add ,name='examsheet-detail' as a third parameter to path.
Have you set app_name in the specific app's urlsconf? Removing it solved the issue for me.
Make sure <int:pk> in the url path for that view and not <int:id> or something else. Else, set lookup_field='id' cos the default is pk.
Make sure that view takes in the pk parameter.

Related

How can I get Django Rest Framework to work with Django Tenants and React?

Here is my setup:
settings.py
SHARED_APPS = (
'django_tenants',
'main',
other apps...
)
TENANT_APPS = (
'rest_framework',
'company',
)
MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware',
other middleware...
]
DATABASE_ROUTERS = (
'django_tenants.routers.TenantSyncRouter',
)
urls.py
from django.urls import include, path
from rest_framework import routers
# other imports
from main.api.v1 import projects
router = routers.DefaultRouter()
router.register(r'api/v1/project', projects.ProjectViewSet)
urlpatterns = [
-- other paths --
path('', include(router.urls)),
]
api/v1/project.py
# other imports
from company.models import Project
from rest_framework import serializers
from rest_framework import viewsets
from rest_framework import permissions
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Project
fields = ['url', 'name', 'keycode']
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all().order_by('id')
serializer_class = ProjectSerializer
permission_classes = [permissions.AllowAny]
main.models.py
from django.contrib.auth.models import User as AuthUser
from django_tenants.models import TenantMixin, DomainMixin
# note, definition of custom "User" model which has an AuthUser 1 to 1 relationship
class Company(TenantMixin):
name = models.CharField(max_length=100)
subdomain = models.CharField(max_length=32)
employees = models.ManyToManyField(User, related_name='companies')
migration_id = models.IntegerField(null=True)
class Domain(DomainMixin):
pass
company.models.py
from django.db import models
class Project(models.Model):
name = models.CharField(max_length=100)
keycode = models.CharField(max_length=8)
And the final detail is that I am not using a Django frontend but rather one created in React. The request which is going to the backend is just a standard request however it is coming from a subdomain and includes a JWT token (but I don't know if that is relevant), here is a shortened version of the request headers:
Request URL: http://localhost:8000/api/v1/project/
Request Method: GET
Authorization: Bearer <token here>
Origin: http://cbd.localhost:3000
The error that I am seeing on the backend is this:
relation "company_project" does not exist
My guess is that this is happening due to the fact that when this query is created in the ProjectViewSet:
queryset = Project.objects.all().order_by('id')
The request is not done within the context of a tenant. But my question is how exactly would that be done. I see in Django Tenants that there is a construct that looks like this:
with tenant_context(tenant):
# All commands here are ran under the schema from the `tenant` object
But I have no idea how I would go about getting the 'tenant' parameter in the class definition where it seems to be needed.
Any ideas?
Well, I figured out one way to do it, but it's ugly.
in my settings.py file I added a new constant:
BASE_TENANT = "cbd_co"
I set this to my first tenant (in my system there will be a default tenant schema which is not actually owned by a client but is a sort of template)
Then, in my model viewset declaration i did this:
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.raw('SELECT * FROM "' + settings.BASE_TENANT + '"."company_project";')
serializer_class = ProjectSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self):
schema = self.request.META['HTTP_ORIGIN'].split("//")[1].split('.')[0] + '_co'
qs = Project.objects.raw('SELECT * FROM "' + schema + '"."company_project" ORDER BY "company_project"."id" ASC;')
return qs
I'm still hoping someone suggests a better solution though...
Notes on this solution. As it is, it is completely insecure. What I will actually have to do is get the authorization header from the request, verify the JWT token, get the user, get all the valid user companies (which also determines the valid subdomains and therefore the valid schemas. and only then would I return the queryset.

How do I get a hyperlinked foreign key in Django REST Framework serializer?

I am trying to serialize a model in my DRF project. The model contains 2 foreign key fields and some simple data type fields. One of the foreign key fields is to my CustomUser model and this field appears as a hyperlink in my API output which is expected. The other foreign key field is to a different model (shown below). I have been cycling through the same few errors over the past several hours as I've tried various solutions recommended in other StackOverflow threads.
Could someone please show me how to get this second foreign key to appear as a link in my API output?
When my code files appear as they do below, I get this error when trying to access the localhost:8000/trade/create/ and localhost:8000/trader-accounts/ URLs:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "traderaccount-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
Of course, I Googled this error messages and portions thereof several times, and went down several DRF rabbit holes trying various things.
trades/models.py
class Trade(models.Model):
trade_owner = models.ForeignKey(CustomUser, related_name='owners', on_delete=models.RESTRICT)
trade_account = models.ForeignKey(TraderAccount, related_name='trades', on_delete=models.RESTRICT)
trader_account/models.py
class TraderAccount(models.Model):
account_owner = models.ForeignKey(
CustomUser, on_delete=models.CASCADE,
)
account_name = models.CharField(
max_length=200, blank=True, verbose_name='Account Name'
)
api/serializers.py
class TraderAccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TraderAccount
fields = ['account_owner', 'account_name',]
class TradeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Trade
fields = ['trade_owner', 'trade_account', 'magic_number',]
api/views.py
class TraderAccountView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
class TradeCreateView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = Trade.objects.all()
serializer_class = TradeSerializer
api/urls.py
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('', include(router.urls)),
path('trader-accounts/', views.TraderAccountView.as_view(), name='trader-accounts'),
path('trade/create/', views.TradeCreateView.as_view(), name='trade-create'),
When I remove 'trader_account' from api.serializers.TradeSerializer.Meta.fields I can successfully access both the localhost:8000/trade/create/ endpoint and the localhost:8000/trader-accounts/ endpoint.
api/serializers.py
# Omit TraderAccountSerializer class for brevity in this example
class TradeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Trade
fields = ['trade_owner', 'magic_number',] # Removed 'trader_account'
I've been looking at this so long now I can't find the mistake. Really appreciate the help!
Based on this api/views.py you have a TraderAccountView(generics.ListCreateAPIView).
You should rename that to TraderAccountList(generics.ListCreateAPIView) and then add an extra view for the details; TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView)
api/views.py is now:
class TraderAccountList(generics.ListCreateAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
class TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
Your urlpatterns should now be
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('', include(router.urls)),
path('trader-accounts/', views.TraderAccountList.as_view(), name='trader-accounts'),
path('trader-accounts/<int:pk>/', views.TraderAccountDetail.as_view()),
]
And remove TradeCreate. As you are now building a RESTful API. to create a trade you do a POST request to the list view to create a new trade.
On the list view you can do GET to get a list of all trades, or a POST to create a new trade.
On the detail view, you can do GET to get a specific trade, or a PUT or PATCH to update, or DELETE to delete the trade.
Have you tried making a route for the account in the same manner as user?
The error message you included says that there is no route with the expected name.
According to the documentation at https://www.django-rest-framework.org/api-guide/serializers/#how-hyperlinked-views-are-determined, for the automapping to work you need a view with the name account-detail which takes 1 integer route parameter.
Hyperlinked serializers are fiddly, as they say, so pay close attention to docs. If you change the view name, you can override it in the serializer.
account = serializers.HyperlinkedRelatedField(
view_name='account-detail', # or whatever name you give it
...
)
Many thanks to Gabbeh for pointing me in the right direction.
I renamed TraderAccountView(generics.ListCreateAPIView) to TraderAccountList(viewsets.ModelViewSet) and added this view to api/views.py:
class TraderAccountDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated,)
queryset = TraderAccount.objects.all()
serializer_class = TraderAccountSerializer
The api/urls.py file is now:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
router.register(r'trader-accounts', views.TraderAccountList)
And everything works!

Improperly Configured URL for hyperlinked relationship

I am trying to use an alternative id besides the pk for a hyperlink, however I am getting an error:
Could not resolve URL for hyperlinked relationship using view name "foos-detail". You may have failed to include the related model in your API, or incorrectly configured the lookup_field attribute on this field.
During handling of the above exception (Reverse for 'foos-detail' with arguments '()' and keyword arguments '{'pk': 27}' not found. 2 pattern(s) tried: ['api/foos/(?P[^/.]+)\.(?P[a-z0-9]+)/?$', 'api/foos/(?P[^/.]+)/$']), another exception occurred:
Serializer:
class FooSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='foos-detail', format='html')
class Meta:
model = Foo
fields = ('url', 'alt_id', 'created', 'modified', 'name')
ViewSet:
class FooViewSet(viewsets.ModelViewSet):
queryset = Foo.objects.all()
serializer_class = FooSerializer
lookup_field = 'alt_id'
Urls:
router = DefaultRouter()
router.register(r'foos', FooViewSet, 'foos')
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
Edit: This is definitely a result of trying to use a lookup_field. Deleting the lookup_field in the viewset results in hyperlinks being displayed correctly for the pk.
You are missing the namespace in the view_name:
url = serializers.HyperlinkedIdentityField(view_name='rest_framework :foos-detail', format='html')
It cost me hours, and I finally made it work. After you make the identification right on class Meta, try these:
name your 'field-list' or 'field-detail' following common habit
add namespace before them for restframework to work well, like
#api_view(['GET'])
def api_root(request, format=None):
return Response({
'users':reverse('snippets:user-list', request=request, format=format),
'snippets':reverse('snippets:snippet-list',request=request, format=format)
})
make var url clear when you use serializers.HyperlinkedModelSerializer
url = serializers.HyperlinkedIdentityField(view_name="snippets:user-detail")

django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "user-detail"

TL;DR: I am getting this error and don't know why:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the 'lookup_field' attribute on this field.
I am going through the django-rest-framework tutorial and am currently at a point where function based views (FBV) were switched to class, mixin, and generic based views (CBV, MBV, GBV respectively). After switching to GBV, when I went to test my API, I received this error AssertionError: Expected view SnippetDetail to be called with a URL keyword argument named "pk". Fix your URL conf, or set the '.lookup_field' attribute on the view correctly.. I did some research and found that lookup_field needs to be set to the in the urlpatterns. Currently, my urls.py look like this:
from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
# API endpoints
urlpatterns = format_suffix_patterns([
url(r'^$', views.api_root),
url(r'^snippets/$',
views.SnippetList.as_view(),
name='snippet-list'),
url(r'^snippets/(?P<id>[0-9]+)/$',
views.SnippetDetail.as_view(),
name='snippet-detail'),
url(r'^users/$',
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<id>[0-9]+)/$',
views.UserDetail.as_view(),
name='user-detail')
])
# Login and logout views for the browsable API
urlpatterns += [
url(r'^auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
and my views.py look like so:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer, UserSerializer
from snippets.permissions import IsOwnerOrReadOnly
from rest_framework import generics
from rest_framework import permissions
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from django.contrib.auth.models import User
#api_view(['GET'])
def api_root(request, format=None):
return Response({
'users': reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly, )
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly, )
when I add lookup_field = 'id' in both UserDetail and SnippetDetail, the exception resolves itself. (Yay!). But, when I visit http://127.0.0.1/users/1/ ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the 'lookup_field' attribute on this field. is thrown. When I check the console, however, there is a second exception:
django.urls.exceptions.NoReverseMatch: Reverse for 'user-detail' with
arguments '()' and keyword arguments '{'pk': 1}' not found. 2
pattern(s) tried: ['users/(?P[0-9]+)\.(?P[a-z0-9]+)/?$',
'users/(?P[0-9]+)/$']
During handling of the above exception, another exception occurred:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for
hyperlinked relationship using view name "user-detail". You may have
failed to include the related model in your API, or incorrectly
configured the 'lookup_field' attribute on this field.
What I find interesting is that the kwargs for the first exception is {'pk': 1}, not {'id':1}. After some help from chat, someone pointed me to this piece of information:
Note that when using hyperlinked APIs you'll need to ensure that both the API views and the serializer classes set the lookup fields if you need to use a custom value.
This is useful as User serializer extends HyperlinkedModelSerializer:
from rest_framework import serializers
from django.contrib.auth.models import User
from snippets.models import Snippet
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
the User model and serializer has a reverse relationship with Snippet. Now, when I add lookup_field='id' to snippets attribute of UserSerializer (snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True, lookup_field='id')), like it asks me to do so here, the error is persistent.
What am I doing incorrectly? What can I do to fix this? Is it not having anything to do with lookup_id?
I understand that I could replace <id> with <pk> in my urlpatterns, but I would like to understand why this is happening.
For anyone else coming across this question after following the the tutorial, here is the answer.
Change these lines
router.register(r'snippets', views.SnippetViewSet,basename="snippets")
router.register(r'users', views.UserViewSet,basename="users")
To these (note the singular basenames)
router.register(r'snippets', views.SnippetViewSet,basename="snippet")
router.register(r'users', views.UserViewSet,basename="user")
If you follow it exactly as the tutorial exactly as written, it generates these router URLs (notice snippets-details). This is what causes the error
<URLPattern '^snippets\.(?P<format>[a-z0-9]+)/?$' [name='snippets-list']>
<URLPattern '^snippets/(?P<pk>[^/.]+)/$' [name='snippets-detail']>
<URLPattern '^snippets/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='snippets-detail']>
<URLPattern '^snippets/(?P<pk>[^/.]+)/highlight/$' [name='snippets-highlight']>
<URLPattern '^snippets/(?P<pk>[^/.]+)/highlight\.(?P<format>[a-z0-9]+)/?$' [name='snippets-highlight']>
I eventually fixed my second exception by printing the serializer in the Django shell/python interactive console. The result I got was this:
>>> from snippets.serializers import UserSerializer
>>> print(UserSerializer())
UserSerializer():
url = HyperlinkedIdentityField(view_name='user-detail')
id = IntegerField(label='ID', read_only=True)
username = CharField(help_text='Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=150, validators=[<django.contrib.auth.validators.UnicodeUsernameValidator object>, <UniqueValidator(queryset=User.objects.all())>])
snippets = HyperlinkedRelatedField(lookup_field='id', many=True, read_only=True, view_name='snippet-detail')
It turns out that to change <pk> to <id> in urlspatterns, you need to add url = HyperlinkedIdentityField(view_name='user-detail', lookup_field='id') in the UserSerializer class.
I just had the same problem and I found, that the HyperlinkedIdentityField wants to insert some placeholder into your URL. But I had used URLs which did not require any placeholders to be set. A ListCreateAPIView to be precise:
url(
r'^users/$', #<-- takes no params
views.UserListView.as_view(), #<-- just prints a list
name="user-list" #<-- HyperlinkedIdentityField pointing
), # here complained bitterly.

Django Rest Framework hyperlink namespace improperly configured

I read a lot about problems affecting hyperlink on DRF when namespaces are used. But I didnt manage to resolve my problem by following the tips and recommandations on both Github and Stackoverflow so far.
I have recently added namespaces to my urls.py
urlpatterns = patterns('',
# API
url(r'^api/', include(core_api_router.urls, namespace='api')),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^api-docs/', include('rest_framework_swagger.urls', namespace='api_docs'), name='api_docs'),
)
Here is my api.py
class MyBaseModelSerializer(DynamicModelSerializerMixin, serializers.ModelSerializer):
status = serializers.HyperlinkedRelatedField(many=True, view_name='api:mybasemodel', read_only=True)
class Meta:
model = models.MyBaseModel
fields = ('id', 'href', 'url', 'sid', 'name', 'status', 'created',)
ordering_fields = '__all__'
ordering = ('name',)
class ChangeViewSet(viewsets.ModelViewSet):
queryset = models.MyBaseModel.objects.all().select_related('status')
serializer_class = MyBaseModelSerializer
router.register('core/mybasemodel', MyBaseModelViewSet)
class MyRelatedModelSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(read_only=True)
href = serializers.HyperlinkedIdentityField(view_name='api:myrelatedmodel')
class Meta:
fields = ('id', 'href', 'key', 'comment', 'position', 'created')
ordering_fields = '__all__'
ordering = ('position',)
class MyRelatedViewSet(viewsets.ModelViewSet):
queryset = models.MyRelatedModel.objects.all()
serializer_class = MyRelatedSerializer
router.register('core/myrelatedmodel', MyRelatedModelViewSet)
In my test I check whether I can modify an existing object via the API
def test_api_update(self):
# Create an entry
entry = self.Meta.factory.create()
url = reverse('api:'+self.api_model_url+'-detail', args=[str(entry.id)])
data = {'sid': 'Modified Entry'}
# Check that an entry can be altered by an administrator via the API
self.api_client.login(username='admin', password='admin')
response = self.api_client.patch(url, data, format='json')
content = self.parse_json_response(response, Status.HTTP_200_OK)
self.assertEqual(content['sid'], 'Modified Entry')
Django raises this exception:
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "mybasemodel-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.
dmw.apps.core.views: ERROR: E500 : 127.0.0.1 : admin-user : http://testserver/api/core/api:mybasemodel/121e6850-3cd8-4795-d9bc-axsa04d1bd12/
My application runs on Python 3.4 with Django 1.8.9, Django Rest Framework 3.3.2 and I have tried with both:
router = routers.DefaultRouter()
and
routeur = routeurs.SimpleRouter()
Thanks in advance for your help!
Cheers!
Looks like the problem is in the HyperlinkedIdentityField of your two serializers MyBaseModelSerializer and MyRelatedModelSerializer.
In view_name you have to specify the the full name, not only the basename like you did. From the docs
view_name - The view name that should be used as the target of the relationship. If you're using the standard router classes this will be a string with the format -detail.
So you should add -detail there (like you do in your test):
# MyBaseModelSerializer
status = serializers.HyperlinkedRelatedField(many=True, view_name='api:mybasemodel-detail', read_only=True)
# MyRelatedModelSerializer
href = serializers.HyperlinkedIdentityField(view_name='api:myrelatedmodel-detail')
Encountered Similar problem, Solved by correcting the fields in serializer.py . Check your fields corresponding to model fields. Any unmatched fields will produce this kind of error.
For Example my default Auth model has following fields:
(defaults fields from Django Auth Model after running migrations)
id, first_name , last_name, password, username,last_login, is_active, is_active, date_joined and email
what serializer does is: Pick up specified fields from your model , then convert it to json for API processing.
So this error raises if you order your serializer to pick up those fields which are certainly not present in your database of same model(table).
I hope it solves. :)

Categories

Resources