Django:复杂的表单集验证,self.instance值没有意义

Django:复杂的表单集验证,self.instance值没有意义,django,validation,formset,inline-formset,Django,Validation,Formset,Inline Formset,我正在尝试覆盖表单集上的clean方法。正如文档建议的那样,我编写了自己的BaseFormSet,并改写了它的clean方法。表单集的某些值取决于父模型表单中的值 模型是保存工作时间的应用程序中的一个条目。一个条目适用于一天,并且只能有一个条目,但是一个条目可以有多个班次。可以想象,模型表单用于输入,表单集用于转换。因此,条目是父项,移位是子项 为了验证移位,我需要访问父条目。我试图通过在clean方法中调用self.instance来实现这一点。在这种情况下,self将是表单集 然而,self

我正在尝试覆盖表单集上的clean方法。正如文档建议的那样,我编写了自己的BaseFormSet,并改写了它的clean方法。表单集的某些值取决于父模型表单中的值

模型是保存工作时间的应用程序中的一个条目。一个条目适用于一天,并且只能有一个条目,但是一个条目可以有多个班次。可以想象,模型表单用于输入,表单集用于转换。因此,条目是父项,移位是子项

为了验证移位,我需要访问父条目。我试图通过在clean方法中调用self.instance来实现这一点。在这种情况下,self将是表单集

然而,self.instance的内容始终是当前的条目,缺少它的所有者。如果我试图添加1月4日的条目,self.instance应该是该日期的条目。但是,当我访问self.instance时,结果是今天的条目。但是,条目的表单是有效的。所有值都输入正确。应该有一个所有者

我的问题是,我是否正确使用self.instance?如果没有,我如何在验证期间从Shift FormSet访问父项的值

班次的定义:

class Shift(m.Model):
    TIME_HELP_TEXT = 'Please use the following format: <em>HH:MM</em>.'
    # meta
    cr_date = m.DateTimeField(auto_now_add=True, editable=False, verbose_name='Date of Creation')  # date of creation
    # content
    entry   = m.ForeignKey(Entry, on_delete=m.CASCADE, related_name='shifts')
    start   = m.TimeField(help_text=TIME_HELP_TEXT)  # starting time
    end     = m.TimeField(help_text=TIME_HELP_TEXT)  # ending time
    recess  = m.PositiveIntegerField(default=0)  # recess time in minutes
    project = m.ForeignKey(Project, on_delete=m.PROTECT)
格式集声明:

class BaseShiftFormSet(f.BaseInlineFormSet):
    warnings = []

    def has_messages(self):
        return self.warnings

    def flush_messages(self, request):
        for w in self.warnings:
            messages.warning(request, w)

    def clean(self):
        if any(self.errors):
            # Don't bother validating the formset unless each form is valid on its own.
            return

        super(BaseShiftFormSet, self).clean()

        # Standard day. Is used to be combined with Time object instances in order to get Datetime object instances,
        # which allow for arithmetic comparison as opposed to Time objects.
        # entry = self.instance
        # day = datetime.date(entry.get_year(), entry.get_month(), entry.get_day())
        day = datetime.date(1, 1, 1)
        one_day = datetime.timedelta(days=1)
        # previous_entry = Entry.get_entry_for_day(entry.get_owner(), day - one_day)
        # next_entry = Entry.get_entry_for_day(entry.get_owner(), day + one_day)
        earliest = datetime.datetime.combine(day, datetime.time(hour=6))
        latest = datetime.datetime.combine(day, datetime.time(hour=20))
        # shifts = entry.get_shifts()
        valid = True
        work_time = datetime.timedelta()
        work_time_limit = datetime.timedelta(hours=10)
        min_break = datetime.timedelta(hours=11)

        for form in self.forms:
            start        = datetime.datetime.combine(day, form.cleaned_data['start'])
            end          = datetime.datetime.combine(day, form.cleaned_data['end'])
            recess_time  = datetime.timedelta(minutes=form.cleaned_data['recess'])
            shift_length = end - start
            # new_shift    = Shift(start=form.cleaned_data['start'], end=form.cleaned_data['end'],
            #                      recess=form.cleaned_data['recess'], project=form.cleaned_data['project'],
            #                      entry=entry)
            work_time   += shift_length - recess_time

            # Validation: LOGICAL ERRORS
            # Start before end.
            if start > end:
                raise ValidationError(ERROR_MESSAGES['shift_inconsistent'])
            # Recess not longer than total work time.
            if recess_time >= shift_length:
                raise ValidationError(ERROR_MESSAGES['shift_recess_too_long'])
            # # No overlapping shifts
            # for shift in shifts:
            #     valid = valid and (new_shift.is_before(shift) or new_shift.is_after(shift))
            # if not valid:
            #     raise ValidationError(ERROR_MESSAGES['shifts_overlap'])

        #     # Validation: WARNINGS
        #     # No night shifts. Applies only to office work.
        #     if entry.is_type_work():
        #         # Avoid starting before 6:00.
        #         if start < earliest:
        #             self.warnings.append(WARNING_MESSAGES['no_night_shifts'])
        #         # Avoid starting after 22:00.
        #         if end > latest:
        #             self.warnings.append(WARNING_MESSAGES['no_night_shifts'])
        #
        # # No shifts longer than ten hours
        # if work_time >= work_time_limit:
        #     self.warnings.append(WARNING_MESSAGES['shift_too_long'])
        #
        # # Minimum break of 11 hours between work days.
        # gap = entry.calculate_time_gap(previous_entry)
        # if gap:
        #     if gap < min_break:
        #         self.warnings.append(WARNING_MESSAGES['no_11_hour_break'].
        #                              format(previous_entry.get_date().strftime('%d %m %Y')))
        # gap = entry.calculate_time_gap(next_entry)
        # if gap:
        #     if gap < min_break:
        #         self.warnings.append(WARNING_MESSAGES['no_11_hour_break'].
        #                              format(next_entry.get_date().strftime('%d %m %Y')))


fields = ('entry', 'project', 'start', 'end', 'recess', )
CreateShiftFormSet = f.inlineformset_factory(Entry, Shift, formset=BaseShiftFormSet, fields=fields, extra=0,
                                             can_delete=False, min_num=1, validate_min=True)
EditShiftFormSet = f.inlineformset_factory(Entry, Shift, formset=BaseShiftFormSet, fields=fields, extra=1,
                                           can_delete=True, min_num=1, validate_min=True)

在内联表单集中,
instance
是所有内联线都与之相关的“父”对象。我知道,但当我调用self.instance时,这些值毫无意义。我试图添加一个1月4日的条目,但self.instance有一个9月19日的条目。无论我在哪一天尝试添加条目,它始终是今天的日期。这没有意义。在内联表单集中,
instance
是所有内联线都与之相关的“父”对象。我知道,但当我调用self.instance时,这些值没有意义。我试图添加一个1月4日的条目,但self.instance有一个9月19日的条目。无论我在哪一天尝试添加条目,它始终是今天的日期。这毫无意义。
class BaseShiftFormSet(f.BaseInlineFormSet):
    warnings = []

    def has_messages(self):
        return self.warnings

    def flush_messages(self, request):
        for w in self.warnings:
            messages.warning(request, w)

    def clean(self):
        if any(self.errors):
            # Don't bother validating the formset unless each form is valid on its own.
            return

        super(BaseShiftFormSet, self).clean()

        # Standard day. Is used to be combined with Time object instances in order to get Datetime object instances,
        # which allow for arithmetic comparison as opposed to Time objects.
        # entry = self.instance
        # day = datetime.date(entry.get_year(), entry.get_month(), entry.get_day())
        day = datetime.date(1, 1, 1)
        one_day = datetime.timedelta(days=1)
        # previous_entry = Entry.get_entry_for_day(entry.get_owner(), day - one_day)
        # next_entry = Entry.get_entry_for_day(entry.get_owner(), day + one_day)
        earliest = datetime.datetime.combine(day, datetime.time(hour=6))
        latest = datetime.datetime.combine(day, datetime.time(hour=20))
        # shifts = entry.get_shifts()
        valid = True
        work_time = datetime.timedelta()
        work_time_limit = datetime.timedelta(hours=10)
        min_break = datetime.timedelta(hours=11)

        for form in self.forms:
            start        = datetime.datetime.combine(day, form.cleaned_data['start'])
            end          = datetime.datetime.combine(day, form.cleaned_data['end'])
            recess_time  = datetime.timedelta(minutes=form.cleaned_data['recess'])
            shift_length = end - start
            # new_shift    = Shift(start=form.cleaned_data['start'], end=form.cleaned_data['end'],
            #                      recess=form.cleaned_data['recess'], project=form.cleaned_data['project'],
            #                      entry=entry)
            work_time   += shift_length - recess_time

            # Validation: LOGICAL ERRORS
            # Start before end.
            if start > end:
                raise ValidationError(ERROR_MESSAGES['shift_inconsistent'])
            # Recess not longer than total work time.
            if recess_time >= shift_length:
                raise ValidationError(ERROR_MESSAGES['shift_recess_too_long'])
            # # No overlapping shifts
            # for shift in shifts:
            #     valid = valid and (new_shift.is_before(shift) or new_shift.is_after(shift))
            # if not valid:
            #     raise ValidationError(ERROR_MESSAGES['shifts_overlap'])

        #     # Validation: WARNINGS
        #     # No night shifts. Applies only to office work.
        #     if entry.is_type_work():
        #         # Avoid starting before 6:00.
        #         if start < earliest:
        #             self.warnings.append(WARNING_MESSAGES['no_night_shifts'])
        #         # Avoid starting after 22:00.
        #         if end > latest:
        #             self.warnings.append(WARNING_MESSAGES['no_night_shifts'])
        #
        # # No shifts longer than ten hours
        # if work_time >= work_time_limit:
        #     self.warnings.append(WARNING_MESSAGES['shift_too_long'])
        #
        # # Minimum break of 11 hours between work days.
        # gap = entry.calculate_time_gap(previous_entry)
        # if gap:
        #     if gap < min_break:
        #         self.warnings.append(WARNING_MESSAGES['no_11_hour_break'].
        #                              format(previous_entry.get_date().strftime('%d %m %Y')))
        # gap = entry.calculate_time_gap(next_entry)
        # if gap:
        #     if gap < min_break:
        #         self.warnings.append(WARNING_MESSAGES['no_11_hour_break'].
        #                              format(next_entry.get_date().strftime('%d %m %Y')))


fields = ('entry', 'project', 'start', 'end', 'recess', )
CreateShiftFormSet = f.inlineformset_factory(Entry, Shift, formset=BaseShiftFormSet, fields=fields, extra=0,
                                             can_delete=False, min_num=1, validate_min=True)
EditShiftFormSet = f.inlineformset_factory(Entry, Shift, formset=BaseShiftFormSet, fields=fields, extra=1,
                                           can_delete=True, min_num=1, validate_min=True)
class EntryView(FormView):
    """ Base view. """
    model = Entry
    form_class = f.EntryForm
    success_url = reverse_lazy('time_manager:time_tracking:index')
    formset = CreateShiftFormSet
    object = None

    def get_success_url(self):
        # Fallback url.
        url = reverse('time_manager:time_tracking:index')
        # If possible, go back to the month were the last entry was added.
        if self.object:
            url = reverse('time_manager:time_tracking:month',
                          kwargs={'year': self.object.get_year(), 'month': self.object.get_month()})
        return url

    def get(self, request, pk=None, *args, **kwargs):
        if pk:
            try:
                self.object = Entry.objects.get(pk=pk)
            except ObjectDoesNotExist:
                self.object = None
        else:
            self.object = None

        if self.object:
            form = f.EntryForm(instance=self.object)
            form_set = self.formset(instance=self.object)
        else:
            form = f.EntryForm(initial={'owner': request.user})
            form_set = self.formset()

        return_dict = {'form': form, 'shift_form': form_set}

        return render(request, self.template_name, return_dict)

    def post(self, request, pk=None, *args, **kwargs):
        if pk:
            self.object = Entry.objects.get(pk=pk)
        else:
            self.object = None

        if self.object:
            form = f.EntryForm(data=request.POST, instance=self.object)
            shift_form_set = self.formset(data=request.POST, instance=self.object)
        else:
            form = f.EntryForm(data=request.POST)
            shift_form_set = self.formset(data=request.POST)

        if form.is_valid() and shift_form_set.is_valid():
            return self.form_valid(request.user, form, shift_form_set)
        else:
            rd = {'form': form, 'shift_form': shift_form_set, 'test': self.object}

            return render(request, self.template_name, rd)

    def form_valid(self, user, form, shift_form):
        """ Stores the entry and all shifts. """
        self.object = form.save(commit=False)
        self.object.owner = user
        self.object.save()
        shift_form.instance = self.object
        shifts = shift_form.save(commit=False)
        for shift in shifts:
            shift.save()

        return HttpResponseRedirect(self.get_success_url())


class EntryCreateView(EntryView):
    """ View for creating new entries. Can be passed an ordinal to create an entry for the day, represented by the
        ordinal. """
    template_name = 'entry/create.html'
    form_class = f.AddWorkDay

    def get(self, request, ordinal=None, *args, **kwargs):
        """ Initiates with a blank form or will populate the day field with the day represented by the passed
            ordinal. """
        if ordinal:
            date = datetime.datetime.fromordinal(int(ordinal))
            form = f.AddWorkDay(initial={'date': date, 'owner': request.user})
        else:
            form = f.AddWorkDay(initial={'owner': request.user})
        shift_form_set = CreateShiftFormSet(instance=self.object)

        return render(request, self.template_name, {'form': form, 'shift_form': shift_form_set})


class EntryEditView(EntryView):
    template_name = 'entry/edit.html'
    formset = EditShiftFormSet