Skip to main content
 首页 » 编程设计

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

2024年08月30日34mq0036

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

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 kwargs比 kwargs.has_key('instance') 简单.

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

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

  • 你可以直接赋值给一个ManyToMany字段而不是调用 clear然后 add (另外,当标签不变时效率更高)。