In my models, I have the following classes:
class Topic(models.Model):
name = models.CharField(max_length=25, unique=True)
class Content(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
topic = models.ForeignKey(Topic, blank=True, null=True)
My serializers is like this:
class TopicSerializer(serializers.ModelSerializer):
class Meta:
model = Topic
fields = ('name')
class ContentSerializer(serializers.ModelSerializer):
topic = TopicSerializer(read_only=True)
class Meta:
model = Content
fields = ('title', 'body', 'topic')
Alright, so in my urls file, I have the following pattern:
urlpatterns = [
...
url(r'^api/topic_detail/(?P<name>[a-zA-Z0-9-]+)/content_list/$', views.topic_content_list, name='topic_content_list'),
...
]
Therefore, when the user goes to say /api/topic_detail/sports/content_list/, we get a list of all the contents that has the topic of sports. Now what I want is if we POST the following data to the above URL, then a Content object is created with the topic field related automatically to sports.
I am trying to do this the following way in my views:
#api_view(['GET', 'POST'])
def topic_content_list(request, name):
try:
topic = Topic.objects.get(name=name)
except:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
contents = Content.objects.filter(topic=topic)
serializer = ContentSerializer(contents, many=True)
return Response(serializer.data)
elif request.method == 'POST':
request.data["topic"] = topic
serializer = ContentSerializer(data=request.data)
print request.data
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Now lets say I go the URL /api/topic_detail/sports/content_list/ and POST this:
{
"title": "My blog post",
"body" : ".....",
}
The content object gets created and the title and body field is set properly. However, the topic field is set to null. How can I fix this? Any help is appreciated.
Also, please don't suggest using generic viewsets, as I am uncomfortable with so many things happening automatically.
EDIT
Alright, so I fixed my dumb mistake :
class ContentSerializer(serializers.ModelSerializer):
topic = TopicSerializer(read_only=False)
class Meta:
model = Content
fields = ('title', 'body', 'topic')
That is, I set the read_only argument to False. However, now the post creates a new error:
{
"topic": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got Topic."
]
}
}
This I am pretty sure refers to the fact that the data.website I'm sending in is not JSON, but instead just a Django model instance. How can I JSONify the single instance?
This is from your serializer.
topic = TopicSerializer(read_only=True)
It means your topic is read only so when you are trying to save your serializer, topic is not getting saved. Remove that and problem would be fixed.
EDIT:
Now as per the second error, it is because it is expecting a dict and you are passing the model instance, so you have two options. Either create the dict by hand.
topic_dict = { "name": topic.name }
and pass that as 'topic' in request.data and then save or give the topic_id, as there is a foreign key relationship, it should work.
So it would be something like:
request.data["topic_id"] = topic.id
Now what you choose to do is totally upto you.
Resurrecting this old thread since it seems to be a common issue people are running into. I just got this working on Django 3.1.6.
Since the Topic and Content models are already linked, the serializer is simply
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'body', 'topic')
No need for topic = TopicSerializer(read_only=False), which will require you to create a new topic with the POST. Now, the body of the POST can be
{
"title": "My blog post",
"body" : ".....",
"topic": 3
}
If you want your GET output to look like
{
"title": "My blog post",
"body" : ".....",
"topic": {
"id": 3
"name": "announcements"
}
}
override to_representation
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'body', 'topic')
def to_representation(self, instance):
response = super().to_representation(instance)
response['topic'] = TopicSerializer(instance.topic).data
return response
Credit for proper usage of to_representation goes to this answer by #PdotNJ
Related
I'm attempting to update a model that has more fields than what I want to pass to it. I've read the DRF documentation and I'm not finding the right approach. I came across using the UpdateModelMixin but I cannot seem to find a way to implement it successfully.
I've taken a few approaches so far, including using APIView in addition to the methods seen below.
Ultimately, I wish to pass the following to my view and have it update just the "order" field in my model. My model has many fields.
[
{
"id": "1",
"order": "5"
},
{
"id": "2",
"order": "3"
}
]
This is my view:
class WaitlistListGen(generics.ListCreateAPIView):
queryset = Waitlist.objects.all()
serializer_class = WaitlistSerializer
class WaitlistDetailGen(RetrieveUpdateDestroyAPIView):
queryset = Waitlist.objects.all()
serializer_class = WaitlistSerializer
And, this is my serializer:
class WaitlistSerializer(serializers.ModelSerializer):
class Meta:
model = Waitlist
fields = '__all__'
I finally figured it out and believe this is the best approach--I'll take feedback if it's not. I had to create a new view, extend the put method, and iterate over the data. I used the existing WaitlistSerializer:
class WaitlistUpdateGen(generics.UpdateAPIView):
serializer_class = WaitlistSerializer
def put(self, request):
waitlist_data = request.data
for waitlist_item in waitlist_data:
try:
waitlist = Waitlist.objects.get(id=waitlist_item['id'])
except Waitlist.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = self.serializer_class(
waitlist, data=waitlist_item, partial=True)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_200_OK)
I am trying to pass into my DRF API a post that needs to be queried against the database. For now I have included only 2 fields that can be queried. I am having trouble getting the view to work. Here is what I have so far.
POST
{
"city": "Denver",
"state": "CO"
}
Serializer only lets city and state be accepted
class EventQuerySerializer(serializers.ModelSerializer):
class Meta:
model = Events
fields = ('city', 'state')
View note that EventsSerializer is used in the return and I can confirm it works for a basic get request
class QueryEvents(APIView):
#staticmethod
def post(request):
serializer = EventQuerySerializer(data=request.data)
if serializer.is_valid():
events = Events.objects.get(serializer)
return Response(EventsSerializer(events).data)
Error
AttributeError: 'CharField' object has no attribute 'split'
You need to run save on you serializer to retrieve instance:
# ...
if serializer.is_valid():
event = serializer.save()
events = Events.objects.get(pk=event.pk)
# ...
I want my nested serializer works like primary key related field when used as input, and works as normal nested serializer when used as output. So I overwrite the to_internal_value, but it only works with application/json. When using form-data and x-www-form-urlencoded, I got a This field is required error.
Here's my code for serializers.
class PrimaryKeyFieldMixin(object):
def to_internal_value(self, data):
model = self.Meta.model
try:
return model.objects.get(pk=data)
except ObjectDoesNotExist:
self.fail('does_not_exist', pk_value=data)
except (TypeError, ValueError):
self.fail('incorrect_type', data_type=type(data).__name__)
class PatientSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name=namespace + ':patient-detail')
class Meta:
model = Patient
fields = '__all__'
class PatientRelatedField(PrimaryKeyFieldMixin, PatientSerializer):
pass
class PatientRecordSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name=namespace + ':patient_records-detail')
patient = PatientRelatedField()
class Meta:
model = PatientRecord
fields = '__all__'
Using with json:
Request:
{
"patient": 1,
"record_data": "some data"
}
Response:
{
"url": record_url,
"patient": {
"url": patient_url,
"patient_data": some_data,
},
"record_data": "some_data"
}
This works exactly as I want. But when using form-data and x-www-form-urlencoded I got a This field is required error on patient field.
After using the debug in pycharm to step through the code, it looks like I need to overwrite get_value function as well, I'm not sure.
it was hard to find a good title for this thread.
I'm developping webservices with django, geodjango (postgis), django-rest-framework and rest_framework_gis. Those webservices will be used in interactive maps.
One of my model is the following:
class Polygon(models.Model):
fk_owner = models.ForeignKey(User, on_delete=models.DO_NOTHING) # the owner of this polygon (User)
external_id = models.CharField(max_length=25) # id in data warehouse
func_type = models.CharField(max_length=15) # e.g 'field', 'building', ...
coordinates = models.PolygonField()
properties = JSONField(default={}) # JSON containing attributes and actions
The serializer of this model:
class PolygonSerializer(GeoFeatureModelSerializer):
class Meta:
model = Polygon
geo_field = "coordinates"
fields = ('external_id', 'fk_owner', 'func_type', 'properties')
And the endpoint
class FieldList(generics.ListCreateAPIView):
serializer_class = PolygonSerializer
lookup_field = 'external_id'
lookup_url_kwarg = 'external_id_field'
def get_queryset(self):
id_user = User.objects.get(external_id=self.kwargs['external_id_user']).id
queryset = Polygon.objects.filter(func_type="field").filter(fk_owner=id_user).all()
return queryset
def perform_create(self, serializer):
user = User.objects.get(external_id=self.kwargs['external_id_user'])
serializer.save(fk_owner=user)
When I use the following curl request:
curl -X POST http://xxx.yyy.zzz/api/v2/users/aZ523AsZm/fields/ -d '{"external_id": "aE15feg64AzaP", "func_type": "field", "coordinates": "POLYGON((62780.8532226825 5415035.177460473, 62941.3759284064 5415283.89540161, 63187.058044587146 5415364.391565912, 63257.96856022246 5414992.852982632, 62780.8532226825 5415035.177460473, 62780.8532226825 5415035.177460473))", "properties": {"a": "b"}}' -H "Content-Type: application/json"
I get {"fk_owner":["This field is required."],"external_id":["This field is required."],"coordinates":["This field is required."],"func_type":["This field is required."]}
However, when I replace GeoFeatureModelSerializer by a simple ModelSerializer, eveything is fine. Why ?
It took me like 1 hour to find the guilty, and it's soon the end of the daywork for me. Any help would be appreciate ! Thanks, and sorry for the long post.
[EDIT 10/11]: My workaround is to use 2 serializers: one for GET and one for CREATE
class PolygonList(generics.ListCreateAPIView):
queryset = Point.objects.all()
filter_backends = (drfg_filters.InBBoxFilter,)
def get_serializer_class(self):
if self.request.method == 'POST':
self.serializer_class = PolygonSerializerCreate
if self.request.method == 'GET':
self.serializer_class = PolygonSerializerRetrieve
return self.serializer_class
The django-rest-framework-gis repo contains a few examples of a POST request to create new objects, for example take a look at this one:
unit test code
view code
serializer code
model code
As you can see, POSTing should work, if it doesn't, it's either a bug or the code you are using is wrong.
From the what you posted in your question it looks like the JSON format you are using is not right. You told me you tried to send GeoJSON in the POST request, but I ask you to take a careful look at the links I just posted and try again. If you think it's a bug, we should test it and fix it.
Federico
I am new to Django. I am trying to save the json data using Django. I am using MongoDB as backend and one to many relationship approach to store data - http://docs.mongodb.org/manual/tutorial/model-embedded-one-to-many-relationships-between-documents/
Here is my model:
class OtherInfo(models.Model):
info_1 = models.CharField(max_length=200)
info_2 = models.CharField(max_length=200)
info_3 = models.CharField(max_length=200)
def __unicode__(self):
return u'%s %s %s' % (self.info_1, self.info_2, self.info_3)
class ModelInfo(models.Model):
name = models.CharField(max_length=200)
email = models.CharField(max_length=200)
other_info = ListField(EmbeddedModelField(OtherInfo))
def __unicode__(self):
return u'%s %s' % (self.name, self.email)
Issue is -
#api_view(['GET', 'PUT', 'POST'])
def save(request, format=None):
serializer = mySerializer(data=request.DATA)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Serializer -
class other_info_serializer(serializers.ModelSerializer):
class Meta:
model = OtherInfo
fields = ('info_1', 'info_2', 'info_3')
class mySerializer(serializers.ModelSerializer):
other_info=other_info_serializer(many=True)
class Meta:
model = ModelInfo
fields = ('name', 'email','other_info')
I want this above information to be saved as below in DB
{
_id: "joe",
name: "Joe Bookreader",
email: "email#example.com",
other_info: [
{
info_1 : "123 Fake Street",
info_2: "Faketon",
},
{
info_1: "1 Some Other Street",
info_2: "Boston",
}
]
}
Now when I send post data in URL
http://127.0.0.1:8080/save/
Parameter 1 : "name":"sample name"
Parameter 2 : "email":"sample#email.com"
Parameter 3 : [{'info_1':'Google','info_2':'Save'},{'info_1':'Hackhathon','info_2':'Present'}]
On execution of the above request I get -
{"other_info": [{"non_field_errors": ["Expected a list of items."]}]}
Any suggestions on whats happening wrong ?
Edit
Tried passing name value pair as well. But still same issue.
Parameter 3 : "other_info" : [{'info_1':'Google','info_2':'Save'},{'info_1':'Hackhathon','info_2':'Present'}]
This problem is caused by RelationsList - it does not have own save function but parent serializer called it.
There is an exact same issue on github issue.
And they says handling nested objects is still under heavy development phase. (Link)
But I made a patch for this(like below) and you can test it via sample project.
class RelationsList(list):
_deleted = []
def save(self, **kwargs):
[obj.save(**kwargs) for obj in self]
MongoDB always expects a name-value pair and you don't supply one at the third parameter:
Parameter 3 : [{'info_1':'Google','info_2':'Save'},{'info_1':'Hackhathon','info_2':'Present'}]
Here you pass just an array of objects.
Your request must look like this:
Parameter 3 : "other_info" : [{'info_1':'Google','info_2':'Save'},{'info_1':'Hackhathon','info_2':'Present'}]