我一直在谷歌搜索这个问题很长时间。可悲的是没有运气。确实,我找到了SO question和SO 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
很明显,实例存在。但它只是试图创建另一个实例。