ModelForm с обратным полем ManytoMany - PullRequest
6 голосов
/ 11 августа 2011

У меня проблемы с получением ModelMultipleChoiceField для отображения начальных значений экземпляра модели.Я не смог найти никакой документации по этой области, и примеры, которые я читал, слишком запутаны. Django: ModelMultipleChoiceField не выбирает начальные варианты выбора. похоже, похоже, но решение, которое было дано там, не является динамическим для экземпляра модели.

Вот мой случай (каждый пользователь базы данныхподключен к одному или нескольким проектам):

models.py

from django.contrib.auth.models import User
class Project(Model):
    users = ManyToManyField(User, related_name='projects', blank=True)

forms.py

from django.contrib.admin.widgets import FilteredSelectMultiple
class AssignProjectForm(ModelForm):
    class Meta:
        model = User
        fields = ('projects',)

    projects = ModelMultipleChoiceField(
        queryset=Project.objects.all(),
        required=False,
        widget=FilteredSelectMultiple('projects', False),
    )

views.py

def assign(request):
    if request.method == 'POST':
        form = AssignProjectForm(request.POST, instance=request.user)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/index/')
    else:
        form = AssignProjectForm(instance=request.user)

    return render_to_response('assign.html', {'form': form})

Форма, которую он возвращает, не выбирает связанные проекты экземпляра (это выглядит так: Многофункциональный виджет Django? ).Кроме того, он не обновляет пользователя с помощью выбора, сделанного при сохранении формы.

Редактировать: удалось решить эту проблему с помощью подхода, приведенного здесь: http://code -blasphemies.blogspot.com /2009/04 / динамически созданные-modelmultiplechoice.html

Ответы [ 2 ]

4 голосов
/ 11 августа 2011

ModelForm не работают автоматически для обратных отношений.

На save() ничего не происходит, потому что ModelForm знает, что делать со своими собственными полями - projects не является полем в модели User, это просто поле в вашей форме.

Вы должны будете сообщить своей форме, как сохранить себя, с этим вашим новым полем.

def save(self, *args, **kwargs):
    for project in self.cleaned_data.get('projects'):
        project.users.add(self.instance)
    return super(AssignProjectForm, self).save(*args, **kwargs)
3 голосов
/ 20 декабря 2018

Вот решение, которое лучше, чем старые, которые действительно не работают.

При создании формы необходимо одновременно загрузить существующие связанные значения из базы данных и сохранить их обратно при сохранении формы. Я использую метод set() для связанного имени (менеджера), который делает всю работу за вас: убирает существующие отношения, которые больше не выбираются, и добавляет новые, которые стали выбранными. Так что вам не нужно делать никаких циклов или проверок.

class AssignProjectForm(ModelForm):

    def __init__(self, *args, **kwargs):
        super(AssignProjectForm, self).__init__(*args, **kwargs)

        # Here we fetch your currently related projects into the field,     
        # so that they will display in the form.
        self.fields['projects'].initial = self.instance.projects.all(
            ).values_list('id', flat=True)

    def save(self, *args, **kwargs):
        instance = super(AssignProjectForm, self).save(*args, **kwargs)

        # Here we save the modified project selection back into the database
        instance.projects.set(self.cleaned_data['projects'])

        return instance

Помимо простоты, использование метода set() имеет еще одно преимущество, которое вступает в силу, если вы используете сигналы Django (например, post_save и т. Д.) В отношении m2m: если вы добавляете и удаляете записи по одному за раз цикл, вы получите сигналы для каждого объекта. Но если вы сделаете это за одну операцию, используя set(), вы получите только один сигнал со списком объектов. Если код в вашем обработчике сигналов выполняет значительную работу, это большое дело.

...