Django: Как отфильтровать варианты ForeignKey (например, с помощью request.user) с помощью ModelFormSet и FormWizard?

Имея несколько ForeignKeys в ModelForm, которые я хочу использовать в мастере форм с modelformset_factory (не на 100% уверен в наборе форм), мне интересно, как ограничить выбор выпадающего поля, потому что мне нужно делать это динамически. Я хотел попробовать это, написав свою собственную фабрику modelformset, но здесь, в stackoverflow, я читал о других подходах, но, к сожалению, я их не понимаю.

Вот как далеко я пришел:

модели.py

#...
class Attendee(models.Model):
    """Event specific attendee details."""
    # event is set by URL.
    event = models.ForeignKey(Event)
    attendee = models.ForeignKey(Person) # Contact details, should be limited to user
    accommodation = models.ForeignKey(Accommodation, blank=True) # *
    workshop = models.ForeignKey(Workshop, blank=True) # *
    volunteer = models.ForeignKey(Volunteer, blank=True) # *
    # *= should be limited to event but I think I will be able to handle that.
    #...

class AttendeeForm(forms.ModelForm):
    class Meta:
        model = Attendee

    def __init__(self, *args, **kwargs):
#    def __init__(self, user, *args, **kwargs): I tried this but I didn't get 
# it to work.
        super(AttendeeForm, self).__init__(*args, **kwargs)
#         self.fields['attendee'].queryset = Person.objects.filter(owner=user)
        self.fields['workshop'].required = True
        #...

# This is from https://stackoverflow.com/a/4858044/2704544 but I don't understand it. ##
def form_setup(**kwargs):
    def make_form(data, prefix=None, initial=None):
        form = (data, prefix, initial)
        for k, v in kwargs.items():
            if k == 'some_list':
                form.fields['some_list'].choices = v # What does that mean?
            ...
        return form
    return make_form
#######################################################################################

(ссылка на источник)

просмотры.py

# This is from https://stackoverflow.com/a/623198/2704544 but I don't understand it. ###
class Callback(object):
    def __init__(self, field_name, aff):
        self._field_name = field_name
        self._aff = aff # What is this aff?
    def cb(self, field, **kwargs):
        nf = field.formfield(**kwargs)
        if field.name == self._field_name:  # this is 'options' field
            nf.queryset = ServiceOption.objects.filter(affiliate=self._aff)
        return nf
#######################################################################################

reg_wiz_forms = (
    ('attendees', modelformset_factory(Attendee, form=AttendeeForm, exclude='event', 
# formfield_callback=Callback('option', affiliate).cb Just copied. Why can you call 
# cb without arguments?
)),)
class RegWizard(SessionWizardView):
    def get_form_instance(self, step):
        instance = None

        if step == '0':
            instance = Attendees.objects.filter(owner__owner=self.request.user)
        #...

(ссылка на источник)

urls.py

#...
url(r'^register/$', views.RegWizard.as_view(views.reg_wiz_forms), name='register'),
#...

Я также читал о curry и других вещах и пытался переопределить get_initkwargs и другие методы WizardView, но больше ничего не нашел документ или намеки на эту тему. Может быть, кто-то может мне помочь.

Обновлять

Теперь он частично работает с curry. Отчасти потому, что он не работает с функцией управления:

просмотры.py

def manage_wizard(request, event):
    AttendeeFormSet = modelformset_factory(Attendee, form=AttendeeForm, exclude='event')
    AttendeeFormSet.form = staticmethod(curry(AttendeeForm, user=request.user))
    wiz = RegWizard
    wiz.event = event
    return wiz.as_view([('attendees', AttendeeFormSet)])

Я получаю AttributeError: «объект 'function' не имеет атрибута 'base_fields'». Это похоже на ту же проблему здесь.

Но когда я переопределяю WizardView get_form_list и вызываю его напрямую в url.py, он работает:

reg_wiz_forms = ('attendees', modelformset_factory(Attendee, form=AttendeeForm, exclude='event'))

class RegWizard(SessionWizardView):
    def get_form_list(self):
        self.form_list['attendees'].form = staticmethod(curry(AttendeeForm, user=self.request.user))
        return super(RegWizard, self).get_form_list()

Теперь мне интересно, есть ли решение без переопределения метода.


person yofee    schedule 02.09.2013    source источник


Ответы (1)


Вы просто хотите, чтобы ваш набор запросов внешнего ключа для атрибута участника в вашей модели был отфильтрованным в вашей модели ModelForm. Вы находитесь на правильных линиях здесь:

self.fields['attendee'].queryset = Person.objects.filter(owner=user)

Это предполагает, что атрибут «владелец» существует в классе Person.

Однако это не сработает, так как где или каков ваш пользовательский аргумент? Одним из решений является каррирование метода формы init, как вы упомянули, для включения правильного объекта пользователя:

    form = staticmethod(curry(AttendeeForm, user=<the-user-obj>))

Теперь вы извлекаете свой пользовательский аргумент из kwargs в методе init:

user = kwargs.pop('user')

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

def __init__(self, user, *args, **kwargs): I tried this but I didn't get 
it to work.

Строка выше не будет работать по ряду причин, основная из которых заключается в том, что она никогда не будет вызываться ниоткуда, вы создаете новую функцию, а не переопределяете существующую, что мы и делаем, перезаписывая метод инициализации.

Просто несколько, возможно, полезных советов по дизайну. У вас здесь много тем, каждая из которых предлагает множество разных идей, что сильно усложняет ситуацию. Попробуйте отфильтровать свою проблему до основных понятий. Здесь это презентация проблемы с данными, поэтому подумайте о том, чтобы начать с формы, для этого она и предназначена. :-)

person professorDante    schedule 02.09.2013
comment
Теперь я мог собрать это воедино. Затем я попытался создать функцию manage_wizard, чтобы получить request и ярлык события и создать ModelFormSets, но думаю, что у меня точно такая же проблема, как описано здесь. Есть ли у вас какие-либо предложения не переопределять WizardView get_form_list (что работает)? - person yofee; 03.09.2013
comment
Если у вас есть другая проблема, я бы предложил опубликовать ваш оскорбительный код в новом вопросе SO, чтобы мы все могли взглянуть, на этот вопрос был дан ответ. - person professorDante; 03.09.2013
comment
Собственно тот же вопрос. Самое смешное, что сначала я попробовал это нетрадиционным способом (переопределением). Но мне это не понравилось, поэтому я попытался изменить это, что не удалось. См. обновление выше для более подробной информации. Не могли бы вы опубликовать новый вопрос? Если бы я все еще мог это сделать, но у него было похожее название. Я также мог бы не принять ваш ответ. - person yofee; 03.09.2013
comment
Я бы сказал, что на вопрос был дан ответ, основываясь на вашем заголовке вопроса «как фильтровать внешние ключи…». Мой ответ отвечает на это. Ваш метод переопределения абсолютно действителен, что заставляет вас думать, что переопределение «нетрадиционно»? Это 100% нормально. - person professorDante; 03.09.2013
comment
Ok. Спасибо за ваше терпение :) Просто для понимания: вы написали, что def __init__(self, user, *args, **kwargs): не будет работать. У меня есть эта идея из b-list. Это работало с Django 1.0, но больше не работает? - person yofee; 04.09.2013
comment
Это не сработает в вашем случае, так как вы не вызываете свой метод init откуда угодно, экземпляр ModelForm создается в кишках Django, так как вы используете modelformset_factory. Отсюда хитрость добавления к kwargs вместо подхода Джеймса Беннетта, который подходит для пользовательской формы. Вы можете убедиться в этом, попробовав; он, вероятно, будет блевать из-за того, что init принимает 2 аргумента, а у меня есть только 1. - person professorDante; 04.09.2013