Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
73ed6524
Commit
73ed6524
authored
Mar 27, 2012
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Started working on the filters.
parent
0edfb4a9
Changes
13
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
609 additions
and
64 deletions
+609
-64
TODO.txt
TODO.txt
+3
-2
simple.py
examples/sqla/simple.py
+4
-1
__init__.py
flask_adminex/ext/sqlamodel/__init__.py
+1
-0
filters.py
flask_adminex/ext/sqlamodel/filters.py
+102
-0
tools.py
flask_adminex/ext/sqlamodel/tools.py
+9
-0
view.py
flask_adminex/ext/sqlamodel/view.py
+73
-28
__init__.py
flask_adminex/model/__init__.py
+1
-0
base.py
flask_adminex/model/base.py
+150
-30
filters.py
flask_adminex/model/filters.py
+70
-0
admin.css
flask_adminex/static/css/admin.css
+17
-1
filters.js
flask_adminex/static/js/filters.js
+108
-0
form.js
flask_adminex/static/js/form.js
+20
-2
list.html
flask_adminex/templates/admin/model/list.html
+51
-0
No files found.
TODO.txt
View file @
73ed6524
...
...
@@ -3,9 +3,9 @@
- Calendar - add validation for time without seconds (automatically add seconds)
- Model Admin
- Ability to sort by fields that are not visible?
- Exclude for list columns
- Exclude for form fields
- List display callables
- Search
- Rename init_search
- Built-in filtering support
- Configurable operations (=, >, <, etc)
- Callable operations
...
...
@@ -20,5 +20,6 @@
- Header title
- Mass-delete functionality
- File size restriction
- Localization
- Unit tests
- Documentation
examples/sqla/simple.py
View file @
73ed6524
...
...
@@ -3,6 +3,7 @@ from flaskext.sqlalchemy import SQLAlchemy
from
flask.ext
import
adminex
,
wtf
from
flask.ext.adminex.ext
import
sqlamodel
from
flask.ext.adminex.ext.sqlamodel
import
filters
# Create application
app
=
Flask
(
__name__
)
...
...
@@ -61,6 +62,8 @@ class PostAdmin(sqlamodel.ModelView):
searchable_columns
=
(
'title'
,
User
.
username
)
column_filters
=
(
User
.
username
,
'title'
,
'date'
,
filters
.
FilterLike
(
Post
.
title
,
'Fixed Title'
,
options
=
((
'test1'
,
'Test 1'
),
(
'test2'
,
'Test 2'
))))
# Pass arguments to WTForms. In this case, change label for text field to
# be 'Big Text' and add required() validator.
form_args
=
dict
(
...
...
@@ -84,4 +87,4 @@ if __name__ == '__main__':
# Start app
app
.
debug
=
True
app
.
run
()
app
.
run
(
'0.0.0.0'
,
8000
)
flask_adminex/ext/sqlamodel/__init__.py
0 → 100644
View file @
73ed6524
from
.view
import
ModelView
flask_adminex/ext/sqlamodel/filters.py
0 → 100644
View file @
73ed6524
from
flask.ext.adminex.model
import
filters
from
flask.ext.adminex.ext.sqlamodel
import
tools
class
BaseSQLAFilter
(
filters
.
BaseFilter
):
def
__init__
(
self
,
column
,
name
,
options
=
None
,
data_type
=
None
):
super
(
BaseSQLAFilter
,
self
)
.
__init__
(
name
,
options
,
data_type
)
self
.
column
=
column
# Common filters
class
FilterEqual
(
BaseSQLAFilter
):
def
apply
(
self
,
query
,
value
):
return
query
.
filter
(
self
.
column
==
value
)
def
__unicode__
(
self
):
return
'
%
s equals'
%
self
.
name
class
FilterNotEqual
(
BaseSQLAFilter
):
def
apply
(
self
,
query
,
value
):
return
query
.
filter
(
self
.
column
!=
value
)
def
__unicode__
(
self
):
return
'
%
s not equal'
%
self
.
name
class
FilterLike
(
BaseSQLAFilter
):
def
apply
(
self
,
query
,
value
):
stmt
=
tools
.
parse_like_term
(
value
)
return
query
.
filter
(
self
.
column
.
ilike
(
stmt
))
def
__unicode__
(
self
):
return
'
%
s like'
%
self
.
name
class
FilterNotLike
(
BaseSQLAFilter
):
def
apply
(
self
,
query
,
value
):
stmt
=
tools
.
parse_like_term
(
value
)
return
query
.
filter
(
~
self
.
column
.
ilike
(
stmt
))
def
__unicode__
(
self
):
return
'
%
s not like'
%
self
.
name
class
FilterGreater
(
BaseSQLAFilter
):
def
apply
(
self
,
query
,
value
):
return
query
.
filter
(
self
.
column
>
value
)
def
__unicode__
(
self
):
return
'
%
s greater than'
%
self
.
name
class
FilterSmaller
(
BaseSQLAFilter
):
def
apply
(
self
,
query
,
value
):
return
query
.
filter
(
self
.
column
<
value
)
def
__unicode__
(
self
):
return
'
%
s smaller than'
%
self
.
name
# Customized type filters
class
BooleanEqualFilter
(
FilterEqual
,
filters
.
BaseBooleanFilter
):
pass
class
BooleanNotEqualFilter
(
FilterNotEqual
,
filters
.
BaseBooleanFilter
):
pass
# Base SQLA filter field converter
class
FilterConverter
(
filters
.
BaseFilterConverter
):
strings
=
(
FilterEqual
,
FilterNotEqual
,
FilterLike
,
FilterNotLike
)
numeric
=
(
FilterEqual
,
FilterNotEqual
,
FilterGreater
,
FilterSmaller
)
def
convert
(
self
,
type_name
,
column
,
name
):
if
type_name
in
self
.
converters
:
return
self
.
converters
[
type_name
](
column
,
name
)
return
None
@
filters
.
convert
(
'String'
,
'Unicode'
,
'Text'
,
'UnicodeText'
)
def
conv_string
(
self
,
column
,
name
):
return
[
f
(
column
,
name
)
for
f
in
self
.
strings
]
@
filters
.
convert
(
'Boolean'
)
def
conv_bool
(
self
,
column
,
name
):
return
[
BooleanEqualFilter
(
column
,
name
),
BooleanNotEqualFilter
(
column
,
name
)]
@
filters
.
convert
(
'Integer'
,
'SmallInteger'
,
'Numeric'
,
'Float'
)
def
conv_int
(
self
,
column
,
name
):
return
[
f
(
column
,
name
)
for
f
in
self
.
numeric
]
@
filters
.
convert
(
'Date'
)
def
conv_date
(
self
,
column
,
name
):
return
[
f
(
column
,
name
,
data_type
=
'datepicker'
)
for
f
in
self
.
numeric
]
@
filters
.
convert
(
'DateTime'
)
def
conv_datetime
(
self
,
column
,
name
):
return
[
f
(
column
,
name
,
data_type
=
'datetimepicker'
)
for
f
in
self
.
numeric
]
flask_adminex/ext/sqlamodel/tools.py
0 → 100644
View file @
73ed6524
def
parse_like_term
(
term
):
if
term
.
startswith
(
'^'
):
stmt
=
'
%
s
%%
'
%
term
[
1
:]
elif
term
.
startswith
(
'='
):
stmt
=
term
[
1
:]
else
:
stmt
=
'
%%%
s
%%
'
%
term
return
stmt
flask_adminex/ext/sqlamodel.py
→
flask_adminex/ext/sqlamodel
/view
.py
View file @
73ed6524
...
...
@@ -12,6 +12,7 @@ from flask import flash
from
flask.ext.adminex
import
form
from
flask.ext.adminex.model
import
BaseModelView
from
flask.ext.adminex.ext.sqlamodel
import
filters
,
tools
class
Unique
(
object
):
...
...
@@ -220,6 +221,11 @@ class ModelView(BaseModelView):
For example, if you entered *=ZZZ*, *ILIKE 'ZZZ'* statement will be used.
"""
filter_converter
=
filters
.
FilterConverter
()
"""
TBD:
"""
def
__init__
(
self
,
model
,
session
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
"""
...
...
@@ -241,8 +247,9 @@ class ModelView(BaseModelView):
self
.
session
=
session
self
.
_search_fields
=
None
self
.
_search_joins
=
None
self
.
_search_joins_names
=
None
self
.
_search_joins_names
=
set
()
self
.
_filter_joins_names
=
set
()
super
(
ModelView
,
self
)
.
__init__
(
model
,
name
,
category
,
endpoint
,
url
)
...
...
@@ -312,6 +319,23 @@ class ModelView(BaseModelView):
return
columns
def
_get_columns_for_field
(
self
,
field
):
if
isinstance
(
field
,
basestring
):
attr
=
getattr
(
self
.
model
,
field
,
None
)
if
field
is
None
:
raise
Exception
(
'Field
%
s was not found.'
%
field
)
else
:
attr
=
field
if
(
not
attr
or
not
hasattr
(
attr
,
'property'
)
or
not
hasattr
(
attr
.
property
,
'columns'
)
or
not
attr
.
property
.
columns
):
raise
Exception
(
'Invalid field
%
s: does not contains any columns.'
%
field
)
return
attr
.
property
.
columns
def
init_search
(
self
):
"""
Initialize search. Returns `True` if search is supported for this
...
...
@@ -322,23 +346,10 @@ class ModelView(BaseModelView):
"""
if
self
.
searchable_columns
:
self
.
_search_fields
=
[]
self
.
_search_joins
=
[]
self
.
_search_joins_names
=
set
()
for
p
in
self
.
searchable_columns
:
# If item is a stirng, resolve it as an attribute
if
isinstance
(
p
,
basestring
):
attr
=
getattr
(
self
.
model
,
p
,
None
)
else
:
attr
=
p
# Only column searches are supported
if
(
not
attr
or
not
hasattr
(
attr
,
'property'
)
or
not
hasattr
(
attr
.
property
,
'columns'
)):
raise
Exception
(
'Invalid searchable column "
%
s"'
%
p
)
for
column
in
attr
.
property
.
columns
:
for
column
in
self
.
_get_columns_for_field
(
p
):
column_type
=
type
(
column
.
type
)
.
__name__
if
not
self
.
is_text_column_type
(
column_type
):
...
...
@@ -349,7 +360,6 @@ class ModelView(BaseModelView):
# If it belongs to different table - add a join
if
column
.
table
!=
self
.
model
.
__table__
:
self
.
_search_joins
.
append
(
column
.
table
)
self
.
_search_joins_names
.
add
(
column
.
table
.
name
)
return
bool
(
self
.
searchable_columns
)
...
...
@@ -363,6 +373,31 @@ class ModelView(BaseModelView):
return
(
name
==
'String'
or
name
==
'Unicode'
or
name
==
'Text'
or
name
==
'UnicodeText'
)
def
scaffold_filters
(
self
,
name
):
columns
=
self
.
_get_columns_for_field
(
name
)
if
len
(
columns
)
>
1
:
raise
Exception
(
'Can not filter more than on one column for
%
s'
%
name
)
column
=
columns
[
0
]
if
not
isinstance
(
name
,
basestring
):
visible_name
=
self
.
get_column_name
(
name
.
property
.
key
)
else
:
visible_name
=
self
.
get_column_name
(
name
)
type_name
=
type
(
column
.
type
)
.
__name__
flt
=
self
.
filter_converter
.
convert
(
type_name
,
column
,
visible_name
)
if
flt
:
# If there's relation to other table, do it
if
column
.
table
!=
self
.
model
.
__table__
:
self
.
_filter_joins_names
.
add
(
column
.
table
.
name
)
return
flt
def
scaffold_form
(
self
):
"""
Create form from the model.
...
...
@@ -395,7 +430,7 @@ class ModelView(BaseModelView):
return
joined
# Database-related API
def
get_list
(
self
,
page
,
sort_column
,
sort_desc
,
search
,
execute
=
True
):
def
get_list
(
self
,
page
,
sort_column
,
sort_desc
,
search
,
filters
,
execute
=
True
):
"""
Return models from the database.
...
...
@@ -409,6 +444,8 @@ class ModelView(BaseModelView):
Search query
`execute`
Execute query immediately? Default is `True`
`filters`
List of filter tuples
"""
# Will contain names of joined tables to avoid duplicate joins
...
...
@@ -416,11 +453,11 @@ class ModelView(BaseModelView):
query
=
self
.
session
.
query
(
self
.
model
)
# Apply search
before counting results
# Apply search
criteria
if
self
.
_search_supported
and
search
:
# Apply search-related joins
if
self
.
_search_joins
:
query
=
query
.
join
(
*
self
.
_search_joins
)
if
self
.
_search_joins
_names
:
query
=
query
.
join
(
*
self
.
_search_joins
_names
)
joins
|=
self
.
_search_joins_names
# Apply terms
...
...
@@ -430,16 +467,24 @@ class ModelView(BaseModelView):
if
not
term
:
continue
if
term
.
startswith
(
'^'
):
stmt
=
'
%
s
%%
'
%
term
[
1
:]
elif
term
.
startswith
(
'='
):
stmt
=
term
[
1
:]
else
:
stmt
=
'
%%%
s
%%
'
%
term
stmt
=
tools
.
parse_like_term
(
term
)
filter_stmt
=
[
c
.
ilike
(
stmt
)
for
c
in
self
.
_search_fields
]
query
=
query
.
filter
(
or_
(
*
filter_stmt
))
# Apply filters
if
self
.
_filters
:
# Apply search-related joins
if
self
.
_filter_joins_names
:
new_joins
=
self
.
_filter_joins_names
-
joins
if
new_joins
:
query
=
query
.
join
(
*
new_joins
)
joins
|=
self
.
_search_joins_names
# Apply filters
for
flt
,
value
in
filters
:
query
=
self
.
_filters
[
flt
]
.
apply
(
query
,
value
)
# Calculate number of rows
count
=
query
.
count
()
...
...
flask_adminex/model/__init__.py
0 → 100644
View file @
73ed6524
from
.base
import
BaseModelView
flask_adminex/model.py
→
flask_adminex/model
/base
.py
View file @
73ed6524
This diff is collapsed.
Click to expand it.
flask_adminex/model/filters.py
0 → 100644
View file @
73ed6524
class
BaseFilter
(
object
):
def
__init__
(
self
,
name
,
options
=
None
,
data_type
=
None
):
self
.
name
=
name
self
.
options
=
options
self
.
data_type
=
data_type
def
get_options
(
self
,
view
):
return
self
.
options
def
validate
(
self
,
value
):
return
True
def
apply
(
self
,
query
):
raise
NotImplemented
()
def
__unicode__
(
self
):
return
self
.
name
# Customized filters
class
BaseBooleanFilter
(
BaseFilter
):
def
__init__
(
self
,
name
,
data_type
=
None
):
super
(
BaseBooleanFilter
,
self
)
.
__init__
(
name
,
((
'1'
,
'Yes'
),
(
'0'
,
'No'
)),
data_type
)
def
validate
(
self
,
value
):
return
value
==
'0'
or
value
==
'1'
class
BaseDateFilter
(
BaseFilter
):
def
__init__
(
self
,
name
,
options
=
None
):
super
(
BaseDateFilter
,
self
)
.
__init__
(
name
,
options
,
data_type
=
'datepicker'
)
def
validate
(
self
,
value
):
# TODO: Validation
return
True
class
BaseDateTimeFilter
(
BaseFilter
):
def
__init__
(
self
,
name
,
options
=
None
):
super
(
BaseDateTimeFilter
,
self
)
.
__init__
(
name
,
options
,
data_type
=
'datetimepicker'
)
def
validate
(
self
,
value
):
# TODO: Validation
return
True
def
convert
(
*
args
):
def
_inner
(
func
):
print
args
func
.
_converter_for
=
args
return
func
return
_inner
class
BaseFilterConverter
(
object
):
def
__init__
(
self
):
self
.
converters
=
dict
()
for
p
in
dir
(
self
):
attr
=
getattr
(
self
,
p
)
if
hasattr
(
attr
,
'_converter_for'
):
for
p
in
attr
.
_converter_for
:
self
.
converters
[
p
]
=
attr
flask_adminex/static/css/admin.css
View file @
73ed6524
/*
Body
*/
/*
Global styles
*/
body
{
padding-top
:
50px
;
}
/* Form customizations */
form
.icon
{
display
:
inline
;
}
...
...
@@ -19,3 +20,18 @@ form.icon button {
a
.icon
{
text-decoration
:
none
;
}
/* Filters */
.filter-row
{
margin
:
4px
;
}
.filter-row
a
,
.filter-row
select
{
margin-right
:
4px
;
}
.filter-row
input
{
margin-bottom
:
0px
;
width
:
208px
;
}
\ No newline at end of file
flask_adminex/static/js/filters.js
0 → 100644
View file @
73ed6524
var
Filters
=
function
(
element
,
operations
,
options
,
types
)
{
var
$root
=
$
(
element
)
var
$container
=
$
(
'#filters'
);
var
count
=
$
(
'#filters>div'
,
$root
).
length
;
function
appendValueControl
(
element
,
id
,
optionId
)
{
var
field
;
// Conditionally generate select or textbox
if
(
optionId
in
options
)
{
field
=
$
(
'<select class="filter-val" />'
).
attr
(
'name'
,
'flt'
+
id
+
'v'
);
$
(
options
[
optionId
]).
each
(
function
()
{
field
.
append
(
$
(
'<option/>'
).
val
(
this
[
0
]).
text
(
this
[
1
]));
});
}
else
{
field
=
$
(
'<input type="text" class="filter-val" />'
).
attr
(
'name'
,
'flt'
+
id
+
'v'
);
}
$
(
element
).
append
(
field
);
if
(
optionId
in
options
)
field
.
chosen
();
if
(
optionId
in
types
)
{
field
.
attr
(
'data-role'
,
types
[
optionId
]);
adminForm
.
applyStyle
(
field
,
types
[
optionId
]);
}
}
function
addFilter
()
{
var
node
=
$
(
'<div class="filter-row" />'
).
attr
(
'id'
,
'fltdiv'
+
count
).
appendTo
(
$container
);
$
(
'<a href="#" class="remove-filter" />'
)
.
append
(
'<i class="icon-remove"/>'
)
.
click
(
removeFilter
)
.
appendTo
(
node
);
var
operation
=
$
(
'<select class="filter-op" />'
)
.
attr
(
'name'
,
'flt'
+
count
)
.
change
(
changeOperation
)
.
appendTo
(
node
);
var
index
=
0
;
$
(
operations
).
each
(
function
()
{
operation
.
append
(
$
(
'<option/>'
).
val
(
index
).
text
(
this
.
toString
()));
index
++
;
});
operation
.
chosen
();
appendValueControl
(
node
,
count
,
0
);
count
+=
1
;
$
(
'button'
,
$root
).
show
();
return
false
;
}
function
removeFilter
()
{
var
row
=
$
(
this
).
parent
();
var
idx
=
parseInt
(
row
.
attr
(
'id'
).
substr
(
6
));
// Remove row
row
.
remove
();
// Renumber any rows that are after
for
(
var
i
=
idx
+
1
;
i
<
count
;
++
i
)
{
row
=
$
(
'#fltdiv'
+
i
);
row
.
attr
(
'id'
,
'fltdiv'
+
(
i
-
1
));
$
(
'.filter-op'
,
row
).
attr
(
'name'
,
'flt'
+
(
i
-
1
));
$
(
'.filter-val'
,
row
).
attr
(
'name'
,
'flt'
+
(
i
-
1
)
+
'v'
);
}
count
-=
1
;
$
(
'button'
,
$root
).
show
();
return
false
;
}
function
changeOperation
()
{
var
row
=
$
(
this
).
parent
();
var
rowIdx
=
parseInt
(
row
.
attr
(
'id'
).
substr
(
6
));
// Get old value field
var
oldValue
=
$
(
'.filter-val'
,
row
);
var
oldValueId
=
oldValue
.
attr
(
'id'
);
// Delete old value
oldValue
.
remove
();
if
(
oldValueId
!=
null
)
$
(
'div#'
+
oldValueId
+
'_chzn'
,
row
).
remove
();
var
optId
=
$
(
this
).
val
();
appendValueControl
(
row
,
rowIdx
,
optId
);
$
(
'button'
,
$root
).
show
();
};
$
(
'#add_filter'
,
$root
).
click
(
addFilter
);
$
(
'.remove-filter'
,
$root
).
click
(
removeFilter
);
$
(
'.filter-op'
).
change
(
changeOperation
);
$
(
'.filter-val'
).
change
(
function
()
{
$
(
'button'
,
$root
).
show
();
});
};
flask_adminex/static/js/form.js
View file @
73ed6524
$
(
function
()
{
var
adminForm
=
new
function
()
{
this
.
applyStyle
=
function
(
el
,
name
)
{
switch
(
name
)
{
case
'chosen'
:
$
(
el
).
chosen
();
break
;
case
'chosenblank'
:
$
(
el
).
chosen
({
allow_single_deselect
:
true
});
break
;
case
'datepicker'
:
$
(
el
).
datepicker
();
break
;
case
'datetimepicker'
:
$
(
el
).
datepicker
({
displayTime
:
true
});
break
;
};
}
// Apply automatic styles
$
(
'[data-role=chosen]'
).
chosen
();
$
(
'[data-role=chosenblank]'
).
chosen
({
allow_single_deselect
:
true
});
$
(
'[data-role=datepicker]'
).
datepicker
();
$
(
'[data-role=datetimepicker]'
).
datepicker
({
displayTime
:
true
});
}
);
}
flask_adminex/templates/admin/model/list.html
View file @
73ed6524
{% extends 'admin/master.html' %}
{% import 'admin/lib.html' as lib %}
{% block head %}
<link
href=
"{{ url_for('admin.static', filename='chosen/chosen.css') }}"
rel=
"stylesheet"
>
<link
href=
"{{ url_for('admin.static', filename='css/datepicker.css') }}"
rel=
"stylesheet"
>
{% endblock %}
{% block body %}
{% if search_supported %}
<form
method=
"GET"
action=
"{{ return_url }}"
class=
"well form-search"
>
...
...
@@ -20,6 +25,38 @@
</form>
{% endif %}
{% if filter_names %}
<form
id=
"filter_form"
method=
"GET"
action=
"{{ return_url }}"
class=
"well"
>
<div
id=
"filters"
>
{%- for idx, flt in enumerate(active_filters) -%}
<div
id=
"fltdiv{{ idx }}"
class=
"filter-row"
>
<a
href=
"#"
class=
"remove-filter"
><i
class=
"icon-remove"
></i></a><select
name=
"flt{{ idx }}"
class=
"filter-op"
data-role=
"chosen"
>
{% for optidx, opt in enumerate(filter_names) -%}
<option
value=
"{{ optidx }}"
{%
if
flt
[
0
]
==
optidx
%}
selected=
"selected"
{%
endif
%}
>
{{ opt }}
</option>
{%- endfor %}
</select>
{%- set data = filter_data.get(flt[0]) -%}
{%- if data -%}
<select
name=
"flt{{ idx }}v"
class=
"filter-val"
data-role=
"chosen"
>
{%- for opt in data %}
<option
value=
"{{ opt[0] }}"
{%
if
flt
[
1
]
==
opt
[
0
]
%}
selected
{%
endif
%}
>
{{ opt[1] }}
</option>
{%- endfor %}
</select>
{%- else -%}
<input
name=
"flt{{ idx }}v"
type=
"text"
value=
"{{ flt[1] or '' }}"
class=
"filter-val"
{%
if
flt
[
0
]
in
filter_types
%}
data-role=
"{{ filter_types[flt[0]] }}"
{%
endif
%}
></input>
{%- endif -%}
</div>
{%- endfor %}
</div>
{% if active_filters %}
<a
href=
"{{ clear_search_url }}"
class=
"btn"
>
Reset Filters
</a>
{% endif %}
<a
id=
"add_filter"
href=
"#"
class=
"btn"
>
Add Filter
</a>
<button
type=
"submit"
class=
"btn"
style=
"display: none"
>
Apply
</button>
</form>
{% endif %}
<table
class=
"table table-striped table-bordered model-list"
>
<thead>
<tr>
...
...
@@ -75,3 +112,17 @@
<a
class=
"btn btn-primary btn-large"
href=
"{{ url_for('.create_view', url=return_url) }}"
>
Create New
</a>
{% endif %}
{% endblock %}
{% block tail %}
<script
src=
"{{ url_for('admin.static', filename='js/bootstrap-datepicker.js') }}"
></script>
<script
src=
"{{ url_for('admin.static', filename='js/form.js') }}"
></script>
<script
src=
"{{ url_for('admin.static', filename='js/filters.js') }}"
></script>
{% if filter_names is not none and filter_data is not none %}
<script
language=
"javascript"
>
var
filter
=
new
Filters
(
'#filter_form'
,
{{
filter_names
|
tojson
|
safe
}},
{{
filter_data
|
tojson
|
safe
}},
{{
filter_types
|
tojson
|
safe
}});
</script>
{% endif %}
{% endblock %}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment