python - Formset 保存除 0 以外的任何小数,即使 formset 已保存且 is_valid() 通过
问题描述
我创建了一个表单集,让用户每周记录小时数。我遇到的问题是我无法在输入字段中保存“0”——除 0 之外的任何小数都有效(请参阅末尾的 gif 以进行说明)
TLDR:Formset 保存除 0 以外的任何输入,不知道为什么。请参阅此处的 gif 以获取说明:https ://imgur.com/a/iCMexQk
我的Timesheet
和TimesheetEntry
模型如下所示:
class Timesheet(Model):
year = PositiveIntegerField(validators=[MaxValueValidator(2500), MinValueValidator(1900)])
week = PositiveIntegerField()
project = ForeignKey("projects.Project", on_delete=CASCADE)
user = ForeignKey(User, on_delete=CASCADE)
status = CharField(
max_length=15, choices=STATUS_CHOICES, blank=True, default=STATUS_OPEN
)
objects = TimesheetManager()
class Meta:
db_table = 'timesheet'
ordering = ('id',)
unique_together = (('year', 'week', 'user', 'project',),)
def __getattr__(self, attr):
allowed_days = (
'day_1', 'day_2', 'day_3', 'day_4', 'day_5', 'day_6', 'day_7'
)
if attr in allowed_days:
day = int(attr[-1]) - 1
entry = self.timesheetentry_set.filter(
date=Week(self.year, self.week).day(day)
).first()
if entry:
return entry.hours
return None
return super().__getattr__(attr)
def total_duration(self):
return self.timesheetentry_set.aggregate(
total_duration=Coalesce(Sum('hours'), 0)
).get('total_duration')
class TimesheetEntry(Model):
timesheet = ForeignKey(Timesheet, on_delete=CASCADE)
hours = DecimalField(max_digits=4, decimal_places=1, null=True, blank=True)
date = DateField()
class Meta:
db_table = 'timesheet_entry'
ordering = ('date',)
外观如下forms.py
:
DAYS = (
'day_1', 'day_2', 'day_3', 'day_4', 'day_5', 'day_6', 'day_7'
)
class TimesheetModelForm(ModelForm):
class Meta:
model = Timesheet
exclude = ("user", "status")
class BaseFormSetValidation(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for form in self.forms:
for day in DAYS:
form.fields[day].initial = getattr(form.instance, day)
def add_fields(self, form, index):
super().add_fields(form, index)
for day in DAYS:
form.fields[day] = DecimalField(required=False)
def save(self, commit=True):
super(BaseFormSetValidation, self).save(commit)
from isoweek import Week
for form in self.forms:
week = Week(form.instance.year, form.instance.week)
for day in DAYS:
if form.cleaned_data.get(day):
date = week.day(int(day[-1]) - 1)
TimesheetEntry.objects.update_or_create(
timesheet=form.instance,
date=date,
defaults={'hours': form.cleaned_data.get(day)}
)
TimesheetModelFormSet = modelformset_factory(
Timesheet,
formset=BaseFormSetValidation,
exclude=("year", "week", "project", "user"),
extra=0,
)
views.py
class TimesheetEditorView(BaseTimesheet, TemplateView):
form_class = TimesheetModelFormSet
template_name = "timesheets/timesheet.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# only show timesheet rows that belongs to logged in user
timesheet = Timesheet.objects.filter(
year=context["year"],
week=context["week"],
user=self.request.user
).order_by("project_id")
timesheet_formset = self.form_class(queryset=timesheet)
create_timesheet_form = TimesheetModelForm(self.request.user)
context.update(
timesheet=Timesheet.objects.none(),
timesheet_formset=timesheet_formset,
create_timesheet_form=create_timesheet_form
)
return context
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
timesheet_formset = self.form_class(request.POST, error_class=DivErrorList)
context.update(timesheet_formset=timesheet_formset)
if timesheet_formset.is_valid():
timesheet_formset.save()
message = "Weekly timesheet (is_valid())"
messages.info(request, message, extra_tags='timesheet')
success_url = reverse("timesheets:current-week", args=(context["year"], context["week"]))
return HttpResponseRedirect(success_url)
else:
print(timesheet_formset.errors)
print(timesheet_formset.non_form_errors)
return render(request, "timesheets/timesheet.html", context)
timesheets.html
<tbody>
{{ timesheet_formset.management_form }}
{% for row in timesheet_formset %}
<tr class="tracker">
<th scope="row" class="align-middle">{{ row.instance.project }} {{ row.id }}</th>
<td>{{ row.day_1 }}</td>
<td>{{ row.day_2 }}</td>
<td>{{ row.day_3 }}</td>
<td>{{ row.day_4 }}</td>
<td>{{ row.day_5 }}</td>
<td>{{ row.day_6 }}</td>
<td>{{ row.day_7 }}</td>
<td class="align-middle">{{ row.instance.total_duration }}</td>
</tr>
{% endfor %}
</tbody>
任何十进制输入都有效,除了 0 - 表单已保存并被is_valid()
触发,但由于某种原因,表单将 0 视为None
并且未正确保存。
我附上了一个 gif 来说明这个问题——注意任何十进制输入是如何工作的,但是当我输入“0”时——它只是恢复到最新的值:
关于为什么会发生这种情况的任何想法?
解决方案
在您的方法save()
中,BaseFormSetValidation
您正在检查价值:
if form.cleaned_data.get(day):
该值0
是假的,因此 if 条件将是False
并且条件不会运行。
None
, 0
,False
是假值。
相反,您可以显式检查None
:
class BaseFormSetValidation(BaseModelFormSet):
...
def save(self, commit=True):
super(BaseFormSetValidation, self).save(commit)
from isoweek import Week
for form in self.forms:
week = Week(form.instance.year, form.instance.week)
for day in DAYS:
if form.cleaned_data.get(day) is not None:
date = week.day(int(day[-1]) - 1)
TimesheetEntry.objects.update_or_create(
timesheet=form.instance,
date=date,
defaults={'hours': form.cleaned_data.get(day)}
)
推荐阅读
- swift - 确定以编程方式创建的视图何时出现在屏幕上
- javascript - JavaScript 如何创建一个函数,该函数返回一个字符串,其中一个字符在字符串中出现的次数
- java - 从存储库生成 CSharp 改装客户端
- mysql - Mysql将父子数据复制到另一个具有不同父子ID的表
- flutter - Flutter path_provider和connectivity_plus版本解决失败
- html - 如何垂直对齐div中的内容
- file - UNIX - 我想在文本文件中查找列数多于预期的记录及其行号
- elasticsearch - 加载配置文件时出错:yaml:第 82 行:没有找到预期的密钥
- csv - 使用 Python 3 将 xml 数据转换为 csv 表
- skiasharp - 如何使用 SkiaSharp 压印具有透明背景的文本?