如何处理与多个相关模型的批量创建?



这里我有一个表单,有多个相同名称的输入值。我想根据下面的模板设计批量创建对象。

我认为当前压缩列表的方法不能正常工作,如果其中一个列表是不相等的。

什么是更好的方法?前面的部分应该是这样的,因为我张贴你可以检查代码片段

<script>
$(document).on("click", ".q-name", function () {
$(this).closest(".untitled").hide();
$(this).siblings('.q-title').prop('hidden', false);
});
</script>
<script>
$(document).on("click", ".addOption", function () {
option = `<div class="item">
<div>
<input
class="form-control"
type="text"
name="title"
placeholder="Enter title"
/>
</div>
<div>
<select name="type" class="form-control">
Select
<option disabled>Select</option>
<option value="1">Option1</option>
<option value="2">Option2</option>
</select>
</div>`;
$(this).closest(".options").prepend(option);
});
$(document).on("click", ".newOptionGroup", function () {
group = `<input type="text" name="q_title" placeholder="model A field" class="form-control q-title"/></div>
<p>Options of that model (Another model fields)</p>
<div class="options">
<div class="item">
<div>
<input
class="form-control"
type="text"
name="title"
placeholder="Enter title"/>
</div>
<div>
<select name="type" class="form-control">
Select
<option disabled>Select</option>
<option value="1">Option1</option>
<option value="2">Option2</option>
</select>
</div>
</div>
<div class="last">
<button type="button" class="btn btn-icon-only addOption">
Add more
</button>
<div>
<div class="custom-control custom-switch">
<input
name="is_document"
type="checkbox"
class="custom-control-input"
id="customSwitche"
value="1"
/>
<label class="custom-control-label" for="customSwitche"
>Is File</label
>
</div>
</div>
<div></div>
</div>
</div>
</div>
<div class="option-group-new newOptionGroup">
<button> Add New group</button>
</div>
</div>
<div class="text-right mt-4">
<button type="submit" class="btn btn-outline-grey">Submit</button>
</div>`;
$(".group-form").append(group);
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form class="group-form" method="post"> 
<input
type="text"
name="q_title"
class="form-control q-title"
placeholder="model A field name"
/>
<p>Options of that model (Another model fields)</p>
</div>
<div class="options">
<div class="item">
<div>
<input
class="form-control"
type="text"
name="title"
placeholder="Enter title"
/>
</div>
<div>
<select name="type" class="form-control">
Select
<option disabled>Select</option>

<option value="1">Option1</option>
<option value="2">Option2</option>
</select>
</div>

</div>
<div class="last">
<button type="button" class="btn btn-icon-only addOption">
Add more
</button>
<div>
<div class="custom-control custom-switch">
<input
name="is_document"
type="checkbox"
class="custom-control-input"
id="customSwitche"
value="1"
/>
<label class="custom-control-label" for="customSwitche"
>Is File</label
>
</div>
</div>
<div></div>
</div>
</div>
</div>
<div class="option-group-new newOptionGroup">
<button type="button"> New group</button>
</div>
</div>
<div class="text-right mt-4">
<button type="submit" class="btn btn-outline-grey">Submit</button>

</div>
</form>
</div>

<Django视图/strong>

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)

编辑:

问题标题和选项标题不相等,因为问题可以有无限个选项。

例如:

questions = ['q1', 'q2']
options = ['q1_option1', 'q1_option2', 'q2_option1', 'q2_option2', 'q2_option3']

我不能跟踪哪个选项将属于特定的问题。

EDIT2:

ques_titles = ['q1-title', 'q2_title']
is_file = [True, False]
# ques_titles and is_file will be equal.
option_titles = ['q1_option1', 'q1_option2', 'q2-option1', 'q2-option2', 'q3-option3']
types = ['option1_type', 'option2_type', 'option3-type', 'option4-type', 'option5-type3'] 
#option_titles and types list will be equal

Try1:

在此尝试将问题fk分配给选项时,只分配一个问题而不是相应的问题。

questions = [Question(title=param[0], is_file=param[1]) for param in ques_params]
ques_objs = Question.objects.bulk_create(questions)
optn_params = zip(optn_title, types)
options = []
for q in ques_objs:
print(q)
options.append([Option(title=param[0], field_type=param[1], question=q) for param in optn_params])
Option.objects.bulk_create(options[0])s)

我假设您有以下模型和ques_titleoptn_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'...

您可能可以在这里使用formset,但问题是您最终会使用嵌套的格式集(因为这里需要多个问题),这可能会使样式设置变得困难。

我建议解决前端的question -> options关系问题。下面是一个使用jQuery的代码片段,在您填写表单并单击"提交"之后,JSON将在pre块中打印出来。

const $questionTemplate = $('#template-question').removeAttr('id');
const $optionTemplate = $('#template-option').removeAttr('id');
const $output = $('#output');
function newOption() {
// clone the option template
return $optionTemplate.clone();
}
function newQuestion() {
// clone the question template, replace the place holder [data-container=options] with 1 Option
const $questionClone = $questionTemplate.clone();
$questionClone
.find('[data-container=options]')
.replaceWith(newOption());
return $questionClone;
}
function addQuestion(btnEl) {
//add a question to before the btnEl
//You might need to change this to find the right location to add the template suit your design
$(btnEl).before(newQuestion());
}
function addOption(btnEl) {
//add a option to before the btnEl
//You might need to change this to find the right location to add the template suit your design
$(btnEl).before(newOption());
}
function getValue($el) {
switch ($el.attr('type')) {
case 'checkbox':
// for checkbox type, jquery will give 'on' if checked, we convert it to a boolean here
return $el.val() === 'on';
default:
// otherwise we just take the raw value.
return $el.val();
}
}
function elementToObject($el) {
//this function maps dom to a javascript object (or python dictionary),
//this implementation only look at one level below the $el
//You most likely need to change how this function finds the `data-attr` and their values
/*
<div>
<input type="text" data-attr="name1" value="value1">
<input type="text" data-attr="name2" value="value2">
<div>
<!-- NOTE: this won't be included, since we used `.children()`, you might want to use `.find()`
<input type="text" data-attr="name3" value="value3">
</div>
</div>
becomes
{
"name1": "value1",
"nam2": "value2"
}
*/
return Object.fromEntries(
$el
.children('[data-attr]')
.toArray()
.map((attr) => {
const $attr = $(attr);
return [$attr.attr('data-attr'), getValue($attr)];
})
);
}
function init() {
const $form = $('#form');
$form.click((e) => {
//binding to the form's click function allows newly added buttons to trigger this even
//without binding events to the new buttons themselves.
//all clicks within the form will be captured by this listener
//but we are only interested in buttons with 'data-action' attributes
switch ($(e.target).attr('data-action')) {
case 'add-question':
return addQuestion(e.target);
case 'add-option':
return addOption(e.target);
}
});
$form.submit((e) => {
e.preventDefault();
const result = $form
.find('[data-container=question]')
.toArray() //find all question elements, turn them into an array
.map((question) => {
const $question = $(question);
const questionObj = elementToObject($question); // serialize it to a object (dictionary)
questionObj.options = $question // add options to the object (dictionary)
.find('[data-container=options]')
.toArray() // find all option elements inside of the question element, turn them into an array
.map((option) => elementToObject($(option))); // serialize it to a object (dictionary)
return questionObj;
});
$output.text(JSON.stringify(result, null, 4));
});
$form.prepend(newQuestion());
}
init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- BEGIN templates -->
<!-- these are just here for cloneing -->
<div style="display: none">
<div
id="template-question"
data-container="question"
style="padding-bottom: 10px"
>
<hr />
Question:
<input type="text" data-attr="question" placeholder="Enter question" />
<div data-container="options"></div>
<button type="button" data-action="add-option">Add more</button>
</div>
<div
id="template-option"
data-container="options"
style="padding: 10px 0px 10px 30px"
>
Title:
<input type="text" placeholder="Enter title" data-attr="title" /><br />
Option:
<select data-attr="option">
<option disabled>Select</option>
<option value="1">Option1</option>
<option value="2">Option2</option></select
><br />
Is File: <input data-attr="is_file" type="checkbox" />
</div>
</div>
<!-- END templates -->
<form id="form">
<button type="button" data-action="add-question">New group</button>
<button type="submit">Submit</button>
</form>
<pre id="output"></pre>

在我们得到我们想要的数据结构之后,你有几个选项将它发送到后端:

Option1(推荐):您可以通过AJAX ($.ajax)使用application/json作为内容类型的请求提交,并在您的视图中,使用json.loads请求体将JSON对象转换为dict,从那里您应该能够轻松地完成bulk_create

Option2:创建另一个表单,只有一个隐藏的textarea,将结果写入textarea,然后提交表单,在您的视图中,您可以执行json.loads(request.GET.get('textareaname'))来获得结果。

Option3:使用django-restframework来处理负载。如果使用模型序列化器,它可以处理错误、反序列化数据,甚至为您创建模型。我个人会选择这个选项,但是你可能需要花一些时间来学习这个框架。

相关内容

  • 没有找到相关文章

最新更新