Django Forms - 多对多关系

时间:2013-08-04 22:20:07

标签: python django forms

对于Django而言相对较新,并尝试将标准实践拼凑在一起以处理表单中的M2M关系。我已经有模型和数据库平方。

对于这个例子,我在我的项目项目中编写了一个应用程序,我正在尝试添加类别。为了简单起见,文章有标题,正文,时间戳(不包含在表格中)和类别。我更喜欢使用复选框来表示文章可以属于的一个或多个类别。

到目前为止,我有:

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',假设它与create非常相似,但我不确定是否或如何处理逻辑以将先前选择的类别与当前提交进行比较。或者,我是否应该删除正在编辑的文章的所有类别条目,并根据当前提交重新输入?这可能是最简单的。再次,这个示例代码会有所帮助。

谢谢!

3 个答案:

答案 0 :(得分: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。此外,其反向(Category中链接回Article的字段)应命名为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,那应该是错误。

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'中,我不确定如何创建作为Article对象一部分的类别。在shell中,我必须通过调用save()来创建文章,然后在之后添加每个类别。我需要在这里做类似的事情,例如创建文章然后遍历每个类别?示例代码表示赞赏。

IIRC,ModelForm.save()将为您解决此问题。

  

2)尚未编写'article_edit',假设它与create非常相似,但我不确定是否或如何处理逻辑以将先前选择的类别与当前提交进行比较。或者,我是否应该删除正在编辑的文章的所有类别条目,并根据当前提交重新输入?这可能是最简单的。再次,这个示例代码会有所帮助。

编辑几乎就像创建一样。您所要做的就是将原始对象与表单相关联。 (通常,您可以从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 :(得分: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而变得更加复杂。

对于编辑视图,是,清除&amp;重新添加是最简单的。有更有效的方法,但没有什么是值得复杂的,直到它实际上是一个问题。方法调用清除将为article.categories.clear(),重新添加与上述相同。

答案 2 :(得分:1)

你可以这样做 例如:

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