django formset:它总是在表单更新时尝试创建

时间:2016-11-11 09:11:34

标签: python django django-forms formset inline-formset

我一直在谷歌搜索这个问题很长时间。可悲的是没有运气。确实,我找到了SO questionSO question等问题。我会说我发现自己与第一个问题有一个非常相似的问题。但是,我不理解这个问题下的讨论。

首先,为了说清楚,我甚至没有得到formset.is_valid()来返回True - 这就是问题所在。确实有唯一性检查,但我的编辑应该能够在数据库级别传递该检查。

这是我的代码:

### models.py
class DataAccess(models.Model):
    user = models.ForeignKey(User, models.CASCADE)
    project_data = models.ForeignKey(ProjectData, models.CASCADE)
    role = models.CharField(
        max_length=2, choices=ROLES, default='N'
    )
    created_at = models.DateTimeField(auto_now=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = (('user', 'project_data', 'role'),)
        # query from project data should happen more
        index_together = [['project_data', 'user', 'role']]

    def __str__(self):
        return '{}&{}&{}'.format(self.user, self.project_data, self.role)

    def save(self, *args, **kwargs):
        curr = datetime.now()
        if not self.created_at:
            self.created_at = curr
        self.updated_at = curr
        return super(DataAccess, self).save(*args, **kwargs)


class DataSyncPath(models.Model):
    server = models.ForeignKey(Server, models.CASCADE)
    project_data = models.ForeignKey(ProjectData, models.CASCADE)
    path = models.CharField(max_length=128)
    created_at = models.DateTimeField(auto_now=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = (('server', 'project_data',),)
        index_together = [['project_data', 'server']]

    def __str__(self):
        return '{}&{}'.format(self.server, self.project_data)

    def save(self, *args, **kwargs):
        curr = datetime.now()
        if not self.created_at:
            self.created_at = curr
        self.updated_at = curr
        return super(DataSyncPath, self).save(*args, **kwargs)

### forms.py
class DataAccessForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(DataAccessForm, self).__init__(*args, **kwargs)
        helper = FormHelper()
        helper.template = 'bootstrap/table_inline_formset.html'
        self.helper = helper
        self.helper.form_tag = False

    class Meta:
        model = DataAccess
        exclude = ['created_at', 'updated_at']


def get_data_access_formset(initial=[], extra=3):
    return inlineformset_factory(
        ProjectData, DataAccess,
        form=DataAccessForm, extra=extra + len(initial)
    )


class DataSyncPathForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(DataSyncPathForm, self).__init__(*args, **kwargs)
        helper = FormHelper()
        helper.template = 'bootstrap/table_inline_formset.html'
        self.helper = helper
        self.helper.form_tag = False

    class Meta:
        model = DataSyncPath
        exclude = ['created_at', 'updated_at']

    def clean(self):
        super(DataSyncPathForm, self).clean()
        if (self.cleaned_data['id'] is None and 'path' in self.errors and
                self.errors['path'][0] == 'This field is required.'):
            # a special hack to silent path error
            self.errors.pop('path')


def get_data_sync_path_formset(initial=[], extra=0):
    return inlineformset_factory(
        ProjectData, DataSyncPath, can_delete=False,
        form=DataSyncPathForm, extra=extra + len(initial)
    )

### views.py, import and everything is taken care of already
class ProjectDataUpdateView(LoginRequiredMixin, UpdateView):
    login_url = reverse_lazy('login')
    redirect_field_name = 'redirect_to'
    template_name = 'project_data/project_data_form.html'
    form_class = ProjectDataForm
    context_object_name = 'project_data'
    pk_url_kwarg = 'project_data_id'
    success_url = reverse_lazy('data_projects:project_data_list')

    def get_queryset(self):
        return dbmanager.get_project_data_by_user(
            self.request.user
        )

    def get_initial(self):
        initial = super(ProjectDataUpdateView, self).get_initial()
        try:
            initial['phabricator_links'] = ','.join(json.loads(
                self.object.phabricator_links))
        except Exception:
            initial['phabricator_links'] = ''
        return initial

    def get_data_access_initial(self):
        data_access_queryset = self.object.dataaccess_set.all()
        data_access_initial = [
            model_to_dict(obj) for obj in data_access_queryset
        ]
        return data_access_initial

    def get_data_sync_path_initial(self):
        datasyncpath_set = self.object.datasyncpath_set.all()
        sync_path_initial = [
            model_to_dict(obj) for obj in datasyncpath_set
        ]
        servers = self.request.user.server_set.all()
        missing_servers = set(
            [server.id for server in servers]) - set(
            [obj['server'] for obj in sync_path_initial]
        )
        for server_id in missing_servers:
            sync_path_initial.append({'server': server_id})
        return sync_path_initial

    def process_context_data(self, data):
        data_access_initial = self.get_data_access_initial()
        sync_path_initial = self.get_data_sync_path_initial()
        if self.request.POST:
            if (self.request.user.is_superuser or
                    self.request.user.groups.filter(name='Manager').exists()):
                data['data_access_formset'] = get_data_access_formset()(
                    self.request.POST, self.request.FILES,
                    instance=self.object, initial=data_access_initial,
                    queryset=self.object.dataaccess_set.all()
                )
            data['data_sync_path_formset'] = get_data_sync_path_formset()(
                self.request.POST, self.request.FILES,
                instance=self.object, initial=sync_path_initial,
                queryset=self.object.datasyncpath_set.all()
            )
        else:
            data['data_access_formset'] = get_data_access_formset(
                initial=data_access_initial
            )(initial=data_access_initial)
            data['data_sync_path_formset'] = get_data_sync_path_formset(
                initial=sync_path_initial
            )(initial=sync_path_initial)
        return data

    def get_context_data(self, **kwargs):
        data = super(ProjectDataUpdateView, self).get_context_data(**kwargs)
        data_access_formset = kwargs.pop('data_access_formset', None)
        data_sync_path_formset = kwargs.pop('data_sync_path_formset', None)
        if data_access_formset and data_sync_path_formset:
            data['data_access_formset'] = data_access_formset
            data['data_sync_path_formset'] = data_sync_path_formset
        else:
            data = self.process_context_data(data)
        data['is_data_sync_path_set'] = True
        return data

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        if not (self.request.user.is_superuser or
                self.request.user.groups.filter(name='Manager').exists()):
            self.request.POST['name'] = self.object.name
            self.request.POST['dmgr'] = self.object.dmgr
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        context_data = self.get_context_data()
        data_access_formset = context_data.get('data_access_formset')
        sync_path_formset = context_data.get('data_sync_path_formset')
        if (form.is_valid() and
                data_access_formset.is_valid() and
                sync_path_formset.is_valid()):
            return self.form_valid(
                form, data_access_formset, sync_path_formset)
        else:
            return self.form_invalid(
                form, data_access_formset, sync_path_formset)

    def form_valid(
            self, form, data_access_formset, sync_path_formset, **kwargs):
        for deleted_form in data_access_formset.deleted_forms:
            try:
                deleted_form.cleaned_data['id'].delete()
            except Exception:
                pass
        self.object = form.save()
        data_access_formset.save()
        sync_path_formset.save()
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(
            self, form, data_access_formset, sync_path_formset, **kwargs):
        return self.render_to_response(
            self.get_context_data(
                form=form, data_access_formset=data_access_formset,
                sync_path_formset=sync_path_formset
            )
        )

我还在UpdateView类的post方法中添加了断点:

ipdb> sync_path_formset.is_valid()
False
ipdb> sync_path_formset.forms[0]
<DataSyncPathForm bound=True, valid=False, fields=(server;project_data;path;id;DELETE)>
ipdb> sync_path_formset.forms[0].cleaned_data
{'DELETE': False, 'project_data': <ProjectData: bbb>, 'path': 'that', 'server': <Server: jgu-pc>, 'id': <DataSyncPath: jgu-pc&bbb>}
ipdb> sync_path_formset.forms[0].instance
<DataSyncPath: jgu-pc&bbb>
ipdb> sync_path_formset.forms[0].save(commit=False)
*** ValueError: The DataSyncPath could not be created because the data didn't validate.
ipdb> sync_path_formset.forms[0].has_changed()
True

很明显,实例存在。但它只是试图创建另一个实例。

0 个答案:

没有答案