Как обрабатывать массовое создание с несколькими связанными моделями?

У меня есть форма, которая имеет несколько входных значений с одинаковыми именами. Я хочу массово создать объекты на основе следующего шаблона дизайна.

Я думаю, что текущий подход с молнией списка не будет работать правильно, если один из списков неравнозначен.

Какой подход будет лучше? Передняя часть должна быть такой, как я написал, вы можете проверить сниппет кода

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 для обработки полезной нагрузки. Он может обрабатывать ошибки, десериализовывать данные, даже создавать ваши модели за вас, если вы используете сериализатор моделей. Лично я бы выбрал этот вариант, но вам, возможно, придется потратить некоторое время на изучение этого фреймворка.

Вернуться на верх