Reputation: 97
I am new to Django REST Framework and trying it for a new project I am working on. So based upon the official tutorial , I am trying to create several get/post/put requests, but with put request I am getting the following error:
Expected view ExampleUpdateView to be called with a URL keyword argument named "pk". Fix your URL conf, or set the
.lookup_field
attribute on the view correctly.
models.py
class ExampleModel(models.Model):
foo_field = models.CharField(primary_key=True, max_length=15)
bar_field = models.CharField(max_length=30)
last_updated_by = models.CharField(max_length=15)
last_updated_on = models.DateTimeField()
class Meta:
managed = True
db_table = 'example_db'
unique_together = (('foo_field', 'bar_field'),)
serializers.py
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = ExampleModel
fields = ('foo_field', 'bar_field', 'last_updated_by', 'last_updated_on')
urls.py
url(r'^get_example/$', views.ExampleCreateView.as_view()),
url(r'^update_example/(?P<pk>\d+)/$',views.ExampleUpdateView.as_view()),
views.py
class ExampleCreateView(generics.CreateAPIView):
serializer_class = ExampleSerializer
queryset = ExampleModel.objects.all()
class ExampleUpdateView(generics.UpdateAPIView):
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
I think the problem I might have is that I have a composite key. I tried other generic views (CreateAPIView & ListAPIView) and they work absolutely fine. Do I need to update the def_update
method? Do I need to change anything in serializers.py
?
This is an existing JSON object that I got from my GET request and was trying to update:
{
"foo_field": "john",
"bar_field": "doe",
"last_updated_by": "batman",
"last_updated_on": "2017-02-09"
}
I looked at the following duplicates but none of these solution seems to work for me:
Django Rest Framework: Unclear error message
Django REST Updateview with PUT POST
How do I use an UpdateView to update a Django Model?
Upvotes: 0
Views: 20139
Reputation: 12012
First I'll refactor your code and present the code examples. Afterwards I'll explain the changes.
models.py
class ExampleModel(models.Model):
foo_field = models.CharField(primary_key=True, max_length=15)
# rest ...
You have set foo_field
as primary key. It is a bad practice to use strings (VARCHAR
) as primary key. It is highly recommended to use surrogate keys. This will be accomplished automatically by Django. It adds a field id
as primary key.
The serializers.py
will now work as it is. No changes are needed. Also the views.py
should work without changes. However I'll cover the option with foo_field
as primary key.
views.py
class ExampleUpdateView(generics.UpdateAPIView): # rest ... lookup_field = `foo_field`
You could also leave this out, but it is important to make some changes in your urls.py
.
urls.py
url(r'^examples/$', views.ExampleCreateView.as_view()), url(r'^examples/(?P<pk>\d+)/$',views.ExampleUpdateView.as_view()),
In order for a web services to be ReSTful, they have to comply with the REST principles. One of the most important rules is that we shouldn't use a verb in the URL for describing the action. Another convention is that the resource name is used in plural to describe the collection and an identifier is used to access single resource.
Your resource is called example, therefore we use examples as collection name. Verbs like get or update have no place in the URL. The HTTP method is responsible for the proper action.
If you'd like to use foo_field
as primary key, then you have to change the url for the update view:
url(r'^examples/(?P<pk>[\w-]+)/$',views.ExampleUpdateView.as_view()),
This is because your foo_field
is a CharField
(or string), so the arguments passed to it should match any alphanumeric character (\w
) and the hyphen (-
).
You can use foo_field
as named group in your url regex instead of pk
if you wish. Pay attention that you adapt that in your view, setting the correct values for lookup_field
and lookup_url_kwarg
.
Again I'll strongly recommend to use surrogate keys and leave Django to accomplish this task.
Now you can understand why ListAPIView
and CreateAPIView
worked. They don't require an argument passed to the url and were properly called.
Your UpdateAPIView
couldn't work because the matching url was accepting only digits and didn't match your primary key foo_field
.
Upvotes: 5
Reputation: 421
Use update()
wiht your UpdateAPIView
.
class ExampleUpdateView(generics.UpdateAPIView):
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
def update(self, request, *args, **kwargs):
instance = self.get_object()
instance.foo_field = request.data.get("foo_field")
instance.bar_field = request.data.get("bar_field")
instance.last_updated_by = request.data.get("last_updated_by")
instance.last_updated_on = request.data.get("last_updated_on")
instance.save()
serializer = self.get_serializer(instance)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
Upvotes: 4