【问题标题】:How to handle bulk create with multiple related models?如何使用多个相关模型处理批量创建?
【发布时间】:2021-10-26 11:43:00
【问题描述】:

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

我认为如果列表之一不相等,当前压缩列表的方法将无法正常工作。

什么是更好的方法?前面部分应该是我贴的这样你可以查看代码sn-p

   <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 视图

    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

尝试1:

在将问题 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)

【问题讨论】:

    标签: python jquery django django-views django-templates


    【解决方案1】:

    我假设您的模型与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)
    

    你必须实现类似这样的代码 sn-p:

    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'...
    

    【讨论】:

    • 创建选项无法正常工作,因为选项标题/字段类型列表对于不同的问题会不相等
    • 能否请您添加不同的场景,以便我思考并回复您?
    • 例如ques_titles = ['ques1', 'ques2'] and option_titles=['ques1_option', 'q1_2nd_option', 'ques2_1option', 'ques2_2nd_option','ques2_3rdoption']
    • 你将如何追踪哪些 option_titles 属于哪些问题
    • 这就是我真正面临的问题
    【解决方案2】:

    您也许可以在此处使用 formset,但问题是您最终会得到一个嵌套的 formset(因为您需要在这里提出多个问题),这可能会使样式设置变得困难。

    我建议在前端解决question -&gt; options 关系问题。这是一个使用 jQuery 的 sn-p,填写完表单并点击“提交”后,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>

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

    选项1(推荐):您可以使用application/json作为内容类型通过AJAX($.ajax)请求提交,并且在您看来,使用json.loads请求正文将JSON对象转换为dict,来自在那里你应该可以轻松地做你的bulk_create

    选项2:创建另一个表单,只有一个隐藏的textarea,将结果写入textarea,然后提交表单,在你的视图中你可以通过json.loads(request.GET.get('textareaname'))得到结果。

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

    【讨论】:

    • 感谢 +1。我认为这种方式更好,但我的代码在实现时搞砸了。你能稍微简化一下你的 jquery 代码,以便我可以在我的代码上实现它吗?
    • 我认为我不能简化太多,我在 JS sn-p 中添加了 cmets,您可能想与 jquery 文档一起阅读它。
    猜你喜欢
    • 2012-10-02
    • 2016-08-15
    • 1970-01-01
    • 2021-04-20
    • 2021-07-14
    • 1970-01-01
    • 2014-06-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多