我在管理表单中发布数据时遇到问题。单击保存按钮后某些字段变空,其他字段则不变(内联保留其数据)。请参阅图像以更好地解释问题。
输入一些数据: 点击[保存]后...... 验证错误! (模型未保存)
我的ModelForm非常简单:我只是更改了一个m2m模型字段的表单字段。
class News(models.Model):
departments = models.ManyToManyField(Department, blank=True, related_name='news', through='NewsDepartmentMembership')
research_groups = models.ManyToManyField(Group, blank=True, related_name='news', through='NewsGroupMembership')
related_news = models.ManyToManyField('self', blank=True, symmetrical=False)
people_involved = models.ManyToManyField(Person, blank=True, related_name='news')
title = models.CharField(_('Title'), max_length=255)
slug = models.SlugField(_('Slug'), unique_for_date='pub_date',
help_text=_('A slug is a short name which uniquely identifies the news item for this day'), )
excerpt = RichTextField(_('Excerpt'), blank=True)
content = RichTextField(_('Content'), blank=True)
is_published = models.BooleanField(_('Published'), default=False)
pub_date = models.DateTimeField(_('Publication date'), default=datetime.datetime.now)
is_feat = models.BooleanField(
_('Featured'), default=False,
help_text=_('Administrators may use this checkbox to promote news to the main news page')
)
published = PublishedNewsManager()
objects = models.Manager()
featured = FeaturedNewsManager()
class NewNewsForm(forms.ModelForm):
class Meta:
model = News
related_news = forms.ModelMultipleChoiceField(
queryset=News.objects.none(),
required=False,
widget=FilteredSelectMultiple(
verbose_name=_('articles'),
is_stacked=False,
)
)
def __init__(self, user=None, *args, **kwargs):
super(NewNewsForm, self).__init__(*args, **kwargs)
if hasattr(user, 'is_superuser'):
self.fields['related_news'].queryset = get_objects_for_user(user, ('news.change_news',)).filter(
is_published__exact=True).order_by('pub_date')
else:
self.fields['related_news'].queryset = News.published.order_by('pub_date')
if self.instance.pk:
self.fields['related_news'].initial = self.instance.related_news.all()
def save(self, commit=True):
news = super(NewNewsForm, self).save(commit=False)
if commit:
news.save()
if news.pk:
news.related_news = self.cleaned_data['related_news']
self.save_m2m()
return news
ModelAdmin非常复杂,它继承了2个ModelAdmins。 The 1st one来自django-modeltranslation包。我从here改编了第二个,只是为了执行交叉内联表格集验证。它在其他包中工作没有任何问题(至少直到今天)。我只需要覆盖方法is_cross_valid来定义交叉内联验证
class NewsAdmin(TranslationAdmin, ModelAdminWithInlines):
fields = ('title', 'slug_en', 'slug_nb', 'excerpt', 'content', 'is_published', 'pub_date', 'related_news', 'is_feat')
inlines = (DepartmentsNewsInline, GroupsNewsInline, PersonNewsInline)
form = NewNewsForm
prepopulated_fields = {'slug_en': ('title',), 'slug_nb': ('title',)}
class Media:
js = (
'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
'http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/jquery-ui.min.js',
'modeltranslation/js/tabbed_translation_fields.js',
)
css = {
'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
}
def queryset(self, request):
return get_objects_for_user(request.user, (u'news.change_news', ))
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'related_news':
return get_objects_for_user(request.user, ('news.change_news',), ).order_by('pub_date')
return super(NewsAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
def is_cross_valid(self, request, form, formsets):
valid = True
# my validation code goes here...
return valid
def get_readonly_fields(self, request, obj=None):
if not request.user.groups.filter(name__exact='administration') and not request.user.is_superuser:
return ('is_feat', )
return ()
def has_change_permission(self, request, obj=None):
if super(NewsAdmin, self).has_change_permission(request, obj):
return True
return request.user.has_perm('news.change_news', obj)
这是我的ModelAdminWithInlines,几乎与here:
相同class ModelAdminWithInlines(ModelAdmin):
"""
Cross formsets validation. See https://stackoverflow.com/a/2746735
"""
def is_cross_valid(self, request, form, formsets):
"""
To perform cross-formset validation.
Should be overriden in every inheriting class.
"""
return True
def add_view(self, request, form_url='', extra_context=None):
"""The 'add' admin view for this model."""
model = self.model
opts = model._meta
if not self.has_add_permission(request):
raise PermissionDenied
ModelForm = self.get_form(request)
formsets = []
inline_instances = self.get_inline_instances(request, None)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
new_object = self.save_form(request, form, change=False)
form_validated = True
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new="_saveasnew" in request.POST,
prefix=prefix, queryset=inline.queryset(request))
formsets.append(formset)
# if all_valid(formsets) and form_validated:
formsets_validated = all_valid(formsets)
cross_validated = self.is_cross_valid(request, form, formsets)
if formsets_validated and form_validated and cross_validated:
self.save_model(request, new_object, form, False)
self.save_related(request, form, formsets, False)
self.log_addition(request, new_object)
return self.response_add(request, new_object)
else:
# Prepare the dict of initial data from the request.
# We have to special-case M2Ms as a list of comma-separated PKs.
initial = dict(request.GET.items())
for k in initial:
try:
f = opts.get_field(k)
except FieldDoesNotExist:
continue
if isinstance(f, ManyToManyField):
initial[k] = initial[k].split(",")
if ModelForm.Meta.model._meta.module_name == 'news' and ModelForm.Meta.model._meta.object_name == 'News':
form = ModelForm(initial=initial, user=request.user) # here I am injecting the user object into the form
# just to be able to access the objects for this user
else:
form = ModelForm(initial=initial)
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=self.model(), prefix=prefix,
queryset=inline.queryset(request))
formsets.append(formset)
adminForm = AdminForm(
form, list(self.get_fieldsets(request)),
self.get_prepopulated_fields(request),
self.get_readonly_fields(request),
model_admin=self)
media = self.media + adminForm.media
inline_admin_formsets = []
for inline, formset in zip(inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request))
readonly = list(inline.get_readonly_fields(request))
prepopulated = dict(inline.get_prepopulated_fields(request))
inline_admin_formset = InlineAdminFormSet(
inline, formset, fieldsets, prepopulated, readonly, model_admin=self
)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
context = {
'title': _('Add %s') % force_text(opts.verbose_name),
'adminform': adminForm,
'is_popup': "_popup" in request.REQUEST,
'media': media,
'inline_admin_formsets': inline_admin_formsets,
'errors': AdminErrorList(form, formsets),
'app_label': opts.app_label,
}
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
我真的无法理解我的问题的根源。谁能发现这个问题?
谢谢!
UPDATE 正如@GarryCairns建议的那样,我试图从shell中保存一个对象。没问题。
>>> n = News.objects.create(title_en='test', slug_en='test', content_en='test')
>>> n.id
4
更新2:
非翻译字段也是空的: - /
更新3:
>>> n = News()
>>> n
<News: >
>>> n.title_en = 'test'
>>> n.slug_en
>>> n.slug_en = 'test'
>>> n.content_en = 'blah blah'
>>> n.save()
>>> n.id
5
固定
似乎从表单 init ()和NewsAdmin.formfield_for_manytomany()方法手动设置ModelMultipleChoiceField查询集都搞乱了整个表单数据......