2010-05-18 4 views
12

tengo los siguientes modelos:Django Admin: Muchos-a-Muchos cuadro de lista no se presenta después de un pase de parámetros

class Message(models.Model): 
    date = models.DateTimeField() 
    user = models.ForeignKey(User)  
    thread = models.ForeignKey('self', blank=True, null=True) 
    ... 

class Forum(models.Model): 
    name = models.CharField(max_length=24) 
    messages = models.ManyToManyField(Message, through="Message_forum", blank=True, null=True) 
    ... 

class Message_forum(models.Model): 
    message = models.ForeignKey(Message) 
    forum = models.ForeignKey(Forum) 
    status = models.IntegerField() 
    position = models.IntegerField(blank=True, null=True) 
    tags = models.ManyToManyField(Tag, blank=True, null=True) 

En el sitio de administración, cuando voy a añadir/cambiar un foro, no veo el cuadro de lista de mensajes como es de esperar. Sin embargo, aparece si elimino el parámetro 'through' en la declaración ManyToManyField. ¿Que pasa con eso? He registrado los tres modelos (más Etiqueta) en el sitio de administración en admin.py.

TIA

Respuesta

18

Documentación dice:

Cuando se especifica un modelo de intermediario con el argumento a través de un ManyToManyField, el administrador no mostrará un widget por defecto.

Pero es probable que sea posible para mostrar los campos M2M en la vista de cambios del administrador, incluso si se define el atributo through.

class ForumAdminForm(forms.ModelForm): 
    mm = forms.ModelMultipleChoiceField(
     queryset=models.Message.objects.all(), 
     widget=FilteredSelectMultiple(_('ss'), False, attrs={'rows':'10'})) 

    def __init__(self, *args, **kwargs): 
     if 'instance' in kwargs: 
      initial = kwargs.setdefault('initial', {}) 
      initial['mm'] = [t.service.pk for t in kwargs['instance'].message_forum_set.all()] 

     forms.ModelForm.__init__(self, *args, **kwargs) 

    def save(self, commit=True): 
     instance = forms.ModelForm.save(self, commit) 

     old_save_m2m = self.save_m2m 
     def save_m2m(): 
      old_save_m2m() 

      messages = [s for s in self.cleaned_data['ss']] 
      for mf in instance.message_forum_set.all(): 
       if mf.service not in messages: 
        mf.delete() 
       else: 
        messages.remove(mf.service) 

      for message in messages: 
       Message_forum.objects.create(message=message, forum=instance) 

     self.save_m2m = save_m2m 

     return instance 

    class Meta: 
     model = models.Forum 

class ForumAdmin(admin.ModelAdmin): 
    form = ForumAdminForm 
+0

Funciona perfectamente, pero tiene referencias no válidas en el código 'service'. – alex

10

Tome un vistazo a the official documentation:

+0

¡Esta solución es bastante sencilla, y funcionó para mí! –

+0

Su enlace está roto ya que la documentación para 1.6 ya no está disponible en su sitio. –

2

Aprendí mucho de la respuesta de @ Fedor, pero algunos comentarios y limpieza pueden ser aún beneficiosos.

class ForumAdminForm(forms.ModelForm): 
    messages = forms.ModelMultipleChoiceField(
        queryset=Message.objects.all(), 
        widget=FilteredSelectMultiple('Message', False)) 


    # Technically, you don't need to manually set initial here for ForumAdminForm 
    # However, you NEED to do the following for MessageAdminForm 
    def __init__(self, *args, **kwargs): 
     if 'instance' in kwargs: 
      # a record is being changed. building initial 
      initial = kwargs.setdefault('initial', {}) 
      initial['messages'] = [t.message.pk for t in kwargs['instance'].message_forum_set.all()] 
     super(ForumAdminForm, self).__init__(*args, **kwargs) 

    def save(self, commit=True): 
     if not self.is_valid(): 
      raise HttpResponseForbidden 
     instance = super(ForumAdminForm, self).save(self, commit) 
     def save_m2m_with_through(): 
      messages = [t for t in self.cleaned_data['messages'] 
      old_memberships = instance.message_forum_set.all() 
      for old in old_memberships: 
       if old.message not in messages: 
        # and old membership is cleaned by the user 
        old.delete() 
      for message in [x for x in messages not in map(lambda x: x.message, old_memberships)]:     
       membership = Member_forum(message=messsage, forum=instance) 
       # You may have to initialize status, position and tag for your need 
       membership.save() 
     if commit: 
      save_m2m_with_through() 
     else: 
      self.save_m2m = save_m2m_with_through 
     return instance 

    class Meta: 
     model = Forum 
     fields = {'name', 'messages') 

Hay una advertencia: si usted tiene otra relación de muchos a muchos en los modelos (es decir, sin través), super(ForumAdminForm, self).save(self, commit) establecerá self.save_m2m en el caso commit es falso. Sin embargo, llamar a esto provocaría un error, porque esta función también intenta guardar el paso de muchos a muchos también. Es posible que deba guardar todas las demás relaciones de muchos a varios manualmente, o atrapar la excepción, o bien.

Cuestiones relacionadas