Django ModelChoiceField allow objects creation

2020-07-25 23:54发布

问题:

Django's ModelChoiceField is the default form field used for foreign keys when deriving a form from a model using ModelForm. Upon validation, the field will check that selected value does exist in the corresponding related table, and raise a ValidationError if it is not the case.

I'm creating a form for a Document model that has a type field, a foreign key to a Type model which does only contain a name attribute. Here is the code of models.py for clarity

class Type(models.Model):
    name = models.CharField(max_length=32)

class Document(models.Model):
    title = models.CharField(max_length=256)
    type = models.ForeignKey(Type, related_name='related_documents')

Instead of a standard select control, I'm using selectize.js to provide auto-completion to the users. Moreover, selectize provides a "create" option, that allows to enter a value that does not exist yet into the select.

I would like to extend the ModelChoiceField in order to create a new Type object when the selected value does not exist (the new value will be assigned to name field, this should be an option of the field for reusability). If possible, I would like the object to not be inserted into DB until save() is called on the validated form (to prevent that multiple failed validation create multiple rows in db). What would be a good way to do so in Django? I tried to look into the documentation and the source code, tried to override ModelChoiceField and tried to build this behavior basted on a TextField but I'm not sure if there isn't a simpler way to do it.

I looked into the following questions but couldn't find the answer.

  • Django ModelChoiceField has no plus button
  • Django: override RelatedFieldWidgetWrapper
  • How to set initial value in a dynamic Django ModelChoiceField

I would like to keep the process of adding new types as simple as possible - i.e.: do not use a pop-up attached to a '+' button. User should be able to type the value, and the value gets created if it doesn't exist.

Thanks

回答1:

This seems like it'd be easier without using a ModelForm. You could create a list of all of your Type objects then pass that to the template as a context variable. Then, you could use that list to construct a <select> element. Then use jquery and selectize to add the necessary attributes to the form.

#views.py
...
types = Type.objects.all()
...



#template.html
...
<form action="" method="POST">{% csrf_token %}
    <input type='text' name='title'>
    <select name='type' id='id_type'>
        {% for type in types %}
            <option value="{{type.id}}">{{type.name}}</option>
        {% endfor %}
    </select>
    <input type='submit'>
</form>
<script type='text/javascript'>
$('#id_type').selectize({
    create: true
    });
</script>
...

Then when you get a form submission, you can process it in a simple view function:

if request.method == POST:
        title_post = request.POST.get('title','')
        type_post = request.POST.get('type',None)
        if type_post is not None:
            try:
                #If an existing Type was selected, we'll have an ID to lookup
                type = Type.objects.get(id=type_post)
            except:
                #If that lookup failed, we've got a user-inputted name to use
                type = Type.objects.create(name=type_post)
            new_doc = Document.objects.create(title=title_post, type=type)