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.