Как обрабатывать массовое создание с несколькими связанными моделями?
У меня есть форма, которая имеет несколько входных значений с одинаковыми именами. Я хочу массово создать объекты на основе следующего шаблона дизайна.
Я думаю, что текущий подход с молнией списка не будет работать правильно, если один из списков неравнозначен.
Какой подход будет лучше? Передняя часть должна быть такой, как я написал, вы можете проверить сниппет кода
Django views
ques = Question.objects.get(id=kwargs["q_id"])
q_title = request.POST.getlist("q_title")
title = request.POST.getlist("title")
types = request.POST.getlist("stype")
is_file = request.POST.getlist("is_file", [0])
params = zip(q_title, is_file, title, types)
for p in params:
q = Question.objects.create(
title=p[0],
is_file=p[1],
)
Option.objects.create(title=p[2], field_type=p[3], question=q)
Я предполагаю, что у вас есть следующие модели и те же форматированные данные, которые используются в
.ques_title
иoptn_title
model.py
class Question(models.Model):
title = models.CharField(max_length=255, help_text='The title of the question')
is_file = models.BooleanField(default=False)
class Option(models.Model):
title = models.CharField(max_length=255, help_text='The title of the option')
field_type = models.CharField(max_length=255, help_text='The field type of the option')
question = models.ForeignKey(Question, on_delete=models.CASCADE)
Вы должны реализовать что-то вроде этого фрагмента кода:
ques_title = ['q1_title', 'q2_title']
is_file = [True, False]
optn_title = ['q1_option1', 'q1_option2', 'q2_option1', 'q2_option2', 'q2_option3']
field_types = ['option1_type', 'option2_type', 'option3_type', 'option4_type', 'option5_type3']
ques_params = zip(ques_title, is_file)
ques_data = question_bulk_create(ques_params=ques_params)
questions = get_option_related_questions(optn_title=optn_title, ques_data=ques_data)
optn_params = zip(optn_title, field_types, questions)
optn_data = option_bulk_create(optn_params=optn_params)
Я реализую это внутри тестовой установки. Вы можете реализовать это в ваших представлениях.
Добавление функций для обработки данных:
common.py
from .models import Question, Option
def question_bulk_create(ques_params):
questions = [Question(title=param[0], is_file=param[1]) for param in ques_params]
ques_obj = Question.objects.bulk_create(questions)
ques_ids = [ques_obj[i].id for i in range(len(ques_obj))]
ques_data = Question.objects.filter(id__in=ques_ids)
return ques_data
def option_bulk_create(optn_params):
options = [Option(title=param[0], field_type=param[1], question=param[2]) for param in optn_params]
optn_objs = Option.objects.bulk_create(options)
optn_ids = [optn_objs[i].id for i in range(len(optn_objs))]
optn_data = Option.objects.filter(id__in=optn_ids)
return optn_data
def get_option_related_questions(optn_title, ques_data):
questions = []
for optn in optn_title:
ques_str = optn.split('_')[0]
ques_obj = ques_data.filter(title__icontains=ques_str).first()
questions.append(ques_obj)
return questions
Полный код примера теста:
test.py
import json
from django.test import TestCase
from .models import Question, Option
from .common import get_option_related_questions, question_bulk_create, option_bulk_create
class TestTheTestConsumer(TestCase):
def setUp(self) -> None:
ques_title = ['q1_title', 'q2_title']
is_file = [True, False]
optn_title = ['q1_option1', 'q1_option2', 'q2_option1', 'q2_option2', 'q2_option3']
field_types = ['option1_type', 'option2_type', 'option3_type', 'option4_type', 'option5_type3']
ques_params = zip(ques_title, is_file)
ques_data = question_bulk_create(ques_params=ques_params)
questions = get_option_related_questions(optn_title=optn_title, ques_data=ques_data)
optn_params = zip(optn_title, field_types, questions)
optn_data = option_bulk_create(optn_params=optn_params)
for data in optn_data:
print(f'title: {data.title} - field type: {data.field_type} - question title: {data.question.title} - question is file: {data.question.is_file}')
print ("Question (test.setUp): ", Question.objects.all())
print ("Option (test.setUp): ", Option.objects.all())
def test_bulk_create(self):
all_ques = Question.objects.all().count()
all_optn = Option.objects.all().count()
self.assertEqual(all_ques, 2)
self.assertEqual(all_optn, 5)
Выход теста:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
title: q1_option1 - field type: option1_type - question title: q1_title - question is file: True
title: q1_option2 - field type: option2_type - question title: q1_title - question is file: True
title: q2_option1 - field type: option3_type - question title: q2_title - question is file: False
title: q2_option2 - field type: option4_type - question title: q2_title - question is file: False
title: q2_option3 - field type: option5_type3 - question title: q2_title - question is file: False
Question (test.setUp): <QuerySet [<Question: Question object (1)>, <Question: Question object (2)>]>
Option (test.setUp): <QuerySet [<Option: Option object (1)>, <Option: Option object (2)>, <Option: Option object (3)>, <Option: Option object (4)>, <Option: Option object (5)>]>
.
----------------------------------------------------------------------
Ran 1 test in 0.010s
OK
Destroying test database for alias 'default'...
Вы могли бы использовать здесь набор форм, но дело в том, что в итоге вы получите вложенные наборы форм (поскольку вам нужно несколько вопросов), что может затруднить стилизацию.
Я бы предложил решить проблему отношения question -> options
во фронтенде. Вот сниппет с использованием jQuery, после заполнения формы и нажатия кнопки "submit", JSON будет выведен в блоке pre
.
После того, как мы получили нужную нам структуру данных, у вас есть несколько вариантов отправки ее в бэкенд:
Вариант1 (рекомендуется): вы можете отправить запрос через AJAX ($.ajax
), используя application/json
в качестве типа содержимого, и в вашем представлении используйте json.loads
тело запроса, чтобы превратить JSON объект в dict
, оттуда вы сможете легко сделать ваш bulk_create
.
Вариант2: создайте другую форму, только со скрытым textarea
, запишите результат в textarea
, затем отправьте форму, в вашем представлении вы можете сделать json.loads(request.GET.get('textareaname'))
, чтобы получить результат.
Вариант 3: Используйте django-restframework
для обработки полезной нагрузки. Он может обрабатывать ошибки, десериализовывать данные, даже создавать ваши модели за вас, если вы используете сериализатор моделей. Лично я бы выбрал этот вариант, но вам, возможно, придется потратить некоторое время на изучение этого фреймворка.