【问题标题】:How to keep a form from submitting when creating a Django model object with many-to-many field selection that is doesn't exist in the related model?在创建具有相关模型中不存在的多对多字段选择的 Django 模型对象时,如何防止提交表单?
【发布时间】:2020-09-17 03:02:26
【问题描述】:

我正在使用 Django 创建一个网络应用程序。我有多个相互之间有关系的 Django 模型。我的一个模型叫做Type,另一个叫做TestEquipmentType 模型与 TestEquipment 具有多对多关系。

为了允许用户创建一个新的Type,我有一个 html 表单并选择哪个TestEquipment 将与那个Type 关联我正在使用带有“芯片”(javascript)的可搜索下拉列表,它加载所有TestEquipment 并允许您搜索和选择要添加的多个对象。当用户提交表单时,会添加选中的TestEquipment

除了用户输入文本并按下回车键之外,一切都很好,而不是从下拉列表中选择,而是添加了一个带有该文本的芯片。当表单提交时,它会尝试添加一个不存在的TestEquipment

我想找到一种方法,要么不允许添加不存在的对象,要么发出警告“必须从现有测试设备中选择”;不知何故,我必须确保表单不会提交给我的构造函数,并在将文本添加到字段时创建新的 Type

我试图找到这个问题的答案,但绝对没有运气。非常感谢任何帮助!

Django 模型代码:

class TestEquipment(models.Model):
    name = models.CharField(max_length=64, unique=True)
    notes = models.TextField(null=True, blank=True)

    def __str__(self):
        return f"{self.name}"


class Type(models.Model):
    name = models.CharField(max_length=64, unique=True)
    type_folder = models.URLField(null = True, blank = True)
    type_test_equipment = models.ManyToManyField(TestEquipment, blank=True, related_name="type_test_equipment")
    type_notes = models.TextField(null=True, blank=True) 
    test_sheet = models.URLField(null=True, blank=True) 
    type_test_guide = models.URLField(max_length=300, null=True, blank=True)

    objects = TypeManager()

    def __str__(self):
        return f"{self.name}"

Views.py 代码:

def create_type_view(request):

    if not request.user.is_authenticated:
        return render(request, "jobs/login.html", {"message": None})
    test_equipments = TestEquipment.objects.all()
    equipment_types = Type.objects.all()
    #pass in existing types of equipment
    context= {
        "equipment_types": equipment_types,
        "test_equipments": test_equipments
        }
    if request.user.is_authenticated:
        return render(request, "jobs/create_type.html", context)

create_type.html 代码(引入具体化样式和 javascript):

<div> 
  <form id="type_form" class="col s12" action="{% url 'create_type' %}" method="post">
    {% csrf_token %}
    <div class="row">
      
      <div class="col s12">
        <h6 style="color:#808080">Select Type-Specific Test Equipment</h6>
        <div class="row">
          <div class="input-field col s12">
            <div id="test_equipment-chips" class="chips chips-autocomplete">
            </div>
            <input id="test_equipment" name="test_equipment" style="visibility: hidden;" value="">
          </div>
        </div>
      </div>
    </div>
    <div class="row">
      <input id="submit-btn" type="submit" value="Confirm Add" onclick="onSubmit();" />
    </div>
  </form>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>

<script>
  

  function onSubmit() {
    var chipInstance = M.Chips.getInstance($("#test_equipment-chips"));
    var value = Array.from(chipInstance.chipsData.map(x=>x.tag)).join(",");
    $("#test_equipment").val(value);
    

  }
 
  $('#test_equipment-chips').chips({
    autocompleteOptions: {
      data: {
          {% for test_equipment in test_equipments %}
            '{{test_equipment}}': null,
          {% endfor %}
        },
    limit: Infinity,
    minLength: 0,
      }
    });

</script>

构造函数代码:

def create_type(request):

    #extract type name from form
    type_name = request.POST["type_name"]

    #create new type object
    new_type=Type.objects.create_type(type_name)
    #fill in all properties that were submitted in the form
    new_type.type_folder = request.POST["type_folder"]
    new_type.test_sheet = request.POST["test_sheet"]
    new_type.type_test_guide = request.POST["type_test_guide"]
    new_type.type_notes = request.POST["type_notes"]
    
    equipment_list=request.POST["test_equipment"].split(",")
    
    for equipment_name in equipment_list:
        
        equipment=TestEquipment.objects.get(name=equipment_name)
        new_type.type_test_equipment.add(equipment.pk)

    #save to database
    new_type.save()

    return HttpResponseRedirect(reverse("jobs"))

【问题讨论】:

    标签: javascript python html django


    【解决方案1】:

    所以最重要的事情是在你的 python 中执行检查,因为你不应该像这样对数据库执行get 请求,除非你知道它不会失败。如您所见,当对象不存在时,它最终会出现 500 个错误。

    因此,在您看来,工作 1 就是这样做;

        for equipment_name in equipment_list:
            try:
                equipment=TestEquipment.objects.get(name=equipment_name)
            except TestEquipment.DoesNotExist:
                # Log the error somehow if you like?
                print(f"Equipment not found with the name {equipment_name}")
            else:
                # This only executes when the exception wasn't raised
                new_type.type_test_equipment.add(equipment.pk)
    

    使用上面的代码,它只会忽略无效输入,这适合最小可行选项。如果您想在输入无效时提供错误,但是您应该遍历equipment_list 中的所有值,以确保它们在添加到关系之前存在。这样您就可以再次呈现带有错误的表单。

    我查看了芯片的演示/文档,它看起来不能帮助防止无效输入,但您可以自己在 javascript 中进行一些验证以防止提交。

    您还应该看看使用 django 的表单,因为您的视图直接访问帖子数据。通过使用表单,您可以在视图之外处理验证逻辑,并且可以从中获得更多好处。如果您还没有找到有关表格的信息,请阅读此页面; https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms

    【讨论】:

    • 谢谢,是的,这将有助于消除错误,但仍会事先创建新的 Type 对象。在提交表单并停止提交之前,我需要能够检查芯片输入中添加的所有对象是否有效,我假设这将使用 javascript 完成。如果我这样做,我不应该需要上述内容,因为表单提交已经停止。你知道怎么做javascript吗?我已经尝试了多个 onsubmit javascript 函数,但我想我不擅长它,因为我无法弄清楚。
    • 你应该总是像我展示的那样做 python 验证,因为你不能保证用户启用了 javascript。如果您想清理类型对象,您可以在异常块中放置一个标志,该标志变为真。然后在该循​​环之外,如果错误标志为真,则删除类型而不是保存。但是,将所有这些放入 Django 表单中,您可以在查看视图之前验证这些值,这会变得容易得多。
    • 关于 python 验证的要点。我补充说。我最初考虑使用 Django 表单,但我根本不喜欢这种格式,而且我特别不喜欢多对多字段的选择器。据我在线阅读,它需要对 Django 源代码进行一些深入的编辑才能对其进行自定义。我走了一条不同的路线,如果有办法在没有它们的情况下进行验证,我宁愿不使用 Django 表单重新开始。我很好奇将 ValidationError 从 django.core.exceptions 导入视图或构造函数文件并在不使用它们的表单的情况下使用它。对此有什么想法吗?
    猜你喜欢
    • 2011-10-23
    • 2013-06-04
    • 1970-01-01
    • 2019-08-17
    • 1970-01-01
    • 1970-01-01
    • 2019-04-20
    • 2018-06-28
    • 1970-01-01
    相关资源
    最近更新 更多