2013-08-04 134 views
2

對於Django來說比較新,並試圖將M2M關係的標準實踐拼湊在一起。我已經有了模型和db平方。Django Forms - 多對多關係

對於這個例子,我在我的文章項目中編寫了一個應用程序,我試圖添加Categories。爲了簡單起見,文章有一個標題,正文,時間戳(不包含在表單中)和類別。我更喜歡複選框來表示文章可以屬於的一個或多個類別。

到目前爲止,我有:

models.py

class Category(models.Model): 
    category = models.CharField(max_length=100) 

    def __unicode__(self): 
     return self.category 


class Article(models.Model): 
    title = models.CharField(max_length=200) 
    body = models.TextField() 
    pub_date = models.DateTimeField(auto_now_add=True) 
    category = models.ManyToManyField(Category) 

    def __unicode__(self): 
     return self.title 

views.py

def article_index(request): 
    return render_to_response('article_index.html', {'articles': Article.objects.all()}) 

def article_detail(request, article_id=1): 
    return render_to_response('article_detail.html', {'article': Article.objects.get(id=article_id)}) 

def article_create(request): 
    if request.method == 'POST': # If the form has been submitted... 
     form = ArticleForm(request.POST) # A form bound to the POST data 
     if form.is_valid(): # All validation rules pass 
      article = Article.objects.create(
       title=form.cleaned_data['title'], 
       body=form.cleaned_data['body'], 
       category=form.cleaned_data['category'] 
      ) 
      return redirect('article_index') # Redirect after POST 
    else: 
     form = ArticleForm() # An unbound form 

    return render(request, 'article_form.html', { 'form': form }) 

forms.py

class ArticleForm(forms.Form): 
    title = forms.CharField(required=True) 
    body = forms.CharField(required=True, widget=forms.Textarea) 
    category = forms.MultipleChoiceField(Category.objects.all(), widget=forms.CheckboxSelectMultiple) 

兩個項目的我目前卡住的是:

1)在視圖'article_create'中,我不確定如何創建類別作爲Article對象的一部分。在shell中,我必須通過調用save()來創建文章,然後添加每個類別。我是否需要在這裏做類似的事情,例如創建文章,然後遍歷每個類別?示例代碼表示讚賞。

2)尚未編碼'article_edit',假設它與創建高度相似,但我不確定是否需要處理用於將以前選擇的類別與當前提交進行比較的邏輯。或者,我是否應該刪除正在編輯的文章的所有類別條目,並根據當前的提交重新輸入它們?這可能是最簡單的。再次,這樣的示例代碼將有所幫助。

謝謝!

回答

10

評論...

models.py

class Category(models.Model): 
    category = models.CharField(max_length=100) 

類別的名稱應命名爲name。一個名爲category的字段我希望是models.ForeignKey("Category")

class Article(models.Model): 
    title = models.CharField(max_length=200) 
    body = models.TextField() 
    pub_date = models.DateTimeField(auto_now_add=True) 
    category = models.ManyToManyField(Category) 

正如亞當指出,這應該被命名爲categories。此外,其反向(連接ArticleCategory中的字段)應命名爲articles。所以我們得到:

categories = models.ManyToManyField(Category, related_name="articles") 

所以,現在你可以在一個類別中的所有物品的查詢集的東西,如:

get_object_or_404(Category, id=int(cat_id, 10)).articles.all() 

views.py

def article_detail(request, article_id=1): 

不要使用在這裏默認。 ID 1沒有特別之處,如果有人忘記了ID,那應該是錯誤的。

def article_create(request): 
    if request.method == 'POST': # If the form has been submitted... 
     form = ArticleForm(request.POST) # A form bound to the POST data 
     if form.is_valid(): # All validation rules pass 
      article = Article.objects.create(
       title=form.cleaned_data['title'], 
       body=form.cleaned_data['body'], 
       category=form.cleaned_data['category'] 
      ) 

通過使用ModelForm,它被簡化成:

def article_create(request): 
    if request.method == 'POST': # If the form has been submitted... 
     form = ArticleForm(request.POST) # A form bound to the POST data 
     if form.is_valid(): # All validation rules pass 
      form.save() 
     return redirect('article_index') # Redirect after POST 
    else: 
     form = ArticleForm() # An unbound form 

    return render(request, 'article_form.html', {'form': form}) 

forms.py

class ArticleForm(forms.Form): 

你真的應該使用ModelForm代替(docs here):

class ArticleForm(forms.ModelForm): 
    class Meta: 
     model = Article 
     fields = ["title", "body", "category"] 
     widgets = { 
      'body': forms.Textarea(), 
      'category': forms.CheckboxSelectMultiple() 
     } 

在您的問題:

1)視圖中的「article_create」,我不知道如何爲文章對象的一部分創建類別(IES)。在shell中,我必須通過調用save()來創建文章,然後添加每個類別。我是否需要在這裏做類似的事情,例如創建文章,然後遍歷每個類別?示例代碼表示讚賞。

IIRC,ModelForm.save()會照顧你。

2)尚未編碼'article_edit',假設它與創建高度相似,但我不確定是否需要處理用於比較先前選擇的類別與當前提交的邏輯。或者,我是否應該刪除正在編輯的文章的所有類別條目,並根據當前的提交重新輸入它們?這可能是最簡單的。再次,這樣的示例代碼將有所幫助。

編輯是幾乎完全一樣創建。您只需將原始對象與表單關聯即可。 (通常,您可以根據URL找出原始對象的內容。)因此,像:

def article_edit(request, article_id): 
    article = get_object_or_404(Article, id=int(article_id, 10)) 

    if request.method == 'POST': # If the form has been submitted... 
     form = ArticleForm(request.POST, instance=article) 
     if form.is_valid(): # All validation rules pass 
      form.save() 
     return redirect('article_index') # Redirect after POST 
    else: 
     form = ArticleForm(instance=article) 

    return render(request, 'article_form.html', {'form': form}) 

編輯:作爲jheld下面的評論,你可以結合article_createarticle_edit到一個視圖方法:

def article_modify(request, article_id=None): 
    if article_id is not None: 
     article = get_object_or_404(Article, id=int(article_id, 10)) 
    else: 
     article = None 

    if request.method == 'POST': # If the form has been submitted... 
     form = ArticleForm(request.POST, instance=article) 
     if form.is_valid(): # All validation rules pass 
      form.save() 
     return redirect('article_index') # Redirect after POST 
    else: 
     form = ArticleForm(instance=article) 

    return render(request, 'article_form.html', {'form': form}) 

然後網址很簡單:

url(r"^/article/edit/(?P<article_id>[0-9]+)$", "app.views.article_modify", name="edit"), 
url(r"^/article/new$", "app.views.article_modify", name="new"), 
+1

你也可以爲'article_create'和'article_edit'創建一個通用的方法,使得你沒有相同的邏輯兩次,但除此之外,我認爲它運行良好......並且只是將'instance'作爲'None'傳遞爲默認值。 – jheld

+1

非常感謝這樣的詳細回覆。我會將此標記爲正確的答案。對於您爲未來讀者的利益做一個快速更新,請注意一點:在'class ArticleForm(forms.ModelForm)'中:'需要在該類定義的其餘部分之前有'class Meta:'行。 – David

2

我首先將模型中的category重命名爲categories,並相應地更新相關代碼 - 單數命名只是一個連續的頭痛。

在這一點上,你非常接近。在提交文章時,在您的成功分支中,將類別分配爲單獨的語句。

article = Article.objects.create(
    title=form.cleaned_data['title'], 
    body=form.cleaned_data['body'] 
) 
# note changed plural name on the m2m attr & form field 
article.categories.add(*form.cleaned_data['categories']) 
# alternately 
# for cat in form.cleaned_data['categories']: 
#  article.categories.add(cat) 
return redirect('article_index') # Redirect after POST 

哦,還有,在避免ModelForm的榮譽。將自己的形式實例連接起來很容易,因此涉及ModelForm這個問題會更加複雜。

對於編輯視圖,是的,清除&重新添加是最容易的。有更有效的方法,但沒有什麼值得的複雜性,直到它實際上是一個問題。調用清除的方法將是article.categories.clear(),重新添加與上面相同。每個文件

+0

是的,我從ModelForm開始,發現它將我抽象得太離譜了,因爲我覺得我需要修改才能做到這一點。感謝您的提示 - 我會放棄並回報。 – David

+0

好吧,如果您要避免使用'ModelForm',不要忘記沒有什麼能夠阻止您閱讀'ModelForm'的源代碼來查看它的確切含義,並將其複製到您的代碼中。 –

+0

這實際上是我使用'django.forms.models'的主要問題 - 如果不知道'django.db'和Python的類型系統的內部信息,就幾乎不可能了。 – AdamKG

1

你可以這樣做 例如:

if todo_list_form.is_valid(): 
       todo_list = todo_list_form.save(commit=False) 
       todo_list.save() 
       todo_list_form.save_m2m()