Як я можу отримати доступ до об'єкта запиту чи будь-якої іншої змінної у методі clean () форми?


99

Я намагаюся ask.user для чистого методу форми, але як я можу отримати доступ до об'єкта запиту? Чи можу я змінити чистий метод, щоб дозволити введення змінних?

Відповіді:


157

Відповідь Бер - зберігати його в потоці передач - дуже погана ідея. Немає абсолютно ніяких причин робити це так.

Набагато кращий спосіб, щоб перевизначити форму по __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
Це не корисно, коли ви продовжуєте форму адміністратора, оскільки ви можете запустити форму, переходячи до вар. Запиту. Будь-яка ідея¿?
Морді

13
Чому, на вашу думку, використання локального потокового сховища - дуже погана ідея? Це дозволяє уникати скидання коду для передачі запиту скрізь.
Майкл Міор

9
Я б не передав сам об’єкт запиту формі, а скоріше поля запиту, які вам потрібні (тобто користувач), інакше ви прив'язуєте свою логіку форми до циклу запитів / відповідей, що робить тестування складніше.
Ендрю Інграм

2
У Кріса Пратта є і хороше рішення, оскільки для роботи з формами в адміністраторі.ModelAdmin
radtek

34

ОНОВЛЕНО 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)

Ви можете отримати доступ до об'єкта запиту від будь-якого методу ModelFormз 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

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

2
Хтось, окрім мене, вважає, що це просто великий безлад, щоб зробити щось, що є досить рудиментарним для веб-рамки? Джанго - це чудово, але це змушує мене взагалі не хочу використовувати CBV.
trpt4him

1
Переваги ІМХО набагато переважають недоліки ФБВ, особливо якщо ви працюєте над великим проектом з кодом написання 25+ розробок, який спрямований на 100% охоплення тесту. Не впевнений, чи новіші версії Django задовольняють автоматичне встановлення requestоб'єкта get_form_kwargs.
Джозеф Віктор Замміт

Аналогічно, чи є спосіб отримати доступ до ідентифікатора екземпляра об'єкта в get_form_kwargs?
Хассан Байг

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


2
Незважаючи на вищезазначені коментарі, цей метод працює, тоді як інший метод не робить. Встановлення атрибута об’єкта форми в init не надійно переноситься на чисті методи, тоді як встановлення локальних рядків потоку дозволяє переносити ці дані.
rplevy

4
@rplevy Ви справді передали об’єкт запиту під час створення примірника форми? Якщо ви цього не помітили, він використовує аргументи ключових слів **kwargs, це означає, що вам доведеться передати об’єкт запиту як MyForm(request.POST, request=request).
unode

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. Цей працює і набагато коротше. Дякую!
Райк

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.
Констант Франсуа

Дійсно приємна відповідь, я люблю це!
Люк Дупін

Django 1.9 забезпечує: 'function' object has no attribute 'base_fields'. Однак простіша (без закриття) відповідь @ Франсуа працює безперебійно.
raratiru

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. Ви не ризикуєте переосмислити однойменну kwarg
  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)

form.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.in вещества будь-яким чистим методом у 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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.