Как получить доступ к объекту запроса или любой другой переменной в методе формы clean ()?

99

Я пытаюсь request.user получить чистый метод формы, но как мне получить доступ к объекту запроса? Могу ли я изменить чистый метод, чтобы разрешить ввод переменных?

нубела
источник

Ответы:

157

Ответ Бера - хранить его в threadlocals - очень плохая идея. Нет абсолютно никаких причин для этого.

Гораздо лучший способ, чтобы переопределить форму по __init__методу взять дополнительный аргумент ключевого слова, request. Это сохраняет запрос в той форме , в которой он требуется, и откуда вы можете получить к нему доступ в своем чистом методе.

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(MyForm, self).__init__(*args, **kwargs)


    def clean(self):
        ... access the request object via self.request ...

и на ваш взгляд:

myform = MyForm(request.POST, request=request)
Дэниел Розман
источник
4
Вы правы в этом случае. Однако, возможно, нежелательно изменять формы / представления в этом случае. Кроме того, существуют варианты использования локального хранилища потоков, когда невозможно добавить параметры метода или переменные экземпляра. Подумайте о вызываемом аргументе фильтра запроса, которому нужен доступ к данным запроса. Вы не можете ни добавить параметр к вызову, ни указать какой-либо экземпляр.
Бер
4
Это бесполезно, когда вы расширяете форму администратора, потому что вы можете запустить свою форму, передав запрос var. Любая идея?
Mordi
13
Почему вы говорите, что использование локального хранилища потоков - очень плохая идея? Это позволяет избежать необходимости отбрасывать код для передачи запроса повсюду.
Michael Mior
9
Я бы не передал в форму сам объект запроса, а скорее поля запроса, которые вам нужны (например, пользователя), в противном случае вы привяжете свою логику формы к циклу запроса / ответа, что затруднит тестирование.
Эндрю Ингрэм
2
У Криса Пратта тоже есть хорошее решение для работы с формами в admin.ModelAdmin
radtek
35

ОБНОВЛЕНО 25.10.2011 : теперь я использую это с динамически созданным классом вместо метода, поскольку в противном случае Django 1.3 отображает некоторую странность.

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
        class ModelFormWithRequest(ModelForm):
            def __new__(cls, *args, **kwargs):
                kwargs['request'] = request
                return ModelForm(*args, **kwargs)
        return ModelFormWithRequest

Затем переопределите MyCustomForm.__init__следующим образом:

class MyCustomForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(MyCustomForm, self).__init__(*args, **kwargs)

Затем вы можете получить доступ к объекту запроса из любого метода ModelFormwith self.request.

Крис Прэтт
источник
1
Крис, это "def __init __ (self, request = None, * args, ** kwargs)" - это плохо, потому что в конечном итоге он будет содержать запрос как в первом позиционном аргументе, так и в kwargs. Я изменил его на «def __init __ (self, * args, ** kwargs)», и это работает.
slinkp
1
Упс. Это было просто моей ошибкой. Я забыл обновить эту часть кода, когда сделал другое обновление. Спасибо за улов. Обновлено.
Крис Пратт
4
Это действительно метакласс? Я думаю, что это просто обычное переопределение, вы добавляете запрос в __new__kwargs, который позже будет передан __init__методу класса . ModelFormWithRequestЯ думаю, что название класса гораздо понятнее по значению, чем ModelFormMetaClass.
k4ml 08
2
Это НЕ метакласс! См stackoverflow.com/questions/100003/...
frnhr
32

Как бы то ни было, если вы используете представления на основе классов , вместо представлений на основе функций, переопределите get_form_kwargsв представлении редактирования. Пример кода для настраиваемого CreateView :

from braces.views import LoginRequiredMixin

class MyModelCreateView(LoginRequiredMixin, CreateView):
    template_name = 'example/create.html'
    model = MyModel
    form_class = MyModelForm
    success_message = "%(my_object)s added to your site."

    def get_form_kwargs(self):
        kw = super(MyModelCreateView, self).get_form_kwargs()
        kw['request'] = self.request # the trick!
        return kw

    def form_valid(self):
        # do something

Приведенный выше код представления будет requestдоступен в качестве одного из аргументов ключевого слова __init__функции конструктора формы . Поэтому в вашем деле ModelForm:

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        # important to "pop" added kwarg before call to parent's constructor
        self.request = kwargs.pop('request')
        super(MyModelForm, self).__init__(*args, **kwargs)
Джозеф Виктор Заммит
источник
1
Это сработало для меня. Я сделал это примечание, потому что я все равно использовал get_form_kwargs из-за сложной логики WizardForm. Никакой другой ответ, который я видел, не относился к WizardForm.
datakid 03
2
Кто-нибудь, кроме меня, думает, что делать что-то, что довольно элементарно для веб-фреймворка, - это просто большой беспорядок? Django великолепен, но из-за этого я вообще не хочу использовать CBV.
trpt4him
1
IMHO преимущества CBV намного перевешивают недостатки FBV, особенно если вы работаете над большим проектом с более чем 25 разработчиками, пишущими код, который нацелен на 100% покрытие модульным тестом. Не уверен, что новые версии Django поддерживают автоматическое размещение requestобъекта внутри get_form_kwargs.
Джозеф Виктор Заммит
Аналогичным образом, есть ли способ получить доступ к идентификатору экземпляра объекта в get_form_kwargs?
Hassan Baig
1
@HassanBaig Возможно, использует self.get_object? CreateViewРасширяет SingleObjectMixin. Но работает ли это или вызывает исключение, зависит от того, создаете ли вы новый объект или обновляете существующий; т.е. проверить оба случая (и удаление конечно).
Джозеф Виктор Заммит
17

Обычный подход - сохранить объект запроса в локальной ссылке потока с использованием промежуточного программного обеспечения. Затем вы можете получить к нему доступ из любого места вашего приложения, включая метод Form.clean ().

Изменение подписи метода Form.clean () означает, что у вас есть собственная модифицированная версия Django, которая может быть не той, которую вы хотите.

Количество благодарностей промежуточного программного обеспечения выглядит примерно так:

import threading
_thread_locals = threading.local()

def get_current_request():
    return getattr(_thread_locals, 'request', None)

class ThreadLocals(object):
    """
    Middleware that gets various objects from the
    request object and saves them in thread local storage.
    """
    def process_request(self, request):
        _thread_locals.request = request

Зарегистрируйте это промежуточное ПО, как описано в документации Django.

Ber
источник
2
Несмотря на приведенные выше комментарии, этот метод работает, а другой - нет. Установка атрибута объекта формы в init не обеспечивает надежного переноса в чистые методы, тогда как установка локальных переменных потока позволяет переносить эти данные.
rplevy
4
@rplevy действительно ли вы передали объект запроса при создании экземпляра формы? Если вы не заметили, что он использует аргументы ключевого слова **kwargs, это означает, что вам нужно будет передать объект запроса как MyForm(request.POST, request=request).
unode 04
13

Для администратора Django в Django 1.8

class MyModelAdmin(admin.ModelAdmin):
    ...
    form = RedirectForm

    def get_form(self, request, obj=None, **kwargs):
        form = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        form.request = request
        return form
Франсуа Констан
источник
1
Метод с наивысшей оценкой, приведенный выше, действительно, похоже, перестал работать где-то между Django 1.6 и 1.9. Этот действительно работает и намного короче. Спасибо!
Raik
9

Я столкнулся с этой конкретной проблемой при настройке админа. Я хотел, чтобы определенное поле было проверено на основе учетных данных конкретного администратора.

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

class MyCustomForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def clean(self):
        # make use of self.request here

class MyModelAdmin(admin.ModelAdmin):
    form = MyCustomForm
    def get_form(self, request, obj=None, **kwargs):
        ModelForm = super(MyModelAdmin, self).get_form(request, obj=obj, **kwargs)
        def form_wrapper(*args, **kwargs):
            a = ModelForm(*args, **kwargs)
            a.request = request
            return a
    return form_wrapper
энтропия
источник
Спасибо за это. Быстрая опечатка: obj=objнет obj=Noneв строке 11.
François Constant
Действительно хороший ответ, мне это нравится!
Люк Дюпен
Django 1.9 обеспечивает: 'function' object has no attribute 'base_fields'. Однако более простой (без закрытия) ответ @ François работает плавно.
раратиру
5

Вы не всегда можете использовать этот метод (и, вероятно, это плохая практика), но если вы используете форму только в одном представлении, вы можете охватить его внутри самого метода представления.

def my_view(request):

    class ResetForm(forms.Form):
        password = forms.CharField(required=True, widget=forms.PasswordInput())

        def clean_password(self):
            data = self.cleaned_data['password']
            if not request.user.check_password(data):
                raise forms.ValidationError("The password entered does not match your account password.")
            return data

    if request.method == 'POST':
        form = ResetForm(request.POST, request.FILES)
        if form.is_valid():

            return HttpResponseRedirect("/")
    else:
        form = ResetForm()

    return render_to_response(request, "reset.html")
Крис
источник
Иногда это действительно хорошее решение: я часто делаю это с помощью get_form_classметода CBV , если знаю, что мне нужно сделать много вещей с запросом. При повторном создании класса могут возникнуть некоторые накладные расходы, но это просто перемещает его из времени импорта во время выполнения.
Мэтью Шинкель
5

Ответ Дэниела Розмана по-прежнему остается лучшим. Однако я бы использовал первый позиционный аргумент для запроса вместо аргумента ключевого слова по нескольким причинам:

  1. Вы не рискуете переопределить одноименный кварг
  2. Запрос не является обязательным, что неверно. В этом контексте атрибут запроса никогда не должен иметь значение None.
  3. Вы можете чисто передать аргументы и kwargs родительскому классу, не изменяя их.

Наконец, я бы использовал более уникальное имя, чтобы избежать переопределения существующей переменной. Таким образом, Мой модифицированный ответ выглядит так:

class MyForm(forms.Form):

  def __init__(self, request, *args, **kwargs):
      self._my_request = request
      super(MyForm, self).__init__(*args, **kwargs)


  def clean(self):
      ... access the request object via self._my_request ...
Андрес Рестрепо
источник
3

У меня есть еще один ответ на этот вопрос, согласно вашему требованию, вы хотите получить доступ к пользователю с помощью чистого метода формы. Вы можете попробовать это. View.py

person=User.objects.get(id=person_id)
form=MyForm(request.POST,instance=person)

forms.py

def __init__(self,*arg,**kwargs):
    self.instance=kwargs.get('instance',None)
    if kwargs['instance'] is not None:
        del kwargs['instance']
    super(Myform, self).__init__(*args, **kwargs)

Теперь вы можете получить доступ к self.instance любым чистым методом в form.py

Нишант Кашьяп
источник
0

Если вы хотите получить к нему доступ через «подготовленные» представления классов Django, как будто CreateViewесть небольшая хитрость, которую нужно знать (= официальное решение не работает из коробки). По своему усмотрению CreateView вам придется добавить такой код:

class MyCreateView(LoginRequiredMixin, CreateView):
    form_class = MyOwnForm
    template_name = 'my_sample_create.html'

    def get_form_kwargs(self):
        result = super().get_form_kwargs()
        result['request'] = self.request
        return result

= короче говоря, это решение для перехода requestк вашей форме с помощью представлений Django Create / Update.

Оливье Понс
источник