pallets-eco / flask-admin

Simple and extensible administrative interface framework for Flask
https://flask-admin.readthedocs.io
BSD 3-Clause "New" or "Revised" License
5.8k stars 1.58k forks source link

Bootstrap 4 X-editable date causes error #2144

Closed mccarthysean closed 3 years ago

mccarthysean commented 3 years ago

I think I've found an error/bug in Flask-Admin's X-editable dates, but only in Bootstrap 4.

Here's a StackOverflow question that's identical to below.

Notice in the below code, template_mode='bootstrap3' works fine, but template_mode='bootstrap4' causes the error.

Code to reproduce the error:

import os
import datetime

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask_admin as admin
from flask_admin.contrib.sqla import ModelView

# Create application
app = Flask(__name__)

# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456789'

# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

# Instantiate the admin class with Bootstrap mode
# admin = admin.Admin(app, 'Example', template_mode='bootstrap3')
admin = admin.Admin(app, 'Example', template_mode='bootstrap4')

# Models
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(64))
    email = db.Column(db.Unicode(64))
    date_added = db.Column(db.Date)

    def __unicode__(self):
        return self.name

class UserAdmin(ModelView):
    column_searchable_list = ('name', 'email')
    column_editable_list = ('name', 'email', 'date_added')
    column_display_pk = True

    can_set_page_size = True
    # the number of entries to display in the list view
    page_size = 5

# Add Flask-Admin views
admin.add_view(UserAdmin(User, db.session))

# Flask normal views
@app.route('/')
def index():
    return '<a href="/admin/">Click me to get to Admin!</a>'

def build_sample_db():
    """Populate a small db with some example entries"""

    db.drop_all()
    db.create_all()

    first_names = [
        'Harry', 'Amelia', 'Oliver', 'Jack', 'Isabella', 'Charlie','Sophie', 'Mia',
    ]
    last_names = [
        'Brown', 'Smith', 'Patel', 'Jones', 'Williams', 'Johnson', 'Taylor', 'Thomas',
    ]

    for i in range(len(first_names)):
        user = User()
        user.name = first_names[i] + " " + last_names[i]
        user.email = first_names[i].lower() + "@example.com"
        user.date_added = datetime.date.today()
        db.session.add(user)

    db.session.commit()
    return

# Build a sample db on the fly, if one does not exist yet.
app_dir = os.path.realpath(os.path.dirname(__file__))
database_path = os.path.join(app_dir, app.config['DATABASE_FILE'])
os.remove(database_path)
if not os.path.exists(database_path):
    build_sample_db()

if __name__ == '__main__':
    # Start app
    app.run(debug=True, host='0.0.0.0')

Here's what the Flask-Admin view looks like with Bootstrap 4, before the error: Flask-Admin X-editable date buggy with Bootstrap 4 2021-09-08 152601

Here's what the error looks like in the JavaScript console, when I click on the "editable" date field: Flask-Admin X-editable date error with Bootstrap 4 2021-09-08 152601

Here's what it looks like using the template_mode='bootstrap3' (i.e. what it's supposed to do): Flask-Admin X-editable no error with Bootstrap 3 2021-09-08 152601

mccarthysean commented 3 years ago

Seems it's related to this issue and the data-template that needs to be removed.

Flask-Admin doesn't remove the data-template for this date field since it's just given a data-role='x-editable' instead lf x-editable-combodate.

There are at least two ways of fixing this (in the JavaScript "forms.js" or in the XEditableWidget class's get_kwargs() method). I chose to fix it in the get_kwargs() method as follows:

class XEditableWidget(object):
    # ...
    def get_kwargs(self, field, kwargs):
        """
            Return extra kwargs based on the field type.
        """
        if field.type == 'StringField':
            kwargs['data-type'] = 'text'
        # ...
        elif field.type == 'DateField':
            kwargs['data-type'] = 'combodate'
            kwargs['data-format'] = 'YYYY-MM-DD'

            # Remove the following so JavaScript doesn't have to do it?
            # kwargs['data-template'] = 'YYYY-MM-DD'

            # Use the following so it adjusts the template
            kwargs['data-role'] = 'x-editable-combodate'

Here's what happens in the "form.js" file on page-load (i.e. where the data-template attribute is removed, in the latest Flask-Admin "master" branch):

            case 'x-editable':
                // No removal of the "data-template" attribute
                $el.editable({
                    params: overrideXeditableParams,
                    combodate: {
                        // prevent minutes from showing in 5 minute increments
                        minuteStep: 1,
                        maxYear: 2030,
                    }                    
                });
                return true;
            case 'x-editable-combodate':
                // Fixes bootstrap4 issue where data-template breaks bs4 popover.
                // https://github.com/flask-admin/flask-admin/issues/2022
                let template  = $el.data('template');
                $el.removeAttr('data-template');
                $el.editable({
                    params: overrideXeditableParams,
                    template: template,
                    combodate: {
                        // prevent minutes from showing in 5 minute increments
                        minuteStep: 1,
                        maxYear: 2030,
                    }
                });
                return true;

The resulting popover is a tiny bit ugly now, for some reason, but functionally it works at least! image

caffeinatedMike commented 3 years ago

@mccarthysean This can be closed as #2146 has been merged into master