2011-10-07 22 views
5

Tengo un conjunto de objetos de documento y etiqueta, y quiero que esos dos objetos se vinculen. Es una relación típica de muchos a muchos. Tengo el siguiente código:¿Cuándo y cómo se crea una relación muchos a muchos al guardar un modelo?

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)) 

Al cargar un documento, que se agrega a la base de datos, sin embargo no hay etiquetas están siendo creado o asociado con el documento. ¿Debo agregar algo explícitamente a la función save() del documento para que esto suceda? ¿O en algún lugar del archivo Views.py? Me imagino que iría algo como:

  • Compruebe si la etiqueta que se está agregando ya existe
  • Si no es así, a continuación, crear una nueva etiqueta
  • Grab tanto el actual id_documento y el nuevo/label_id existente
  • Añadir un registro a la tabla document_labels (creado automáticamente para la relación muchos-a-muchos)

me siento como que es una funcionalidad bastante estándar que asumí sería construida en a am relación de cualquiera a muchos en django, pero parece que no funciona para mí hasta ahora. Estoy tratando de evitar reinventar la rueda aquí. Algo nuevo para django.

¡Gracias de antemano!

+0

¿Está utilizando un formulario? ¿Está agregando 'Document's por otros medios? ¿De qué método save() estás hablando? ¿El de 'Documento', 'Etiqueta' o un formulario? Proporcione alguna vista de muestra/formulario/otro código que guarde el objeto. – andreaspelme

+0

Supongo que no estoy muy seguro de qué método de guardado debe ir. De ahí que mi pregunta =] los documentos se estén agregando todos a través de un Formulario en el sitio. He actualizado la pregunta para incluir ese código desde views.py –

Respuesta

4

Como dijeron otras personas, no se puede guardar en un objeto de documento único y su campo ManyToMany, porque django crea "intermediatary join table", que necesita el ID del objeto del documento, que no está definido en ese momento.

Hay una función save_m2m en ModelForm, que se supone que ser llamado por el propio formulario, como se describe en the doc

Sin embargo, si esto no funciona, puede que un truco es llamar save_m2m en la vista función, así:

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)) 

creo que sirve, Stéphane

+0

Ahh, encontré el problema. Para simplificar el código en mi ejemplo, saqué la línea 'new_document = form.save (commit = False)'. Tuve que usar commit = False ya que estoy trabajando más en el objeto del documento antes de guardarlo en la base de datos, pero no creo que eso afecte el ahorro de m2m. Su enlace a https://docs.djangoproject.com/en/1.3/topics/forms/modelforms/#the-save-method tenía justo lo que necesitaba, ¡gracias! –

2

Sugiero referirme a cómo funciona la aplicación Django Admin en situaciones como esta. Típicamente, esto sería una operación de dos etapas; Primero crearía varias etiquetas, luego crearía un documento, elegiría las etiquetas que desea asociar de una lista de selección múltiple y luego lo guardaría. Django asociaría automáticamente las etiquetas seleccionadas en la lista a través de la tabla de muchos a muchos entre Documentos y Etiquetas.

Si espera hacerlo todo en un solo paso, existe la posibilidad de utilizar inline formsets. La aplicación de administración usa estos principalmente para claves externas (Encuesta y Preguntas, por ejemplo), pero también se pueden usar en un grado limitado con relaciones de muchos a muchos.

Tenga cuidado, los formularios en línea pueden ser complicados. Si puede dividir la operación en dos vistas separadas, sería mucho más fácil. Simplemente cree una vista para crear Etiquetas y otra para crear Documentos, que tendrá automáticamente una lista para elegir qué etiquetas asociar con el documento.

0

Los objetos vinculados no se crean automáticamente en save(). Debe crear otro formulario para Etiquetas y guardarlo explícitamente.

+0

Tengo que hacer otro * formulario * para las etiquetas? ¿Significaría esto eliminar el campo "Etiqueta" del formulario "Documento" y luego guardarlos al mismo tiempo? –

+0

Sí, elimine el campo "etiqueta" del formulario "Documento", cree un nuevo formulario "Etiqueta" y guárdelo después de guardar el documento –

2

Sus etiquetas en el modelo de documento es un campo M2M, por lo que terminará representando una selección múltiple en la forma procesada (mostrando todas las etiquetas disponibles en el sistema).

Suponiendo que es lo que quieres,

en 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)) 

He actualizado el modelo de etiqueta en modelos.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 

Si estaba pensando en tener el usuario también crear etiquetas sobre la marcha, es necesario anular el campo de las etiquetas en su forma con otra cosa, como un campo de entrada. A modo de ejemplo, si se le pide a los usuarios introducir etiquetas separadas por comas, entonces usted tendrá una views.py actualizada como,

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() 

Hope esto resuelve su problema o le das algo a trabajar.

Cuestiones relacionadas