何时以及如何在保存模型时创建多对多关系?
我有一组文档对象和标签对象,我想把这两个对象连接起来。这是一种典型的多对多关系。我有以下代码:
Models.py:
class Document(models.Model):
title = models.CharField(max_length=50, unique=True)
title_slug = models.SlugField(max_length=50, unique=True, editable=False)
labels = models.ManyToManyField('Label')
def save(self, *args, **kwargs):
self.title_slug = slugify(self.title)
super(Document, self).save(*args, **kwargs)
class Label(models.Model):
name = models.CharField(max_length=40, unique=True)
slug = models.SlugField(max_length=40, unique=True, editable=False)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Document, self).save(*args, **kwargs)
Views.py:
class DocumentForm(ModelForm):
class Meta:
model = Document
fields = ["title","labels"]
def upload_document(request):
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
new_document = form.save()
return HttpResponseRedirect("/thanks/")
else:
form = DocumentForm()
return render_to_response('upload_page.html', {'form':form}, context_instance=RequestContext(request))
当我上传一个文档时,它会被添加到数据库中,但没有标签被创建或与文档关联。我需要在文档的保存函数中明确添加什么东西来实现这个吗?或者在Views.py文件的某个地方?我想应该是这样的:
- 检查要添加的标签是否已经存在
- 如果不存在,就创建一个新标签
- 获取当前文档的ID和新标签或已有标签的ID
- 在文档标签表中添加一条记录(这个表是为了多对多关系自动创建的)
我觉得这应该是一个很标准的功能,我以为在Django的多对多关系中会自带这个功能,但到目前为止似乎并没有正常工作。我想避免重复造轮子。
5 个回答
在你的文档模型中,标签是一个多对多的字段,这意味着在显示的表单中会出现一个多选框,里面会列出系统中所有可用的标签。
假设这正是你想要的,
在 views.py 文件中,
def upload_document(request):
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
labels = request.POST.getlist('labels')
new_document = form.save()
for label_id in labels: # we're only going to add currently defined labels
label = Label.objects.get(id=int(label_id))
new_document.labels.add(label)
new_document.save()
return HttpResponseRedirect("/thanks/")
else:
form = DocumentForm()
return render_to_response('doc_form.html', {'form':form}, context_instance=RequestContext(request))
我在 models.py 文件中更新了标签模型,
class Label(models.Model):
name = models.CharField(max_length=40, unique=True)
slug = models.SlugField(max_length=40, unique=True, editable=False)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Label, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
如果你想让用户能够随时创建标签,那你需要在表单中用其他东西替代标签字段,比如一个输入框。举个例子,如果你让用户输入用逗号分隔的标签,那么你的 views.py 文件会更新成这样,
for label in labels: # labels entered by user
try:
lbl = Label.objects.get(name='label')
except Label.DoesNotExist:
lbl = None
if not lbl:
lbl = Label()
lbl.name = label
lbl.save()
newDoc.labels.add(lbl)
newDoc.save()
我建议你看看Django管理应用在这种情况下是怎么工作的。通常,这个过程分为两个步骤:首先,你需要创建几个标签,然后再创建一个文档,从一个可以多选的列表中选择你想要关联的标签,最后保存。Django会自动通过文档和标签之间的多对多关系表,把你选择的标签和文档关联起来。
如果你希望一步到位,也可以考虑使用内联表单集。管理应用主要是用它们来处理外键(比如投票和问题),但在多对多关系中也可以有限地使用。
不过要注意,内联表单集可能会比较复杂。如果你能把这个操作分成两个不同的视图,那会简单很多。你可以创建一个视图来处理标签的创建,另一个视图来处理文档的创建,这样在创建文档时就会自动有一个列表让你选择要关联的标签。
正如其他人所说,你不能一次性保存一个文档对象及其多对多字段,因为 Django 会创建一个“中间连接表”,这个表需要文档对象的 ID,而在那个时候这个 ID 还没有定义。
在 ModelForm 中有一个 save_m2m 函数,应该由表单自己来调用,具体可以参考文档中的说明。
不过,如果这个方法不奏效,可以尝试在视图函数中手动调用 save_m2m,像这样:
def upload_document(request):
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
new_document = form.save()
form.save_m2m()
return HttpResponseRedirect("/thanks/")
else:
form = DocumentForm()
return render_to_response('upload_page.html', {'form':form}, context_instance=RequestContext(request))