2012-03-10 51 views
2

我的博客文章模型的保存方法model.save()稱爲具有標籤多對一對多領域:Django的admin.py:save_model()不通過的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.我在做什麼錯?我可以手動調用此方法嗎?

回答

3

您將不得不在保存實例後保存標籤,這意味着在您的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和逗號分隔標記名稱列表之間來回轉換,然後在save_model方法中調用form.save_m2m()。但是這種方法的缺點是,即使表單沒有保存(可能是因爲表單中的其他地方存在驗證錯誤),在解碼窗口小部件的值時,您也必須創建新的標記。

所以我覺得在這種情況下,更好的方法是你自己的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(也,它的效率更高,當代碼不會改變)。

+0

非常感謝你這麼好的回答!也感謝風格的改進。我會在工作中考慮到他們。 – 2012-03-11 03:11:53