Skip to content

Commit ce1482f

Browse files
committed
added many features to baseviews, mainly ability to dynamically add default routes to future views, also added better automatic imports for installed views and models
1 parent cbc3226 commit ce1482f

File tree

8 files changed

+106
-32
lines changed

8 files changed

+106
-32
lines changed

flask_xxl/apps/admin/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@
77
url_prefix='/admin')
88

99

10-
from views import *

flask_xxl/apps/admin/models.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import sqlalchemy as sa
22
from ...basemodels import BaseMixin
33
from LoginUtils import check_password, encrypt_password
4-
print 'importing flask_xxl.apps.admin.models as ',__name__
4+
from ...baseviews import is_verbose
5+
6+
7+
if is_verbose():
8+
print 'importing flask_xxl.apps.admin.models as ',__name__
59

610

711
class Setting(BaseMixin):
@@ -22,11 +26,10 @@ def widget(self):
2226

2327
class Type(BaseMixin):
2428

25-
2629
name = sa.Column(sa.String(255),nullable=False)
2730
widgets = sa.orm.relationship('Widget',backref=sa.orm.backref(
2831
'type'),lazy='dynamic')
29-
html = sa.orm.Column(sa.orm.Text)
32+
html = sa.Column(sa.Text)
3033
field_type = sa.Column(sa.String(255))
3134
required = sa.Column(sa.Boolean,default=False)
3235
data_type = sa.Column(sa.String(255))

flask_xxl/apps/page/__init__.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,6 @@
99
static_url_path='/_page_static/',
1010
static_folder=os.path.abspath(os.path.dirname(__file__)),
1111
url_prefix='/page')
12-
13-
14-
15-
for module in ['views','models','admin']:
16-
if exists(module):
17-
_module = __import__(module,globals(),locals(),['.'])
18-
for attr in dir(_module):
19-
if not attr.startswith('_'):
20-
globals()[attr] = getattr(_module,attr)
21-
22-
23-
#from .views import *
24-
#from .models import *
25-
#from .admin import *
26-
2712
'''
2813
@page.before_app_request
2914
def add_pages():

flask_xxl/apps/page/views.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
from flask_xxl.baseviews import BaseView
1+
from flask_xxl.baseviews import BaseView,ModelAPIView
22
import flask
33
from . import page
44
from .forms import TestForm,ContactUsForm
5+
from .models import Page
56

67

8+
class TestView(ModelAPIView):
9+
_model = Page
710

811
class ContactFormView(BaseView):
912
_template = 'contact.html'

flask_xxl/basemodels.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ class ModelDeclarativeMeta(_BoundDeclarativeMeta):
4343

4444
@as_declarative(name='BaseMixin',metaclass=ModelDeclarativeMeta)
4545
class BaseMixin(object):
46+
__table_args__ = {
47+
'extend_existing':True
48+
}
4649
__abstract__ = True
4750
_session = None
4851
_e = None

flask_xxl/baseviews.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,49 @@
1+
import os
12
from flask.views import MethodView
23
from flask.templating import render_template
34
from flask.helpers import url_for
45
from flask import redirect, flash
56
from wtforms.form import FormMeta
7+
from .basemodels import classproperty
68

7-
class BaseView(MethodView):
9+
10+
is_verbose = lambda: os.environ.get('VERBOSE') and True or False
11+
12+
class Flasher(object):
13+
DEFAULT_CATEGORY = 'warning'
14+
15+
def flash(self,msg,cat=None):
16+
cat = cat or self.DEFAULT_CATEGORY
17+
flash(msg,cat)
18+
19+
def add_warning(self,*args,**kwargs):
20+
self.flash(*args,**kwargs)
21+
22+
def add_error(self,msg):
23+
self.flash(msg=msg,cat='danger')
24+
25+
def add_info(self,msg):
26+
self.flash(msg,'info')
27+
28+
def add_success(self,msg):
29+
self.flash(msg,'success')
30+
31+
class BaseView(MethodView,Flasher):
832
_template = None
933
_form = None
1034
_context = {}
1135
_form_obj = None
1236
_obj_id = None
1337
_form_args = {}
38+
_default_view_routes = {}
39+
40+
@classmethod
41+
def _add_default_routes(cls,app=None):
42+
for route,endpoint in cls._default_view_routes.items():
43+
if is_verbose():
44+
print 'attaching',route,'to view func',endpoint
45+
app.add_url_rule(route,endpoint,view_func=cls.as_view(endpoint))
46+
1447

1548
def render(self,**kwargs):
1649
if self._template is None:
@@ -35,6 +68,7 @@ def render(self,**kwargs):
3568
inner_field = getattr(getattr(field,field.__name__),getattr(fiels.__name__))
3669
if hasattr(inner_field,'choices'):
3770
setattr(inner_field,'choices',choices)
71+
# the 6 lines above replace the line below, delete it once we verify it works
3872
#self._context['form'].template.template.choices = choices
3973
for f,v in self._form_args.items():
4074
self._form.__dict__[f].data = v
@@ -45,12 +79,6 @@ def redirect(self,endpoint,**kwargs):
4579
return redirect(url_for(endpoint,**kwargs))
4680
return redirect(endpoint,**kwargs)
4781

48-
49-
def flash(self,*args,**kwargs):
50-
if not 'category' in kwargs:
51-
kwargs['category'] = 'warning'
52-
flash(*args,**kwargs)
53-
5482
def form_validated(self):
5583
if self._form:
5684
return self._form().validate()
@@ -74,6 +102,14 @@ def get_env(self):
74102

75103

76104
class ModelView(BaseView):
105+
# ModelView is an abstract class
106+
# just an interface really
107+
# to use the ModelView create your own view class
108+
# and use this as the parent class, and
109+
# set its _model class attr to the class to wrap ie:
110+
#
111+
# class UserModelView(ModelView):
112+
# _model = User
77113
_model = None
78114

79115
def render(self,**kwargs):
@@ -110,3 +146,28 @@ def update(self,model_id,**kwargs):
110146
def get_by_id(self,model_id):
111147
tmp = self._model.get_by_id(model_id)
112148
return tmp
149+
150+
151+
class ModelAPIView(ModelView):
152+
__abstract__ = True
153+
154+
@classproperty
155+
def _default_view_routes(cls):
156+
if cls is ModelAPIView:
157+
return {}
158+
name = cls.__name__.lower()
159+
_default_view_routes = {
160+
'/{}/list'.format(name):'{}-list'.format(name),
161+
'/{}/detail'.format(name):'{}-detail'.format(name),
162+
'/{}/edit'.format(name):'{}-edit'.format(name),
163+
}
164+
return _default_view_routes
165+
166+
167+
def render(self,**kwargs):
168+
old_rtn = super(ModelAPIView,self).render(**kwargs)
169+
rtn = make_response(json.dumps(self._context))
170+
rtn.headers['Content-Type'] = 'application/json'
171+
return rtn
172+
173+

flask_xxl/main.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
import jinja2_highlight
99
import sys
1010
import os
11-
from flask import Flask
11+
from flask import Flask,views
1212
from werkzeug.utils import import_string,find_modules
1313
import jinja2_highlight
14+
from .baseviews import is_verbose
15+
1416

1517
class MyFlask(Flask):
1618
jinja_options = dict(Flask.jinja_options)
@@ -43,6 +45,7 @@ def get_app(self, app_module_name, **kwargs):
4345
self.app = MyFlask(app_module_name, **kwargs)
4446
self.app.config.from_object(self.app_config)
4547
self.app.config.from_envvar(self.app_envvar, silent=True)
48+
self.app.config['VERBOSE'] = is_verbose()
4649

4750

4851
self._set_path()
@@ -60,20 +63,37 @@ def get_app(self, app_module_name, **kwargs):
6063
def _set_path(self):
6164
sys.path.append(self.app.config.get('ROOT_PATH',''))
6265

66+
def _is_public_attr(self,name):
67+
return not name.startswith('_')
68+
6369
def _get_imported_stuff_by_path(self, path):
6470
module_name, object_name = path.rsplit('.', 1)
6571
module = import_string(module_name)
66-
6772
return module, object_name
6873

6974
def _load_resource(self,typename):
7075
for blueprint_path in self.app.config.get('BLUEPRINTS', []):
7176
module_name, object_name = blueprint_path.rsplit('.', 1)
77+
blueprint_module, bp_name = self._get_imported_stuff_by_path(blueprint_path)
78+
blueprint = getattr(blueprint_module,bp_name)
7279
modules = find_modules(module_name)
7380
for module in modules:
7481
if typename in module:
75-
print module
76-
import_string(module)
82+
mod = import_string(module)
83+
for itm in dir(mod):
84+
cls = getattr(mod,itm)
85+
if self._is_public_attr(itm) and\
86+
itm[0] == str(itm[0]).upper() and\
87+
'class' in str(cls) and\
88+
'view' in str(itm).lower():
89+
90+
if hasattr(cls,'_add_default_routes') and\
91+
getattr(cls,'_default_view_routes'):
92+
if is_verbose():
93+
print 'getting default routes for ',cls.__name__
94+
getattr(cls,'_add_default_routes')(app=blueprint or self.app)
95+
96+
7797

7898
def _load_views(self):
7999
return self._load_resource('views')

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ alembic==0.6.7
1717
argparse==1.2.1
1818
blinker==1.3
1919
flask-codemirror==0.0.3
20-
flask-xxl==0.6.6
20+
flask-xxl==0.7.11
2121
itsdangerous==0.24
2222
jinja2-highlight==0.6.1
2323
mr.bob2==0.2.3

0 commit comments

Comments
 (0)