简体   繁体   中英

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?

I'm using Django to create a web app. I have multiple Django models with relationships between each other. One of my models is called Type and another is called TestEquipment . The Type model has a many-to-many relationship with TestEquipment .

To allow the user to create a new Type , I have an html form and to select which TestEquipment will be associated with that Type I have am using searchable dropdown with "chips" (javascript) which loads all TestEquipment and allows you to search and select multiple objects to add. When the user submits the form, the selected TestEquipment is added.

Everything works great other than when the user enters text and presses enter, instead of selecting from the dropdown, a chip is added with that text. When the form submits it tries to add a TestEquipment that doesn't exists.

I would like to find a way to either not allow adding an objects that doesn't exist or throw an alert "must select from existing test equipment"; somehow I have to ensure that the form does not submit to my constructor and create the new Type if text is added to the field.

I've tried to find an answer to this and absolutely have had no luck. Any help is REALLY appreciated!

Django Models code:

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 code:

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 code (pulls in materialize styling and 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>

code for constructor:

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"))

So the most important thing to do there is to perform a check in your python because you shouldn't perform a get request to the database like that unless you know it can't fail. As you're seeing, it just ends up with 500 errors when objects don't exist.

So job 1 would be to do this in your view;

    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)

Using the above code, it'd just ignore invalid input, which suits a minimum viable option. If you wanted to provide an error in the event of invalid input however you should run through all values in equipment_list to ensure they exist before adding them to the relationship. That way you could render the form again with the errors attached.

I've had a look at the demo/docs for chips and it doesn't look like it can help prevent invalid inputs, but you could do some validation in javascript yourself to prevent submission.

You should also have a look at using django's forms because your view is directly accessing post data. By using forms you can handle validation logic outside of your view & there's a bunch more benefits from them. If you're yet to find out about forms, give this page a read; https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM