27 votes

Quelles sont les étapes pour faire fonctionner un ModelForm avec une relation ManyToMany avec un modèle intermédiaire dans Django?

  • J'ai un Client et Groupe Modèle.
  • Un Client peut faire partie de plusieurs groupes.
  • Les Clients qui font partie d'un groupe peut utiliser son libre du groupe taux de location à tout moment, mais seulement une fois. C'est là que le modèle intermédiaire (ClientGroupe) vient avec les données supplémentaires.

Pour l'instant, quand j'essaie d'enregistrer le m2m de données, il vient de se meurt et dit que je devrais utiliser le ClientGroupe Manager...donc ce qu'il manque?

Voici mes modèles:

class Groupe(models.Model):
    nom = models.CharField(max_length=1500, blank=True)

class Client(models.Model):
    nom = models.CharField(max_length=450, blank=True)
    prenom = models.CharField(max_length=450, blank=True)
    groupes = models.ManyToManyField(Groupe, null = True, blank = True, through='ClientGroupe')

class ClientGroupe(models.Model):
    client = models.ForeignKey(Client)
    groupe = models.ForeignKey(Groupe)
    dt = models.DateField(null=True, blank=True) # the date the client is using its group's free rental rate    

    class Meta:
        db_table = u'clients_groupes'

et voici mon avis:

def modifier(request, id):
    client = Client.objects.get(id=id)    
    form = ClientForm(instance = client)

    dict = {
        "form": form
        , "instance" : client
    }

    if request.method == "POST":
        form = ClientForm(request.POST, instance = client)

        if form.is_valid():
            client_mod = form.save()

            id = client_mod.id
            return HttpResponseRedirect(
                "/client/%(id)s/?err=success" % {"id" : id}
            )
        else:
            return HttpResponseRedirect(
                "/client/%(id)s/?err=warning" % {"id" : id}
            )

    return render_to_response(
        "client/modifier.html"
        , dict
        , context_instance=RequestContext(request)
    )

EDIT:

et voici le ClientForm code:

class ClientForm(ModelForm):
    class Meta:
        model = Client

EDIT #2: voici le message d'erreur:

AttributeError at /client/445/

Cannot set values on a ManyToManyField which specifies an intermediary model. Use ClientGroupe's Manager instead.

Request Method:     POST
Request URL:    http://localhost/client/445/
Exception Type:     AttributeError
Exception Value:    Cannot set values on a ManyToManyField which specifies an intermediary model.  Use ClientGroupe's Manager instead.

Exception Location:     C:\Python25\lib\site-packages\django\db\models\fields\related.py  in __set__, line 574
Python Executable:  C:\xampp\apache\bin\apache.exe
Python Version:     2.5.2

17voto

Ross Light Points 1994

Si vous utilisez la méthode save maintenant, Django va essayer de l'enregistrer à l'aide du gestionnaire (Django ne permet pas). Malheureusement, le comportement que vous voulez, c'est un peu plus compliqué que ce qu' ModelForm le fait par défaut. Ce que vous devez faire est de créer un formset.

Tout d'abord, vous aurez besoin de modifier les options de votre ClientForm , de sorte qu'il n'affiche pas l' groupes d'attribut.

class ClientForm(ModelForm):
    class Meta:
        model = Client
        exclude = ('groupes',)

Ensuite, vous devez modifier l'affichage de l'ensemble:

from django.forms.models import inlineformset_factory

def modifier(request, id):
    client = Client.objects.get(id=id)    
    form = ClientForm(instance = client)
    # Create the formset class
    GroupeFormset = inlineformset_factory(Client, Groupe)
    # Create the formset
    formset = GroupeFormset(instance = client)

    dict = {
        "form": form
        , "formset" : formset
        , "instance" : client
    }

    if request.method == "POST":
        form = ClientForm(request.POST, instance = client)
        formset = GroupeFormset(request.POST, instance = client)

        if form.is_valid() and formset.is_valid():
            client_mod = form.save()
            formset.save()

            id = client_mod.id
            return HttpResponseRedirect(
                "/client/%(id)s/?err=success" % {"id" : id}
            )
        else:
            return HttpResponseRedirect(
                "/client/%(id)s/?err=warning" % {"id" : id}
            )

    return render_to_response(
        "client/modifier.html"
        , dict
        , context_instance=RequestContext(request)
    )

Et bien évidemment, vous devez également modifier votre modèle pour rendre le formset.

Si vous avez besoin d'autres conseils sur les jeux de formulaires, consultez ces articles:

Modèle formsets
Formsets

10voto

Anber Points 199
…
if form.is_valid():
    client_mod = form.save(commit=False)
    client_mod.save()
    for groupe in form.cleaned_data.get('groupes'):
        clientgroupe = ClientGroupe(client=client_mod, groupe=groupe)
        clientgroupe.save()
    …

4voto

IfLoop Points 59461

Vous devrez probablement supprimer le champ ManyToMany de votre modèle client, ou bien l'exclure soigneusement de votre formulaire. Malheureusement, le widget par défaut pour le champ ManyToMany ne peut pas remplir correctement le modèle ClientGroupe (même si le champ manquant, dt, a été défini sur autonow = True). C'est quelque chose que vous devrez soit décliner sous une autre forme, soit gérer selon vous.

0voto

Vladimir Prudnikov Points 2106

Lorsque vous enregistrez votre formulaire, vous enregistrez l'objet client. Maintenant, si vous souhaitez affecter un client au groupe, vous devez le faire:

 clientgroupe = ClientGroupe.objects.create(client=client_instance, groupe=groupe_instance, dt=datetime.datetime.now())
 

où client_instance et groupe_instance vos objets client et groupe.

0voto

hcliff Points 183

Je propose une solution alternative en raison de problèmes rencontrés avec le non-appel de forms_valid:

 class SplingCreate(forms.ModelForm):
class Meta:
    model = SplingModel
    fields = ('Link', 'Genres', 'Image', 'ImageURL',)

def save(self, commit=True):
    from django.forms.models import save_instance

    if self.instance.pk is None:
        fail_message = 'created'
    else:
        fail_message = 'changed'
    fields = set(self._meta.fields) - set(('Genres',))
    instance = save_instance(self, self.instance, fields,
                             fail_message, commit, construct=False)

    genres = self.cleaned_data.get('Genres')
    for genre in genres:
        SplingGenreModel.objects.get_or_create(spling=instance, genre=genre)

    return instance
 

J'ai copié la logique de djangos forms / models.py, mon champ Genres est une multitude de noms avec une table intermédiaire - je l'exclus de la save_instance puis la sauvegarde séparément.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X