pythondjangomaxlengthmodelmultiplechoicefield

Django: ModelMultipleChoiceField is applying underlying model's max_length validator to total length of selected results


I'm using a ModelMultipleChoiceField to allow users to select certain model objects to delete. It works fine when a single object is selected, but when multiple objects are selected then a validation error is triggered: "Ensure this value has at most 50 characters". The 50 character limit is from the underlying model's max_length attribute. I'm not sure why this validation is happening at all since I am selecting existing model objects, and even less sure why they are combining the character lengths of all my selections instead of validating each selection individually. I've also noticed that approximately 20 extra characters are being counted for each object selected when totalling the character length. Any help is appreciated.

Here is my code:

Model:

class Template(models.Model):
  # Relationships
  user = models.ForeignKey("users.CustomUser", on_delete=models.CASCADE)

  # Fields
  name = models.CharField(max_length=50)
  description = models.TextField(max_length=250)
  docx_file = models.FileField(("DOCX File"), 
    upload_to=user_directory_path, 
    validators=[FileExtensionValidator(allowed_extensions=['docx'])])
  created = models.DateTimeField(auto_now_add=True, editable=False)
  last_updated = models.DateTimeField(auto_now=True, editable=False)

  def __str__(self):
    return self.name

Form:

class TemplateChoiceDelete(forms.ModelForm):
  name = forms.ModelMultipleChoiceField(queryset=Template.objects.all())

  class Meta:
    model = Template
    fields = ['name']

  # Limit results to templates owned by the current user
  def __init__(self, *args, **kwargs):
      user = kwargs.pop('user')
      super(TemplateChoiceDelete, self).__init__(*args, **kwargs)
      self.fields['name'].queryset = Template.objects.filter(user=user)

View: (ignore the filter code, that is a work in progress relating to another feature)

def manage_templates(request):
    f = TemplateFilter(request.GET, queryset=Template.objects.filter(user=request.user))
    if request.method == 'GET':
        choice_form = TemplateChoiceDelete(request.GET, user=request.user)
        print(choice_form)
        if choice_form.is_valid():
            choice_form.cleaned_data['name'].delete()
    else:
        form = TemplateChoiceDelete
    return render(request, 'docs/manage_templates.html', {'filter': f, 'choice_form': choice_form})

Template: (again, please ignore the code relating to filters)

{% block content %}
    <p> <b> Search </b> </p>
    <form method="get">
        {{ filter.form.as_p }}
        <input type="submit" value="Search" />
    </form>
    {% for obj in filter.qs %}
        {{ obj.name }} | {{ obj.description }}<br />
    {% endfor %}
    <br>
    <p><b> Delete Templates </b></p>
    <form method="GET">
      {{ choice_form.as_p }}
      <input type="submit" onclick="return confirm('Are you sure? This will delete the selected template(s)')" value="Delete">
    </form>
{% endblock %}

The error


Solution

  • This is not a ModelForm, if you use a ModelForm, it will of course "inherit" certain validators from the model. This is a simple Form, so:

    class TemplateChoiceDelete(forms.Form):
        name = forms.ModelMultipleChoiceField(queryset=Template.objects.all())
    
        # no Meta
    
        # Limit results to templates owned by the current user
        def __init__(self, *args, **kwargs):
            user = kwargs.pop('user')
            super().__init__(*args, **kwargs)
            self.fields['name'].queryset = Template.objects.filter(user=user)

    A ModelForm is a Form that is tailored towards a certain model to create or update a model instance, not to edit items that have "something to do" with that model.


    Note: Section 9 of the HTTP protocol specifies that requests like GET and HEAD should not have side-effets, so you should not change entities with such requests. Normally POST, PUT, PATCH, and DELETE requests are used for this. In that case you make a small <form> that will trigger a POST request, or you use some AJAX calls.