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
424108be
Commit
424108be
authored
Oct 28, 2018
by
PJ Janse van Rensburg
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add sqla_utils types to 'sqla' example app.
parent
d36672b2
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
164 additions
and
104 deletions
+164
-104
app.py
examples/sqla/app.py
+161
-104
requirements.txt
examples/sqla/requirements.txt
+3
-0
No files found.
examples/sqla/app.py
View file @
424108be
import
os
import
os.path
as
op
from
flask
import
Flask
from
flask
import
Flask
,
Markup
from
flask_sqlalchemy
import
SQLAlchemy
from
sqlalchemy.ext.hybrid
import
hybrid_property
from
sqlalchemy.orm
import
composite
import
uuid
import
random
import
string
from
wtforms
import
validators
...
...
@@ -13,16 +17,15 @@ from flask_admin.contrib.sqla import filters
from
flask_admin.contrib.sqla.form
import
InlineModelConverter
from
flask_admin.contrib.sqla.fields
import
InlineModelFormList
from
flask_admin.contrib.sqla.filters
import
BaseSQLAFilter
,
FilterEqual
from
flask_admin.babel
import
gettext
from
sqlalchemy_utils.types
import
ChoiceType
,
EmailType
from
sqlalchemy_utils
import
ChoiceType
,
EmailType
,
UUIDType
,
URLType
,
CurrencyType
,
Currency
from
colour
import
Color
from
sqlalchemy_utils
import
ColorType
,
ArrowType
,
IPAddressType
,
TimezoneType
import
arrow
import
enum
class
EnumChoices
(
enum
.
Enum
):
first
=
1
second
=
2
# Create application
app
=
Flask
(
__name__
)
...
...
@@ -39,20 +42,51 @@ app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE
app
.
config
[
'SQLALCHEMY_ECHO'
]
=
True
db
=
SQLAlchemy
(
app
)
class
EnumChoices
(
enum
.
Enum
):
first
=
1
second
=
2
AVAILABLE_USER_TYPES
=
[
(
u'admin'
,
u'Admin'
),
(
u'content-writer'
,
u'Content writer'
),
(
u'editor'
,
u'Editor'
),
(
u'regular-user'
,
u'Regular user'
),
]
# Create models
class
User
(
db
.
Model
):
AVAILABLE_TYPES
=
[
(
u'admin'
,
u'Admin'
),
(
u'regular-user'
,
u'Regular user'
)
]
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
id
=
db
.
Column
(
UUIDType
(
binary
=
False
),
default
=
uuid
.
uuid4
,
primary_key
=
True
)
# use a regular string field, for which we can specify a list of available choices later on
type
=
db
.
Column
(
db
.
String
(
100
))
# fixed choices can be handled in a number of different ways:
enum_choice_field
=
db
.
Column
(
db
.
Enum
(
EnumChoices
),
nullable
=
True
)
sqla_utils_choice_field
=
db
.
Column
(
ChoiceType
(
AVAILABLE_USER_TYPES
),
nullable
=
True
)
sqla_utils_enum_choice_field
=
db
.
Column
(
ChoiceType
(
EnumChoices
,
impl
=
db
.
Integer
()),
nullable
=
True
)
first_name
=
db
.
Column
(
db
.
String
(
100
))
last_name
=
db
.
Column
(
db
.
String
(
100
))
type
=
db
.
Column
(
ChoiceType
(
AVAILABLE_TYPES
),
nullable
=
True
)
# some sqlalchemy_utils data types (see https://sqlalchemy-utils.readthedocs.io/)
email
=
db
.
Column
(
EmailType
,
unique
=
True
,
nullable
=
False
)
pets
=
db
.
relationship
(
'Pet'
,
backref
=
'owner'
)
enum_choice_field
=
db
.
Column
(
ChoiceType
(
EnumChoices
,
impl
=
db
.
Integer
()),
nullable
=
True
)
website
=
db
.
Column
(
URLType
)
ip_address
=
db
.
Column
(
IPAddressType
)
currency
=
db
.
Column
(
CurrencyType
,
nullable
=
True
,
default
=
None
)
timezone
=
db
.
Column
(
TimezoneType
(
backend
=
'pytz'
))
dialling_code
=
db
.
Column
(
db
.
Integer
())
local_phone_number
=
db
.
Column
(
db
.
String
(
10
))
featured_post_id
=
db
.
Column
(
db
.
Integer
,
db
.
ForeignKey
(
'post.id'
))
featured_post
=
db
.
relationship
(
'Post'
,
foreign_keys
=
[
featured_post_id
])
@
hybrid_property
def
phone_number
(
self
):
if
self
.
dialling_code
and
self
.
local_phone_number
:
number
=
str
(
self
.
local_phone_number
)
return
"+{} ({}){} {} {}"
.
format
(
self
.
dialling_code
,
number
[
0
],
number
[
1
:
3
],
number
[
3
:
6
],
number
[
6
::])
return
def
__str__
(
self
):
return
"{}, {}"
.
format
(
self
.
last_name
,
self
.
first_name
)
...
...
@@ -61,16 +95,6 @@ class User(db.Model):
return
"{}: {}"
.
format
(
self
.
id
,
self
.
__str__
())
class
Pet
(
db
.
Model
):
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
name
=
db
.
Column
(
db
.
String
(
50
),
nullable
=
False
)
person_id
=
db
.
Column
(
db
.
Integer
,
db
.
ForeignKey
(
'user.id'
))
available
=
db
.
Column
(
db
.
Boolean
)
def
__str__
(
self
):
return
self
.
name
# Create M2M table
post_tags_table
=
db
.
Table
(
'post_tags'
,
db
.
Model
.
metadata
,
db
.
Column
(
'post_id'
,
db
.
Integer
,
db
.
ForeignKey
(
'post.id'
)),
...
...
@@ -84,9 +108,12 @@ class Post(db.Model):
text
=
db
.
Column
(
db
.
Text
,
nullable
=
False
)
date
=
db
.
Column
(
db
.
Date
)
user_id
=
db
.
Column
(
db
.
Integer
(),
db
.
ForeignKey
(
User
.
id
))
user
=
db
.
relationship
(
User
,
backref
=
'posts'
)
# some sqlalchemy_utils data types (see https://sqlalchemy-utils.readthedocs.io/)
background_color
=
db
.
Column
(
ColorType
)
created_at
=
db
.
Column
(
ArrowType
,
default
=
arrow
.
utcnow
())
user_id
=
db
.
Column
(
UUIDType
(
binary
=
False
),
db
.
ForeignKey
(
User
.
id
))
user
=
db
.
relationship
(
User
,
foreign_keys
=
[
user_id
],
backref
=
'posts'
)
tags
=
db
.
relationship
(
'Tag'
,
secondary
=
post_tags_table
)
def
__str__
(
self
):
...
...
@@ -95,25 +122,12 @@ class Post(db.Model):
class
Tag
(
db
.
Model
):
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
name
=
db
.
Column
(
db
.
Unicode
(
64
))
name
=
db
.
Column
(
db
.
Unicode
(
64
)
,
unique
=
True
)
def
__str__
(
self
):
return
"{}"
.
format
(
self
.
name
)
class
UserInfo
(
db
.
Model
):
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
key
=
db
.
Column
(
db
.
String
(
64
),
nullable
=
False
)
value
=
db
.
Column
(
db
.
String
(
64
))
user_id
=
db
.
Column
(
db
.
Integer
(),
db
.
ForeignKey
(
User
.
id
))
user
=
db
.
relationship
(
User
,
backref
=
'info'
)
def
__str__
(
self
):
return
"{} - {}"
.
format
(
self
.
key
,
self
.
value
)
class
Tree
(
db
.
Model
):
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
name
=
db
.
Column
(
db
.
String
(
64
))
...
...
@@ -124,17 +138,6 @@ class Tree(db.Model):
return
"{}"
.
format
(
self
.
name
)
class
Screen
(
db
.
Model
):
__tablename__
=
'screen'
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
width
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
)
height
=
db
.
Column
(
db
.
Integer
,
nullable
=
False
)
@
hybrid_property
def
number_of_pixels
(
self
):
return
self
.
width
*
self
.
height
# Flask views
@
app
.
route
(
'/'
)
def
index
():
...
...
@@ -154,59 +157,109 @@ class FilterLastNameBrown(BaseSQLAFilter):
# Customized User model admin
inline_form_options
=
{
'form_label'
:
"Info item"
,
'form_columns'
:
[
'id'
,
'key'
,
'value'
],
'form_args'
:
None
,
'form_extra_fields'
:
None
,
}
def
phone_number_formatter
(
view
,
context
,
model
,
name
):
return
Markup
(
"<nobr>{}</nobr>"
.
format
(
model
.
phone_number
))
if
model
.
phone_number
else
None
def
is_numberic_validator
(
form
,
field
):
if
field
.
data
and
not
field
.
data
.
isdigit
():
raise
validators
.
ValidationError
(
gettext
(
'Only numbers are allowed.'
))
class
UserAdmin
(
sqla
.
ModelView
):
can_view_details
=
True
# show a modal dialog with records details
action_disallowed_list
=
[
'delete'
,
]
column_display_pk
=
True
form_choices
=
{
'type'
:
AVAILABLE_USER_TYPES
,
}
form_args
=
{
'dialling_code'
:
{
'label'
:
'Dialling code'
},
'local_phone_number'
:
{
'label'
:
'Phone number'
,
'validators'
:
[
is_numberic_validator
]
},
}
form_widget_args
=
{
'id'
:{
'readonly'
:
True
}
}
column_list
=
[
'
id
'
,
'
type
'
,
'last_name'
,
'first_name'
,
'email'
,
'ip_address'
,
'currency'
,
'timezone'
,
'phone_number'
,
]
column_searchable_list
=
[
'first_name'
,
'last_name'
,
'email'
,
]
column_editable_list
=
[
'type'
,
'currency'
,
'timezone'
]
column_details_list
=
[
'id'
,
'featured_post'
,
'website'
,
'enum_choice_field'
,
'sqla_utils_choice_field'
,
'sqla_utils_enum_choice_field'
,
]
+
column_list
form_columns
=
[
'id'
,
'type'
,
'pets'
,
'featured_post'
,
'enum_choice_field'
,
'sqla_utils_choice_field'
,
'sqla_utils_enum_choice_field'
,
'last_name'
,
'first_name'
,
'email'
,
'website'
,
'dialling_code'
,
'local_phone_number'
,
]
column_auto_select_related
=
True
column_default_sort
=
[(
'last_name'
,
False
),
(
'first_name'
,
False
)]
# sort on multiple columns
# custom filter: each filter in the list is a filter operation (equals, not equals, etc)
# filters with the same name will appear as operations under the same filter
column_filters
=
[
'first_name'
,
FilterEqual
(
column
=
User
.
last_name
,
name
=
'Last Name'
),
FilterLastNameBrown
(
column
=
User
.
last_name
,
name
=
'Last Name'
,
options
=
((
'1'
,
'Yes'
),
(
'0'
,
'No'
)))
options
=
((
'1'
,
'Yes'
),
(
'0'
,
'No'
))),
'email'
,
'ip_address'
,
'currency'
,
'timezone'
,
]
inline_models
=
[(
UserInfo
,
inline_form_options
),
]
column_formatters
=
{
'phone_number'
:
phone_number_formatter
}
# setup create & edit forms so that only
'available' pets can be selected
# setup create & edit forms so that only
posts created by this user can be selected as 'featured'
def
create_form
(
self
):
return
self
.
_
use_filtered_parent
(
return
self
.
_
filtered_posts
(
super
(
UserAdmin
,
self
)
.
create_form
()
)
def
edit_form
(
self
,
obj
):
return
self
.
_
use_filtered_parent
(
return
self
.
_
filtered_posts
(
super
(
UserAdmin
,
self
)
.
edit_form
(
obj
)
)
def
_
use_filtered_parent
(
self
,
form
):
form
.
pets
.
query_factory
=
self
.
_get_parent_list
def
_
filtered_posts
(
self
,
form
):
form
.
featured_post
.
query_factory
=
lambda
:
Post
.
query
.
filter
(
Post
.
user_id
==
form
.
_obj
.
id
)
.
all
()
return
form
def
_get_parent_list
(
self
):
# only show available pets in the form
return
Pet
.
query
.
filter_by
(
available
=
True
)
.
all
()
# Customized Post model admin
class
PostAdmin
(
sqla
.
ModelView
):
column_list
=
[
'id'
,
'user'
,
'title'
,
'date'
,
'tags'
]
column_display_pk
=
True
column_list
=
[
'id'
,
'user'
,
'title'
,
'date'
,
'tags'
,
'background_color'
,
'created_at'
,]
column_editable_list
=
[
'background_color'
,
]
column_default_sort
=
(
'date'
,
True
)
column_sortable_list
=
[
'id'
,
...
...
@@ -217,11 +270,13 @@ class PostAdmin(sqla.ModelView):
column_labels
=
dict
(
title
=
'Post Title'
)
# Rename 'title' column in list view
column_searchable_list
=
[
'title'
,
User
.
first_name
,
User
.
last_name
,
'user.first_name'
,
'user.last_name'
,
'tags.name'
,
]
column_filters
=
[
'background_color'
,
'created_at'
,
'user'
,
'title'
,
'date'
,
...
...
@@ -233,10 +288,10 @@ class PostAdmin(sqla.ModelView):
export_types
=
[
'csv'
,
'xls'
]
# Pass arguments to WTForms. In this case, change label for text field to
# be 'Big Text' and add
r
equired() validator.
form_args
=
dict
(
text
=
dict
(
label
=
'Big Text'
,
validators
=
[
validators
.
r
equired
()])
)
# be 'Big Text' and add
DataR
equired() validator.
form_args
=
{
'text'
:
dict
(
label
=
'Big Text'
,
validators
=
[
validators
.
DataR
equired
()])
}
form_ajax_refs
=
{
'user'
:
{
...
...
@@ -259,14 +314,6 @@ class TreeView(sqla.ModelView):
form_excluded_columns
=
[
'children'
,
]
class
ScreenView
(
sqla
.
ModelView
):
column_list
=
[
'id'
,
'width'
,
'height'
,
'number_of_pixels'
]
# not that 'number_of_pixels' is a hybrid property, not a field
column_sortable_list
=
[
'id'
,
'width'
,
'height'
,
'number_of_pixels'
]
# Flask-admin can automatically detect the relevant filters for hybrid properties.
column_filters
=
(
'number_of_pixels'
,
)
# Create admin
admin
=
admin
.
Admin
(
app
,
name
=
'Example: SQLAlchemy'
,
template_mode
=
'bootstrap3'
)
...
...
@@ -274,14 +321,10 @@ admin = admin.Admin(app, name='Example: SQLAlchemy', template_mode='bootstrap3')
admin
.
add_view
(
UserAdmin
(
User
,
db
.
session
))
admin
.
add_view
(
sqla
.
ModelView
(
Tag
,
db
.
session
))
admin
.
add_view
(
PostAdmin
(
db
.
session
))
admin
.
add_view
(
sqla
.
ModelView
(
Pet
,
db
.
session
,
category
=
"Other"
))
admin
.
add_view
(
sqla
.
ModelView
(
UserInfo
,
db
.
session
,
category
=
"Other"
))
admin
.
add_view
(
TreeView
(
Tree
,
db
.
session
,
category
=
"Other"
))
admin
.
add_view
(
ScreenView
(
Screen
,
db
.
session
,
category
=
"Other"
))
admin
.
add_sub_category
(
name
=
"Links"
,
parent_name
=
"Other"
)
admin
.
add_link
(
MenuLink
(
name
=
'Back Home'
,
url
=
'/'
,
category
=
'Links'
))
admin
.
add_link
(
MenuLink
(
name
=
'Google'
,
url
=
'http://www.google.com/'
,
category
=
'Links'
))
admin
.
add_link
(
MenuLink
(
name
=
'Mozilla'
,
url
=
'http://mozilla.org/'
,
category
=
'Links'
))
admin
.
add_link
(
MenuLink
(
name
=
'External link'
,
url
=
'http://www.example.com/'
,
category
=
'Links'
))
def
build_sample_db
():
...
...
@@ -307,13 +350,35 @@ def build_sample_db():
'Ali'
,
'Mason'
,
'Mitchell'
,
'Rose'
,
'Davis'
,
'Davies'
,
'Rodriguez'
,
'Cox'
,
'Alexander'
]
countries
=
[
(
"ZA"
,
"South Africa"
,
27
,
"ZAR"
,
"Africa/Johannesburg"
),
(
"BF"
,
"Burkina Faso"
,
226
,
"XOF"
,
"Africa/Ouagadougou"
),
(
"US"
,
"United States of America"
,
1
,
"USD"
,
"America/New_York"
),
(
"BR"
,
"Brazil"
,
55
,
"BRL"
,
"America/Sao_Paulo"
),
(
"TZ"
,
"Tanzania"
,
255
,
"TZS"
,
"Africa/Dar_es_Salaam"
),
(
"DE"
,
"Germany"
,
49
,
"EUR"
,
"Europe/Berlin"
),
(
"CN"
,
"China"
,
86
,
"CNY"
,
"Asia/Shanghai"
),
]
user_list
=
[]
for
i
in
range
(
len
(
first_names
)):
user
=
User
()
country
=
random
.
choice
(
countries
)
user
.
type
=
random
.
choice
(
AVAILABLE_USER_TYPES
)[
0
]
user
.
first_name
=
first_names
[
i
]
user
.
last_name
=
last_names
[
i
]
user
.
email
=
first_names
[
i
]
.
lower
()
+
"@example.com"
user
.
info
.
append
(
UserInfo
(
key
=
"foo"
,
value
=
"bar"
))
user
.
website
=
"https://www.example.com"
user
.
ip_address
=
"127.0.0.1"
user
.
coutry
=
country
[
1
]
user
.
currency
=
country
[
3
]
user
.
timezone
=
country
[
4
]
user
.
dialling_code
=
country
[
2
]
user
.
local_phone_number
=
'0'
+
''
.
join
(
random
.
choices
(
'123456789'
,
k
=
9
))
user_list
.
append
(
user
)
db
.
session
.
add
(
user
)
...
...
@@ -370,6 +435,7 @@ def build_sample_db():
post
.
user
=
user
post
.
title
=
entry
[
'title'
]
post
.
text
=
entry
[
'content'
]
post
.
background_color
=
random
.
choice
([
"#cccccc"
,
"red"
,
"lightblue"
,
"#0f0"
])
tmp
=
int
(
1000
*
random
.
random
())
# random number between 0 and 1000:
post
.
date
=
datetime
.
datetime
.
now
()
-
datetime
.
timedelta
(
days
=
tmp
)
post
.
tags
=
random
.
sample
(
tag_list
,
2
)
# select a couple of tags at random
...
...
@@ -389,15 +455,6 @@ def build_sample_db():
leaf
.
parent
=
branch
db
.
session
.
add
(
leaf
)
db
.
session
.
add
(
Pet
(
name
=
'Dog'
,
available
=
True
))
db
.
session
.
add
(
Pet
(
name
=
'Fish'
,
available
=
True
))
db
.
session
.
add
(
Pet
(
name
=
'Cat'
,
available
=
True
))
db
.
session
.
add
(
Pet
(
name
=
'Parrot'
,
available
=
True
))
db
.
session
.
add
(
Pet
(
name
=
'Ocelot'
,
available
=
False
))
db
.
session
.
add
(
Screen
(
width
=
500
,
height
=
2000
))
db
.
session
.
add
(
Screen
(
width
=
550
,
height
=
1900
))
db
.
session
.
commit
()
return
...
...
examples/sqla/requirements.txt
View file @
424108be
...
...
@@ -2,3 +2,6 @@ Flask
Flask-Admin
Flask-SQLAlchemy
tablib
sqlalchemy_utils
arrow
colour
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