Подтвердить что ты не робот

Каковы шаги, чтобы заставить ModelForm работать с отношениями ManyToMany с промежуточной моделью в Django?

  • У меня есть модель клиента и группы.
  • Клиент может быть частью нескольких групп.
  • Клиенты, входящие в группу, могут использовать групповую бесплатную арендную плату в любое время, но только один раз. Вот где промежуточная модель (ClientGroupe) поставляется с этими дополнительными данными.

На данный момент, когда я пытаюсь сохранить данные m2m, он просто умирает и говорит, что я должен использовать ClientGroupe Manager... так что пропало?

Вот мои модели:

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 free rental rate    

    class Meta:
        db_table = u'clients_groupes'

и здесь мой взгляд:

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

ИЗМЕНИТЬ

и здесь код ClientForm:

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

РЕДАКТИРОВАТЬ № 2: здесь сообщение об ошибке:

AttributeError at /client/445/

Cannot set values on a ManyToManyField which specifies an intermediary model. Use ClientGroupe 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 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
4b9b3361

Ответ 1

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

Ответ 2

Если вы используете метод сохранения прямо сейчас, Django попытается сохранить с помощью менеджера (который Django не разрешает). К сожалению, поведение, которое вы хотите, немного сложнее, чем по умолчанию ModelForm. Что вам нужно сделать, это создать набор форм.

Прежде всего, вам нужно будет изменить параметры вашего ClientForm, чтобы он не отображал атрибут groupes.

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

Затем вы должны изменить представление для отображения набора форм:

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

И, очевидно, вы также должны настроить свой шаблон для рендеринга набора форм.

Если вам нужны другие советы по наборам форм, см. следующие статьи:

Форматы моделей
Formsets

Ответ 3

Вероятно, вам нужно удалить поле ManyToMany из своей модели клиента или же тщательно исключить его из своей формы. К сожалению, виджет по умолчанию для поля ManyToMany не может правильно заполнить модель ClientGroupe (даже если отсутствующее поле dt было установлено как autonow = True). Это то, что вам нужно будет либо вырваться в другую форму, либо обработать ваше мнение.

Ответ 4

При сохранении формы вы сохраняете объект Client. Теперь, если вы хотите назначить клиента группе, вы должны сделать это:

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

где client_instance и groupe_instance выполняются с вашим клиентом и группой.

Ответ 5

Я предоставляю альтернативное решение из-за проблем, с которыми я столкнулся при вызове form_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

Я скопировал логику из djangos forms/models.py, мое поле Genres - многоголосие с промежуточной таблицей - я исключаю его из save_instance, а затем сохраняю его отдельно.