使用AJAX

时间:2018-03-02 09:17:40

标签: jquery ajax python-3.x flask flask-wtforms

我使用Flask-WTForms在Flask中创建了一个简单的表单,其中父母可以注册自己和他的孩子。通过点击“添加孩子”按钮,家长可以根据需要注册多个孩子。通过使用FieldList功能,WTForms使这很容易实现。

然而,点击按钮后,添加孩子'页面刷新自己,因为它向服务器发出请求。我想使用AJAX请求添加子表单,因此页面不会刷新。

我知道如何执行AJAX请求,发送回复并将此响应添加到html页面。但是,我不知道如何将一个条目附加到表单对象并以更新的表单对象返回页面本身(不知何故)。这甚至可能吗?

我的表格:

class ChildForm(FlaskForm):

    name = StringField(label='Name child')
    age = IntegerField(label='Age child')

    class Meta:
        # No need for csrf token in this child form
        csrf = False

class ParentForm(FlaskForm):

    name = StringField(label='Name parent')
    children = FieldList(FormField(ChildForm), label='Children')
    add_child = SubmitField(label='Add child')

    submit = SubmitField()

我的路线:

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = ParentForm()

    if form.add_child.data:
        form.children.append_entry()
        return render_template('register.html', form=form)

    if form.validate_on_submit():
        # do something with data

    return render_template('register.html', form=form)

register.html:

<form action="{{ url_for('register') }}" method="post" id="parentForm">
    {{ form.hidden_tag() }}
    {{ form.name.label }} {{ form.name }}

    {{ form.add_child }}

    {% for childform in form.children %}

        {% for field in childform %}
            {{ field.label }} {{ field }}   
        {% endfor %}

    {% endfor %}

    {{ form.submit }}
</form>

3 个答案:

答案 0 :(得分:1)

我认为这应该有效。除非有错别字。

views.py

########
# SET UP YOUR FLASK APP HERE
########

from flask_wtf import FlaskForm
from wtforms_alchemy import model_form_factory

class Family(db.Model):
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column(db.String(500), info={'label': 'Familyname', 'validators': DataRequired()})

class Member(db.Model):
    id = db.Column('id', db.Integer, primary_key=True)
    name = db.Column(db.String(500), info={'label': 'Member', 'validators': DataRequired()})


BaseModelForm = model_form_factory(FlaskForm)

class ModelForm(BaseModelForm):
    @classmethod
    def get_session(self):
        return db.session

class MemberForm(ModelForm):    

    class Meta:
        model = Machine


class MemberEditForm(MemberForm):
    pass

class MainForm(ModelForm):

    add_member = SubmitField('+ Member')
    members = ModelFieldList(FormField(MemberForm))
    class Meta:
        model = Family



@app.route('/form')
def main_form():

    family = Family()
    form = MainForm(obj=family)

    if form.add_member.data:
        getattr(form,'members').append_entry()
        return render_template('form.html', form=form)

    if form.validate_on_submit():
        form.populate_obj(family)
        db.session.add(order)
        db.session.commit()

    return render_template('form.html', form=form)


@app.route('/process_add_member', methods=['POST'])
def add_member():
    form = MainForm()

    getattr(form,'members').append_entry()

    return render_template('members.html', form=form)

form.html

<script type="text/javascript">

    $("#add-member").on('click', function(event){
        $.ajax({
            url: "{{ url_for('add_member') }}",
            type : "POST",
            //dataType : 'json', // data type
            data : $("#main-form").serialize(),
            success : function(result) {
                console.log(result);
                $("#members").html(result);
            },
            error: function(xhr, resp, text) {
                console.log(xhr, resp, text);
            }
        });
        event.preventDefault();

        });
</script>


<form method="post" action="{{ url_for('main_form') }}" id="main-form">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name }}


<fieldset class="form-group border p-2">
    <span id="members">{% include 'members.html' %}</span>

    <div class="form-row">
        {{ form.add_member(id="add-member") }}
    </div>
</fieldset>

</form>

members.html

<span class="h3">Members</span>
{% if form.members%}
    <div class="form-row">
        <div class="col-1 form-group">Name</div>
    </div>
{% endif %}

{% for member in form.members %}    
    <div class="form-row">
        {% for field in member %}
            <div class="col-1 form-group">{{ field(class="form-control") }}</div>
        {% endfor %}
    </div>
{% endfor %}

答案 1 :(得分:1)

在下面的设置中,用户输入的内容指定了他将在表单上提交多少个文本区域(它们动态显示和消失),然后可以使用AJAX提交。

Python后端

from flask import jsonify
from webargs import flaskparser, fields, validate

USER_ARGS_POST = {
    'list_of_items': fields.List(fields.Str(required=True)),
    'num_items': fields.Int(required=True)
}

@api.route('/json_form', methods=['POST']
def json_form_post():
    parsed_args = flaskparser.parser.parse(USER_ARGS_POST, request)
    for data_item in parsed_args['list_of_data']:
        # do something with each data item, e.g. create db tuple and add
        db.session.add(new_data_item_tuple)
    db.session.commit()
    return jsonify({'result': 'SUCCESS', 'desc': 'All items appended to DB'})

@web.route('/index', methods=['GET'])
def index():
    return render_template('index.html')

JS前端和HTML(使用Vue.js和Jquery)

<div id="vue-container">

    <h2>Form Input List</h2>
    Number of items:<br>
    <input type="number" min="1" max="10" v-model="form_data.num_items" placeholder="Number of items for form.."><br>

    <template v-for="n in Number(form_data.num_items)">
        <textarea v-model="form_data.list_of_data[n-1]" v-bind:placeholder="'Items ' + n"></textarea><br>
    </template>

    <button type="button" v-on:click="submit_('/json_form', form_data)">Submit Form</button>

    {({ form_data })} <br>
    {({ ajax_message })} <br>

</div>

<script>
    'use strict';

    var vm = new Vue({
        el: '#vue-container',
        delimiters: ['{({', '})}'], // separate vue from jinja2 globally
        data: {
            ajax_message: '',
            form_data: {
                num_items: 1,
                list_of_data: [null],
            },
        },
        methods: {
            ajax_: function (url, action, form_data, cb) {
                /*
                Wrapper to send a JSON version of form data to a specified url and execute
                callback on success, or register error message to Vue Instance data variable

                Args:
                    url: ajax API request url
                    action: 'post', 'put', 'delete', 'get', based on API
                    form_data: dict of kwargs consistent with API end route
                    cb: execute on success, should be callable with one argument: 'data'.

                Returns:
                    None: executes asyncronously the supplied callback.
                 */

                self = this;

                $.ajax({
                    url: url,
                    dataType: "json",
                    contentType: "application/json;charset=utf-8",
                    type: action,
                    data: JSON.stringify(form_data),
                    success: function (data) {
                        cb.bind(self)(data);
                    },
                    error: function (xhr, status, error) {
                        var res = JSON.parse(xhr.responseText);
                        console.log("Ajax error:", res.description);
                        self.ajax_message = res.description;
                    }
                });
            },
            submit_: function (route, form_data) {
                var cb = function (data) {
                    this.ajax_message = data.desc;
                    alert('Submitted');
                };
                this.ajax_($API_SCRIPT_ROOT + route, 'post', form_data, cb);
            },
         }
    })
</script>

===== 编辑以发表评论。

模板化

服务器端模板之间存在差异,即:

{% for childform in form.children %}

    {% for field in childform %}
        {{ field.label }} {{ field }}   
    {% endfor %}

{% endfor %}

客户端模板,即:

<template v-for="n in Number(form_data.num_items)">
    <textarea v-model="form_data.list_of_data[n-1]" v-bind:placeholder="'Items ' + n"></textarea><br>
</template>

OP请求:

  

我想使用AJAX请求添加子表单,因此页面不会自动刷新。

在这种情况下,您需要使用Javascript动态更新页面。服务器只能在页面刷新时更新其模板,而客户端Javascript可以使用AJAX查询获取数据,然后动态更新。在这种情况下,form_data.num_items会增加textareas的数量。

在本示例中,我使用了Vue,因为它是一个相当用户友好的JS库。但是您也可以使用React或Angular或使用普通的旧JS,但是您需要选择一个。

数据库

OP引用了数据库:

if form.validate_on_submit():
    # do something with data

我对数据库所做的唯一等价的参考是在这里:

for data_item in parsed_args['list_of_data']:
    # do something with each data item, e.g. create db tuple and add
    db.session.add(new_data_item_tuple)
db.session.commit()

但是,您当然可以随意使用表单数据,而不必向数据库提交任何内容。在我的示例中,根据传递到列表的项目数,创建了项目。

摘要

如果您允许页面刷新,则可以在用户每次单击添加按钮时将项目添加到服务器端的表单中。这更易于编写代码,但效率较低,因为数据在客户端和服务器之间反复传输,并且可能更难以维护。

如果您想要更流畅,更高效的用户体验和更可维护的解决方案,那么我的示例当然不是库的唯一组合,但是它们的运行方式都非常相似:

即:

  • 从基本服务器端代码加载基本页面
  • 使用Javascript基于添加/操作表单输入(可能的AJAX查询以查找数据)来操纵该页面
  • 要么将该表单提交给API以进行JSON响应(以我的情况为例),要么将其作为具有重定向或网页响应的网络路线提交。

答案 2 :(得分:0)

对于那些想以更基本的非RESTful方式使用表单的人来说,仍然存在着如何正确地在服务器端持久保存表单数据的挑战。如果不正确,更新表单中的现有子项将在数据库中附加新的子项,而不是更新现有的子项。下面,我提供了两个烧瓶视图,一个用于注册,一个用于更新注册。我对更新注册的看法有效,但有点笨拙。如果有人知道如何更优雅地编写它,我会很想得到一些反馈:

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = ParentForm()

    if form.add_child.data:
        form.children.append_entry()
        return render_template('register.html', form=form)

    if form.validate_on_submit():
        parentObj = Parent(name=form.name.data)
        for child in form.children.data:
            childObj = Child(name=child['name'],age=child['age'])
            parentObj.children.append(childObj)
        db.session.add(parentObj)
        db.session.commit()
        flash('Parent Added!!')
        return redirect(url_for('home_page'))

    return render_template('register.html', form=form)

@app.route('/update_registration', methods=['GET', 'POST'])
def update_registration():
    parentObj = Parent.query.filter(Parent.id == 1).first()
    form = ParentForm(id=parentObj.id, name=parentObj.name, children=parentObj.children)

    if form.add_child.data:
        form.children.append_entry()
        return render_template('update_registration.html', form=form)
    if form.validate_on_submit():
        parentObj.name=form.name.data
        # There should be a way to update the existing children objects rather than deleting and readding them
        # But in the below we delete and re-add.  Otherwise updated children simply append to existing children list
        for i in range(len(parentObj.children)):
            db.session.delete(parentObj.children[i])
        for child in form.children.data:
            childObj = Child(name=child['name'],age=child['age'])
            parentObj.children.append(childObj)
        db.session.add(parentObj)
        db.session.commit()
        flash('Parent [and children] Updated!!')
        return redirect(url_for('home_page'))
    return render_template('update_registration.html', form=form)