#16328: FilePathField should include blank option even when required=True
----------------------------+---------------------------------------
Reporter: ringemup@… | Owner: Harish Bonu
Type: Bug | Status: assigned
Component: Forms | Version: 1.3
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 1
----------------------------+---------------------------------------
Changes (by blighj):
* needs_better_patch: 0 => 1
* owner: Mahmoud Nasser => Harish Bonu
* status: new => assigned
Comment:
The behaviour seen on this ticket is caused because FilePathField doesn't
have a blank 'Select an option' option inserted if it is required. Other
choice fields will insert this option regardless of whether the field is
marked required or not and the inline handling code knows to ignore a
formset if all required fields are sent blank.
Because FilePathField doesn't have this blank option it will always be set
to the first value, even in a new inline row. If there are no other
required fields on that inline form, it will create a new row, which is
what the regression test shows. If there are other required fields you
won't be able to save the form, as the other fields validation will fail,
which is what the reporter saw.
The fix is the same for both, FilePathField should always add the blank
option, similar to other fields.
{{{#!diff
diff --git a/django/forms/fields.py b/django/forms/fields.py
--- a/django/forms/fields.py
+++ b/django/forms/fields.py
@@ -1200,10 +1200,7 @@ class FilePathField(ChoiceField):
self.set_choices()
def set_choices(self):
- if self.required:
- self.choices = []
- else:
- self.choices = [("", get_blank_choice_label())]
+ self.choices = [("", get_blank_choice_label())]
if self.match is not None:
self.match_re = re.compile(self.match)
}}}
You can reproduce the broken behaviour with the following code
{{{#!python
# models.py
import os
from django.db import models
# Any directory containing at least a couple of files works; the app dir
will do.
FILES_DIR = os.path.dirname(__file__)
class BlankFalseParent(models.Model):
name = models.CharField(max_length=100)
class BlankFalseChild(models.Model):
parent = models.ForeignKey(BlankFalseParent, on_delete=models.CASCADE)
path = models.FilePathField(path=FILES_DIR, match=r".*\.py$",
blank=False)
note = models.CharField(max_length=100) # second required field
class BlankTrueParent(models.Model):
name = models.CharField(max_length=100)
class BlankTrueChild(models.Model):
parent = models.ForeignKey(BlankTrueParent, on_delete=models.CASCADE)
path = models.FilePathField(path=FILES_DIR, match=r".*\.py$",
blank=True)
note = models.CharField(max_length=100)
class ChoicesParent(models.Model):
name = models.CharField(max_length=100)
class ChoicesChild(models.Model):
parent = models.ForeignKey(ChoicesParent, on_delete=models.CASCADE)
choices_field = models.CharField(max_length=100, blank=False,
choices=[("name", "name")])
note = models.CharField(max_length=100)
class StandaloneChoices(models.Model):
choices_field = models.CharField(max_length=100, blank=False,
choices=[("name", "name")])
note = models.CharField(max_length=100)
class StandaloneRecord(models.Model):
path = models.FilePathField(path=FILES_DIR, match=r".*\.py$",
blank=False)
note = models.CharField(max_length=100)
}}}
{{{#!python
# admin.py
from django.contrib import admin
from .models import (
BlankFalseChild, BlankFalseParent,
BlankTrueChild, BlankTrueParent,
ChoicesChild, ChoicesParent,
StandaloneChoices, StandaloneRecord,
)
class BlankFalseChildInline(admin.StackedInline):
model = BlankFalseChild
extra = 1
class BlankTrueChildInline(admin.StackedInline):
model = BlankTrueChild
extra = 1
class ChoicesChildInline(admin.StackedInline):
model = ChoicesChild
extra = 1
@admin.register(BlankFalseParent)
class BlankFalseParentAdmin(admin.ModelAdmin):
inlines = [BlankFalseChildInline]
@admin.register(BlankTrueParent)
class BlankTrueParentAdmin(admin.ModelAdmin):
inlines = [BlankTrueChildInline]
@admin.register(ChoicesParent)
class ChoicesParentAdmin(admin.ModelAdmin):
inlines = [ChoicesChildInline]
admin.site.register(StandaloneChoices)
admin.site.register(StandaloneRecord)
}}}
--
Ticket URL: <
https://code.djangoproject.com/ticket/16328#comment:9>