Несколько форм Python Flask приводят к нескольким идентификаторам #csrf_token [duplicate]

Что такое «переменная область»?

Переменные имеют ограниченный «объем» или «места, из которых они доступны». Только потому, что вы написали $foo = 'bar'; один раз где-то в вашем приложении, это не значит, что вы можете ссылаться на $foo из всюду else внутри приложения. Переменная $foo имеет определенную область действия, в пределах которой она действительна, и только код в той же области доступа имеет доступ к переменной.

Как область, определенная в PHP?

Very просто: PHP имеет область функций . Это единственный вид разделителя областей, который существует в PHP. Переменные внутри функции доступны только внутри этой функции. Переменные вне функций доступны вне функций, но не внутри какой-либо функции. Это означает, что в PHP есть одна специальная область: глобальная область . Любая переменная, объявленная вне любой функции, находится в пределах этой глобальной области.

Пример:

<?php

$foo = 'bar';

function myFunc() {
    $baz = 42;
}

$foo находится в области global , $baz находится внутри локального области внутри myFunc. Только код внутри myFunc имеет доступ к $baz. Только код вне myFunc имеет доступ к $foo. Ни один из них не имеет доступа к другому:

<?php

$foo = 'bar';

function myFunc() {
    $baz = 42;

    echo $foo;  // doesn't work
    echo $baz;  // works
}

echo $foo;  // works
echo $baz;  // doesn't work

Сфера охвата и включенные файлы

Границы файлов не разделяют область :

a.php

<?php

$foo = 'bar';

b.php

<?php

include 'a.php';

echo $foo;  // works!

Те же правила применяются к коду include d как применяется к любому другому коду: только отдельная область function. Для области видимости вы можете включить такие файлы, как копирование и вставка кода:

c.php

<?php

function myFunc() {
    include 'a.php';

    echo $foo;  // works
}

myFunc();

echo $foo;  // doesn't work!

В приведенном выше примере, a.php был включен внутри myFunc, любые переменные внутри a.php имеют только область локальной функции. Просто потому, что они появляются в глобальной области действия в a.php, не обязательно означают, что они есть, это фактически зависит от того, в каком контексте этот код включен / выполнен.

Что касается функций внутри функций и классов?

Каждое новое объявление function вводит новую область, это так просто.

(анонимные) функции внутри функций

function foo() {
    $foo = 'bar';

    $bar = function () {
        // no access to $foo
        $baz = 'baz';
    };

    // no access to $baz
}

classes

$foo = 'foo';

class Bar {

    public function baz() {
        // no access to $foo
        $baz = 'baz';
    }

}

// no access to $baz

Что такое область видимости?

Работа с проблемами определения области может показаться раздражающей, но ограниченная переменная область важна для написания сложных приложений! Если каждая переменная, которую вы объявляете, будет доступна везде, где бы вы ни находились, вы будете переключаться со всеми своими переменными без реального способа отслеживать, что изменит что. Есть только так много разумных имен, которые вы можете дать своим переменным, вы, вероятно, захотите использовать переменную «$name» в нескольких местах. Если бы вы могли использовать только одно уникальное имя переменной в своем приложении, вам придется прибегать к действительно сложным схемам именования, чтобы убедиться, что ваши переменные уникальны и что вы не меняете неправильную переменную из неправильной части кода.

Обратите внимание:

function foo() {
    echo $bar;
}

Если не было области, что бы сделала эта функция выше? Откуда $bar? Какое состояние оно имеет? Он даже инициализирован? Вы должны проверять каждый раз? Это не поддерживается. Это приводит нас к ...

границы границ пересечения

Правильный путь: передача переменных в и из

function foo($bar) {
    echo $bar;
    return 42;
}

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

$baz   = 'baz';
$blarg = foo($baz);

Расширение области видимости в анонимных функциях

$foo = 'bar';

$baz = function () use ($foo) {
    echo $foo;
};

$baz();

Анонимная функция явно включает $foo из ее окружения. Обратите внимание, что это не то же самое, что область global .

Неправильный путь: global

Как уже было сказано, глобальный охват несколько особый, и функции могут явно импортировать переменные из него:

$foo = 'bar';

function baz() {
    global $foo;
    echo $foo;
    $foo = 'baz';
}

Эта функция использует и изменяет глобальную переменную $foo. Не делай это! [Если вы действительно действительно действительно знаете, что делаете, и даже тогда: не делайте этого!) [/ ​​G45]

Все вызывающие функции этой функции видят следующее:

baz(); // outputs "bar"
unset($foo);
baz(); // no output, WTF?!
baz(); // outputs "baz", WTF?!?!!

Нет никаких указаний на то, что у этой функции есть какие-либо побочные эффекты , но это так. Это очень легко становится запутанным беспорядком, поскольку некоторые функции продолжают изменять и требуют некоторого глобального состояния. Вы хотите, чтобы функции были stateless , действуя только на их входы и возвращая определенный выход, однако много раз вы их вызываете.

Вам следует избегать использования глобальной области в любом случае насколько это возможно; конечно, вы не должны «вытягивать» переменные из глобальной области в локальную область.

46
задан Suit Boy Apps 12 February 2015 в 06:01
поделиться

4 ответа

Я использовал комбинацию из двух флеш-фрагментов. Первый добавляет префикс к форме , а затем вы проверяете префикс с validate_on_submit (). Я также использую шаблон Louis Roché для определения того, какие кнопки выталкиваются в форме .

Чтобы процитировать Dan Jacob:

Пример:

form1 = FormA(prefix="form1")
form2 = FormB(prefix="form2")
form3 = FormC(prefix="form3")

Затем добавьте скрытое поле (или просто проверьте поле отправки):

if form1.validate_on_submit() and form1.submit.data:

Чтобы процитировать Louis Roché's:

У меня есть в моем шаблоне:

<input type="submit" name="btn" value="Save">
<input type="submit" name="btn" value="Cancel">

И чтобы выяснить, какая кнопка была передана на стороне сервера, у меня есть в моих представлениях. py файл:

if request.form['btn'] == 'Save':
    something0
else:
    something1
50
ответ дан AlexLordThorsen 21 August 2018 в 11:06
поделиться

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

Сначала определите несколько SubmitField с разными именами, например:

class Form1(Form):
    name = StringField('name')
    submit1 = SubmitField('submit')

class Form2(Form):
    name = StringField('name')
    submit2 = SubmitField('submit')

....

Затем добавьте фильтр в view.py:

....
form1 = Form1()
form2 = Form2()
....

if form1.submit1.data and form1.validate(): # notice the order 
....
if form2.submit2.data and form2.validate(): # notice the order 
....

Теперь проблема решена.

Если вы хотите погрузиться в него, продолжайте читать.

Здесь validate_on_submit():

def validate_on_submit(self):
    """
    Checks if form has been submitted and if so runs validate. This is
    a shortcut, equivalent to ``form.is_submitted() and form.validate()``
    """
    return self.is_submitted() and self.validate()

И вот is_submitted():

def is_submitted():
    """Consider the form submitted if there is an active request and
    the method is ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
    """
    return _is_submitted()  # bool(request) and request.method in SUBMIT_METHODS

Когда вы вызываете form.validate_on_submit(), он проверяет, была ли форма передана методом HTTP, независимо от того, какая кнопка отправки нажата. Таким образом, маленький трюк выше - это просто добавить фильтр (чтобы проверить, есть ли у submit данные, т. Е. form1.submit1.data).

Кроме того, мы меняем порядок if, поэтому, когда мы нажимаем кнопку submit, он вызывает только эту validate(), предотвращая ошибку проверки для обеих форм.

История еще не закончена. Вот .data:

@property
def data(self):
    return dict((name, f.data) for name, f in iteritems(self._fields))

Он возвращает dict с именем поля (ключ) и данными поля (значение), однако наша кнопка отправки формы имеет то же имя submit (ключ)!

Когда мы щелкаем по первой кнопке отправки (в форме1), вызов из form1.submit1.data возвращает a dict следующим образом:

temp = {'submit': True}

Нет никаких сомнений, когда мы вызываем if form1.submit.data:, он возвращает True.

Когда мы нажимаем вторую кнопку отправки (в форме2), вызов .data в if form1.submit.data: сначала добавляет значение ключа в dict, а затем вызов из if form2.submit.data: добавьте еще одно значение ключа, в конце концов, dict будет так:

temp = {'submit': False, 'submit': True}

Теперь мы вызываем if form1.submit.data:, он возвращает True, даже если кнопка отправки, которую мы нажали, была в форме2.

Вот почему нам нужно определить два SubmitField с разными именами. Кстати, спасибо за чтение (здесь)!

Обновить

Существует другой способ обработки нескольких форм на одной странице. Вы можете использовать несколько видов для обработки форм. Например:

...
@app.route('/')
def index():
    register_form = RegisterForm()
    login_form = LoginForm()
    return render_template('index.html', register_form=register_form, login_form=login_form)

@app.route('/register', methods=['POST'])
def register():
    register_form = RegisterForm()
    login_form = LoginForm()

    if register_form.validate_on_submit():
        ...  # handle the register form
    # render the same template to pass the error message
    # or pass `form.errors` with `flash()` or `session` then redirect to /
    return render_template('index.html', register_form=register_form, login_form=login_form)


@app.route('/login', methods=['POST'])
def login():
    register_form = RegisterForm()
    login_form = LoginForm()

    if login_form.validate_on_submit():
        ...  # handle the login form
    # render the same template to pass the error message
    # or pass `form.errors` with `flash()` or `session` then redirect to /
    return render_template('index.html', register_form=register_form, login_form=login_form)

В шаблоне (index.html) вам необходимо отобразить обе формы и установить для целевого представления атрибут action:

<h1>Register</h1>
<form action="{{ url_for('register') }}" method="post">
    {{ register_form.username }}
    {{ register_form.password }}
    {{ register_form.email }}
</form>

<h1>Login</h1>
<form action="{{ url_for('login') }}" method="post">
    {{ login_form.username }}
    {{ login_form.password }}
</form>
30
ответ дан Grey Li 21 August 2018 в 11:06
поделиться

Простой способ состоит в том, чтобы иметь разные имена для разных полей отправки. Например:

forms.py:

class Login(Form):

    ...
    login = SubmitField('Login')


class Register(Form):

    ...
    register = SubmitField('Register')

views.py:

@main.route('/')
def index():

    login_form = Login()
    register_form = Register()


    if login_form.validate_on_submit() and login_form.login.data:
        print "Login form is submitted"

    elif register_form.validate_on_submit() and register_form.register.data:
        print "Register form is submitted"

    ...
15
ответ дан Jeff O'Neill 21 August 2018 в 11:06
поделиться
  • 1
    малая коррекция if login.validate_on_submit() and login_form.login.data: должна быть if login_form.validate_on_submit() and login_form.login.data: – Ash 16 April 2018 в 00:41
  • 2
    @Ash, я отредактировал ответ, чтобы исправить опечатку, которую вы указали. – Jeff O'Neill 28 April 2018 в 13:18

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

Затем флеш-веб-действие выглядит ниже: обратите внимание на formdata и obj, которые помогают соответственно инициализировать / сохранить поля формы:

@bp.route('/do-stuff', methods=['GET', 'POST'])
def do_stuff():
    result = None

    form_1 = None
    form_2 = None
    form_3 = None

    if "submit_1" in request.form:
        form_1 = Form1()
        result = do_1(form_1)
    elif "submit_2" in request.form:
        form_2 = Form2()
        result = do_2(form_2)
    elif "submit_3" in request.form:
        form_3 = Form3()
        result = do_3(form_3)

    if result is not None:
        return result

    # Pre-populate not submitted forms with default data.
    # For the submitted form, leave the fields as they were.

    if form_1 is None:
        form_1 = Form1(formdata=None, obj=...)
    if form_2 is None:
        form_2 = Form2(formdata=None, obj=...)
    if form_3 is None:
        form_3 = Form3(formdata=None, obj=...)

    return render_template("page.html", f1=form_1, f2=form_2, f3=form_3)


def do_1(form):
    if form.validate_on_submit():
        flash("Success 1")
        return redirect(url_for(".do-stuff"))


def do_1(form):
    if form.validate_on_submit():
        flash("Success 2")
        return redirect(url_for(".do-stuff"))

def do_3(form):
    if form.validate_on_submit():
        flash("Success 3")
        return redirect(url_for(".do-stuff"))
1
ответ дан turdus-merula 21 August 2018 в 11:06
поделиться