Here is the code:
Models.py
class Question(models.Model):
lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, related_name='questions')
question = models.CharField('lesson name', max_length=120)
class Option(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='options')
option = models.CharField('test option', max_length=100)
correct = models.BooleanField(default=False)
Views.py
class QuestionAPIView(generics.CreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
Serializers.py
class OptionSerializer(serializers.ModelSerializer):
class Meta:
model = Option
fields = ('option', 'correct')
class QuestionSerializer(serializers.ModelSerializer):
options = OptionSerializer(many=True)
class Meta:
model = Question
fields = ('lesson', 'question', 'options')
def create(self, validated_data):
options_data = validated_data.pop('options')
question = Question.objects.create(**validated_data)
for option_data in options_data:
Option.objects.create(question=question, **option_data)
return question
I'm trying to create a test question with a few options for answers like this:
data = {
"lesson": 2,
"question": "This is our first question?",
"options": [
{
"option": "Option 1",
"correct": True
},
{
"option": "Option 2 ",
"correct": False
},
{
"option": "Option 3",
"correct": True
}
]
}
But in postman and in the TestCase I've got:
{
"options": [
"This field is required."
]
}
What is the problem of validation? Documentation here https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers Thanks.
It does work perfectly in my testing. Just note your JSON object, you used True / False as Python boolean varible not JSONs:
(Tested both with postman and DRF Browsable API)
data = {
"lesson": 2,
"question": "This is our first question?",
"options": [
{
"option": "Option 1",
"correct": true
},
{
"option": "Option 2 ",
"correct": false
},
{
"option": "Option 3",
"correct": true
}
]
}
Related
I have 3 tables in my database : Quiz - question - answer, with this models
class Quiz(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
class Question(models.Model):
id = models.AutoField(primary_key=True)
question = models.CharField(max_length=255)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name="questions")
class Answer(models.Model):
id = models.AutoField(primary_key=True)
answer = models.CharField(max_length=255)
is_correct = models.BooleanField()
question = models.ForeignKey(
Question, on_delete=models.CASCADE, related_name="answers"
)
i need to override create and update method for create or put/patch a quiz with questions and answers in one time with the default route.
i try différent things to do this.
My create methode is is operational
thise is the serializers of quizz questio nand answers :
class AnswerSerializer(ModelSerializer):
class Meta:
model = Answer
fields = (
"id",
"answer",
"is_correct",
)
class QuestionSerializer(ModelSerializer):
answers = AnswerSerializer(many=True, required=True)
class Meta:
model = Question
fields = ("id", "question", "answers")
class QuizSerializer(ModelSerializer):
questions = QuestionSerializer(many=True, required=True)
class Meta:
model = Quiz
fields = ("id", "name", "questions")
# create function for create a quiz with questions and answers
def create(self, validated_data: Dict[str, Any]) -> Quiz:
questions = validated_data.pop("questions")
quiz = Quiz.objects.create(**validated_data)
for question in questions:
answers = question.pop("answers")
question = Question.objects.create(
quiz=quiz, question=question.get("question")
)
for answer in answers:
Answer.objects.create(
question=question,
answer=answer.get("answer"),
is_correct=answer.get("is_correct"),
)
return quiz
my views :
class AnswerViewSet(ModelViewSet):
serializer_class = AnswerSerializer
def get_queryset(self) -> Answer:
return Answer.objects.all()
class QuestionViewSet(ModelViewSet):
serializer_class = QuestionSerializer
def get_queryset(self) -> Question:
return Question.objects.all()
class QuizViewSet(ModelViewSet):
serializer_class = QuizSerializer
def get_queryset(self) -> Quiz:
return Quiz.objects.all()
for the update this my last code with it i can update quiz table but not questions and answers
def update(self, instance, validated_data):
questions_data = validated_data.pop('questions')
for question_data in questions_data:
try:
question = instance.questions.get(id=question_data.get('id'))
answers_data = question_data.pop('answers')
question_serializer = QuestionSerializer(question,data=question_data)
question_serializer.is_valid(raise_exception=True)
question_serializer.save()
for answer_data in answers_data:
try:
answer = question.answers.get(id=answer_data.get('id'))
answer_serializer = AnswerSerializer(answer,data=answer_data)
answer_serializer.is_valid(raise_exception=True)
answer_serializer.save()
except Answer.DoesNotExist:
continue
except Question.DoesNotExist:
continue
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
this is the json for update :
{
"id": 6,
"name": "Quiz photoshop intermediaire",
"questions": [
{
"id": 1,
"question": "comment est composé un fichier image photoshop",
"quiz": 6,
"answers": [
{
"id": 1,
"answer": "de pizza 4 chaussures",
"is_correct": false,
"question": 1
},
{
"id": 2,
"answer": "d'un système de poulies",
"is_correct": false,
"question": 1
},
{
"id": 3,
"answer": "de calques",
"is_correct": true,
"question": 1
}
]
},
{
"id": 2,
"question": "quelles sont les modes de couleurs indesign",
"quiz": 6,
"answers": [
{
"id": 4,
"answer": "c'est un tquiz photoshop boulet !",
"is_correct": true,
"question": 2
},
{
"id": 5,
"answer": "CMJN",
"is_correct": true,
"question": 2
},
{
"id": 6,
"answer": "nono le petit robot",
"is_correct": false,
"question": 2
},
{
"id": 7,
"answer": "bombe de peinture",
"is_correct": false,
"question": 2
}
]
}
]
}
thanks for help :)
I need to get the child list under the parent list as a group.
class ServiceSerializer(serializers.ModelSerializer):
cleaning_type = serializers.CharField(source='cleaning_type.cleaning_type_name')
class Meta:
model = Service
fields = ('id', 'cleaning_type','service_name')
class ServiceTypeViewSet(ModelViewSet):
serializer_class = ServiceSerializer
http_method_names = ["get"]
queryset = Service.objects.all()
def get_queryset(self):
"""
This view should return a list of all the service types.
"""
servicename_list = Service.objects.all()
return servicename_list
It shows:
[
{
"id": 1,
"cleaning_type": "Lite service",
"service_name": "Floors",
},
{
"id": 2,
"cleaning_type": "Lite service",
"service_name": "Bathrooms",
},
{
"id": 3,
"cleaning_type": "Lite service",
"service_name": "Kitchen",
}
]
I want this to be in the following format:
[
{
id: 1,
cleaning_type: 'Lite service',
service_name: ['Floors', 'bathroom', 'kitchen'],
},
{
id: 2,
cleaning_type: 'Moving cleaning',
service_name: ['Kitchen Including All Appliances And Cabinets'],
},
]
That means all child elements will be under a separate parent list. Not separate by separate.
models.py is here:
Cleaning Type Model:
class CleaningType(models.Model):
cleaning_type_name = models.CharField(
_("Select Cleaning Type"), blank=True, null=True, max_length=255)
price = models.DecimalField(default=0,max_digits=6, decimal_places=2)
def __str__(self):
return self.cleaning_type_name
Service Model:
class Service(models.Model):
cleaning_type = models.ForeignKey(
CleaningType, on_delete=models.CASCADE)
service_name = models.CharField(
_("Service Name"), blank=True, null=True, max_length=255)
#string type added
def __str__(self):
return str(self.service_name)
I want sub categories under parent caterories. Here cleaning_type is the parent category and service is the child category of cleaning_type. i.e : cleaning_type >> service_type
I'd create a view for the parent category and then get child categories for each parent category. First, you should create a serializer for CleaningType model:
class CleaningTypeSerializer(serializers.ModelSerializer):
service_types = serializers.SerializerMethodField('get_service_types')
def get_service_types(self, cleaning_type_name):
return Service.objects.filter(cleaning_type=cleaning_type_name).values_list("service_name", flat=True)
class Meta:
model = CleaningType
fields = "__all__"
Then, create a view using the new serializer:
class CleaningTypesViewSet(ModelViewSet):
serializer_class = CleaningTypeSerializer
http_method_names = ["get"]
queryset = CleaningType.objects.all()
The response looks something like this:
[
{
"id": 1,
"service_types": [
"Moving Service 1",
"Moving Service 2",
"Moving Service 3"
],
"cleaning_type_name": "Moving Cleaning",
"price": "200.00"
},
{
"id": 2,
"service_types": [
"Another Service 1",
"Another Service 2"
],
"cleaning_type_name": "Another Cleaning",
"price": "300.00"
}
]
I have this issue while running some test on my django project:
TypeError: Direct assignment to the reverse side of a related set is prohibited. Use files.set() instead.
Here is what I try to post
{
"daily_hash": "w54c6w546w5v46w5v4",
"modules": [
{
"module": "main",
"commits": [
{
"commit_hash": "11feb543f016114c700046d42b912910321230da",
"author": "Test name 1",
"subject": "[TICKET] Subject of the issue",
"files": []
},
{
"commit_hash": "093b19f710c6d2358b0812434dfcf1549c9c6fbb",
"author": "Test name 1",
"subject": "[TICKET] Subject of the issue",
"files": []
}
]
},
{
"module": "submodule",
"commits": [
{
"commit_hash": "dce22dea52a6a4b7160034d3f84a7af7b389ee96",
"author": "Test name 3",
"subject": "[TICKET] Subject of the issue",
"files": [
{
"name": "my_file_1.c"
},
{
"name": "my_file_2.c"
}
]
},
{
"commit_hash": "cee433fc4ab46464afb96d6ecae2839409fe0313",
"author": "Test name 4",
"subject": "[TICKET] Subject of the issue",
"files": []
},
{
"commit_hash": "4534f511b2a6a8c1632a1ab97b598d8e4dedada7",
"author": "Test name 1",
"subject": "[TICKET] Subject of the issue",
"files": []
}
]
}
]
}
You can find below my models.py:
from django.db import models
from status.models import Daily
class Component(models.Model):
"""
Component model
"""
module = models.CharField(max_length=40)
daily = models.ForeignKey(Daily, on_delete=models.CASCADE)
class Meta:
db_table = 'gds_component'
class Commit(models.Model):
"""
Commits model
"""
commit_hash = models.CharField(max_length=40)
author = models.CharField(max_length=60)
subject = models.CharField(max_length=250)
component = models.ForeignKey(
Component, related_name='commits',
on_delete=models.CASCADE)
class Meta:
db_table = 'gds_commit'
class File(models.Model):
"""
Commit files model
"""
name = models.CharField(max_length=250)
commit = models.ForeignKey(
Commit, related_name='files',
on_delete=models.CASCADE)
class Meta:
db_table = 'gds_commit_file'
My serializer here:
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = models.File
exclude = ['commit']
class CommitSerializer(serializers.ModelSerializer):
files = FileSerializer(
required=False,
allow_null=True,
many=True
)
class Meta:
model = models.Commit
fields = ('commit_hash', 'author', 'subject', 'files')
def create(self, validated_data):
files_valid_data = validated_data.pop('files')
commit = models.Commit.objects.create(**validated_data)
for file_data in files_valid_data:
models.File.objects.create(commit=commit, **file_data)
return commit
class CompoSerializer(serializers.ModelSerializer):
commits = CommitSerializer(
required=False,
allow_null=True,
many=True
)
class Meta:
model = models.Component
fields = ('module', 'daily', 'commits')
def create(self, validated_data):
commits_valid_data = validated_data.pop('commits')
component = models.Component.objects.create(**validated_data)
for commit_data in commits_valid_data:
models.Commit.objects.create(component=component, **commit_data)
return component
And finally my view.py
#api_view(['POST'])
def post_commit(request):
if request.method == 'POST':
# Get the md5 hash to get it's id
valid_data = request.data
hash_data = valid_data.pop('daily_hash')
try:
daily_obj = Daily.objects.get(daily_key=hash_data)
except Daily.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
# Add daily_id to all modules
serializer_data = valid_data.pop('modules')
for elem in serializer_data:
elem['daily'] = daily_obj.id
# Serialize the data
serializer = serializers.CompoSerializer(data=serializer_data, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
# throw an error if something wrong hapen
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I tried so many things and now i'm totally lost. I think the issue is because I have multiple nested serializer but I'm not sure. Can anyone tell me the right direction to take ?
Best regards
Steph
Sorry for my bad English hope u can understand what i mean.
OUT PUT IM GETTING NOW:
[
{
"MainCatName": 1,
"Name": "harry potter",
"Image": "/media/101029496--sites-default-files-images-101029496-3176173-1748009911-hp.jp-1_MoxqrLp.jpg"
},
{
"MainCatName": 2,
"Name": "Princes Girl",
"Image": "/media/character_princess_rapunzel_8320d57a.jpeg"
},
{
"MainCatName": 3,
"Name": "sex in the city",
"Image": "/media/250px-SATC_Title.jpg"
},
{
"MainCatName": 4,
"Name": "who is dragon",
"Image": "/media/Reggio_calabria_museo_nazionale_mosaico_da_kaulon.jpg"
},
{
"MainCatName": 2,
"Name": "drama queen",
"Image": "/media/15241421_170761763390015_7913498865987146084_n.jpg"
}
]
WHAT I WANT :
I WANT TO TO RETURN THE FORIGN KEY'S (MainCatName) VALUE WHICH IS IN TABLE INSTEAD OF ID. ie value in the [CategoryName = models.CharField(max_length=50)] in my models.py
LIKE
[
{
"MainCatName": Story Books,
"Name": "harry potter",
"Image": "/media/101029496--sites-default-files-images-101029496-3176173-1748009911-hp.jp-1_MoxqrLp.jpg"
},
{
"MainCatName": Darama,
"Name": "Princes Girl",
"Image": "/media/character_princess_rapunzel_8320d57a.jpeg"
},
{
"MainCatName": Roamance,
"Name": "sex in the city",
"Image": "/media/250px-SATC_Title.jpg"
},
{
"MainCatName": sex,
"Name": "who is dragon",
"Image": "/media/Reggio_calabria_museo_nazionale_mosaico_da_kaulon.jpg"
},
{
"MainCatName": darama,
"Name": "drama queen",
"Image": "/media/15241421_170761763390015_7913498865987146084_n.jpg"
}
]
Here is my code :
veiws.py :
class GETCATVeiw(APIView):
def get(self, request):
data = Products.objects.only('MainCatName','Name','Image')
serializer = GETCAT(data, many=True)
return Response(serializer.data)
def post(self):
pass
models.py :
class Category(models.Model):
CategoryName = models.CharField(max_length=50)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.CategoryName
class Products(models.Model):
MainCatName = models.ForeignKey(Category, on_delete=models.CASCADE)
Name = models.CharField(max_length=50)
Image = models.ImageField()
Price = models.IntegerField()
DiscriptionHeading = models.CharField(max_length=100)
DiscriptionParagraph = models.TextField(max_length=1000)
class Meta:
verbose_name_plural = 'Products'
def __str__(self):
return str(self.MainCatName)+' - '+self.Name+' - '+str(self.Price)+' $'
serializers.py :
class GETCAT(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('MainCatName', 'Name', 'Image')
You have the SlugRelatedField for that:
class GETCAT(serializers.ModelSerializer):
MainCatName = serializers.SlugRelatedField(read_only=True, slug_field='CategoryName')
class Meta:
model = Products
fields = ('MainCatName', 'Name', 'Image')
Edit:
I'm not sure it'll work with Products.objects.only('MainCatName','Name','Image') as you'll span a database relation. Also you'll likely want to use a select_related to avoid getting N+1 DB query.
The generic Django way is to define natural keys on your model, in this case Category:
Serializing by natural key: Add a natural_key(self) method to your Category class in which you return the category's CategoryName. It has to be unique!
def natural_key(self):
return self.CategoryName
Deserializing by natural key: You want to define a default manager for your Category model:
objects = CategoryManager()
and define the get_by_natural_key(self, name) method in your CategoryManager(models.Manager) class, which returns the category:
class CategoryManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(CategoryName=name)
I have two serializers: one for the Restaurant model, another for the MainMenu model:
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
class MainMenuSerializer(serializers.ModelSerializer):
restaurant = RestaurantSerializer()
main_menu_items = serializers.StringRelatedField(many=True)
class Meta:
model = MenuMain
fields = ('id', 'restaurant', 'main_menu_items')
The current output of the MainMenuSerializer is
[
{
"id": 1,
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B"
},
"main_menu_items": [
"Fried Rice"
]
},
{
"id": 2,
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B",
},
"main_menu_items": [
"Noodles"
]
}
]
But I want the RestaurantSerializer to only output once, something like this:
[
{
"restaurant": {
"id": 1,
"name": "Restaurant A",
"location": "Street B"
}
},
[
{
"id": 1,
"main_menu_items": [
"Fried Rice",
"Meat Balls"
]
},
{
"id": 2,
"main_menu_items": [
"Noodles"
]
}
]
]
EDIT:
models used
class Restaurant(models.Model):
name = models.CharField(max_length=100, default='')
location = models.CharField(max_length=100, default='')
class MenuMain(models.Model):
price = models.IntegerField()
restaurant = models.ForeignKey(Restaurant, related_name='main_menus')
class MenuMainItems(models.Model):
menu_main = models.ForeignKey(MenuMain, related_name='main_menu_items')
item = models.CharField(max_length=150, default='')
The more "Django REST" way of doing this is to set up a url node for each restaurant that returns all the menu items in the restaurant. For instance,
urls.py
url(r'restaurant-menu/(?P<pk>[0-9]+)$', MenuItemViewset.as_view())
In your viewset class
class MenuItemViewset(viewsets.ModelViewSet):
serializer_class = MainMenuSerializer
def retrieve(self, request, pk=None):
return Restaurant.objects.get(pk=pk).menumain_set.all()[0].menumainitems_set.all()
This of course assumes one menu per restaurant. If there are multiple menus and you want to get all of them, it's probably best practice to break it up into two calls: one for the restaurant's menu, and to get a particular menu's items. Otherwise too many assumptions are being made about the organization of the data, and that's a rather fragile design pattern.
Link to docs on fetching related objects
If you still are intent on getting a response like you originally asked for, you just need to reverse the serializer nesting, i.e. add a menu field to the RestaurantSerializer.
Here is the very simplest approach I came with. I know this can be improved.
Please comment if you have any query or improvement suggestion.
class RestaurantSerializer(serializers.ModelSerializer):
menu = serializers.SerializerMethodField()
def get_menu(self, obj):
dict_l = {}
res = MainMenu.objects.all() # filter as per your requirement
'''
flds = MainMenu._meta.local_fields
for ins in res:
for f in flds:
print f.name
'''
for ins in res:
dict_l['id'] = ins.id
dict_l['name'] = ins.name
dict_l['restaurant'] = ins.restaurant_id
# return {1: 1, 2: 2}
return dict_l
class Meta:
model = Restaurant
fields = ('id', 'name', 'menu',)