85 | is an open source data visualization platform that provides easy 86 | exploration of your data and allows you to create and share 87 | beautiful charts and dashboards 88 |
89 |' + p[fd.entity] + ' (' + p.group + ') |
15 | 16 | | 17 | {% endif %} 18 | 19 | {% if can_show or can_edit or can_delete %} 20 |21 | {% endif %} 22 | 23 | {% for item in include_columns %} 24 | {% if item in order_columns %} 25 | {% set res = item | get_link_order(modelview_name) %} 26 | {% if res == 2 %} 27 | | {{label_columns.get(item)}} 28 | | 29 | {% elif res == 1 %} 30 |{{label_columns.get(item)}} 31 | | 32 | {% else %} 33 |{{label_columns.get(item)}} 34 | | 35 | {% endif %} 36 | {% else %} 37 |{{label_columns.get(item)}} | 38 | {% endif %} 39 | {% endfor %} 40 |
---|---|---|---|---|---|
50 | 51 | | 52 | {% endif %} 53 | {% if can_show or can_edit or can_delete %} 54 |60 | {% if item[value].__class__.__name__ == 'bool' %} 61 | 68 | {% else %} 69 | {{ item[value]|safe }} 70 | {% endif %} 71 | | 72 | {% endfor %} 73 |
4 | {{ art }}
5 | {{ title }}
6 | {{ error_msg }}
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/panoramix/templates/panoramix/viz.html:
--------------------------------------------------------------------------------
1 | {% if viz.form_data.get("json") == "true" %}
2 | {{ viz.get_json() }}
3 | {% else %}
4 | {% if viz.request.args.get("standalone") == "true" %}
5 | {% extends 'panoramix/standalone.html' %}
6 | {% else %}
7 | {% extends 'panoramix/explore.html' %}
8 | {% endif %}
9 |
10 |
11 | {% block head_css %}
12 | {{super()}}
13 | {% for css in viz.css_files %}
14 |
15 | {% endfor %}
16 | {% endblock %}
17 |
18 |
19 | {% block tail %}
20 | {{super()}}
21 | {% for js in viz.js_files %}
22 |
23 | {% endfor %}
24 | {% endblock %}
25 | {% endif %}
26 |
--------------------------------------------------------------------------------
/panoramix/utils.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | import functools
3 | import hashlib
4 | import json
5 | from dateutil.parser import parse
6 | from sqlalchemy.types import TypeDecorator, TEXT
7 | from flask import g, request, Markup
8 | from markdown import markdown as md
9 | import parsedatetime
10 |
11 | from panoramix import db
12 |
13 |
14 | class memoized(object):
15 | """Decorator that caches a function's return value each time it is called.
16 | If called later with the same arguments, the cached value is returned, and
17 | not re-evaluated.
18 | """
19 |
20 | def __init__(self, func):
21 | self.func = func
22 | self.cache = {}
23 |
24 | def __call__(self, *args):
25 | try:
26 | return self.cache[args]
27 | except KeyError:
28 | value = self.func(*args)
29 | self.cache[args] = value
30 | return value
31 | except TypeError:
32 | # uncachable -- for instance, passing a list as an argument.
33 | # Better to not cache than to blow up entirely.
34 | return self.func(*args)
35 |
36 | def __repr__(self):
37 | """Return the function's docstring."""
38 | return self.func.__doc__
39 |
40 | def __get__(self, obj, objtype):
41 | """Support instance methods."""
42 | return functools.partial(self.__call__, obj)
43 |
44 |
45 | def parse_human_datetime(s):
46 | """
47 | Use the parsedatetime lib to return ``datetime.datetime`` from human
48 | generated strings
49 |
50 | >>> parse_human_datetime('2015-04-03')
51 | datetime.datetime(2015, 4, 3, 0, 0)
52 | >>> parse_human_datetime('2/3/1969')
53 | datetime.datetime(1969, 2, 3, 0, 0)
54 | >>> parse_human_datetime("now") <= datetime.now()
55 | True
56 | >>> parse_human_datetime("yesterday") <= datetime.now()
57 | >>> date.today() - timedelta(1) == parse_human_datetime('yesterday').date()
58 | True
59 | """
60 | try:
61 | dttm = parse(s)
62 | except:
63 | cal = parsedatetime.Calendar()
64 | dttm = dttm_from_timtuple(cal.parse(s)[0])
65 | return dttm
66 |
67 |
68 | def dttm_from_timtuple(d):
69 | return datetime(
70 | d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec)
71 |
72 |
73 | def merge_perm(sm, permission_name, view_menu_name):
74 | pv = sm.find_permission_view_menu(permission_name, view_menu_name)
75 | if not pv:
76 | sm.add_permission_view_menu(permission_name, view_menu_name)
77 |
78 |
79 | def parse_human_timedelta(s):
80 | """
81 | Use the parsedatetime lib to return ``datetime.datetime`` from human
82 | generated strings
83 |
84 | >>> parse_human_datetime("now") <= datetime.now()
85 | True
86 | """
87 | cal = parsedatetime.Calendar()
88 | dttm = dttm_from_timtuple(datetime.now().timetuple())
89 | d = cal.parse(s, dttm)[0]
90 | d = datetime(
91 | d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec)
92 | return d - dttm
93 |
94 |
95 | class JSONEncodedDict(TypeDecorator):
96 | """Represents an immutable structure as a json-encoded string."""
97 | impl = TEXT
98 |
99 | def process_bind_param(self, value, dialect):
100 | if value is not None:
101 | value = json.dumps(value)
102 |
103 | return value
104 |
105 | def process_result_value(self, value, dialect):
106 | if value is not None:
107 | value = json.loads(value)
108 | return value
109 |
110 |
111 | def color(s):
112 | """
113 | Get a consistent color from the same string using a hash function
114 |
115 | >>> color("foo")
116 | '#FF5A5F'
117 | """
118 | colors = [
119 | "#007A87",
120 | "#00D1C1",
121 | "#4EDED2",
122 | "#4FA3AB",
123 | "#565A5C",
124 | "#7B0051",
125 | "#898C8C",
126 | "#8CE071",
127 | "#9CA299",
128 | "#A14D83",
129 | "#B4A76C",
130 | "#C9BF97",
131 | "#FF5A5F",
132 | "#FFAA91",
133 | "#FFB400",
134 | "#FFC4B3",
135 | "#FFCA4F",
136 | ]
137 | s = s.encode('utf-8')
138 | h = hashlib.md5(s)
139 | i = int(h.hexdigest(), 16)
140 | return colors[i % len(colors)]
141 |
142 |
143 | def init():
144 | """
145 | Inits the Panoramix application with security roles and such
146 | """
147 | from panoramix import appbuilder
148 | from panoramix import models
149 | from flask_appbuilder.security.sqla import models as ab_models
150 |
151 | sm = appbuilder.sm
152 | alpha = sm.add_role("Alpha")
153 | admin = sm.add_role("Admin")
154 |
155 | merge_perm(sm, 'all_datasource_access', 'all_datasource_access')
156 |
157 | perms = db.session.query(ab_models.PermissionView).all()
158 | for perm in perms:
159 | if perm.view_menu.name not in (
160 | 'UserDBModelView', 'RoleModelView', 'ResetPasswordView',
161 | 'Security'):
162 | sm.add_permission_role(alpha, perm)
163 | sm.add_permission_role(admin, perm)
164 | gamma = sm.add_role("Gamma")
165 | for perm in perms:
166 | s = perm.permission.name
167 | if (
168 | perm.view_menu.name not in (
169 | 'UserDBModelView',
170 | 'RoleModelView',
171 | 'ResetPasswordView',
172 | 'Security') and
173 | perm.permission.name not in (
174 | 'can_edit',
175 | 'can_add',
176 | 'can_save',
177 | 'can_download',
178 | 'muldelete',
179 | 'all_datasource_access',
180 | 'datasource_access',
181 | )):
182 | sm.add_permission_role(gamma, perm)
183 | session = db.session()
184 | table_perms = [
185 | table.perm for table in session.query(models.SqlaTable).all()]
186 | table_perms += [
187 | table.perm for table in session.query(models.Datasource).all()]
188 | for table_perm in table_perms:
189 | merge_perm(sm, 'datasource_access', table.perm)
190 |
191 |
192 | def log_this(f):
193 | '''
194 | Decorator to log user actions
195 | '''
196 |
197 | @functools.wraps(f)
198 | def wrapper(*args, **kwargs):
199 | user_id = None
200 | if g.user:
201 | user_id = g.user.id
202 | from panoramix import models
203 |
204 | log = models.Log(
205 | action=f.__name__,
206 | json=json.dumps(request.args.to_dict()),
207 | user_id=user_id)
208 |
209 | db.session.add(log)
210 | db.session.commit()
211 |
212 | return f(*args, **kwargs)
213 |
214 | return wrapper
215 |
216 |
217 | def datetime_f(dttm):
218 | if dttm:
219 | dttm = dttm.isoformat()
220 | now_iso = datetime.now().isoformat()
221 | if now_iso[:10] == dttm[:10]:
222 | dttm = dttm[11:]
223 | elif now_iso[:4] == dttm[:4]:
224 | dttm = dttm[5:]
225 | return Markup("