python - ModelForm 的 save 方法中的 Django admin.py : save_model() is not called by model. save()

标签 python django django-forms django-admin

我的博客文章模型有一个多对多的标签字段:

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()

因此,我收到一个新错误:列“author_id”中的空值违反了非空约束。我做错了什么?我可以手动调用这个方法吗?

最佳答案

保存实例后,您必须保存标签,这意味着在 save_model 中执行此操作。功能。这与您的标签操作代码无关:如果您查看 documentation for the Form.save method它说:

Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn't possible to save many-to-many data for an instance until the instance exists in the database.

To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you've manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.

有几种方法可以解决您的问题。你可以写一个widget在标签 ID 列表和逗号分隔的标签名称之间来回转换,然后调用 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 kwargskwargs.has_key('instance') 更简单.

  • 生成器表达式 (t.name for t in kwargs['instance'].tags.all())比在 for 中构建列表更简单循环。

  • get_or_create method是一个方便的快捷方式,可以避免 try: ... except ObjectDoesNotExist: ...

  • 您可以直接分配给 ManyToMany字段而不是调用 clear然后add (而且,当标签不改变时效率会更高)。

关于python - ModelForm 的 save 方法中的 Django admin.py : save_model() is not called by model. save(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9646085/

相关文章:

python - 远程 : ImportError: No module named gitlab

python - cx_freeze - 包括我自己的模块?

python - 使用python进行帧差异

python - Django 表格 : disable field if booleanfield is checked

python - 使用 django-allauth 启用 oauth 登录,但使用自定义提供程序

django - South 的 add_introspection_rules() 有 Django 1.7+ 的替代品吗?

python - 如何在 Django 中访问通过表单集上传的图像?

Django 不会上传和保存文档

django - 为 django 选择小部件设置默认选择的最简单方法是什么?

python - 如何在 map 函数中使用 strip