I have a set of document objects and label objects, and I want those two objects to be linked. It's a typical many-to-many relationship. I have the following code:
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))
When I upload a document, it gets added to the database, however no labels are being created or associated with the document. Do I need to explici开发者_高级运维tly add something to the Document's save() function to make this happen? Or somewhere in the Views.py file? I'd imagine it'd go something like:
- Check to see if the label that's being added already exists
- If it doesn't, then create a new label
- Grab both the current document_id and the new/existing label_id
- Add a record to the document_labels table (automatically created for the many-to-many relationship)
I feel like that's pretty standard functionality that I assumed would be built in to a many-to-many relationship in django, but it doesn't seem to be working for me so far. I'm trying to avoid reinventing the wheel here.
As other people said, you cannot save in one-shot Document object and its ManyToMany field, because django create "intermediatary join table", which need the Document object's ID, which is not defined at that point.
There is a save_m2m function in ModelForm, that is supposed to be called by the Form itself, as described in the doc
However, if it doesn't work, maybe a trick is to call save_m2m in the view function, like this:
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))
I would suggest referring to how the Django Admin app works in situations like this. Typically, this would be a two stage operation; First you'd create several Labels, then you'd create a Document, pick the labels you want associated from a multi-select list, then save it. Django would then automatically associate the labels selected in the list via the many-to-many table between Documents and Labels.
If you're hoping to do this all in one step, there is the possibility of using inline formsets. The admin app uses these mainly for foreign keys (Poll and Questions, for example), but they can be used to a limited degree with many-to-many relationships as well.
Be warned, inline formsets can be tricky. If you can split the operation up into two separate views, it would be much easier. Simply create one view for creating Labels and another for creating Documents, which will automatically have a list for picking which labels to associate with the document.
I think this is easy to resolve if you understande the forms and foms-factory of django
Maybe this doc can help you:
https://docs.djangoproject.com/en/1.3/topics/forms/modelforms/#inline-formsets
Your labels in the Document model is a M2M field, so that's going to end up rendering a multi-select in the rendered form (showing all the labels available in the system).
Assuming that's what you want,
in 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))
I updated the model for Label in 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
If you were thinking of having the user also create labels on the fly, you need to override the labels field in your form with something else, like an input field. As an example, if you're instructing the users to enter labels seperated by commas, then you'll have an updated views.py like,
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()
The linked objects are not created automatically on save(). You should create another form for Labels, and save them explicitly.
精彩评论