pythonflaskflask-sqlalchemyflask-loginflask-admin

Flask-admin how to add button beside rows


I make a tuition payment app using Flask and using Flask-admin to managing the payment.

Flask-Admin automatically generated rows of tables by the SQLAlchemy that I declared on my models.py

Here is the image of my table: enter image description here Now I want to give a button beside the every total bill number for checkout purpose the bill.

I know how to add columns with column_list method that provide by ModelView, as I try like this code below:

column_list = ('student_id', 'total_bill', 'Pay Now')

and it will give the view display like this: enter image description here Now I want column Pay Now have a button for every that rows like I code manually with HTML like this: enter image description here There is have checkout button for every rows, as I mentioned above this purpose is for the checkout button.

So, how to do that..?, any help will be much appreciated


Solution

  • You can use column_formatters to render the column whichever way you wish.

    Example code on Github, flask-admin-row-form.

    For example, each row "Checkout" button could be rendered as a HTML form with a submit button and the student id rendered as a hidden field within the form.

    Simple example below (Python 2.7), files all reside in the root directory. views.py contains the important code, the rest is straightforward Flask stuff.

    enter image description here

    Class StudentView defines a method _format_pay_now which either renders a string "Paid" if the model's is_paid value is True or otherwise a HTML form.

    Class StudentView also exposes a route '/admin/student/checkout' via the method checkout_view to process the submitted form. In this particular instance the is_paid column is set True and the list view re-rendered.

    views.py

    from flask import redirect, flash, url_for
    from flask_admin import expose
    from flask_admin.contrib import sqla
    from flask_admin.helpers import get_form_data
    from flask_admin.babel import gettext
    from markupsafe import Markup
    
    
    class StudentView(sqla.ModelView):
    
        page_size = 5
    
        column_list = ('id', 'cost', 'Pay Now')
        column_editable_list = ['cost']
    
        # override the column labels
        column_labels = {
            'id': 'Student ID',
            'cost': 'Total Bill',
        }
    
        def _format_pay_now(view, context, model, name):
    
            if model.is_paid:
                return 'Paid'
    
            # render a form with a submit button for student, include a hidden field for the student id
            # note how checkout_view method is exposed as a route below
            checkout_url = url_for('.checkout_view')
    
            _html = '''
                <form action="{checkout_url}" method="POST">
                    <input id="student_id" name="student_id"  type="hidden" value="{student_id}">
                    <button type='submit'>Checkout</button>
                </form
            '''.format(checkout_url=checkout_url, student_id=model.id)
    
            return Markup(_html)
    
        column_formatters = {
            'Pay Now': _format_pay_now
        }
    
        @expose('checkout', methods=['POST'])
        def checkout_view(self):
    
            return_url = self.get_url('.index_view')
    
            form = get_form_data()
    
            if not form:
                flash(gettext('Could not get form from request.'), 'error')
                return redirect(return_url)
    
            # Form is an ImmutableMultiDict
            student_id = form['student_id']
    
            # Get the model from the database
            model = self.get_one(student_id)
    
            if model is None:
                flash(gettext('Student not not found.'), 'error')
                return redirect(return_url)
    
            # process the model
            model.is_paid = True
    
            try:
                self.session.commit()
                flash(gettext('Student, ID: {student_id}, set as paid'.format(student_id=student_id)))
            except Exception as ex:
                if not self.handle_view_exception(ex):
                    raise
    
                flash(gettext('Failed to set student, ID: {student_id}, as paid'.format(student_id=student_id), error=str(ex)), 'error')
    
            return redirect(return_url)
    

    models.py

    from flask_sqlalchemy import SQLAlchemy
    
    
    db = SQLAlchemy()
    
    
    class Student(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        cost = db.Column(db.Integer(), nullable=False)
        is_paid = db.Column(db.Boolean(), nullable=False)
    
        def __str__(self):
            return unicode(self).encode('utf-8')
    
        def __unicode__(self):
            return "ID: {id}; Cost : {cost}".format(id=self.id, cost=self.cost)
    

    commands.py

    Use flask create-database to generate the SQLite database.

    import random
    from flask.cli import click, with_appcontext
    from models import db, Student
    
    
    @click.command('create-database')
    @with_appcontext
    def create_database():
    
        # Create 100 students
    
        db.drop_all()
        db.create_all()
    
        for _ in range(0, 100):
            _project = Student(
                cost=random.randrange(10, 200),
                is_paid=False
            )
            db.session.add(_project)
    
        db.session.commit()
    

    app.py

    from flask import Flask
    from flask_admin import Admin
    from models import db, Student
    from commands import create_database
    
    app = Flask(__name__)
    
    # Create dummy secrey key so we can use sessions
    app.config['SECRET_KEY'] = '123456790'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    
    # Create in-memory database
    app.config['DATABASE_FILE'] = 'sample_db.sqlite'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
    db.init_app(app)
    
    app.cli.add_command(create_database)
    
    
    # Flask views
    @app.route('/')
    def index():
        return '<a href="/admin/">Click me to get to Admin!</a>'
    
    
    from views import StudentView
    
    admin = Admin(app, template_mode="bootstrap3")
    admin.add_view(StudentView(Student, db.session))
    
    
    if __name__ == '__main__':
        app.run()
    

    requirements.txt

    Click==7.0
    enum34==1.1.6
    Flask==1.0.2
    Flask-Admin==1.5.3
    Flask-SQLAlchemy==2.3.2
    itsdangerous==1.1.0
    Jinja2==2.10
    MarkupSafe==1.1.0
    SQLAlchemy==1.2.17
    Werkzeug==0.14.1
    WTForms==2.2.1