Django admin.py: save_model() не вызывается model.save() в методе сохранения ModelForm
В моей модели блога есть поле "многие ко многим" для тегов:
tags = models.ManyToManyField(PostTag)
Но редактировать его было неудобно, и я изменил свою модель следующим образом:
def _get_tagging(self): # Returns comma separated list of tags
tagging = []
for tag in self.tags.all():
tagging.append(tag.name)
return ", ".join(tagging)
def _set_tagging (self, tagging): # Saves tags from comma separated list
tagging = tagging.split(", ")
self.tags.clear()
for tag in tagging:
if len(tag) < 1:
continue
try:
self.tags.add(PostTag.objects.get(name=tag))
except ObjectDoesNotExist:
self.tags.create(name=tag)
tagging = property(_get_tagging, _set_tagging)
Затем я изменил свой admin.py
:
class BlogAdminForm (forms.ModelForm):
tagging = forms.CharField(required=False, label="Tags", max_length=200,
widget=forms.TextInput(attrs={'class':'vTextField'}))
class Meta:
model = BlogPost
def __init__(self, *args, **kwargs):
super(BlogAdminForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
instance = kwargs['instance']
self.initial['tagging'] = instance.tagging
def save(self, commit=True):
model = super(BlogAdminForm, self).save(commit=False)
model.tagging = self.cleaned_data["tagging"]
if commit:
model.save()
return model
И это работало нормально, но только для редактирования объектов. Я получил ошибку, когда попытался создать новый объект. Зачем? Поскольку отношение "многие ко многим" можно использовать с объектом, которого еще нет в базе данных и который не имеет первичного ключа (экземпляр "BlogPost" должен иметь значение первичного ключа, прежде чем отношение "многие ко многим" может использоваться). Я попытался решить это, отредактировав метод сохранения следующим образом:
def save(self, commit=True):
model = super(BlogAdminForm, self).save(commit=False)
try:
model.tagging = self.cleaned_data["tagging"]
except ValueError:
model.save()
model.tagging = self.cleaned_data["tagging"]
if commit:
model.save()
Это решило исходную проблему. Но сейчас model.save()
не вызывает save_model
метод моей модели администратора:
class BlogAdmin (admin.ModelAdmin):
# ...
form = BlogAdminForm
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
В результате этого я получаю новую ошибку: null value in column "author_id" violates not-null constraint.
Что я делаю неправильно? Можно ли вызвать этот метод вручную?
1 ответ
Вам нужно будет сохранить теги после сохранения экземпляра, а это значит сделать это в вашем save_model
функция. Это не имеет ничего общего с вашим кодом манипуляции тегами: если вы посмотрите на документацию для Form.save
метод это говорит:
Еще один побочный эффект от использования
commit=False
видна, когда ваша модель имеет отношение многие ко многим с другой моделью. Если ваша модель имеет отношение многие ко многим, и вы указываетеcommit=False
При сохранении формы Django не может сразу сохранить данные формы для отношения "многие ко многим". Это связано с тем, что невозможно сохранить данные "многие ко многим" для экземпляра до тех пор, пока этот экземпляр не существует в базе данных.Чтобы обойти эту проблему, каждый раз, когда вы сохраняете форму, используя
commit=False
Джанго добавляетsave_m2m()
метод к вашемуModelForm
подкласс. После того, как вы вручную сохранили экземпляр, созданный формой, вы можете вызватьsave_m2m()
сохранить данные формы многие-ко-многим.
Есть несколько способов решить вашу проблему. Вы можете написать виджет, который конвертирует туда и обратно между списками идентификаторов тегов и имен тегов через запятую, а затем вызывать form.save_m2m()
в вашем save_model
метод. Но у этого подхода есть тот недостаток, что вам придется создавать новые теги при декодировании значения из виджета, даже если форма не сохранена (возможно, из-за ошибки проверки в другом месте формы).
Поэтому я думаю, что лучше в этом случае добавить свой собственный save_tags
метод к форме:
class BlogAdminForm(forms.ModelForm):
tagging = forms.CharField(required=False, label="Tags", max_length=200,
widget=forms.TextInput(attrs={'class':'vTextField'}))
class Meta:
model = Post
def __init__(self, *args, **kwargs):
super(BlogAdminForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
tags = (t.name for t in kwargs['instance'].tags.all())
self.initial['tagging'] = ', '.join(tags)
def save_tags(self, obj):
obj.tags = (Tag.objects.get_or_create(name = tag.strip())[0]
for tag in self.cleaned_data['tagging'].split(','))
class BlogPostAdmin(admin.ModelAdmin):
form = BlogAdminForm
def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
form.save_tags(obj)
Обратите внимание, что я переместил код манипуляции тегами в форму: я думаю, что он принадлежит здесь, а не модели, потому что все дело в вводе пользователем. Я также сделал несколько стилистических улучшений:
'instance' in kwargs
проще чемkwargs.has_key('instance')
,Генератор выражений
(t.name for t in kwargs['instance'].tags.all())
это проще, чем создание списка вfor
петля.get_or_create
Метод является удобным способом, который позволяет избежать необходимостиtry: ... except ObjectDoesNotExist: ...
Вы можете назначить непосредственно
ManyToMany
поле вместо вызоваclear
а потомadd
(также, это более эффективно, когда теги не меняются).