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 /
Related
Hi I'm new to Django and the Django rest framework so my terminology may be off.
I'm trying to build an API that gives back a list of items from a model but filtered based on fields in another related model.
I'll provide my current view and serializer classes and models
class service(models.Model):
name = models.CharField(max_length=50)
vendor = models.CharField(max_length=50)
version = models.CharField(max_length=10)
registration_status = models.BooleanField(default=False)
class service_network(models.Model):
service = models.OneToOneField(
service,
related_name='network',
on_delete=models.CASCADE,
primary_key=True,
)
forwarded_port = models.CharField(max_length=50)
class ServiceNetworkSerializer(serializers.ModelSerializer):
class Meta:
model = service_network
fields = '__all__'
class ServiceSerializer(serializers.ModelSerializer):
network = ServiceNetworkSerializer()
class Meta:
model = service
fields = [
'id',
'name',
'vendor',
'version',
'registration_status',
'network',
]
class ServiceAPI(ModelViewSet):
queryset = service.objects.all()
serializer_class = ServiceSerializer
filterset_fields = '__all__'
Currently I can get back lists using a URL query string
{{baseUrl}}/engine/service?registration_status=true
What I want to do is something like this
{{baseUrl}}/engine/service/network?forwarded_port=8080
Which I would expect to give back a list of services where the related network field "forwarded_port" is equal to 8080.
Is there another way to query this API? Maybe using a POST with a body containing the query? If there something in the DOCS that I can read, I've tried to look through filtering and querysets but I wasn't able to find anything that would do this out of the box
I'm also new to stackoverflow and I've tried to keep my question short with as much relevant information so if there anything missing I'd be happy to edit my question
I was able to solve this using the following queryset override
def get_queryset(self):
if len(self.request.GET) > 0:
query_set = {}
for query in self.request.GET:
query_set[query] = self.request.GET.get(query)
return service.objects.filter(**query_set)
else:
return service.objects.all()
What this does is lets you filter fields without explicitly specifying what they are, in cases when you have many fields that need filtering. I also have to say as I'm not experienced with Django, I'm not sure what kind of errors this may bring up but its a hack that's worked for me. If I find this is really bad I'll come back and remove this.
Try this:
{{baseUrl}}/engine/service?network__forwarded_port=8080
Probably it works.
Doc: https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships-1
EDIT:
If the above answer doesn't work, you can change the ServiceApi class and filter by yourself:
class ServiceAPI(ModelViewSet):
def get_queryset(self):
if self.request.GET.get(network__forwarded_port)
return service.objects.filter(network__forwarded_port = self.request.GET.get(network__forwarded_port))
else:
return service.objects.all()
serializer_class = ServiceSerializer
filterset_fields = '__all__'
i am in a situation where i need to create a django api which on call from frontend gives a response fetched internally from some other source but not from serializers and models. Currently i am using django rest framework to create api like below
MODELS
# Create your models here.
class submission(models.Model):
# fixed fields to show on today panel/pending panel
email_id = models.CharField(max_length=200, primary_key=True)
email_sender = models.CharField(max_length=200, null=True, blank=True)
def __str__(self):
return self.email_id
SERIALIZERS
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = submission
fields = ('__all__')
VIEWS
class SAMPLE(viewsets.ModelViewSet):
queryset = submission.objects.all()
serializer_class = UserSerializer
URLS
router = DefaultRouter()
# user table
router.register('user_table_all_data', SAMPLE, basename='user_table_all_data')
and it works as charm , but in order to work with django rest framework i always need to call like this 1. Create model or use existing model, 2. Create serializers,3. Create Views,4. Create URLs,
But what if i dont want to use models say for example i want to show data from other source say which contains a sample json like below
sampleData = {"Name":"RAJNISH","Age":32,"ADDRESS":"India"}
so if my API is say 'sample/someid (not pk)'
returns response as
{"Name":"RAJNISH","Age":32,"ADDRESS":"India"}
how can i do this in django rest framework?
Use Function based views instead of Class based views
def sample(request):
return Response(data={"Name":"RAJNISH","Age":32,"ADDRESS":"India"})
Since there are a few questions about m2m and DRF I'll try narrow down what specifically I'm interested in. Let's call the two models 'article' and 'publication'. Assume that:
The 'publication' object already exists.
The 'article' object may or may not exist. specifically:
a) If a previous publication contained the article, then it will already be
there.
b) If not, then the article will need to be created.
I want to send a post http request with the article data in the body
and the publication id available from the url which will:
a) if the article already exists, link it to the publication
b) if the article does not exist, create it, and then link it to the publication
Going for the 'default' strategy below did not work out. I can think of two ways to approach this problem:
Overriding the create method on the article serializer. However I'm scepticle of doing that since this seems like a problem that should be common and have a non-custom solution.
Creating an endpoint to directly work with the 'through' model. I could then split up the process into two steps (and 2 requests) where I first get_or_create the article, and then post to the through model endpoint to create the link.
Are there any other approaches or built-in DRF solutions to this problem?
Here's where I'm at currently:
models.py
class Publication(models.Model):
name = models.CharField(max_length=255, unique=True)
collection = models.CharField(max_length=255)
class Article(models.Model):
major = models.IntegerField()
minor = models.IntegerField()
publication = models.ManyToManyField(Publication)
class Meta:
constraints = [models.UniqueConstraint(fields=['major', 'minor'], name='unique_article')]
views.py
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
serializers.py
class ArticleSerializer(serializers.ModelSerializer):
publication = serializers.SlugRelatedField(slug_field='name', queryset=Publication.objects.all()), many=True)
class Meta:
model = Article
fields = '__all__'
When posting to this endpoint I'll get a 'duplicate entry' integrity error if the article does already exist, instead of the article then just being linked.
This is the way I have handled this issue in the past. If your using the Primary keys these calls are not very expensive.
pub = Publications.objects.get(id=1)
article, created = Articles.objects.get_or_create(
id=1,
defaults= {other_params:'value', param : 'value'},
)
pub.articles.add(article)
Supposing some standard Django relational setup like this:
models.py
class Book(models.Model):
title = models.CharField(max_length=30)
class Page(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
text = models.CharField(max_length=100)
I'd like to create a book and all its pages with one request. If we start with serializers like this:
serializers.py
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = '__all__'
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('title', 'pages')
pages = PageSerializer(many=True)
Then the problem is that the PageSerializer now requires a book foreign key. But I don't know the key of the book until I've created the book, which is only after I've sent the POST request. So I cannot include the book pk in the POST data that the client sends.
An obvious solution is to override the create function on the Book serializer. But then I am still faced with the problem that the validators will say that the book field is required and the POST data will fail to validate.
I could make book a not-required field on the PageSerialzer. But this seems very bad. The book field IS required. And the BookSerializer create method will be able to supply it. It's just the client that doesn't know it.
So my suspicion is that the best way to do this is to leave book as required on the PageSerializer, but somehow make it so that the validators on the BookSerializer don't check for whether that is in the POST data when I post to BookSerializer.
Is this the correct way to achieve what I want? And if so, how do I do it? Thank you.
Why not try handling it in the create viewset. You can validate the data for the Book object first, before creating it. Then validate the data for the Page object using the created Book object and the other data sent from the request to the page.
I'd link your ViewSet to a BookCreateSerializer, and from this specific serializer I'd then add a function to not only verify the received data but make sure you link the parent's id to the child's one during creation.
IMPORTANT NOTE
This works if a parent only has one child, not sure about when passing multiple children.
Here is what is could look like.
BookCreateSerializer:
class BookCreateSerializer(serializers.ModelSerializer):
"""
Serializer to create a new Book model in DB
"""
pages = PageCreateSerializer()
class Meta:
model = Book
fields = [
'title',
'pages'
]
def create(self, validated_data):
page_data = validated_data.pop('page')
book = Book.objects.create(**validated_data)
Page.objects.create(book=book, **page_data)
return book
PageCreateSerializer
class PageCreateSerializer(serializers.ModelSerializer):
"""
Serializer to create a new Page model in DB
"""
class Meta:
model = Page
fields = [
'book',
'text'
]
To make sure that your Book instance understands what a page field is in the serializer, you have to define a related_name in its child's Model (Page). The name you choose is up to you. It could look like:
class Page(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='page')
text = models.CharField(max_length=100)
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. :)