Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When and how is a many-to-many relationship created when saving a model?

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 explicitly 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.

like image 529
Hartley Brody Avatar asked Oct 07 '11 04:10

Hartley Brody


3 Answers

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))
like image 51
Stéphane Avatar answered Oct 13 '22 10:10

Stéphane


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.

like image 24
Soviut Avatar answered Oct 13 '22 09:10

Soviut


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

like image 29
diegueus9 Avatar answered Oct 13 '22 08:10

diegueus9