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

时间:2016-09-19 14:14:26

标签: django validation formset inline-formset

我正在尝试覆盖formset上的clean方法。正如文档提出的那样,我编写了自己的BaseFormSet并覆盖了它的干净方法。 FormSet的某些值取决于父母模型表单中的值。

模型是包含工作时间的应用程序中的条目。条目适用于一天,并且只能有一个条目,但条目可以有多个班次。可以想象,模型表单用于Entry,FormSet用于Shifts。所以Entry是父母,Shifts是孩子。

为了验证Shift,我需要访问父条目。我尝试通过从clean方法中调用self.instance来实现。在这种情况下,self将是FormSet。

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

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

Shift的定义:

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 Entry(m.Model):
    """ Represents an entry in a accounting. An entry is either for work, sick leave or VACATION. """
    # meta
    cr_date = m.DateTimeField(auto_now_add=True, editable=False, verbose_name='Date of Creation')  # date of creation
    owner   = m.ForeignKey(User, on_delete=m.PROTECT, unique_for_date='date')
    final   = m.BooleanField(default=False)
    # content
    type    = m.CharField(max_length=1, choices=type_choices, default='w')
    date    = m.DateField(default=now)

FormSet声明:

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

0 个答案:

没有答案