Skip to content

Latest commit

 

History

History
283 lines (195 loc) · 10.6 KB

advanced.rst

File metadata and controls

283 lines (195 loc) · 10.6 KB

Advanced Configuration

Security

To block or set the allowed permissions on a view, just set the base_permissions property with the base permissions

class GroupModelView(ModelView):
    datamodel = SQLAInterface(Group)
    base_permissions = ['can_add','can_delete']

With this initial config, the framework will only create 'can_add' and 'can_delete' permissions on GroupModelView as the only allowed. So users and even the administrator of the application will not have the possibility to add list or show permissions on Group table view. Base available permission are: can_add, can_edit, can_delete, can_list, can_show. More detailed info on security

Custom Fields

Custom Model properties can be used on lists. This is useful for formatting values like currencies, time or dates. or for custom HTML. This is very simple to do, first define your custom property on your Model and then use the @renders decorator to tell the framework to map you class method with a certain Model property:

from flask_appbuilder.models.decorators import renders

class MyModel(Model):
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique = True, nullable=False)
    custom = Column(Integer(20))

    @renders('custom')
    def my_custom(self):
    # will render this columns as bold on ListWidget
        return Markup('<b>' + self.custom + '</b>')

On your view reference your method as a column on list:

class MyModelView(ModelView):
    datamodel = SQLAInterface(MyTable)
    list_columns = ['name', 'my_custom']

Base Filtering

To filter a views data, just set the base_filter property with your base filters. These will allways be applied first on any search.

It's very flexible, you can apply multiple filters with static values, or values based on a function you define. On this next example we are filtering a view by the logged in user and with column name starting with "a"

base_filters is a list of lists with 3 values [['column name',FilterClass,'filter value],...]:

from flask import g
from flask_appbuilder import ModelView
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.models.sqla.filters import FilterEqualFunction, FilterStartsWith
# If you're using Mongo Engine you should import filters like this, everything else is exactly the same
# from flask_appbuilder.models.mongoengine.filters import FilterStartsWith, FilterEqualFunction

from .models import MyTable


def get_user():
    return g.user


class MyView(ModelView):
    datamodel = SQLAInterface(MyTable)
    base_filters = [['created_by', FilterEqualFunction, get_user],
                    ['name', FilterStartsWith, 'a']]

Since version 1.5.0 you can use base_filter with dotted notation, necessary joins will be handled for you on the background. Study the following example to see how:

https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/extendsecurity

Default Order

Use a default order on your lists, this can be overridden by the user on the UI. Data structure ('col_name':'asc|desc'):

class MyView(ModelView):
    datamodel = SQLAInterface(MyTable)
    base_order = ('my_col_to_be_ordered','asc')

Template Extra Arguments

You can pass extra Jinja2 arguments to your custom template, using extra_args property:

class MyView(ModelView):
    datamodel = SQLAInterface(MyTable)
    extra_args = {'my_extra_arg':'SOMEVALUE'}
    show_template = 'my_show_template.html'

Your overriding the 'show' template to handle your extra argument. You can still use F.A.B. show template using Jinja2 blocks, take a look at the templates chapter

Forms - Override automatic form creation

Define your own Add, Edit forms using WTForms to override the automatic form creation:

class MyView(ModelView):
    datamodel = SQLAInterface(MyModel)
    add_form = AddFormWTF

Forms - Add or remove fields

Define what columns will be included on Add or Edit forms, for example if you have automatic fields like user or date, you can remove them from the Add Form:

class MyView(ModelView):
    datamodel = SQLAInterface(MyModel)
    add_columns = ['my_field1', 'my_field2']
    edit_columns = ['my_field1']

To contribute with any additional fields that are not on a table/model, for example a confirmation field:

class ContactModelView(ModelView):
    datamodel = SQLAInterface(Contact)
    add_form_extra_fields = {
        'extra': TextField(gettext('Extra Field'),
        description=gettext('Extra Field description'),
        widget=BS3TextFieldWidget())
    }

Forms - Readonly fields

Define/override readonly fields like this, first define a new Readonly field:

from flask_appbuilder.fieldwidgets import BS3TextFieldWidget


class BS3TextFieldROWidget(BS3TextFieldWidget):
    def __call__(self, field, **kwargs):
        kwargs['readonly'] = 'true'
        return super(BS3TextFieldROWidget, self).__call__(field, **kwargs)

Next override your field using your new widget:

class ExampleView(ModelView):
    datamodel = SQLAInterface(ExampleModel)
    edit_form_extra_fields = {
        'field2': TextField('field2', widget=BS3TextFieldROWidget())
    }

Readonly select fields are a special case, but it's solved in a simpler way:

# Define the field query
def department_query():
    return db.session.query(Department)


class EmployeeView(ModelView):
    datamodel = SQLAInterface(Employee)

    list_columns = ['employee_number', 'full_name', 'department']

    # override the 'department' field, to make it readonly on edit form
    edit_form_extra_fields = {
        'department':  QuerySelectField(
                            'Department',
                            query_factory=department_query,
                            widget=Select2Widget(extra_classes="readonly")
                       )
    }

Forms - Custom validation rules

Contribute with your own additional form validations rules. Remember FAB will automatically validate any field that is defined on the database with Not Null (Required) or Unique constraints:

class MyView(ModelView):
    datamodel = SQLAInterface(MyModel)
    validators_columns = {
        'my_field1':[EqualTo('my_field2', message=gettext('fields must match'))]
    }

You can create a custom query filter for all related columns like this:

from flask_appbuilder.models.sqla.filters import FilterStartsWith


class ContactModelView(ModelView):
    datamodel = SQLAInterface(Contact)
    add_form_query_rel_fields = {'group': [['name', FilterStartsWith, 'W']]}

This will filter list combo on Contact's model related with ContactGroup model. The combo will be filtered with entries that start with W. You can define individual filters for add,edit and search using add_form_quey_rel_fields, edit_form_query_rel_fields, search_form_query_rel_fields respectively. Take a look at the api If you want to filter multiple related fields just add new keys to the dictionary, remember you can add multiple filters for each field also, take a look at the base_filter property:

class ContactModelView(ModelView):
    datamodel = SQLAInterface(Contact)
    add_form_query_rel_fields = {
        'group': [['name', FilterStartsWith, 'W']],
        'gender': [['name', FilterStartsWith, 'M']]
    }

To use AJAX select2 (combo) fields and make use of the REST API, by default all fields are previously populated on the server. Here's a simple example:

class ContactModelView(ModelView):
    datamodel = SQLAInterface(Contact)

    add_form_extra_fields = {
        'contact_group': AJAXSelectField(
                            'contact_group',
                            description='This will be populated with AJAX',
                            datamodel=datamodel,
                            col_name='contact_group',
                            widget=Select2AJAXWidget(endpoint='/contactmodelview/api/column/add/contact_group')
                         ),
    }

Even better you can (since 1.7.0) create related select2 fields, if you have two (or more) relationships that are related them self's, like a group and subgroup on a contact, when the user selects the group the second select2 combo will be populated with the subgroup values that belong to the group. Extending the previous example:

class ContactModelView(ModelView):
    datamodel = SQLAInterface(Contact)

    add_form_extra_fields = {
                    'contact_group': AJAXSelectField('contact_group',
                    description='This will be populated with AJAX',
                    datamodel=datamodel,
                    col_name='contact_group',
                    widget=Select2AJAXWidget(endpoint='/contactmodelview/api/column/add/contact_group')),

                    'contact_sub_group': AJAXSelectField('Extra Field2',
                    description='Extra Field description',
                    datamodel=datamodel,
                    col_name='contact_sub_group',
                    widget=Select2SlaveAJAXWidget(master_id='contact_group',
                    endpoint='/contactmodelview/api/column/add/contact_sub_group?_flt_0_contact_group_id={{ID}}'))
                    }

So as seen before add_form_extra_fields is a dictionary that expects keys as column names and values as WTF Fields.

AJAXSelectField is expecting the following parameters for the constructor: - label: A label for the column. - description: A description to render on the form. - datamodel: SQLAlchemy initialized with the model. - col_name: The column name. - widget: Use Select2AJAXWidget (for the master) and Select2SlaveAJAXWidget for the slave. - endpoint: The REST API that will be used to populate the select2.

You have 3 endpoint's API that will return data ready to use by this fields:

  • /<YOUR MODELVIEW NAME>/api/column/add|edit/<COLUMN NAME> : you can append query string's to filter data. This will return all values of the related column on the model.
  • /<YOUR MODELVIEW NAME>/api/readvalues: This will return all values on the modelview prepared to be used on a select2.