Skip to content

Commit b74014e

Browse files
committed
fix action for multiple items on show_cascade view
1 parent bbf2adb commit b74014e

File tree

12 files changed

+28028
-3
lines changed

12 files changed

+28028
-3
lines changed

examples/issue_2054/NAMES.DIC

Lines changed: 27607 additions & 0 deletions
Large diffs are not rendered by default.

examples/issue_2054/README.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Quick How to Example
2+
--------------------
3+
4+
Simple contacts application.
5+
6+
Insert test data::
7+
8+
$ python testdata.py
9+
10+
Run it::
11+
12+
$ export FLASK_APP=app/__init__.py
13+
$ flask fab create-admin
14+
$ flask run
15+

examples/issue_2054/app/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import logging
2+
3+
from flask import Flask
4+
from flask_appbuilder import AppBuilder, SQLA
5+
6+
logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s")
7+
logging.getLogger().setLevel(logging.DEBUG)
8+
9+
app = Flask(__name__)
10+
app.config.from_object("config")
11+
db = SQLA(app)
12+
appbuilder = AppBuilder(app, db.session)
13+
14+
from . import models, views # noqa

examples/issue_2054/app/models.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import datetime
2+
3+
from flask_appbuilder import Model
4+
from sqlalchemy import Column, Date, ForeignKey, Integer, String
5+
from sqlalchemy.orm import relationship
6+
7+
mindate = datetime.date(datetime.MINYEAR, 1, 1)
8+
9+
10+
class ContactGroup(Model):
11+
id = Column(Integer, primary_key=True)
12+
name = Column(String(50), unique=True, nullable=False)
13+
14+
def __repr__(self):
15+
return self.name
16+
17+
18+
class Gender(Model):
19+
id = Column(Integer, primary_key=True)
20+
name = Column(String(50), unique=True, nullable=False)
21+
22+
def __repr__(self):
23+
return self.name
24+
25+
26+
class Contact(Model):
27+
id = Column(Integer, primary_key=True)
28+
name = Column(String(150), unique=True, nullable=False)
29+
address = Column(String(564))
30+
birthday = Column(Date, nullable=True)
31+
personal_phone = Column(String(20))
32+
personal_celphone = Column(String(20))
33+
contact_group_id = Column(Integer, ForeignKey("contact_group.id"), nullable=False)
34+
contact_group = relationship("ContactGroup")
35+
gender_id = Column(Integer, ForeignKey("gender.id"), nullable=False)
36+
gender = relationship("Gender")
37+
38+
def __repr__(self):
39+
return self.name
40+
41+
def month_year(self):
42+
date = self.birthday or mindate
43+
return datetime.datetime(date.year, date.month, 1) or mindate
44+
45+
def year(self):
46+
date = self.birthday or mindate
47+
return datetime.datetime(date.year, 1, 1)
Binary file not shown.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Portuguese translations for PROJECT.
2+
# Copyright (C) 2014 ORGANIZATION
3+
# This file is distributed under the same license as the PROJECT project.
4+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
5+
#
6+
msgid ""
7+
msgstr ""
8+
"Project-Id-Version: PROJECT VERSION\n"
9+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10+
"POT-Creation-Date: 2014-01-13 00:29+0000\n"
11+
"PO-Revision-Date: 2014-01-13 00:18+0000\n"
12+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+
"Language-Team: pt <LL@li.org>\n"
14+
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
15+
"MIME-Version: 1.0\n"
16+
"Content-Type: text/plain; charset=utf-8\n"
17+
"Content-Transfer-Encoding: 8bit\n"
18+
"Generated-By: Babel 1.3\n"
19+
20+
#: app/views.py:42
21+
msgid "List Groups"
22+
msgstr "Lista de Grupos"
23+
24+
#: app/views.py:43
25+
msgid "List Contacts"
26+
msgstr ""
27+

examples/issue_2054/app/views.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import calendar
2+
3+
from flask import redirect
4+
from flask_appbuilder import ModelView
5+
from flask_appbuilder.actions import action
6+
from flask_appbuilder.charts.views import GroupByChartView
7+
from flask_appbuilder.models.group import aggregate_count
8+
from flask_appbuilder.models.sqla.interface import SQLAInterface
9+
10+
from . import appbuilder, db
11+
from .models import Contact, ContactGroup, Gender
12+
13+
14+
def fill_gender():
15+
try:
16+
db.session.add(Gender(name="Male"))
17+
db.session.add(Gender(name="Female"))
18+
db.session.commit()
19+
except Exception:
20+
db.session.rollback()
21+
22+
23+
class ContactModelView(ModelView):
24+
datamodel = SQLAInterface(Contact)
25+
26+
list_columns = ["name", "personal_celphone", "birthday", "contact_group.name"]
27+
28+
base_order = ("name", "asc")
29+
show_fieldsets = [
30+
("Summary", {"fields": ["name", "gender", "contact_group"]}),
31+
(
32+
"Personal Info",
33+
{
34+
"fields": [
35+
"address",
36+
"birthday",
37+
"personal_phone",
38+
"personal_celphone",
39+
],
40+
"expanded": False,
41+
},
42+
),
43+
]
44+
45+
add_fieldsets = [
46+
("Summary", {"fields": ["name", "gender", "contact_group"]}),
47+
(
48+
"Personal Info",
49+
{
50+
"fields": [
51+
"address",
52+
"birthday",
53+
"personal_phone",
54+
"personal_celphone",
55+
],
56+
"expanded": False,
57+
},
58+
),
59+
]
60+
61+
edit_fieldsets = [
62+
("Summary", {"fields": ["name", "gender", "contact_group"]}),
63+
(
64+
"Personal Info",
65+
{
66+
"fields": [
67+
"address",
68+
"birthday",
69+
"personal_phone",
70+
"personal_celphone",
71+
],
72+
"expanded": False,
73+
},
74+
),
75+
]
76+
77+
@action("muldelete", "Delete", "Delete all Really?", "fa-rocket")
78+
def muldelete(self, items):
79+
if isinstance(items, list):
80+
self.datamodel.delete_all(items)
81+
self.update_redirect()
82+
else:
83+
self.datamodel.delete(items)
84+
return redirect(self.get_redirect())
85+
86+
87+
class GroupModelView(ModelView):
88+
datamodel = SQLAInterface(ContactGroup)
89+
related_views = [ContactModelView]
90+
show_template = "appbuilder/general/model/show_cascade.html"
91+
92+
@action("muldelete", "Delete", "Delete all Really?", "fa-rocket")
93+
def muldelete(self, items):
94+
if isinstance(items, list):
95+
self.datamodel.delete_all(items)
96+
self.update_redirect()
97+
else:
98+
self.datamodel.delete(items)
99+
return redirect(self.get_redirect())
100+
101+
102+
def pretty_month_year(value):
103+
return calendar.month_name[value.month] + " " + str(value.year)
104+
105+
106+
def pretty_year(value):
107+
return str(value.year)
108+
109+
110+
class ContactTimeChartView(GroupByChartView):
111+
datamodel = SQLAInterface(Contact)
112+
113+
chart_title = "Grouped Birth contacts"
114+
chart_type = "AreaChart"
115+
label_columns = ContactModelView.label_columns
116+
definitions = [
117+
{
118+
"group": "month_year",
119+
"formatter": pretty_month_year,
120+
"series": [(aggregate_count, "group")],
121+
},
122+
{
123+
"group": "year",
124+
"formatter": pretty_year,
125+
"series": [(aggregate_count, "group")],
126+
},
127+
]
128+
129+
130+
db.create_all()
131+
fill_gender()
132+
appbuilder.add_view(
133+
GroupModelView,
134+
"List Groups",
135+
icon="fa-folder-open-o",
136+
category="Contacts",
137+
category_icon="fa-envelope",
138+
)
139+
appbuilder.add_view(
140+
ContactModelView, "List Contacts", icon="fa-envelope", category="Contacts"
141+
)
142+
appbuilder.add_separator("Contacts")
143+
appbuilder.add_view(
144+
ContactTimeChartView,
145+
"Contacts Birth Chart",
146+
icon="fa-dashboard",
147+
category="Contacts",
148+
)

examples/issue_2054/babel/babel.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[python: **.py]
2+
[jinja2: **/templates/**.html]
3+
encoding = utf-8

examples/issue_2054/config.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import os
2+
3+
from flask_appbuilder.const import AUTH_DB
4+
from flask_appbuilder.exceptions import PasswordComplexityValidationError
5+
6+
basedir = os.path.abspath(os.path.dirname(__file__))
7+
8+
CSRF_ENABLED = True
9+
SECRET_KEY = "\2\1thisismyscretkey\1\2\e\y\y\h"
10+
11+
OPENID_PROVIDERS = [
12+
{"name": "Google", "url": "https://www.google.com/accounts/o8/id"},
13+
{"name": "Yahoo", "url": "https://me.yahoo.com"},
14+
{"name": "AOL", "url": "http://openid.aol.com/<username>"},
15+
{"name": "Flickr", "url": "http://www.flickr.com/<username>"},
16+
{"name": "MyOpenID", "url": "https://www.myopenid.com"},
17+
]
18+
19+
SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "app.db")
20+
# SQLALCHEMY_DATABASE_URI = 'mysql://username:password@mysqlserver.local/quickhowto'
21+
# SQLALCHEMY_DATABASE_URI = 'postgresql://scott:tiger@localhost:5432/myapp'
22+
# SQLALCHEMY_ECHO = True
23+
SQLALCHEMY_POOL_RECYCLE = 3
24+
25+
BABEL_DEFAULT_LOCALE = "en"
26+
BABEL_DEFAULT_FOLDER = "translations"
27+
LANGUAGES = {
28+
"en": {"flag": "gb", "name": "English"},
29+
"pt": {"flag": "pt", "name": "Portuguese"},
30+
"pt_BR": {"flag": "br", "name": "Pt Brazil"},
31+
"es": {"flag": "es", "name": "Spanish"},
32+
"fr": {"flag": "fr", "name": "French"},
33+
"de": {"flag": "de", "name": "German"},
34+
"zh": {"flag": "cn", "name": "Chinese"},
35+
"ru": {"flag": "ru", "name": "Russian"},
36+
"pl": {"flag": "pl", "name": "Polish"},
37+
"el": {"flag": "gr", "name": "Greek"},
38+
"ja_JP": {"flag": "jp", "name": "Japanese"},
39+
}
40+
41+
FAB_API_MAX_PAGE_SIZE = 100
42+
43+
44+
def custom_password_validator(password: str) -> None:
45+
"""
46+
A simplistic example for a password validator
47+
"""
48+
if len(password) < 8:
49+
raise PasswordComplexityValidationError("Must have at least 8 characters")
50+
51+
52+
# FAB_PASSWORD_COMPLEXITY_VALIDATOR = custom_password_validator
53+
54+
FAB_PASSWORD_COMPLEXITY_ENABLED = True
55+
56+
# ------------------------------
57+
# GLOBALS FOR GENERAL APP's
58+
# ------------------------------
59+
UPLOAD_FOLDER = basedir + "/app/static/uploads/"
60+
IMG_UPLOAD_FOLDER = basedir + "/app/static/uploads/"
61+
IMG_UPLOAD_URL = "/static/uploads/"
62+
AUTH_TYPE = AUTH_DB
63+
# AUTH_LDAP_SERVER = "ldap://dc.domain.net"
64+
AUTH_ROLE_ADMIN = "Admin"
65+
AUTH_ROLE_PUBLIC = "Public"
66+
APP_NAME = "F.A.B. Example"
67+
APP_THEME = "" # default
68+
# APP_THEME = "cerulean.css" # COOL
69+
# APP_THEME = "amelia.css"
70+
# APP_THEME = "cosmo.css"
71+
# APP_THEME = "cyborg.css" # COOL
72+
# APP_THEME = "flatly.css"
73+
# APP_THEME = "journal.css"
74+
# APP_THEME = "readable.css"
75+
# APP_THEME = "simplex.css"
76+
# APP_THEME = "slate.css" # COOL
77+
# APP_THEME = "spacelab.css" # NICE
78+
# APP_THEME = "united.css"
79+
# APP_THEME = "darkly.css"
80+
# APP_THEME = "lumen.css"
81+
# APP_THEME = "paper.css"
82+
# APP_THEME = "sandstone.css"
83+
# APP_THEME = "solar.css"
84+
# APP_THEME = "superhero.css"

0 commit comments

Comments
 (0)