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. :)
Related
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!
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.
I'm using django and rest-framework to develop a website. I have a users app in which the models are shown below:
class User(AbstractUser):
pass
class Comment(models.Model):
comment_text = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL,default=DefaultUser,on_delete=models.SET_DEFAULT,related_name='author')
# DefaultUser is similar to AnonymousUser in django.contrib.aut.models
date = models.DateTimeField(default=now)
class User_Comment(Comment):
on_user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='on_user',default=DefaultUser)
class User_Comment(Comment):
on_user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='on_user',default=DefaultUser)
so it's basically a comment system where a user can comment on another user.
I've used a rest framework serializer for posting comments:
class User_CommentSerilizer(serializers.ModelSerializer):
comment = User_Comment
class Meta:
model = User_Comment
fields = ('comment_text','on_user')
# extra_kwargs = {'password': {'write-only': True}}
def create(self, validated_data):
comment = User_Comment(
author= User.objects.filter(username=self.context['request'].user)[0],
on_user= User.objects.filter(username=validated_data["on_user"])[0],
validated=False,
comment_text= validated_data["comment_text"]
)
comment.save()
return comment
and then using a UserCommentViewSet in views.py:
class User_CommentViewSet(viewsets.ViewSet):
serializer_class = User_CommentSerilizer
queryset = User_Comment.objects.all()
and finally in the urls file I've registered the view:
router = DefaultRouter()
router.register('profile' , views.UserViewSet)
router.register('comment' , views.User_CommentViewSet)
router.register('login' ,views.LoginViewSet, base_name='login')
urlpatterns = [
path('users/', include(router.urls)),
]
the profile and login routers are working fine. however the comment router is not showing at all (and returns 404) without raising any other error. it's like the router is not registered.
I can't figure out what the problem is, Although I did find out that it has something to do with the queryset part. I'd really much appreciate it if anyone could figure this out.
the bug was simply because I was using a ViewSet instead of ModelViewSet in User_CommentViewSet function.
I'm new to Django and trying to play with restframework. I have created a simple model and I'd like to POST to this model via REST and sending JSON data.
This is what I've done so far:
models.py
class Contact(models.Model):
name = models.CharField(max_length=120)
email = models.EmailField()
phone = models.CharField(max_length=15)
city = models.CharField(max_length=120)
comment = models.CharField(max_length=500)
timestamp = models.DateTimeField(auto_now_add=True)
serializers.py
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = ('name', 'email', 'phone', 'city', 'comment', 'timestamp')
urls.py
url(r'^api/contact/(?P<pk>[0-9]+)/$', ContactDetail.as_view()),
views.py
class ContactDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
format = None
but when I try to post to http://127.0.0.1:8001/api/contact I get this error
13. ^index.html#/verifyEmail/(?P<key>\w+)/$ [name='account_confirm_email']
14. ^api/contact/(?P<pk>[0-9]+)/$
The current URL, api/contact, didn't match any of these.
Question
How can I POST data to my model and save it?
You got a couple of problems here:
You are using generics.RetrieveUpdateDestroyAPIView which will provide the PUT, GET and DELETE methods. If you want to be able to POST (this means create) to that endpoint, you should be using another one. Replace it for viewsets.ModelViewSet, it will provide all CRUD methods. Don't forget to import the module (+more info).
You are trying to build the urls yourself which is not correct, drf provides routers to build them automatically. Just follow the docs, they are really well explained.
Once you fix those issues, you will be able to POST to /api/contact/ to create a new one.
Your main issue is that the regular expression here:
url(r'^api/contact/(?P<pk>[0-9]+)/$', ContactDetail.as_view())
does not match the URL:
http://127.0.0.1:8001/api/contact
A match should look more like the following:
http://127.0.0.1:8001/api/contact/123412213/
Including the trailing slash /
I am following the quick Quick Start Tutorial(http://www.django-rest-framework.org/tutorial/quickstart#quickstart) Its possible to create/delete/update a user in database if we know its "id", But is it possible to do the same for a user with particular email ?
Please also suggest the modifications needed to make this possible and enable API to lookup by email like users/email.
Set the lookup_field and lookup_url_kwarg on your view or viewset that subclasses GenericAPIView. A basic example using a ModelViewSet and a SimpleRouter would look like this:
views.py:
class UserViewSet(viewsets.ModelViewSet):
lookup_field = 'email'
lookup_url_kwarg = 'email'
urls.py:
router = routers.SimpleRouter()
router.register(r'^users', UserViewSet)
urlpatterns = router.urls
If you are using HyperlinkedModelSerializer, you must set lookup_field in the serializer too.
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username')
lookup_field = 'email'