I'm trying to build an api that have an endpoint which in the POST receives the JSON like bellow:
{
"title":"Quiz 1",
"questions":[
{
"description":"Question 1?",
"answers":[
{
"description":"Answer 1",
"true_or_false":true
},
{
"description":"Answer 2",
"true_or_false":false
}
]
},
{
"description":"Question 2?",
"answers":[
{
"description":"Answer 1",
"true_or_false":true
},
{
"description":"Answer 2",
"true_or_false":false
}
]
}
]
}
But I don't know how to build the serializers for double nested fields, like a list of answers within a question and a list of questions within a quiz. The code I made so far is below:
models.py
from django.db import models
class Quiz(models.Model):
title = models.CharField(max_length=200, blank=False)
def __str__(self):
return self.title
class Question(models.Model):
description = models.CharField(max_length=10000, blank=False)
quiz = models.ForeignKey(Quiz, related_name='questions', on_delete=models.CASCADE, default=None)
def __str__(self):
return self.description
class Answer(models.Model):
question = models.ForeignKey(Question, related_name='answers', on_delete=models.CASCADE)
description = models.CharField(max_length=1000, blank=False)
true_or_false = models.BooleanField(default=False, blank=False)
def __str__(self):
return self.description
views.py
class QuestionViewSet(viewsets.ModelViewSet):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class AnswerViewSet(viewsets.ModelViewSet):
queryset = Answer.objects.all()
serializer_class = AnswerSerializer
class QuizViewSet(viewsets.ModelViewSet):
queryset = Quiz.objects.all()
serializer_class = QuizSerializer
class OnlyQuizViewSet(generics.ListAPIView):
def get_queryset(self):
queryset = Quiz.objects.filter(id=self.kwargs['pk'])
return queryset
serializer_class = QuizSerializer
serializers.py
class AnswerSerializer(serializers.ModelSerializer):
class Meta:
model = Answer
fields = ['id', 'description', 'true_or_false']
class QuestionSerializer(serializers.ModelSerializer):
answers = AnswerSerializer(many=True)
class Meta:
model = Question
fields = ['id','description','answers']
read_only_fields = ('quiz',)
def create(self, validated_data):
answers_data = validated_data.pop('answers')
question = Question.objects.create(**validated_data)
for answer in answers_data:
Answer.objects.create(question=question, **answer)
return question
class QuizSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Quiz
fields = ['id', 'title', 'questions']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
quiz = Quiz.objects.create(**validated_data)
for questions in questions_data:
Question.objects.create(quiz=quiz, **questions)
return quiz
With the above serializers, I'm getting the error below:
Direct assignment to the reverse side of a related set is prohibited. Use answers.set() instead.
So what's the right way to build double-nested serializers? So far I haven't been able to find anything to help me
I'm gonna assume you get that error when you try posting to the Quiz api, you'll have a problem when you want to create a question
Question.objects.create(quiz=quiz, **questions)
since this question also includes a list of answers that needs to be created first.
Try using the serializer you already have like this
class QuizSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Quiz
fields = ['id', 'title', 'questions']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
quiz = Quiz.objects.create(**validated_data)
for questions in questions_data:
question_serializer = QuestionSerializer({**questions, "quiz": quiz}) # might need to send quiz.id instead
question_serializer.is_valid()
question_serializer.save()
return quiz
and update your QuestionSerializer to use AnswerSerializer the same way (or you can create Answers explicitly there)
Related
SerializerClass:
class VacancySerializer(serializers.ModelSerializer):
organization_small_name = serializers.CharField(source='organization.short_name', read_only=True)
class Meta:
model = Vacancy
fields = ['organization', 'description', 'specs', 'type', 'publication_date',
'is_published', 'withdrawal_data', 'organization_small_name', ]
read_only_fields = ['publication_date', 'is_published', 'withdrawal_data',]
ViewSet:
class VacancyViewSet(viewsets.ModelViewSet):
queryset = Vacancy.objects.all()
serializer_class = VacancySerializer
filter_backends = [filters.SearchFilter]
search_fields = ['organization_small_name']
...
Model:
class Vacancy(models.Model):
organization = models.OneToOneField(DictOrganization, on_delete=models.CASCADE, related_name='vacancies')
description = models.TextField('Описание')
specs = models.ManyToManyField(DictSpec, blank=True)
type = models.CharField('Тип', max_length=20, choices=VacancyType.choices(), default=VacancyType.PRACTICE.value)
publication_date = models.DateField('Дата публикации', null=True, blank=True)
is_published = models.BooleanField('Опубликовано', default=False)
withdrawal_data = models.DateField('Дата снятия с публикации', null=True, blank=True)
My goal is to make API search by 'organization_small_name' field
that is in VacancySerializer.
Server runs successfully, but as soon as i add ?search parameter, i get next error:
Why it doesn't recognize 'organization_small_name' field, even thought it is decribed in serializer, and how can i fix it?
I am trying to create a post request for a game api. The game implies that a user can label a picture. A label entered once is a tagging, a label entered twice for the same resource is a tag.
This is how I am trying to create a Tagging so far:
saved_tagging = Tagging.objects.create(user_id=current_user_id,
gameround=gameround,
resource=random_resource,
tag='tag newwww',
created=datetime.now(),
score=score,
origin=origin
)
tagging_serializer = TaggingSerializer(saved_tagging)
At the moment I am getting the ValueError: Cannot assign "'tag newwww'": "Tagging.tag" must be a "Tag" instance.
Is there any way that I can avoid this?
Here are also my models and the relevant serializer.
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
Tag.objects.create(**tag_data)
return tag_data
def __str__(self):
return self.name or ''
class Tagging(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name='tagging')
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
origin = models.URLField(max_length=256, blank=True, default='')
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
tagging = Tagging.objects.create(**validated_data)
Tag.objects.create(name=tagging, **tag_data)
return tagging
def __str__(self):
return str(self.tag) or ''
serializers.py
class TaggingSerializer(serializers.ModelSerializer):
tag = StringRelatedField()
resource = ResourceSerializer(read_only=True)
gameround = GameroundSerializer(read_only=True)
class Meta:
model = Tagging
fields = ('id', 'tag', 'gameround', 'created', 'score', 'resource', 'origin')
def create(self, validated_data):
return Tagging.objects.create(**validated_data)
def to_representation(self, data):
data = super().to_representation(data)
return data
tag must be a Tag Instance !
So... you can do this in two ways (in my opinion).
First you can create a Tag object in your view and than pass this object to tag value in your Tagging create method.
Or create a service layer on your app, and create a custom create method for your model. This in my opinion is superior because your a centralizing your rules in one method instead of in one view.
Ex.:
services/tag_service.py
def create(user_id,gameround,resource,tag,...origin):
if not isinstance(tag, Tag):
#create your tag model based on the String passed
your_new_tag_object = Tag.objects.create(tag=tag, ...)
# Here your create others rules too for model creation
# Return your model .create method
return Tagging.objects.create(user_id=user_id,...tag=your_new_tag_object,...)
And Than use this new create method inside your POST serializer.
from services import tag_service
class TaggingSerializer(serializers.ModelSerializer):
# your normal serializer here
def create(self, validated_data):
return tag_service.create(**validated_data)
I have two models Question and Options
class Question(models.Model):
question_identifier = models.CharField(max_length=255)
question_text = models.TextField(blank=False)
question_category = models.ManyToManyField('Category')
question_tags = models.CharField(max_length=255, blank=True)
class Options(models.Model):
question = models.OneToOneField('Question', related_name='options', blank=False, null=False)
option1 = models.CharField(max_length=255,blank=False,null=True)
option2 = models.CharField(max_length=255,blank=False,null=True)
option3 = models.CharField(max_length=255,blank=True,null=True)
option4 = models.CharField(max_length=255,blank=True,null=True)
I have written a serializer for Question model which serializes options as well (reverse relation). I want to omit the question field from the Options? Is there a way to achieve this?
My Question Serializer:-
class QuestionSerializer(serializers.ModelSerializer):
"""
Serializer for Question object from qna.models
"""
question_category = CategorySerializer(many=True,write_only=True)
class Meta:
model = Question
fields = ('id', 'options', 'question_identifier','question_text','question_tags','question_category')
depth = 1
You can achieve that by creating a separate serializer for an Options model.And use it instead of default field, without depth value.
class OptionsSerializer(serializers.ModelSerializer):
class Meta:
model = Options
fields = ('option1', 'option2', 'option3')
class QuestionSerializer(serializers.ModelSerializer):
question_category = CategorySerializer(many=True,write_only=True)
options = OptionsSerializer()
class Meta:
model = Question
fields = ('id', 'options', 'question_identifier','question_text','question_tags','question_category')
I'm using django-autocomplete-light with django 1.8.
It sometimes fails. Here is the javascript console error:
TypeError: $(...).select2 is not a function
;(function ($) {
$(document).on('autocompleteLightInitialize', '[data-autocomplete-light-function=select2]', function() {
var element = $(this);
// This widget has a clear button
$(this).find('option[value=""]').remove();
$(this).select2({
It worked fine yesterday and nothing has changed but today it is not working.
I'm also using django suit.
I feel that there is a javascript incompatibility between my django apps causing this random behavious but I do not know how to track down the cause.
Any ideas?
Here is all of my code:
# models.py
class Sample(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
class Doctor(models.Model):
name = models.CharField(max_length=200)
address = models.CharField(max_length=200, null=True, blank=True, default='')
def __unicode__(self):
return self.name
class Patient(models.Model):
name = models.CharField(max_length=200)
sample = models.OneToOneField(Sample, null=True, blank=True)
doctor = models.ForeignKey(Doctor, null=True, blank=True, default=None)
def __unicode__(self):
return self.name
----------
# urls.py
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^pat-sam-autocomplete/$', PatientSampleAutocomplete.as_view(), name='pat-sam-autocomplete',),
url(r'^pat-doc-autocomplete/$', PatientDoctorAutocomplete.as_view(), name='pat-doc-autocomplete',),
)
----------
# views.py
class PatientSampleAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated():
return Sample.objects.none()
qs = Sample.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
return qs
class PatientDoctorAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated():
return Doctor.objects.none()
qs = Doctor.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
return qs
--------------
# admin.py
from django import forms
from dal import autocomplete
class PatientForm(forms.ModelForm):
class Meta:
model = Patient
fields = ('__all__')
widgets = {
'sample': autocomplete.ModelSelect2(url='pat-sam-autocomplete'),
'doctor': autocomplete.ModelSelect2(url='pat-doc-autocomplete')
}
#admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):
list_display = ('name', 'address')
#admin.register(Sample)
class SampleAdmin(admin.ModelAdmin):
list_display = ('name',)
#admin.register(Patient)
class PatientAdmin(admin.ModelAdmin):
list_display = ('name', 'sample')
form = PatientForm
I rearranged the INSTALLED_APPS in the settings.py and that seem to have fixed it:
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'dal',
'dal_select2',
'suit',
'myapp',
'django.contrib.admin',
'django_extensions',
'simple_history',
'django_tables2',
'django.contrib.admindocs',
)
I can't find a valid way with Django OMR in order to get : ( a raw query is also fine )
the Sites.sitename which made the Analysis where (Analysi_Items.name='somename' and Analysis_Items.value='somevalue') and (Analysi_items_name='somename' and Analysis_Items.value='somevalue') and (Analysis_items.name='somename' and Analysis_Items.value='somevalue').
class Sites(models.Model):
region = models.CharField(max_length=1000)
province = models.CharField(max_length=1000)
sitename = models.CharField(max_length=1000, primary_key=True)
class Meta:
verbose_name_plural = "Sites"
def __unicode__(self):
return self.sitename
class Analysis_Items(models.Model):
code = models.ForeignKey('Analysis')
name = models.CharField(max_lenght=100)
value = models.CharField(max_length=20)
class Meta:
verbose_name_plural = "Analysis Type"
class Analysis(models.Model):
date = models.DateField()
site = models.ForeignKey('Sites')
def __unicode__(self):
return str(self.date)
class Meta:
verbose_name_plural = "Analysis"
Hope this is clear enough. thank you in advance!
Site.objects.filter(analysis__analysis_items__name='some_name', analysis__analysis_items__value='some_value')
You can keep adding additional parameters in the same keep AND'ing them all together.