├── version ├── flask_xxl ├── apps │ ├── __init__.py │ ├── menu │ │ ├── urls.py │ │ ├── filters.py │ │ ├── templates │ │ │ ├── header.html │ │ │ ├── frontend_nav.html │ │ │ └── _header.html │ │ ├── views.py │ │ ├── models.py │ │ ├── __init__.py │ │ └── context_processors.py │ ├── admin │ │ ├── templates │ │ │ ├── layout.html │ │ │ ├── view_block.html │ │ │ ├── view_page.html │ │ │ ├── admin │ │ │ │ └── _header.html │ │ │ ├── view_template.html │ │ │ ├── dashboard.html │ │ │ ├── list_pages.html │ │ │ ├── info │ │ │ │ └── info_page.html │ │ │ ├── add.html │ │ │ ├── admin.html │ │ │ ├── list.html │ │ │ └── _macros.html │ │ ├── __init__.py │ │ ├── models.py │ │ ├── utils.py │ │ ├── urls.py │ │ ├── forms.py │ │ └── cfg.py │ ├── auth │ │ ├── filters.py │ │ ├── templates │ │ │ ├── header.html │ │ │ ├── error.html │ │ │ ├── single_column.html │ │ │ ├── frontend_nav.html │ │ │ ├── login.html │ │ │ ├── _header.html │ │ │ ├── three_column_even.html │ │ │ ├── two_column_right.html │ │ │ ├── two_column_left.html │ │ │ ├── dub.html │ │ │ ├── auth_login.html │ │ │ ├── three_column_center.html │ │ │ ├── register.html │ │ │ └── _bs3macros.html │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── context_processors.py │ │ ├── forms.py │ │ ├── utils.py │ │ ├── views.py │ │ └── models.py │ └── page │ │ ├── context_processors.py │ │ ├── templates │ │ ├── includes │ │ │ └── _messages.html │ │ ├── index2.html │ │ ├── index.html │ │ ├── buttons.html │ │ └── new_post.html │ │ ├── urls.py │ │ ├── widgets.py │ │ ├── fields.py │ │ ├── __init__.py │ │ ├── forms.py │ │ ├── filters.py │ │ ├── admin.py │ │ ├── models.py │ │ └── views.py ├── templates │ ├── __init__.py │ ├── blueprint │ │ ├── __init__.py │ │ ├── +blueprint.name+ │ │ │ ├── forms.py.bob │ │ │ ├── models.py.bob │ │ │ ├── urls.py.bob │ │ │ ├── __init__.py.bob │ │ │ ├── views.py.bob │ │ │ ├── filters.py.bob │ │ │ └── context_processors.py.bob │ │ └── .mrbob.ini │ ├── project │ │ ├── __init__.py │ │ ├── +project.name+ │ │ │ ├── __init__.py │ │ │ ├── imports.py.bob │ │ │ ├── +project.name+ │ │ │ │ ├── models.py.bob │ │ │ │ ├── views.py.bob │ │ │ │ ├── urls.py.bob │ │ │ │ ├── __init__.py.bob │ │ │ │ └── context_processors.py.bob │ │ │ ├── app.py.bob │ │ │ ├── local_settings.py.bob │ │ │ ├── ext.py.bob │ │ │ ├── manage.py.bob │ │ │ └── settings.py.bob │ │ └── .mrbob.ini │ ├── angular_service │ │ ├── value │ │ │ └── +service.service_name+.js.bob │ │ ├── provider │ │ │ └── +service.service_name+.js.bob │ │ ├── factory │ │ │ └── +service.service_name+.js.bob │ │ ├── service │ │ │ └── +service.service_name+.js.bob │ │ ├── constant │ │ │ └── +service.service_name+.js.bob │ │ └── .mrbob.ini │ ├── includes │ │ ├── base.app.html │ │ └── deps.html │ ├── angular_app │ │ ├── .mrbob.ini │ │ └── app │ │ │ └── +app.app_name+ │ │ │ └── +app.app_name+.js.bob │ └── angular_app_coffee │ │ └── app │ │ └── +app.app_name+ │ │ └── +app.app_name+.coffee.bob ├── __init__.py ├── basewidgets.py ├── context_processors.py ├── manage.py ├── filters.py ├── cli.py ├── hooks.py ├── testing.py ├── mr_bob.py ├── basemodels.py ├── baseviews.py └── main.py ├── .gitignore ├── run_tests.py ├── tests ├── local_settings.py ├── __init__.py ├── test_local_settings.py ├── setup_app.py ├── test_settings.py └── settings.py ├── MANIFEST.in ├── requirements.txt ├── manage.py ├── setup.py ├── README.md └── docs └── index.md /version: -------------------------------------------------------------------------------- 1 | 0,3,1 2 | -------------------------------------------------------------------------------- /flask_xxl/apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bc.py 2 | *.pyc 3 | -------------------------------------------------------------------------------- /flask_xxl/templates/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/urls.py: -------------------------------------------------------------------------------- 1 | ## TEST 2 | routes = [] 3 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/imports.py.bob: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /run_tests.py: -------------------------------------------------------------------------------- 1 | from tests.setup_app import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/layout.html: -------------------------------------------------------------------------------- 1 | {% block content %}{% endblock %} 2 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_service/value/+service.service_name+.js.bob: -------------------------------------------------------------------------------- 1 | value 2 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_service/provider/+service.service_name+.js.bob: -------------------------------------------------------------------------------- 1 | provider 2 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/+project.name+/models.py.bob: -------------------------------------------------------------------------------- 1 | from flask_xxl.basemodels import BaseMixin 2 | 3 | 4 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/filters.py: -------------------------------------------------------------------------------- 1 | # register blueprint specific template filters here 2 | 3 | def not_a_filter(data): 4 | return data 5 | 6 | 7 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/filters.py: -------------------------------------------------------------------------------- 1 | # register blueprint specific template filters here 2 | 3 | def not_a_filter(data): 4 | return data 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/local_settings.py: -------------------------------------------------------------------------------- 1 | class LocalConfig(object): 2 | SECRET_KEY = 'secret' 3 | 4 | DATABASE_URI = 'sqlite:///xxx' 5 | 6 | VERBOSE = True 7 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_xxl.main import AppFactory 2 | from .settings import BaseConfig 3 | 4 | app = AppFactory(BaseConfig).get_app(__name__) 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/test_local_settings.py: -------------------------------------------------------------------------------- 1 | class LocalConfig(object): 2 | SECRET_KEY = 'secret' 3 | 4 | DATABASE_URI = 'sqlite:///xxx' 5 | 6 | VERBOSE = True 7 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_service/factory/+service.service_name+.js.bob: -------------------------------------------------------------------------------- 1 | {{% extends 'includes/base.app.html' %}} 2 | {{%- block service_type -%}}factory{{%- endblock -%}} 3 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_service/service/+service.service_name+.js.bob: -------------------------------------------------------------------------------- 1 | {{% extends 'includes/base.app.html' %}} 2 | {{%- block service_type -%}}service{{%- endblock -%}} 3 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/context_processors.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def is_page(obj): 4 | return obj.__class__.__name__ == 'Page' 5 | 6 | 7 | def add_is_page(): 8 | return {'is_page':is_page} 9 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/+blueprint.name+/forms.py.bob: -------------------------------------------------------------------------------- 1 | ''' 2 | add any forms here 3 | ''' 4 | from flask.ext.wtforms import Form 5 | from wtforms import validators,fields 6 | 7 | 8 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/view_block.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% block content %} 3 | {% if obj %} 4 | {{obj}} 5 | {% endif %} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/view_page.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% block content %} 3 | {% if obj %} 4 | {{obj.content}} 5 | {% endif %} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/header.html: -------------------------------------------------------------------------------- 1 | {% from '_header.html' import render_frontend_nav %} 2 | 3 | {% if nav_links and title %} 4 | {{ render_frontend_nav(nav_links,title) }} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/templates/header.html: -------------------------------------------------------------------------------- 1 | {% from '_header.html' import render_frontend_nav %} 2 | 3 | {% if nav_links and title %} 4 | {{ render_frontend_nav(nav_links,title) }} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/admin/_header.html: -------------------------------------------------------------------------------- 1 | {% from '_macros.html' import render_navbar with context %} 2 | {{ render_navbar(brand=admin_nav_title,nav_links=admin_nav_links,dropdowns=admin_dropdowns,inverse=true,auth=true) }} 3 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | 4 | 5 | admin = Blueprint('admin', __name__, 6 | template_folder='templates', 7 | url_prefix='/admin') 8 | 9 | 10 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_service/constant/+service.service_name+.js.bob: -------------------------------------------------------------------------------- 1 | {{% extends 'includes/base.app.html' %}} 2 | {{%- block service_type -%}}constant{{%- endblock -%}}{{%- block service_value -%}}{{{ service.value|default('') }}}{{%- endblock -%}} 3 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

The page your looking for can't be found...

4 |

Sorry bud

5 |

Home

6 | {% endblock %} 7 | 8 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/+blueprint.name+/models.py.bob: -------------------------------------------------------------------------------- 1 | from flask.ext.xxl.basemodels import BaseMixin 2 | from ext import db 3 | 4 | class {{{ blueprint.model }}}(BaseMixin,db.Model): 5 | name = db.Column(db.String(255),nullable=False,unique=True) 6 | -------------------------------------------------------------------------------- /flask_xxl/__init__.py: -------------------------------------------------------------------------------- 1 | #from baseviews import BaseView, ModelView 2 | #from basemodels import BaseMixin 3 | #from main import AppFactory 4 | from flask import Flask 5 | 6 | class FlaskXXL(Flask): 7 | pass 8 | 9 | flaskxxl = FlaskXXL(__name__) 10 | 11 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/+project.name+/views.py.bob: -------------------------------------------------------------------------------- 1 | from flask_xxl.baseviews import BaseView 2 | 3 | class {{{ project.name }}}View(BaseView): 4 | _template = '' 5 | 6 | def get(self): 7 | return self.render() 8 | 9 | 10 | -------------------------------------------------------------------------------- /flask_xxl/basewidgets.py: -------------------------------------------------------------------------------- 1 | 2 | class Widget(object): 3 | 4 | def render(self): 5 | raise NotImplementedError 6 | 7 | def __call__(self): 8 | return self.render() 9 | 10 | 11 | 12 | class ModelWidget(Widget): 13 | _model = None 14 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/+project.name+/urls.py.bob: -------------------------------------------------------------------------------- 1 | from {{{ project.name }}} import {{{ project.name }}} 2 | from .views import ExampleIndexView 3 | 4 | routes = [ 5 | (({{{ project.name }}},), 6 | ('/',ExampleIndexView.as_view('index')), 7 | ] 8 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | auth = Blueprint('auth',__name__, 4 | template_folder='templates', 5 | url_prefix='/auth') 6 | 7 | 8 | 9 | from views import * 10 | 11 | 12 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/templates/includes/_messages.html: -------------------------------------------------------------------------------- 1 | {% set msgs = get_flashed_messages(with_categories=true) %} 2 | {% if msgs %} 3 | {% for cat,msg in msgs %} 4 |
5 | {{msg}} 6 |
7 | {% endfor %} 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/+blueprint.name+/urls.py.bob: -------------------------------------------------------------------------------- 1 | from {{{ blueprint.name }}} import {{{ blueprint.name }}} 2 | from .views import {{{ blueprint.name|title }}}View 3 | 4 | routes = [ 5 | (({{{ blueprint.name }}}), 6 | ('/',{{{ blueprint.name|title }}}View.as_view('index')), 7 | ) 8 | ] 9 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/views.py: -------------------------------------------------------------------------------- 1 | from ...baseviews import BaseView 2 | 3 | 4 | class MenuView(BaseView): 5 | 6 | _template = '' 7 | _form = '' 8 | _context = {} 9 | 10 | def get(self): 11 | return self.render() 12 | 13 | 14 | def post(self): 15 | return self.render() 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/app.py.bob: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | app.py 5 | ~~~~~~ 6 | 7 | app initalization 8 | """ 9 | from flask.ext.xxl.main import AppFactory 10 | from settings import DevelopmentConfig 11 | 12 | app = AppFactory(DevelopmentConfig).get_app(__name__) 13 | 14 | 15 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/+project.name+/__init__.py.bob: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | 4 | {{{ project.name.lower() }}} = Blueprint('{{{ project.name.lower() }}}',__name__, 5 | template_folder='templates',url_prefix='/') 6 | 7 | 8 | from .views import * 9 | from .models import * 10 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/+blueprint.name+/__init__.py.bob: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | {{{ blueprint.name }}} = Blueprint('{{{ blueprint.name }}}',__name__, 4 | template_folder='templates/{{{ blueprint.name }}}', 5 | url_prefix='{{{ blueprint.name }}}') 6 | 7 | from .views import * 8 | from .models import * 9 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/view_template.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% block content %} 3 | {% if obj %} 4 | {{obj.name}} 5 | {% if lines %} 6 | {% for line in lines %} 7 |
{{line}} 8 | {% endfor %} 9 | {% endif %} 10 | {% endif %} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include Tutorial.md 3 | include version 4 | include manage.py 5 | 6 | include flask_xxl/templates/project/.mrbob.ini 7 | include flask_xxl/templates/blueprint/.mrbob.ini 8 | recursive-include flask_xxl/templates * 9 | recursive-include flask_xxl/templates/project * 10 | recursive-include flask_xxl/templates/blueprint * 11 | recursive-include flask_xxl/apps * 12 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/urls.py: -------------------------------------------------------------------------------- 1 | from . import auth 2 | from .views import AuthLogoutView,AuthLoginView,AuthSignupView 3 | 4 | routes = [ 5 | ((auth), 6 | ('/logout',AuthLogoutView.as_view('logout')), 7 | ('/','admin_list',AuthLoginView), 8 | ('/login','login',AuthLoginView), 9 | ('/register','signup',AuthSignupView), 10 | ) 11 | ] 12 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/urls.py: -------------------------------------------------------------------------------- 1 | from . import page 2 | from .views import ContactFormView,PageSlugView,PagesView,AddPageView 3 | 4 | routes = [ 5 | ( 6 | (page), 7 | ('/','page_list',PagesView), 8 | ('/','page',PageSlugView), 9 | ('/add_page','add_page',AddPageView), 10 | ('/contact-us','contact_us',ContactFormView), 11 | ) 12 | ] 13 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/widgets.py: -------------------------------------------------------------------------------- 1 | from basewidgets import ModelWidget 2 | from blog import models 3 | 4 | class TagWidget(ModelWidget): 5 | _model = models.Tag 6 | 7 | def render(self,*args,**kwargs): 8 | rtn = '' 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/context_processors.py: -------------------------------------------------------------------------------- 1 | from flask import session 2 | 3 | def user_context(): 4 | if 'email' in session: 5 | if 'user_id' in session: 6 | from auth.models import User 7 | return { 8 | 'user':User.get_by_id(session.get('user_id',None)), 9 | 'email':session.get('email',None) 10 | } 11 | return {'user':None} 12 | # register this in settings.py to make it avialible in all templates 13 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/+project.name+/context_processors.py.bob: -------------------------------------------------------------------------------- 1 | ''' 2 | example context_processor 3 | 4 | to use this in a template put this in the CONTEXT_PROCESSORS setting 5 | 6 | '{{{project.name}}}.context_processors.add_get_ip', 7 | 8 | ''' 9 | from flask import request 10 | 11 | def get_users_ip(): 12 | return request.environ['REMOTE_ADDRESS'] 13 | 14 | 15 | def add_get_ip(): 16 | return dict(get_users_ip=get_users_ip) 17 | 18 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/+blueprint.name+/views.py.bob: -------------------------------------------------------------------------------- 1 | from flask.ext.xxl.baseviews import BaseView 2 | 3 | class {{{ blueprint.name|title }}}View(BaseView): 4 | _template = '{{% if blueprint.main_template -%}} 5 | {{{ blueprint.main_template }}}{{%- endif %}}' 6 | _form = None 7 | _form_obj = None 8 | _form_args = {} 9 | _context = {} 10 | 11 | def get(self): 12 | return self.render() 13 | 14 | def post(self): 15 | return self.render() 16 | 17 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/fields.py: -------------------------------------------------------------------------------- 1 | from wtforms import fields, widgets 2 | 3 | class CKTextEditorWidget(widgets.TextArea): 4 | def __call__(self,field,**kwargs): 5 | if kwargs.get('class_',False): 6 | kwargs['class_'] += ' ckeditor' 7 | else: 8 | kwargs['class_'] = 'ckeditor' 9 | kwargs['rows'] = '8' 10 | return super(CKTextEditorWidget,self).__call__(field,**kwargs) 11 | 12 | class CKTextEditorField(fields.TextAreaField): 13 | widget = CKTextEditorWidget() 14 | -------------------------------------------------------------------------------- /flask_xxl/context_processors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | context_processors.py 4 | """ 5 | 6 | from inflection import camelize 7 | 8 | def get_model(model_name, blueprint=None): 9 | class_name = camelize( 10 | model_name 11 | ) 12 | return __import__( 13 | blueprint or model_name.lower() + 14 | '.models', globals(), locals(), 15 | fromlist=[], 16 | ).models.__dict__[class_name] 17 | 18 | 19 | def add_get_model(): 20 | return {'get_model': get_model} 21 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% from '_macros.html' import render_nav_sidebar %} 3 | {% block content %} 4 |
5 |
6 |
7 |

Content

8 | {{ render_nav_sidebar('','') }} 9 |
10 |
11 |

Content

12 |
13 |
14 |
15 | {% endblock content %} 16 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/single_column.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block header %} 3 | {% include 'header.html' with context %} 4 | {% endblock header %} 5 | {% block body %} 6 |
7 | 11 |
12 |
13 |

{{ lipsum() }}

14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /flask_xxl/templates/includes/base.app.html: -------------------------------------------------------------------------------- 1 | {{{ service.app_var|default('app') }}}. 2 | {{%- block service_type -%}} 3 | {{%- endblock service_type -%}} 4 | ('{{{ service.service_name }}}',{{%- if service.service_type not in ['value','constant','provider'] -%}} 5 | [{{%- include 'includes/deps.html' with context -%}} 6 | ,function({{{ ','.join(service.dep_names) }}}){ 7 | }]{{%- else -%}}{{% block service_value %}}{{% endblock service_value %}}{{%- endif -%}} 8 | ); 9 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/models.py: -------------------------------------------------------------------------------- 1 | from ...basemodels import BaseMixin 2 | import sqlalchemy as sa 3 | 4 | class Menu(BaseMixin): 5 | 6 | name = sa.Column(sa.String(255),unique=True) 7 | 8 | 9 | class MenuLink(BaseMixin): 10 | 11 | name = sa.Column(sa.String(255),nullable=False) 12 | menu = sa.orm.relationship('Menu',backref=sa.orm.backref('links',lazy='dynamic')) 13 | menu_id = sa.Column(sa.Integer,sa.ForeignKey('menus.id')) 14 | text = sa.Column(sa.String(255)) 15 | endpoint = sa.Column(sa.String(255)) 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.12.4 2 | Flask-Alembic==1.0.2 3 | Flask-DebugToolbar==0.9.2 4 | Flask-PageDown==0.1.5 5 | Flask-Script==2.0.5 6 | Flask-WTF==0.10.2 7 | Jinja2==2.10.1 8 | LoginUtils==1.0.1 9 | Mako==1.0.0 10 | MarkupSafe==0.23 11 | Pygments==2.0.1 12 | SQLAlchemy==1.3.0 13 | WTForms==2.0.1 14 | Werkzeug==0.15.3 15 | alembic==0.6.7 16 | argparse==1.2.1 17 | blinker==1.3 18 | flask-codemirror==0.0.3 19 | flask-xxl==0.9.3 20 | itsdangerous==0.24 21 | jinja2-highlight==0.6.1 22 | mr.bob2==0.2.3 23 | requests==2.20.0 24 | six==1.10.0 25 | flask-router==0.0.1 26 | wsgiref==0.1.2 27 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/+blueprint.name+/filters.py.bob: -------------------------------------------------------------------------------- 1 | ''' 2 | add any blueprint specific filters here, example filter below 3 | ''' 4 | import os 5 | from os import path as op 6 | 7 | 8 | def dir_contents(data): 9 | if data: 10 | if op.exists(data): 11 | if op.realpath(op.isdir(data)): 12 | return '\n'.join(map(str,[os.listdir(op.realpath(data))])) 13 | return '' 14 | 15 | ''' 16 | then register in setting.py under TEMPLATE_FILTERS like this 17 | 18 | "{{{ blueprint.name }}}.filters.dir_contents", 19 | ''' 20 | 21 | 22 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/frontend_nav.html: -------------------------------------------------------------------------------- 1 | 2 | {% macro frontend_nav_link(endpoint,label) %} 3 | {{ label }} 4 | {% endmacro %} 5 | 6 | {% macro render_frontend_nav(link_list,title) %} 7 |
8 | 13 |

{{ title|title }}

14 |
15 | {% endmacro %} -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/login.html: -------------------------------------------------------------------------------- 1 | {% from "macros/form_helpers.html" import common_form_field %} 2 | 3 |
4 | {{ form.hidden_tag() }} 5 | {{ common_form_field(form.email, class_='form-control') }} 6 | {{ common_form_field(form.password, class_='form-control') }} 7 |
8 | 9 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/.mrbob.ini: -------------------------------------------------------------------------------- 1 | [defaults] 2 | blueprint.template_dir = 'templates' 3 | blueprint.main_template = index.html 4 | 5 | [questions] 6 | blueprint.name.question = what will you name this blueprint 7 | blueprint.name.required = True 8 | 9 | blueprint.url_prefix.question = what url prefix should the blueprint be avialble at 10 | blueprint.url_prefix.required = True 11 | 12 | blueprint.template_dir.question = where will you put the templates 13 | 14 | blueprint.main_template.question = what template should the main view use 15 | 16 | blueprint.model.question = what should the first model be called 17 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/local_settings.py.bob: -------------------------------------------------------------------------------- 1 | 2 | class LocalConfig(object): 3 | {{% if local_settings.recaptcha_public != '' %}} 4 | 5 | RECAPTCHA_PUBLIC_KEY = '{{{ local_settings.recaptcha_public }}}' 6 | 7 | {{% endif %}} 8 | 9 | {{% if local_settings.recaptcha_private != '' %}} 10 | 11 | RECAPTCHA_PRIVATE_KEY = '{{{ local_settings.recaptcha_private }}}' 12 | 13 | {{% endif %}} 14 | 15 | SECRET_KEY = 'A Secret Shhh' 16 | 17 | {{% if project.use_database %}} 18 | 19 | SQLALCHEMY_DATABASE_URI = '{{{ project.db_uri }}}' 20 | 21 | {{% endif %}} 22 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/templates/frontend_nav.html: -------------------------------------------------------------------------------- 1 | 2 | {% macro frontend_nav_link(endpoint,label) %} 3 | {{ label }} 4 | {% endmacro %} 5 | 6 | {% macro render_frontend_nav(link_list,title) %} 7 |
8 | 13 |

{{ title|title }}

14 |
15 | {% endmacro %} 16 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/_header.html: -------------------------------------------------------------------------------- 1 | 2 | {% macro frontend_nav_link(endpoint,label) %} 3 | {{ label }} 4 | {% endmacro %} 5 | 6 | {% macro render_frontend_nav(link_list,title) %} 7 |
8 | 13 | 14 |
15 | {% endmacro %} 16 | 17 | 18 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/templates/_header.html: -------------------------------------------------------------------------------- 1 | 2 | {% macro frontend_nav_link(link) %} 3 | 4 | {{ link.text }} 5 | 6 | {% endmacro %} 7 | 8 | {% macro render_frontend_nav(link_list,title) %} 9 |
10 | 15 | 16 |
17 | {% endmacro %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /flask_xxl/templates/blueprint/+blueprint.name+/context_processors.py.bob: -------------------------------------------------------------------------------- 1 | ''' 2 | context_processors.py 3 | 4 | use this file to register context processors on your app 5 | 6 | just define a function that returns a dict, 7 | and the contents of that dict will be avialible in your templates. 8 | 9 | example: 10 | def fake_ctx_processor(): 11 | return { 12 | 'var':'val', 13 | 'boo':'far', 14 | } 15 | 16 | then register in settings.py under CONTEXT_PROCESSORS 17 | 18 | '{{{ blueprint.name }}}.context_processors.fake_ctx_processor', 19 | 20 | dont forget to always add the ending comma 21 | ''' 22 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_app/.mrbob.ini: -------------------------------------------------------------------------------- 1 | [template] 2 | pre_render = flask.ext.xxl.hooks:choose_app_template 3 | 4 | [questions] 5 | app.app_name.question = 'your application name' 6 | app.app_name.required = true 7 | app.app_name.default = 'app' 8 | 9 | app.deps.question = 'does your app have dependencies on other apps' 10 | app.deps.post_ask_question = mrbob.hooks.to_boolean 11 | app.deps.post_ask_question = flask_xxl.hooks:skip_next_if_not 12 | 13 | app.dep_names.question = 'dependency names please, in a comma seperated list' 14 | app.dep_names.post_ask_question = flask_xxl.hooks:to_list 15 | 16 | 17 | app.language.question = 'Javascript or CoffeeScript' 18 | 19 | 20 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | manage 6 | ~~~~~~ 7 | """ 8 | import subprocess 9 | from flask.ext.script import Shell, Manager, prompt_bool 10 | from flask.ext.script.commands import Clean,ShowUrls 11 | from flask.ext.xxl import flaskxxl 12 | manager = Manager(flaskxxl) 13 | from flask.ext.xxl.mr_bob import manager as mrbob_manager 14 | 15 | def main(): 16 | flaskxxl.test_request_context().push() 17 | manager.add_command('mrbob',mrbob_manager) 18 | manager.add_command('clean',Clean()) 19 | manager.add_command('urls',ShowUrls()) 20 | manager.run(default_command='mrbob') 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import Form 2 | from wtforms import fields, validators 3 | 4 | class BaseUserForm(Form): 5 | email = fields.StringField('Email Address',validators=[validators.InputRequired(), 6 | validators.Email()]) 7 | password = fields.PasswordField('Password',validators=[validators.DataRequired()]) 8 | 9 | class UserLoginForm(BaseUserForm): 10 | keep_me_logged_in = fields.BooleanField('Keep Me Logged In') 11 | 12 | 13 | class UserSignupForm(BaseUserForm): 14 | confirm = fields.PasswordField('Confirm',validators=[validators.DataRequired(), 15 | validators.EqualTo('password')]) 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/three_column_even.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block header %} 3 | {% include 'header.html' with context %} 4 | {% endblock header %} 5 | {% block body %} 6 |
7 | 10 |
11 |
12 |

{{ lipsum() }}

13 |
14 |
15 |

{{ lipsum() }}

16 |
17 |
18 |

{{ lipsum() }}

19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/two_column_right.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block header %} 3 | {% include 'header.html' with context %} 4 | {% endblock header %} 5 | {% block body %} 6 |
7 | 10 |
11 |
12 | 15 |
16 |
17 |

{{ lipsum() }}

18 |

{{ lipsum() }}

19 |
20 |
21 |
22 | {% endblock body %} 23 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint,request,current_app 2 | 3 | menu = Blueprint('menu',__name__, 4 | template_folder='', 5 | url_prefix="menu") 6 | 7 | 8 | 9 | from . import views 10 | from . import models 11 | 12 | 13 | @menu.before_app_request 14 | def add_menu_context(): 15 | if not 'static' in request.path and not 'templates' in request.path: 16 | links = models.MenuLink.query.all() 17 | for link in links: 18 | link.active = False 19 | if request.endpoint == link.endpoint: 20 | link.active = True 21 | link.save() 22 | current_app.jinja_env.globals['nav_links'] = links 23 | 24 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/two_column_left.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block header %} 3 | {% include 'header.html' with context %} 4 | {% endblock header %} 5 | {% block body %} 6 |
7 | 10 |
11 |
12 |

{{ lipsum() }}

13 |

{{ lipsum() }}

14 |
15 |
16 | 19 |
20 |
21 |
22 | {% endblock body %} 23 | -------------------------------------------------------------------------------- /flask_xxl/templates/includes/deps.html: -------------------------------------------------------------------------------- 1 | {{%- set dep_str = '' -%}} 2 | {{%- if service.deps and service.dep_names|count >= 1 -%}} 3 | {{%- if service.dep_names|count == 1 -%}} 4 | '{{{ dep_name[0] }}}' 5 | {{%- set dep_str = dep_name[0] -%}} 6 | {{%- else -%}} 7 | {{%- set _deps = '%s' -%}} 8 | {{%- for dep_name in service.dep_names -%}} 9 | {{% if not loop.last -%}} 10 | {{%- set _deps = _deps + ',' -%}} 11 | {{%- endif -%}} 12 | '{{{ dep_name }}}' 13 | {{%- set dep_str = dep_str + (_deps % dep_name) -%}} 14 | {{%- if not loop.last -%}},{{%- endif -%}} 15 | {{%- endfor -%}} 16 | {{%- endif -%}} 17 | {{%- endif -%}} 18 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/dub.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block header %} 3 | {% include 'header.html' with context %} 4 | {% endblock header %} 5 | {% block body %} 6 |
7 | 11 |
12 |
13 |
14 |
15 |

{{ lipsum() }}

16 |
17 |
18 |

{{ lipsum() }}

19 |
20 |
21 |
22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/list_pages.html: -------------------------------------------------------------------------------- 1 | {% macro render_page_list_item(item) %} 2 |
  • {{item.text}}
  • 3 | {% endmacro %} 4 | 5 | {% macro start_page_list() %} 6 |
    7 |
      8 | {% endmacro %} 9 | 10 | {% macro end_page_list() %} 11 |
    12 |
    13 | {% endmacro %} 14 | 15 | {% macro render_page_list(page_list) %} 16 | {{ start_page_list() }} 17 | {% for itm in page_list %} 18 | {{ render_page_list_item(itm) }} 19 | {% endfor %} 20 | {{ end_page_list() }} 21 | {% endmacro %} 22 | {% extends 'layout.html' %} 23 | {% block content %} 24 | {% if page_list %} 25 | {{ render_page_list(page_list) }} 26 | {% endif %} 27 | {% endblock content %} 28 | 29 | 30 | -------------------------------------------------------------------------------- /flask_xxl/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | manage 6 | ~~~~~~ 7 | """ 8 | import subprocess 9 | from flask_script import Shell, Manager, prompt_bool 10 | from flask_script.commands import Clean,ShowUrls 11 | from flask_xxl import flaskxxl 12 | manager = Manager(flaskxxl) 13 | from flask_xxl.mr_bob import manager as mrbob_manager 14 | import sys 15 | 16 | def main(auto=False): 17 | if not auto: 18 | sys.argv.insert(1,'mrbob') 19 | flaskxxl.test_request_context().push() 20 | manager.add_command('mrbob',mrbob_manager) 21 | manager.add_command('clean',Clean()) 22 | manager.add_command('urls',ShowUrls()) 23 | default_cmd = 'mrbob' 24 | if len(sys.argv) > 2: 25 | default_cmd = 'clean' 26 | manager.run(default_command=default_cmd) 27 | 28 | if __name__ == '__main__': 29 | main(auto=True) 30 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/auth_login.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% from '_bs3macros.html' import render_form %} 3 | {% block header %} 4 | {% endblock header %} 5 | {% block content %} 6 |
    7 |
    8 |
    9 |
    10 |
    11 |
    12 |
    13 |

    Sign in

    14 |
    15 |
    16 | {{ render_form(form=form,action='',action_text='Sign In',btn_class='btn btn-primary') }} 17 |
    18 |
    19 |
    20 |
    21 |
    22 | {% endblock content %} 23 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/templates/index2.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block header %} 3 | {% include 'header.html' with context %} 4 | {% endblock header %} 5 | {% block body %} 6 | {% if articles %} 7 | {% for a in articles %} 8 | {{ a }} 9 | {% endfor %} 10 | {% endif %} 11 | {% endblock body %} 12 | {% block footer_js %} 13 | {{ super() }} 14 | 27 | 28 | {% endblock footer_js %} 29 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/ext.py.bob: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | ext.py 5 | ~~~ 6 | :license: BSD, see LICENSE for more details 7 | """ 8 | 9 | from flask.ext.debugtoolbar import DebugToolbarExtension 10 | from flask.ext.sqlalchemy import SQLAlchemy 11 | from flask.ext.wtf import Form 12 | from flask.ext.codemirror import CodeMirror 13 | from flask.ext.pagedown import PageDown 14 | #from flask.ext.script import Manager 15 | from flask.ext.alembic import Alembic 16 | 17 | 18 | #manager = Manager() 19 | pagedown = PageDown() 20 | db = SQLAlchemy() 21 | codemirror = CodeMirror() 22 | alembic = Alembic() 23 | # Almost any modern Flask extension has special init_app() 24 | # method for deferred app binding. But there are a couple of 25 | # popular extensions that no nothing about such use case. 26 | 27 | toolbar = lambda app: DebugToolbarExtension(app) # has no init_app() 28 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/three_column_center.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block header %} 3 | {% include 'header.html' with context %} 4 | {% endblock header %} 5 | {% block body %} 6 |
    7 | 10 |
    11 |
    12 | 15 |
    16 |
    17 |

    {{ lipsum() }}

    18 |

    {{ lipsum() }}

    19 |
    20 |
    21 | 24 |
    25 |
    26 |
    27 | {% endblock body %} 28 | -------------------------------------------------------------------------------- /tests/setup_app.py: -------------------------------------------------------------------------------- 1 | from flask_xxl.main import AppFactory 2 | from flask_xxl.basemodels import BaseMixin as BaseModel 3 | from test_settings import BaseConfig 4 | from flask_testing import TestCase 5 | 6 | 7 | class TestApp(TestCase): 8 | 9 | def setUp(self): 10 | BaseModel.metadata.bind = BaseModel.engine 11 | BaseModel.metadata.bind.echo = True 12 | BaseModel.metadata.create_all() 13 | 14 | def create_app(self): 15 | from . import app 16 | self.app = app 17 | self.client = self.app.test_client() 18 | return self.app 19 | 20 | def test_one(self): 21 | self.assertTrue(True) 22 | 23 | def test_two(self): 24 | self.assertEquals(set(['admin','page','auth']),set(self.app.blueprints.keys())) 25 | 26 | def test_three(self): 27 | res = self.client.get('/') 28 | self.assertEquals('',res.data) 29 | 30 | def test_four(self): 31 | res = self.client.get() 32 | 33 | 34 | if __name__ == "__main__": 35 | main() 36 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_service/.mrbob.ini: -------------------------------------------------------------------------------- 1 | [template] 2 | pre_render = flask_xxl.hooks:get_service_type 3 | 4 | 5 | [questions] 6 | service.app_var.question = 'App var name' 7 | service.app_var.default = app 8 | 9 | service.service_name.question = 'service name' 10 | 11 | service.service_type.question = 'type of service' 12 | service.service_type.post_ask_question = mrbob.hooks:validate_choices 13 | service.service_type.choices = service factory provider value constant 14 | service.service_type.default = factory 15 | 16 | service.deps.question = 'does this service have dependencys' 17 | service.deps.post_ask_question = mrbob.hooks:to_boolean 18 | service.deps.post_ask_question = flask_xxl.hooks:skip_next_if_not 19 | 20 | service.dep_names.question = 'dependency names, in a comma seperated list' 21 | service.dep_names.post_ask_question = flask_xxl.hooks:to_list 22 | 23 | service.service_value.question = 'service value' 24 | service.service_value.pre_ask_question = flask_xxl.hooks:only_if_type 25 | service.service_value.check_type = constant 26 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/utils.py: -------------------------------------------------------------------------------- 1 | from flask import session, g, flash, redirect, url_for, request 2 | from functools import wraps 3 | 4 | 5 | def login_user(user): 6 | session['logged_in'] = True 7 | session['user_id'] = user.id 8 | session['email'] = user.email 9 | 10 | def logout_user(user): 11 | session.pop('logged_in',None) 12 | session.pop('user_id',None) 13 | session.pop('email',None) 14 | 15 | def login_required(view): 16 | @wraps(view) 17 | def wrapper(*args, **kwargs): 18 | if 'logged_in' in session: 19 | return view(*args, **kwargs) 20 | else: 21 | flash('You need to login first.') 22 | return redirect(url_for('auth.login',next=request.url)) 23 | return wrapper 24 | 25 | def admin_required(view): 26 | @wraps(view) 27 | def wrapper(*args,**kwargs): 28 | if 'user_id' in session: 29 | if User.get_by_id(session.get('user_id')).is_admin: 30 | return view(*args,**kwargs) 31 | flash('You need to login as an administrator to access that page.') 32 | return redirect(url_for('auth.login',next=request.url)) 33 | return wrapper 34 | 35 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/info/info_page.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | 4 | {% block content %} 5 |
    6 | 9 |
    10 |

    11 | As I've already said, you can find more info about Flask Kit on 12 | GitHub. 13 | There is nothing to learn, just follow the proposed project structure. 14 |

    15 |

    16 | This Kit uses some popular extensions, like 17 | Flask-DebugToolbar and 18 | Flask-SQLAlchemy. 19 | Feel free to install more, but don't forget to define them in ext.py and to register them 20 | in settings.py. You can find examples in the code. 21 |

    22 |
    23 |
    24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/manage.py.bob: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | manage 6 | ~~~~~~ 7 | """ 8 | import subprocess 9 | from flask.ext.script import Shell, Manager, prompt_bool 10 | from flask.ext.script.commands import ShowUrls,Clean 11 | from flask.ext.xxl.mr_bob import manager as mrbob_manager 12 | from flask.ext.alembic.cli.script import manager as alembic_manager 13 | from app import app 14 | from ext import db,alembic 15 | 16 | manager = Manager(app) 17 | 18 | 19 | 20 | @manager.command 21 | def init_data(): 22 | """Fish data for project""" 23 | if prompt_bool('Do you want to kill your db?'): 24 | try: 25 | db.drop_all() 26 | except: 27 | pass 28 | try: 29 | db.create_all() 30 | except: 31 | pass 32 | 33 | manager.add_command('shell', Shell(make_context=lambda:{'app': app, 'db': db})) 34 | 35 | 36 | if __name__ == '__main__': 37 | manager.add_command('clean',Clean()) 38 | manager.add_command('urls',ShowUrls()) 39 | manager.add_command('mrbob',mrbob_manager) 40 | manager.add_command('db',alembic_manager) 41 | manager.run() 42 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | import os 3 | from os import path as op 4 | 5 | exists = lambda x: op.exists(op.join(os.getcwd(),x)) 6 | 7 | page = Blueprint('page',__name__, 8 | template_folder='templates', 9 | static_url_path='/_page_static', 10 | static_folder=os.path.abspath(os.path.dirname(__file__)), 11 | url_prefix='/page') 12 | ''' 13 | @page.before_app_request 14 | def add_pages(): 15 | pages = Page._session.query(Page).filter(Page.add_to_nav==True).all() 16 | if flask.request.view_args: 17 | slug = flask.request.view_args.get('slug') 18 | else: 19 | slug = None 20 | if slug is None: 21 | slug = flask.request.endpoint 22 | for page in pages: 23 | if page.slug == slug: 24 | page._current = True 25 | else: 26 | page._current = False 27 | app.jinja_env.globals['page_width'] = '-fluid' 28 | app.jinja_env.globals['pages'] = pages 29 | app.jinja_env.globals['slug'] = slug 30 | ''' 31 | @page.before_app_request 32 | def get_contact_us_data(): 33 | #data = json.loads(open(os.path.join(base,'contact_data.json'),'r').read()) 34 | #app.jinja_env.globals['data'] = data 35 | pass 36 | -------------------------------------------------------------------------------- /flask_xxl/filters.py: -------------------------------------------------------------------------------- 1 | from flask import Markup 2 | try: 3 | from markdown2 import markdown as md2 4 | except ImportError: 5 | from markdown import markdown as md2 6 | # Jinja 7 | 8 | def date(value): 9 | """Formats datetime object to a yyyy-mm-dd string.""" 10 | return value.strftime('%Y-%m-%d') 11 | 12 | 13 | def date_pretty(value): 14 | """Formats datetime object to a Month dd, yyyy string.""" 15 | return value.strftime('%B %d, %Y') 16 | 17 | 18 | def datetime(value): 19 | """Formats datetime object to a mm-dd-yyyy hh:mm string.""" 20 | return value.strftime('%m-%d-%Y %H:%M') 21 | 22 | 23 | def pluralize(value, one='', many='s'): 24 | """Returns the plural suffix when needed.""" 25 | return one if abs(value) == 1 else many 26 | 27 | 28 | def month_name(value): 29 | """Return month name for a month number.""" 30 | from calendar import month_name 31 | return month_name[value] 32 | 33 | 34 | def markdown(value): 35 | """Convert plain text to HTML.""" 36 | extras = ['fenced-code-blocks', 'wiki-tables','attr_list', 37 | 'fenced-code','def-list','tables','extras','meta', 38 | 'nl2br','smart_lists','toc','markdown_checklist.extension'] 39 | return Markup(md2(value, extras=extras)) 40 | 41 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% macro render_link(obj) %} 3 | View 4 | {% endmacro %} 5 | {% macro render_post(article) %} 6 |
    7 |

    {{ article.title }}

    8 | 9 |

    {{ article.date_added|date_pretty }} | {{ article.category }}

    10 |

    {{ article.content | markdown | truncate(10) }}

    11 | 12 |
    13 | {{ render_link(article) }} 14 |
    15 | {% endmacro %} 16 | {% block header %} 17 | {% include 'header.html' with context %} 18 | {% endblock header %} 19 | {% block body %} 20 | {% if articles %} 21 | {% for a in articles %} 22 | {{ render_post(a) }} 23 | {% endfor %} 24 | {% endif %} 25 | {% endblock body %} 26 | {% block footer_js %} 27 | {{ super() }} 28 | 41 | 42 | {% endblock footer_js %} 43 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% from '_bs3macros.html' import render_form %} 3 | {% macro render_bs3_field(type,class,placeholder,value='') %} 4 | {% if kwargs.get('size','small') == 'small' %} 5 |
    6 | {% endif %} 7 |
    8 | 9 | {% if kwargs.get('size','small') == 'small' %} 10 |
    11 |
    12 | {% endif %} 13 | {% endmacro %} 14 | 15 | {% macro render_field(field) %} 16 | {{ render_bs3_field(field.type,field._class,field.text) }} 17 | {% endmacro %} 18 | 19 | {% block body %} 20 |
    21 |
    22 |
    23 |
    24 |
    25 |

    Sign up

    26 |
    27 |
    28 | {{ render_form(form=form,action='',action_text='Register',btn_class='btn btn-primary') }} 29 |
    30 |
    31 |
    32 |
    33 |
    34 | {% endblock body %} 35 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/models.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy as sa 2 | from ...basemodels import BaseMixin 3 | from LoginUtils import check_password, encrypt_password 4 | from ...baseviews import is_verbose 5 | 6 | 7 | if is_verbose(): 8 | print 'importing flask_xxl.apps.admin.models as ',__name__ 9 | 10 | 11 | class Setting(BaseMixin): 12 | 13 | name = sa.Column(sa.String(255),nullable=False,unique=True) 14 | setting_type_id = sa.Column(sa.Integer,sa.ForeignKey('types.id')) 15 | setting_type = sa.orm.relationship('Type',backref=sa.orm.backref( 16 | 'settings',lazy='dynamic')) 17 | default = sa.Column(sa.String(255)) 18 | value = sa.Column(sa.String(255)) 19 | 20 | @property 21 | def widget(self): 22 | if self.type: 23 | return self.type.widgets 24 | else: 25 | return '' 26 | 27 | class Type(BaseMixin): 28 | 29 | name = sa.Column(sa.String(255),nullable=False) 30 | widgets = sa.orm.relationship('Widget',backref=sa.orm.backref( 31 | 'type'),lazy='dynamic') 32 | html = sa.Column(sa.Text) 33 | field_type = sa.Column(sa.String(255)) 34 | required = sa.Column(sa.Boolean,default=False) 35 | data_type = sa.Column(sa.String(255)) 36 | 37 | def __repr__(self): 38 | return self.name or '' 39 | 40 | class Widget(BaseMixin): 41 | 42 | name = sa.Column(sa.String(255),nullable=False) 43 | title = sa.Column(sa.String(255)) 44 | content = sa.Column(sa.Text,nullable=False) 45 | type_id = sa.Column(sa.Integer,sa.ForeignKey('types.id')) 46 | 47 | def __repr__(self): 48 | return self.name 49 | 50 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/forms.py: -------------------------------------------------------------------------------- 1 | from flask.ext.codemirror.fields import CodeMirrorField 2 | from flask.ext.pagedown.fields import PageDownField 3 | from flask.ext.wtf import Form 4 | from flask.ext.wtf.recaptcha.fields import RecaptchaField 5 | from wtforms import fields,validators 6 | 7 | from .fields import CKTextEditorField 8 | 9 | class EditContentForm(Form): 10 | content = CKTextEditorField('content') 11 | 12 | 13 | class ContactUsForm(Form): 14 | name = fields.StringField('Name',validators=[validators.InputRequired()]) 15 | email = fields.StringField('Email',validators=[validators.InputRequired()]) 16 | #subject = fields.SelectField('Subject',validators=[validators.Optional()],choices=get_choices()) 17 | message = fields.TextAreaField('Message',validators=[validators.InputRequired()]) 18 | recaptcha = RecaptchaField('are you a human') 19 | ip_address = fields.HiddenField() 20 | 21 | 22 | class ContactUsSettingsForm(Form): 23 | address = fields.StringField('Business Address') 24 | email = fields.StringField('Contact Email') 25 | phone = fields.StringField('Contact Phone Number') 26 | hours = fields.StringField('Company Hours') 27 | facebook_link = fields.StringField('Facebook link') 28 | twitter_link = fields.StringField('twitter Link') 29 | google_link = fields.StringField('Google+ link') 30 | 31 | 32 | class TestForm(Form): 33 | test = fields.RadioField( 34 | 'test', 35 | choices = ( 36 | ('val','label'), 37 | ('val2','label2'), 38 | ('val3','label3'), 39 | ('val4','label4'), 40 | ) 41 | ) 42 | 43 | class AddPageForm(Form): 44 | pass 45 | 46 | class FrontendEditPageForm(Form): 47 | title = fields.StringField('Title') 48 | content = PageDownField('Content') 49 | -------------------------------------------------------------------------------- /flask_xxl/cli.py: -------------------------------------------------------------------------------- 1 | from flask.ext.script import prompt_bool 2 | from manage import manager 3 | from mrbob import cli 4 | import sys 5 | import os 6 | from flask_xxl import flaskxxl 7 | 8 | 9 | 10 | def get_template_dir(): 11 | EXT_ROOT = os.path.abspath(os.path.dirname(__file__)) 12 | return os.path.join(EXT_ROOT,'templates') 13 | 14 | 15 | def print_usage(): 16 | print 'flaskxxl-manage.py [start-project] [start-blueprint]' 17 | 18 | def run_mrbob(template_dir,testing): 19 | if testing: 20 | target_dir = './testing' 21 | else: 22 | target_dir = os.curdir 23 | if target_dir == os.curdir: 24 | if not prompt_bool('not testing, are you sure you want to continue...'): 25 | sys.exit(0) 26 | list_questions = False 27 | non_interactive = False 28 | args = [template_dir,'-O',target_dir,'-v','-w'] 29 | cli.main(args) 30 | 31 | 32 | @manager.command 33 | def start_blueprint(testing=False): 34 | template_dir = os.path.join(get_template_dir(),'blueprint') 35 | run_mrbob(template_dir,testing) 36 | 37 | 38 | 39 | 40 | @manager.command 41 | def start_project(testing=False): 42 | template_dir = os.path.join(get_template_dir(),'project') 43 | run_mrbob(template_dir,testing) 44 | 45 | 46 | def main(): 47 | func = None 48 | testing = False 49 | if len(sys.argv) <= 1: 50 | print_usage() 51 | else: 52 | if sys.argv[1] == 'start-project': 53 | func = start_project 54 | elif sys.argv[1] == 'start-blueprint': 55 | func = start_blueprint 56 | else: 57 | print_usage() 58 | sys.exit() 59 | if len(sys.argv) > 2: 60 | if sys.argv[2] == '-t' or sys.argv[2] == '--testing': 61 | testing = True 62 | else: 63 | testing = False 64 | if func is not None: 65 | func(testing) 66 | 67 | if __name__ == "__main__": 68 | with flaskxxl.context() as ctx: 69 | manager.run() 70 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/utils.py: -------------------------------------------------------------------------------- 1 | from ext import db 2 | from flask import url_for 3 | from settings import BaseConfig 4 | 5 | class Pagination(object): 6 | _model = None 7 | _query = None 8 | _page_num = 1 9 | _objs = [] 10 | _obj_page = [] 11 | _per_page = BaseConfig.ADMIN_PER_PAGE 12 | _page_count = _per_page 13 | 14 | def __init__(self,model,page_num=None,query=None): 15 | if query is None: 16 | self._query = model.query.order_by(db.desc(model.id)) 17 | else: 18 | self._query = query 19 | self._model = model 20 | self._page_num = page_num or self._page_num 21 | self._objs = self._query.all() 22 | self.set_page(self._objs) 23 | 24 | def set_page(self,obj_list): 25 | s = ((self._per_page*self._page_num)-self._per_page) 26 | self._obj_page = [] 27 | total = 0 28 | while total != self._per_page: 29 | for i in range(self._per_page): 30 | try: 31 | self._obj_page.append(obj_list[s]) 32 | except: 33 | break 34 | finally: 35 | s+=1 36 | total += 1 37 | break 38 | 39 | 40 | 41 | @property 42 | def has_next(self): 43 | return self._page_num != self._per_page*len(self._objs) 44 | 45 | @property 46 | def has_prev(self): 47 | return self._page_num != 1 48 | 49 | @property 50 | def next_link(self): 51 | if self.has_next: 52 | return url_for('admin.page_{}'.format(self._model.__name__.lower()),page_num=self.page_num+1) 53 | return '#' 54 | 55 | @property 56 | def prev_link(self): 57 | if self.has_prev: 58 | return url_for('admin.page_{}'.format(self._model.__name__.lower()),page_num=self._page_num-1) 59 | return '#' 60 | 61 | 62 | def get_pk(obj): 63 | if 'id' in obj.__dict__: 64 | return obj.id 65 | raise IOError 66 | 67 | -------------------------------------------------------------------------------- /flask_xxl/hooks.py: -------------------------------------------------------------------------------- 1 | from mrbob import bobexceptions as mbe 2 | 3 | # post question hooks - takes (configurator,question,answer) 4 | def skip_next_if(c,q,a): 5 | if a: 6 | c.questions.pop(c.questions.index(q)+1) 7 | return a 8 | 9 | def skip_next_if_not(c,q,a): 10 | if not a: 11 | c.questions.pop(c.questions.index(q)+1) 12 | return a 13 | 14 | def to_list(c,q,a): 15 | return a.split(',') 16 | 17 | def choose_app_template(c): 18 | if c.variables['app.language'].startswith('c'): 19 | c.template_dir = '{}_coffee'.format(c.template_dir) 20 | 21 | # pre question hooks - takes (configurator,question) 22 | def only_if_type(c,q): 23 | print c.variables['service.service_type'] 24 | print q.extra.get('check_type') 25 | if c.variables['service.service_type'] != q.extra.get('check_type'): 26 | raise mbe.SkipQuestion 27 | 28 | def add_apps(c,q): 29 | if not c.variables['project.add_apps']: 30 | raise mbe.SkipQuestion 31 | 32 | def check_for_captcha(c,q): 33 | if not c.variables['local_settings.use_captcha']: 34 | raise mbe.SkipQuestion 35 | 36 | def db_related_skip(c,q): 37 | if not c.variables['project.use_database']: 38 | raise mbe.SkipQuestion 39 | 40 | # pre render hooks - takes nothing - maybe configurator 41 | 42 | def get_service_type(c): 43 | c.template_dir = '{}/{}'.format(c.template_dir,c.variables['service.service_type']) 44 | 45 | def make_db_uri(c): 46 | if c.variables['project.use_database']: 47 | fmt = '%s://%s:%s@%s:%d/%s' 48 | c.variables['project.db_uri'] = fmt % (c.variables['project.db-driver'], 49 | c.variables['project.db-username'], 50 | c.variables['project.db-password'], 51 | c.variables['project.db-host'], 52 | c.variables['project.db-port'], 53 | c.variables['project.db-name']) 54 | # post render hooks - takes (configurator) 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/templates/buttons.html: -------------------------------------------------------------------------------- 1 | {# Macros for using Bootstrap3 buttons #} 2 | {# file contain: #} 3 | {# render_icon, render_button, render_save_button, render_edit_button, render_view_button, render_del_button #} 4 | 5 | {# PRIVATE #} 6 | {% macro _create_button_class(color,size,link=false) %} 7 | {% if color == 'yellow' %} 8 | {% set color = 'warning' %} 9 | {% elif color == 'green' %} 10 | {% set color = 'success' %} 11 | {% elif color == 'red' %} 12 | {% set color = 'danger' %} 13 | {% elif color == 'grey' %} 14 | {% set color = 'default' %} 15 | {% else %} 16 | {% set color = 'primary' %} 17 | {% endif %} 18 | {% if not size == '' %} 19 | {% set size = 'btn-%s' % size %} 20 | {% endif %} 21 | {% set color = 'btn-%s' % color %} 22 | {% set _class = 'btn %s %s' % (size,color) %} 23 | {{ _class }} 24 | {% endmacro %} 25 | 26 | {% macro render_icon(name,lib='glyph') %} 27 | 28 | {% endmacro %} 29 | 30 | {% macro render_button(type,class,text,icon) %} 31 | 32 | {% endmacro %} 33 | 34 | {% macro render_save_button(size='',color='blue') %} 35 | {% set _class = '%s' % _create_button_class(color,size) %} 36 | {{ render_button('submit',_class,'Save','floppy-disc') }} 37 | {% endmacro %} 38 | 39 | {% macro render_edit_button(size='',color='blue') %} 40 | {% set _class = '%s' % _create_button_class(color,size) %} 41 | {{ render_button('submit',_class,'Edit','pencil') }} 42 | {% endmacro %} 43 | 44 | {% macro render_view_button(size='',color='blue') %} 45 | {% set _class = '%s' % _create_button_class(color,size) %} 46 | {{ render_button('submit',_class,'View','eye-open') }} 47 | {% endmacro %} 48 | 49 | {% macro render_del_button(size='',color='blue') %} 50 | {% set _class = '%s' % _create_button_class(color,size) %} 51 | {{ render_button('submit',_class,'Delete','trash') }} 52 | {% endmacro %} 53 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_app/app/+app.app_name+/+app.app_name+.js.bob: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var {{{ app.app_var|default('app') }}} = angular.module('{{{ app.app_name }}}',[{{%- 4 | if app.deps 5 | -%}} 6 | {{%- 7 | for dep in app.dep_names 8 | -%}} 9 | '{{{ dep }}}' 10 | {{%- 11 | if app.dep_names|count 12 | > 1 and 13 | app.dep_names.index(dep) 14 | != app.dep_names|count-1 15 | -%}}, 16 | {{%- 17 | endif 18 | -%}} 19 | {{%- 20 | endfor 21 | -%}} 22 | {{%- 23 | endif 24 | -%}}]); 25 | 26 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/filters.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import math 3 | 4 | __all__ = ['human_time','date','date_pretty','datetime','pluralize','month_name'] 5 | 6 | def datetimeformat(value): 7 | delta = datetime.datetime.now() - value 8 | if delta.days == 0: 9 | formatting = 'today' 10 | elif delta.days < 10: 11 | formatting = '{0} days ago'.format(delta.days) 12 | elif delta.days < 20: 13 | formatting = '{0} weeks ago'.format(int(math.ceil(delta.days/7.0))) 14 | elif value.year == datetime.datetime.now().year: 15 | formatting = 'this year, on %d %b' 16 | else: 17 | formatting = 'on %b %d %Y' 18 | return value.strftime(formatting) 19 | 20 | def parse_date(date): 21 | import dateutil.parser as p 22 | return p.parse(str(date)) 23 | 24 | 25 | def human_time(timestamp): 26 | d = parse_date(timestamp) 27 | return datetimeformat(datetime.datetime(d.year,d.month,d.day)) 28 | 29 | def date(value): 30 | """Formats datetime object to a yyyy-mm-dd string.""" 31 | value = parse_date(value) 32 | return value.strftime('%Y-%m-%d') 33 | 34 | def date_time_pretty(value): 35 | """Formats datetime object to a Month dd, yyyy string.""" 36 | value = parse_date(value) 37 | return value.strftime('%B %d, %Y at %I:%M %P') 38 | 39 | def date_pretty(value): 40 | """Formats datetime object to a Month dd, yyyy string.""" 41 | value = parse_date(value) 42 | return value.strftime('%h %d, %Y' ) 43 | 44 | 45 | def dt(value): 46 | """Formats datetime object to a mm-dd-yyyy hh:mm string.""" 47 | value = parse_date(value) 48 | return value.strftime('%m-%d-%Y %H:%M') 49 | 50 | 51 | def pluralize(value, one='', many='s'): 52 | """Returns the plural suffix when needed.""" 53 | value = parse_date(value) 54 | return one if abs(value) == 1 else many 55 | 56 | 57 | def month_name(value): 58 | """Return month name for a month number.""" 59 | value = parse_date(value) 60 | from calendar import month_name 61 | return month_name[value.month] 62 | 63 | def get_day(value): 64 | value = parse_date(value) 65 | return value.day 66 | 67 | 68 | 69 | def split(value,symbol=' '): 70 | return value.split(symbol) 71 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/urls.py: -------------------------------------------------------------------------------- 1 | from .views import (AdminDashboardView,AdminPageView, 2 | AdminTemplateView,AdminBlockView, 3 | AdminCMSListView,AdminListPageView, 4 | AdminDetailView,AdminEditView, 5 | PageListView,AdminBlogView,AdminAddCategoryView, 6 | AdminAddBlogView) 7 | from . import admin 8 | 9 | routes = [ 10 | ((admin), 11 | ('','index',AdminDashboardView), 12 | ('/settings','settings',AdminDashboardView), 13 | ('/add/category','add_category',AdminAddCategoryView), 14 | ('/add/page','add_page',AdminPageView), 15 | ('/add/blog','add_blog',AdminAddBlogView), 16 | ('/edit/page/content','page_content',AdminPageView), 17 | ('/add/template','add_template',AdminTemplateView), 18 | ('/add/block','add_block',AdminBlockView), 19 | ('/edit/block/content','block_content',AdminBlockView), 20 | ('/list/blocks','blocks',AdminCMSListView), 21 | ('/list/pages','pages',AdminCMSListView), 22 | ('/list/users','users',AdminCMSListView), 23 | ('/list/templates','templates',AdminCMSListView), 24 | ('/paged/page/','page_page',AdminListPageView), 25 | ('/paged/user/','page_users',AdminListPageView), 26 | ('/paged/template/','page_template',AdminListPageView), 27 | ('/paged/block/','page_block',AdminListPageView), 28 | ('/view/block/','block_view',AdminDetailView), 29 | ('/view/page/','page_view',AdminDetailView), 30 | ('/view/template/','template_view',AdminDetailView), 31 | ('/edit/block/','edit_block',AdminEditView), 32 | ('/edit/page/','edit_page',AdminEditView), 33 | ('/edit/template/','edit_template',AdminEditView), 34 | ('/edit/page/content/','edit_page_content',AdminEditView), 35 | ('/edit/block/content/','edit_block_content',AdminEditView), 36 | ('/pages','page_list',PageListView), 37 | ('/blogs','blogs',AdminBlogView), 38 | ) 39 | ] 40 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/templates/_bs3macros.html: -------------------------------------------------------------------------------- 1 | {# render_bs3_field(field,label=true) #} 2 | {% macro render_field(field,label=true) %} 3 |
    4 | {% if (field.type != 'HiddenField' and field.type != 'CSRFTokenField') and label %} 5 | 6 | {% endif %} 7 | {{ field(class_='form-control',**kwargs) }} 8 | {% if field.errors %} 9 | {% for e in field.errors %} 10 |

    {{ e }}

    11 | {% endfor %} 12 | {% endif %} 13 |
    14 | {% endmacro %} 15 | 16 | {% macro render_checkbox(field) %} 17 |
    18 | 21 |
    22 | {% endmacro %} 23 | 24 | {% macro render_radio(field) %} 25 | {% for value,label,_ in field.iter_choices() %} 26 |
    27 | 30 |
    31 | {% endfor %} 32 | {% endmacro %} 33 | 34 | {% macro render_form(form,action='',action_text='Submit',class_='',btn_class='btn btn-default') %} 35 |
    36 | {{ form.hidden_tag() if form.hidden_tag }} 37 | {% if caller %} 38 | {{ caller() }} 39 | {% else %} 40 | {% for f in form %} 41 | {% if f.type == 'BooleanField' %} 42 | {{ render_checkbox(f) }} 43 | {% elif f.type == 'RadioField' %} 44 | {{ render_radio(f) }} 45 | {% else %} 46 | {{ render_field(f) }} 47 | {% endif %} 48 | {% endfor %} 49 | {% endif %} 50 | 51 |
    52 | {% endmacro %} 53 | 54 | -------------------------------------------------------------------------------- /flask_xxl/templates/angular_app_coffee/app/+app.app_name+/+app.app_name+.coffee.bob: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | {{{ app.app_var|default('app') }}} = angular.module '{{{ app.app_name }}}',[ 4 | {{%- 5 | if app.deps 6 | -%}} 7 | {{%- 8 | for dep in app.dep_names 9 | -%}} 10 | '{{{ dep }}}' 11 | {{%- 12 | if app.dep_names|count 13 | > 1 and 14 | app.dep_names.index(dep) 15 | != app.dep_names|count-1 16 | -%}}, 17 | {{%- 18 | endif 19 | -%}} 20 | {{%- 21 | endfor 22 | -%}} 23 | {{%- 24 | endif 25 | -%}}] 26 | 27 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/admin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from flask import current_app 4 | from flask_admin import Admin, BaseView,expose,helpers,AdminIndexView 5 | from flask_admin.contrib.sqla.view import ModelView 6 | from .models import Page, Category 7 | from .forms import ContactUsSettingsForm 8 | from . import page 9 | 10 | 11 | 12 | 13 | class IndexView(AdminIndexView): 14 | @expose() 15 | def index(self,*args,**kwargs): 16 | return self.render('admin/dashboard.html') 17 | 18 | admin = Admin(name='Page Admin',index_view=IndexView(),template_mode='bootstrap3') 19 | 20 | @page.before_app_first_request 21 | def add_admin(): 22 | current_app.test_request_context().push() 23 | admin.init_app(current_app) 24 | current_app.test_request_context().pop() 25 | 26 | class PageAdmin(ModelView): 27 | column_list = ['title','slug','template_file','date_modified'] 28 | 29 | def __init__(self,*args,**kwargs): 30 | ModelView.__init__(self,model=Page,session=Page._session,*args,**kwargs) 31 | 32 | class ContactUsAdmin(BaseView): 33 | @expose('/',methods=['post','get']) 34 | def index(self): 35 | form = ContactUsSettingsForm(flask.request.form) 36 | if flask.request.method.lower() == 'post': 37 | data = dict( 38 | address=form.address.data, 39 | email=form.email.data, 40 | phone=form.phone.data, 41 | hours=form.hours.data, 42 | facebook_link=form.facebook_link.data, 43 | twitter_link=form.twitter_link.data, 44 | google_link=form.google_link.data, 45 | ) 46 | with open(os.path.join(base,'contact_data.json'),'w') as f: 47 | f.write(json.dumps(data)) 48 | data = json.loads(open(os.path.join(base,'contact_data.json'),'r').read()) 49 | form = ContactUsSettingsForm(**data) 50 | return self.render('admin/settings.html',form=form) 51 | 52 | class CategoryAdmin(ModelView): 53 | def __init__(self,*args,**kwargs): 54 | ModelView.__init__(self,model=Category,session=Category._session,*args,**kwargs) 55 | 56 | for category,_admin in [ 57 | ('settings',ContactUsAdmin), 58 | ('cms',PageAdmin), 59 | ('cms',CategoryAdmin), 60 | ]: 61 | admin.add_view( 62 | _admin( 63 | category = category 64 | ) 65 | ) 66 | -------------------------------------------------------------------------------- /flask_xxl/testing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | testing.py 4 | ~~~~~~~ 5 | TestCase for testing 6 | :license: BSD3 7 | """ 8 | from flask.ext.testing import TestCase 9 | from .main import AppFactory 10 | from .basemodels import BaseMixin as BaseModel 11 | from settings import TestingConfig 12 | from sqlalchemy import MetaData 13 | 14 | meta = BaseModel.metadata 15 | 16 | 17 | class BaseTestCase(TestCase): 18 | 19 | def create_app(self): 20 | return AppFactory(TestingConfig).get_app(__name__) 21 | 22 | def setUp(self): 23 | self.app = self.create_app() 24 | self.db = BaseModel 25 | import sqlalchemy_utils as squ 26 | if squ.database_exists(self.db.engine.url): 27 | squ.drop_database(self.db.engine.url) 28 | squ.create_database(self.db.engine.url) 29 | #import imports 30 | #for module in dir(imports): 31 | # globals()[module] = getattr(imports,module) 32 | meta.bind = self.db.engine 33 | meta.create_all() 34 | 35 | def tearDown(self): 36 | self.db.session.close() 37 | meta.drop_all() 38 | 39 | 40 | def assertContains(self, response, text, count=None, 41 | status_code=200, msg_prefix=''): 42 | """ 43 | Asserts that a response indicates that some content was retrieved 44 | successfully, (i.e., the HTTP status code was as expected), and that 45 | ``text`` occurs ``count`` times in the content of the response. 46 | If ``count`` is None, the count doesn't matter - the assertion is true 47 | if the text occurs at least once in the response. 48 | """ 49 | 50 | if msg_prefix: 51 | msg_prefix += ": " 52 | 53 | self.assertEqual(response.status_code, status_code, 54 | msg_prefix + "Couldn't retrieve content: Response code was %d" 55 | " (expected %d)" % (response.status_code, status_code)) 56 | 57 | real_count = response.data.count(text) 58 | if count is not None: 59 | self.assertEqual(real_count, count, 60 | msg_prefix + "Found %d instances of '%s' in response" 61 | " (expected %d)" % (real_count, text, count)) 62 | else: 63 | self.assertTrue(real_count != 0, 64 | msg_prefix + "Couldn't find '%s' in response" % text) 65 | -------------------------------------------------------------------------------- /flask_xxl/apps/menu/context_processors.py: -------------------------------------------------------------------------------- 1 | # define bluepriont specific context processors 2 | 3 | def example_context(): 4 | return {'example':'value'} 5 | 6 | '''def nav(): 7 | brand = 'Admin' 8 | nav_links = { 9 | 'Home':'index', 10 | 'Files':'index', 11 | 'Other':'index' 12 | } 13 | 14 | dropdowns = { 15 | 'Dropdown One':{ 16 | 'nothing':'index', 17 | 'more':'index', 18 | 'sep':None, 19 | 'more nothing':'admin' 20 | } 21 | } 22 | ''' 23 | def frontend_nav(): 24 | return {'nav_links': 25 | ( 26 | ('core.index','Home'), 27 | ('blog.index','Blog'), 28 | ('core.meet','Meet Us'), 29 | ('core.about','About'), 30 | ('core.contact','Contact'), 31 | ), 32 | 'nav_title':'My Site', 33 | } 34 | 35 | def admin_nav(): 36 | return {'admin_nav_links': 37 | ( 38 | ('DashBoard','admin.index'), 39 | ), 40 | 'admin_nav_title':'Admin', 41 | 'admin_dropdowns':( 42 | dict( 43 | Manage=dict( 44 | Users='admin.users', 45 | Blogs='admin.blogs', 46 | Settings='admin.settings', 47 | ) 48 | ), 49 | dict( 50 | List=dict( 51 | Blocks='admin.blocks', 52 | Pages='admin.pages', 53 | Templates='admin.templates', 54 | ) 55 | ), 56 | dict( 57 | Add=dict( 58 | Template='admin.add_template', 59 | Page='page.add_page', 60 | Block='admin.add_block', 61 | Blog='admin.add_blog', 62 | ) 63 | ), 64 | ), 65 | } 66 | 67 | 68 | 69 | 70 | # frontend nav title 71 | 72 | 73 | #def admin_title(): 74 | # return {'admin_title':'Admin'} 75 | # register this in settings.py to make it avialible in all templates 76 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/views.py: -------------------------------------------------------------------------------- 1 | from flask import session,redirect,request,render_template 2 | from flask_xxl.baseviews import BaseView 3 | from . import auth 4 | from .forms import UserLoginForm, UserSignupForm 5 | from .utils import login_user, logout_user, login_required, admin_required 6 | from sqlalchemy.exc import IntegrityError 7 | 8 | 9 | class AuthLoginView(BaseView): 10 | _template = 'auth_login.html' 11 | _form = UserLoginForm 12 | _context = {} 13 | 14 | def get(self): 15 | return self.render() 16 | 17 | def post(self): 18 | form = self._form() 19 | from .models import User 20 | if form.validate(): 21 | email = form.email.data 22 | pw = form.password.data 23 | remember = form.keep_me_logged_in.data 24 | u = User.get_by_email(email) 25 | if u is not None: 26 | if u.check_password(pw): 27 | login_user(u) 28 | if 'next' in request.args: 29 | return redirect(request.args['next']) 30 | else: 31 | return self.redirect('core.index') 32 | else: 33 | self.flash('incorrect password') 34 | else: 35 | self.flash('user does not exist') 36 | return self.redirect('auth.signup') 37 | return self.render() 38 | 39 | class AuthSignupView(BaseView): 40 | _template = 'register.html' 41 | _form = UserSignupForm 42 | _context = {} 43 | 44 | def get(self): 45 | return self.render() 46 | 47 | def post(self): 48 | from auth.models import User 49 | form = self._form() 50 | if form.validate(): 51 | email = form.email.data 52 | pw = form.password.data 53 | u = User(email=email) 54 | u.password = pw 55 | login_user(u) 56 | self.flash("Thank you for signing up {}".format(email)) 57 | return self.redirect('core.index') 58 | return self.redirect('auth.signup') 59 | 60 | 61 | class AuthLogoutView(BaseView): 62 | 63 | def get(self): 64 | from .models import User 65 | if 'user_id' in session: 66 | user = User.get_by_id(session['user_id']) 67 | logout_user(user) 68 | return self.redirect('core.index') 69 | 70 | 71 | @auth.app_errorhandler(404) 72 | def not_found(e): 73 | return render_template('error.html',error_code=404) 74 | 75 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | VERSION = '0,10,8' 2 | import os 3 | from setuptools import setup, find_packages,findall 4 | from glob import glob 5 | 6 | def get_description(): 7 | return open('README.md','r').read() 8 | 9 | def get_version(): 10 | l,m,s = VERSION.split(',') 11 | return '{0}.{1}.{2}'.format(l,m,s).strip() 12 | 13 | data = os.walk(os.path.dirname(__file__)) 14 | 15 | make_file = lambda dn,f: os.path.join(os.curdir,os.sep,dn,f) 16 | 17 | def get_pkg_data(): 18 | data = os.walk(os.path.abspath(os.curdir)) 19 | pkg_data = [] 20 | 21 | for dn,dl,fl in data: 22 | if 'templates' in dn.split('/'): 23 | for f in fl: 24 | if not f.endswith('.py'): 25 | pkg_data.append(make_file(dn,f)) 26 | return pkg_data 27 | 28 | config = dict( 29 | name='flaskxxl', 30 | version=get_version(),#'0.0.9', 31 | include_package_data=True, 32 | author='Kyle Roux', 33 | author_email='kyle@level2designs.com', 34 | description='quick way to design large flask projects', 35 | long_description=get_description(), 36 | long_description_content_type='text/markdown', 37 | packages=['flask_xxl'], 38 | package_data = {'':findall('flask_xxl')}, #['*.bob','*.html','*.js','*.css','*',]}, 39 | install_requires=[ 40 | 'flask>=0.10.1', 41 | 'flask-alembic==1.0.2', 42 | 'flask-sqlalchemy==2.0', 43 | 'flask-script==2.0.5', 44 | 'flask-WTF==0.10.2', 45 | 'jinja2==2.7.3', 46 | 'LoginUtils==1.0.1', 47 | 'Mako==1.0.0', 48 | 'MarkupSafe==0.23', 49 | 'SQLAlchemy==0.9.8', 50 | 'WTForms==2.0.1', 51 | 'Werkzeug==0.15.3', 52 | 'alembic==0.6.7', 53 | 'argparse==1.2.1', 54 | 'itsdangerous==0.24', 55 | 'wsgiref==0.1.2', 56 | 'six==1.8.0', 57 | 'mr.bob2==0.2.3', 58 | 'Flask-DebugToolbar==0.9.2', 59 | 'Flask-PageDown==0.1.5', 60 | 'Pygments==2.0.1', 61 | 'flask-codemirror==0.0.3', 62 | 'jinja2-highlight==0.6.1', 63 | 'requests==2.5.1', 64 | 'inflection==0.2.1', 65 | 'markdown==2.5.2', 66 | ], 67 | zip_safe=False, 68 | entry_points=dict( 69 | console_scripts='flaskxxl-manage.py=flask_xxl.manage:main' 70 | ), 71 | ) 72 | 73 | if __name__ == "__main__": 74 | setup(**config) 75 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% from '_macros.html' import render_panel_form with context %} 3 | {% block extra_head %} 4 | {% if codemirror %} 5 | {#{{ codemirror.include_codemirror() }}#} 6 | {% endif %} 7 | {% if pagedown %} 8 | {{ pagedown.include_pagedown() }} 9 | {% endif %} 10 | {# 11 | #} 16 | 24 | {% endblock extra_head %} 25 | {% block body %} 26 | {% if obj %} 27 | {% if obj.content %} 28 | 31 | {% endif %} 32 | {% endif %} 33 | {% if form %} 34 |
    35 | {% if form_args %} 36 | {{ render_panel_form(form,**form_args) }} 37 | {% else %} 38 | {{ render_panel_form(form) }} 39 | {% endif %} 40 | {% for f in form %} 41 | {% if f.type == 'FormField' %} 42 | {% for t in f %} 43 | {{ t.type }} 44 | {% endfor %} 45 | {% endif %} 46 | {% endfor %} 47 | 48 |
    49 | {% endif %} 50 | {% endblock %} 51 | {% block footer_js %} 52 | {{ super() }} 53 | 68 | {% endblock footer_js %} 69 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | {##} 12 | {% if title is defined %} 13 | {{ title }} 14 | {% endif %} 15 | 19 | {% block extra_head %} 20 | {% endblock extra_head %} 21 | 22 | 23 | {% if not admin %} 24 |
    25 | {% endif %} 26 | {% block header %} 27 | {% include 'admin/_header.html' with context %} 28 | {% endblock header %} 29 | {% if admin %} 30 |
    31 | {% endif %} 32 | {% block messages %} 33 | {% include 'includes/_messages.html' with context %} 34 | {% endblock messages %} 35 | {% block body %}{% endblock body %} 36 | {% block content %}{% endblock content %} 37 |
    38 | {% block footer %}{% endblock footer %} 39 |
    40 |
    41 | {% block footer_js %} 42 | 43 | 45 | {% if form %} 46 | {% set ck = false %} 47 | {% for f in form %} 48 | {% if f.type == 'FormField' %} 49 | {% for field in f %} 50 | {% if field.type == 'CKTextEditorField' %} 51 | {% set ck = true %} 52 | {% endif %} 53 | {% endfor %} 54 | {% endif %} 55 | {% endfor %} 56 | {% if codemirror %} 57 | {#{{ codemirror.include_codemirror() }}#} 58 | {% endif %} 59 | 60 | 63 | {% endif %} 64 | {% endblock footer_js %} 65 | 66 | 67 | -------------------------------------------------------------------------------- /flask_xxl/apps/auth/models.py: -------------------------------------------------------------------------------- 1 | from flask_xxl.basemodels import BaseMixin 2 | from flask import url_for 3 | from LoginUtils import encrypt_password, check_password 4 | from datetime import datetime 5 | from sqlalchemy import Column,String,Integer,Boolean,Date,DateTime,ForeignKey,UnicodeText,Table 6 | from sqlalchemy.orm import relationship,backref 7 | 8 | 9 | #import sqlalchemy to global namespace 10 | 11 | class UnknownUser(object): 12 | is_unknown = True 13 | 14 | class Role(BaseMixin): 15 | 16 | name = Column(String(255)) 17 | can_view = Column(Boolean,default=True,nullable=False) 18 | can_add = Column(Boolean,default=False,nullable=False) 19 | can_edit = Column(Boolean,default=False,nullable=False) 20 | can_delete = Column(Boolean,default=False,nullable=False) 21 | 22 | class User(BaseMixin): 23 | 24 | username = Column(String(255),unique=True,nullable=False) 25 | first_name = Column(String(255),default="") 26 | last_name = Column(String(255),default="") 27 | email = Column(String(255),nullable=False,unique=True) 28 | role_id = Column(Integer,ForeignKey('roles.id')) 29 | role = relationship('Role',backref=backref( 30 | 'users',lazy='dynamic')) 31 | add_date = Column(DateTime,default=datetime.now) 32 | _pw_hash = Column(UnicodeText,nullable=False) 33 | age = Column(Integer) 34 | 35 | 36 | def __init__(self,*args,**kwargs): 37 | if 'first_name' in kwargs: 38 | self.first_name = kwargs.pop('first_name') 39 | if 'last_name' in kwargs: 40 | self.last_name = kwargs.pop('last_name') 41 | if 'email' in kwargs: 42 | self.email = kwargs.pop('email') 43 | if 'role' in kwargs: 44 | self.role = kwargs.pop('role') 45 | if 'role_id' in kwargs: 46 | self.role_id = kwargs.pop('role_id') 47 | if 'password' in kwargs: 48 | self.password = kwargs.pop('password') 49 | 50 | @property 51 | def is_unknown(self): 52 | return False 53 | 54 | def check_password(self, pw): 55 | return check_password(pw,self._pw_hash) 56 | 57 | @classmethod 58 | def get_by_email(cls, email): 59 | return cls.query.filter_by(email=email).first() 60 | 61 | @property 62 | def password(self): 63 | raise ValueError('Private Value!!!!') 64 | 65 | @password.setter 66 | def password(self,pw): 67 | self._pw_hash = encrypt_password(pw) 68 | 69 | @property 70 | def full_name(self): 71 | return '{} {}'.format(self.first_name.title(),self.last_name.title()) 72 | 73 | def __str__(self): 74 | if self.first_name != "": 75 | rtn = self.full_name 76 | else: 77 | rtn = self.email 78 | return rtn 79 | 80 | def __repr__(self): 81 | return 'User<{} {}'.format(self.email,self.first_name) 82 | 83 | def _get_absolute_url(self): 84 | return url_for('member.profile',member_id=str(int(self.id))) 85 | 86 | def _get_edit_url(self): 87 | return '#' 88 | 89 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import Form 2 | from wtforms.ext.sqlalchemy.orm import QuerySelectField 3 | from wtforms import fields, validators,widgets 4 | from flask_pagedown.fields import PageDownField 5 | from flask_codemirror.fields import CodeMirrorField 6 | from ..page.fields import CKTextEditorField 7 | from wtforms.widgets.html5 import DateInput as DateWidget 8 | 9 | #factory = Type.query.all() 10 | 11 | class AddSettingForm(Form): 12 | name = fields.StringField('Setting Name',validators=[validators.InputRequired()]) 13 | #type = QuerySelectField('setting type',query_factory=factory,validators=[validators.InputRequired()]) 14 | default = fields.StringField('Default Value') 15 | value = fields.StringField('Setting Value') 16 | 17 | class AddSettingTypeForm(Form): 18 | name = fields.StringField('Setting Type Name',validators=[validators.InputRequired()]) 19 | widget = fields.StringField('Input Widget') 20 | 21 | class TestForm(Form): 22 | title = fields.StringField('Title') 23 | content = PageDownField('content') 24 | 25 | class BaseTemplateForm(Form): 26 | template = fields.SelectField('base template',validators=[validators.InputRequired()])#,choices=[('a','a'),('b','b'),('c','c')]) 27 | 28 | class TemplateBodyFieldForm(Form): 29 | body = CKTextEditorField('body') 30 | 31 | 32 | class AddBlogForm(Form): 33 | name = fields.StringField('Blog Name',validators=[validators.InputRequired()]) 34 | title = fields.StringField('Blog Title',validators=[validators.InputRequired()]) 35 | slug = fields.StringField('Url Slug') 36 | author_id = fields.HiddenField() 37 | date_added = fields.HiddenField() 38 | 39 | 40 | 41 | class AddPageForm(Form): 42 | date_added = fields.DateField('Publish On:',format="%m-%d-%Y",widget=DateWidget()) 43 | date_end = fields.DateField('Expire On:',format="%m-%d-%Y",validators=[validators.Optional()],widget=DateWidget()) 44 | name = fields.StringField('Page Name',validators=[validators.InputRequired()]) 45 | description = fields.TextAreaField('Description',validators=[validators.Optional()]) 46 | slug = fields.StringField('Page Slug',validators=[validators.InputRequired()]) 47 | short_url = fields.StringField('Url',validators=[validators.Optional()]) 48 | title = fields.StringField('Page Title',validators=[validators.InputRequired()]) 49 | add_to_nav = fields.BooleanField('Add to Navbar') 50 | add_sidebar = fields.BooleanField('Add Sidebar') 51 | visible = fields.SelectField(choices=((1,'Publish'),(0,'Draft'))) 52 | meta_title = fields.StringField('Meta Title',validators=[validators.InputRequired()]) 53 | content = CodeMirrorField('Content',language='xml',config={'lineNumbers':'true'}) 54 | template = fields.FormField(BaseTemplateForm,label="Template",separator='_') 55 | blocks = fields.SelectMultipleField(label="blocks",choices=[('a','a'),('b','b'),('c','c')]) 56 | category = QuerySelectField('category') 57 | #tags = TagField('Tags') 58 | use_base_template = fields.BooleanField('Use Base Template') 59 | base_template = fields.SelectField('base template',validators=[validators.InputRequired()]) 60 | submit = fields.SubmitField('Save') 61 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% from '_macros.html' import render_pagination %} 3 | {% macro render_obj_table(headings,objs) %} 4 | 5 | 6 | 7 | 8 | {% for itm in headings %} 9 | 10 | {% endfor %} 11 | {% if is_page(obj) %} 12 | 13 | {% endif %} 14 | 15 | 16 | 17 | 18 | 19 | {% for obj in objs %} 20 | 21 | 22 | {% for itm in columns %} 23 | 24 | {% endfor %} 25 | {% if is_page(obj) %} 26 | 32 | {% endif %} 33 | 40 | 46 | 47 | {% endfor %} 48 | 49 |
    {{itm}}View on siteshowedit
    {{obj|attr(itm)}} 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 |
    50 | {% endmacro %} 51 | {% macro render_admin_nav(objs) %} 52 | {% set create_url = '#' %} 53 | {% if objs|count > 0 %} 54 | {% if objs[0]._get_create_url %} 55 | {% set create_url = objs[0]._get_create_url() %} 56 | {% endif %} 57 | {% endif %} 58 | 63 | {% endmacro %} 64 | {% block content %} 65 | {% if objs %} 66 | {{ render_admin_nav(objs) }} 67 | {% if headings is defined and objs is defined %} 68 | {{ render_obj_table(headings,objs) }} 69 | {% endif %} 70 | {% if pagination %} 71 | {{ render_pagination(pagination) }} 72 | {% endif %} 73 | {% endif %} 74 | {% endblock %} 75 | 76 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/models.py: -------------------------------------------------------------------------------- 1 | import flask 2 | import json 3 | import os 4 | import sqlalchemy as sq 5 | from sqlalchemy import orm 6 | sa = sq 7 | import datetime as dt 8 | from flask_xxl.basemodels import AuditMixin,BaseMixin as Model 9 | #try: 10 | # from ext import db as Model 11 | #except ImportError: 12 | # import sys 13 | # print 'the flask blueprint flask_xxl.apps.page needs a database configured as db in the ext module to work correctley' 14 | # sys.exit(1) 15 | 16 | app_base_dir = os.path.abspath(os.path.dirname(__file__)) 17 | 18 | class Template(Model): 19 | name = sq.Column(sq.String(255)) 20 | 21 | class Block(Model): 22 | name = sq.Column(sq.String(255)) 23 | 24 | class Page(Model): 25 | 26 | __tablename__ = 'pages' 27 | 28 | DEFAULT_TEMPLATE = 'page.html' 29 | 30 | id = sq.Column(sq.Integer,primary_key=True) 31 | 32 | parent_id = sa.Column(sa.Integer,sa.ForeignKey('pages.id')) 33 | 34 | children = orm.relationship('Page',backref=orm.backref( 35 | 'parent',remote_side=[id]),lazy='dynamic',primaryjoin='Page.id==Page.parent_id', 36 | cascade='all,delete-orphan') 37 | title = sq.Column(sq.String(255),unique=True,nullable=False) 38 | keywords = sq.Column(sq.Text) 39 | slug = sq.Column(sq.String(255),unique=True, 40 | nullable=False) 41 | template_file = sq.Column(sq.String(255), 42 | nullable=False,default=DEFAULT_TEMPLATE) 43 | meta_title = sa.Column(sa.String(255)) 44 | add_right_sidebar = sq.Column(sq.Boolean,default=False) 45 | add_left_sidebar = sq.Column(sq.Boolean,default=False) 46 | add_to_nav = sq.Column(sq.Boolean,default=False) 47 | body_content = sq.Column(sq.Text) 48 | 49 | _current = False 50 | 51 | @property 52 | def has_children(self): 53 | return bool(any(self.children)) 54 | 55 | def is_parent_to(self,page=False): 56 | if page: 57 | return page.id == self.parent.id 58 | return self.has_children 59 | 60 | def is_child_of(self,page=None): 61 | if page: 62 | return page in self.children 63 | return self.parent is not None 64 | 65 | def add_child(self,page): 66 | # dont reference any page twice 67 | if not page in self.children: 68 | # dont refrence parent 69 | if self.parent and self.parent.id != page.id: 70 | self.children.append(page) 71 | self.save() 72 | 73 | @property 74 | def navlink(self): 75 | return (self.title,self.get_absolute_url()) 76 | 77 | def get_absolute_url(self): 78 | return flask.url_for('.page',slug=self.slug) 79 | 80 | @classmethod 81 | def get_by_slug(cls,slug): 82 | return cls.query().filter(Page.slug==slug).first() 83 | 84 | @classmethod 85 | def get_page_count(cls): 86 | return cls.query().count() 87 | 88 | def __repr__(self): 89 | return '<{}: #{}-{}'.format(self.__class__.__name__,self.id,self.slug) 90 | 91 | def __str__(self): 92 | return unicode(self) 93 | 94 | def __unicode__(self): 95 | return self.title 96 | 97 | 98 | class Category(Model): 99 | 100 | name = sa.Column(sa.String(255),nullable=False,unique=True) 101 | description = sa.Column(sa.Text) 102 | 103 | def __unicode__(self): 104 | return self.name 105 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/.mrbob.ini: -------------------------------------------------------------------------------- 1 | [template] 2 | pre_render = flask.ext.xxl.hooks:make_db_uri 3 | 4 | [questions] 5 | project.name.question = What will you name this webapp 6 | project.name.required = True 7 | project.name.help = You need a name to start a good project 8 | 9 | project.add_apps.question = would you like me to add any of flask_xxl\'s premade apps to your settings 10 | project.add_apps.post_ask_question = mrbob.hooks:to_boolean 11 | 12 | project.add_page.question = add the page app 13 | project.add_page.help = a small, functional cms app 14 | project.add_page.pre_ask_question = flask_xxl.hooks:add_apps 15 | 16 | project.add_menu.question = add the menu app 17 | project.add_menu.help = a menu app to control the navbars 18 | project.add_menu.pre_ask_question = flask_xxl.hooks:add_apps 19 | 20 | project.add_auth.question = add the auth app 21 | project.add_auth.help = a small, app with User and Role models as well as authentication helpers 22 | project.add_auth.pre_ask_question = flask_xxl.hooks:add_apps 23 | 24 | project.add_admin.question = add the admin app 25 | project.add_admin.help = a small, functional admin app that uses flask_admin as a base 26 | project.add_admin.pre_ask_question = flask_xxl.hooks:add_apps 27 | 28 | project.use_database.question = Will this webapp need database support 29 | project.use_database.required = True 30 | project.use_database.default = no 31 | project.use_database.post_ask_question = mrbob.hooks:to_boolean 32 | 33 | project.db-username.question = Database username **(usually not "your" username) 34 | project.db-username.required = True 35 | project.db-username.pre_ask_question = flask.ext.xxl.hooks:db_related_skip 36 | project.db-username.help = the username for the app to login to the db and add tables 37 | 38 | project.db-password.question = password for user to connect to database 39 | project.db-password.pre_ask_question = flask.ext.xxl.hooks:db_related_skip 40 | project.db-password.command_prompt = getpass:getpass 41 | 42 | project.db-host.question = database host 43 | project.db-host.pre_ask_question = flask.ext.xxl.hooks:db_related_skip 44 | project.db-host.default = localhost 45 | 46 | project.db-port.question = port for database connection 47 | project.db-port.pre_ask_question = flask.ext.xxl.hooks:db_related_skip 48 | project.db-port.default = 3306 49 | project.db-port.post_ask_question = mrbob.hooks:to_integer 50 | 51 | project.db-name.question = name of database to connect to 52 | project.db-name.pre_ask_question = flask.ext.xxl.hooks:db_related_skip 53 | 54 | project.db-driver.question = database driver to use 55 | project.db-driver.pre_ask_question = flask.ext.xxl.hooks:db_related_skip 56 | project.db-driver.post_ask_question = mrbob.hooks:validate_choices 57 | project.db-driver.choices = mysql mysql+pymysql postgresql sqlite 58 | 59 | 60 | project.add_migration.question = do you need migration support 61 | project.add_migration.pre_ask_question = flask.ext.xxl.hooks:db_related_skip 62 | 63 | local_settings.use_captcha.question = does this project need recaptcha support added 64 | local_settings.use_captcha.post_ask_question = mrbob.hooks:to_boolean 65 | 66 | local_settings.recaptcha_public.pre_ask_question = flask_xxl.hooks:check_for_captcha 67 | local_settings.recaptcha_public.question = recaptcha public key 68 | 69 | local_settings.recaptcha_private.pre_ask_question = flask_xxl.hooks:check_for_captcha 70 | local_settings.recaptcha_private.question = recaptcha public key 71 | -------------------------------------------------------------------------------- /flask_xxl/mr_bob.py: -------------------------------------------------------------------------------- 1 | from flask_script import Manager, prompt_bool 2 | import os 3 | from mrbob import cli 4 | import mrbob 5 | from jinja2.loaders import FileSystemLoader 6 | import sys 7 | 8 | 9 | manager = Manager(usage="performs mr.bob template runs") 10 | 11 | EXT_ROOT = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | MRBOB_TEMPLATE_DIR = os.path.join(EXT_ROOT,'templates') 14 | mrbob.rendering.jinja2_env.loader = FileSystemLoader(MRBOB_TEMPLATE_DIR) 15 | 16 | 17 | def get_all_files(dirname,seen=None): 18 | rtn = set() 19 | if seen is None: 20 | seen = set() 21 | seen.add(dirname) 22 | for dname,dlist,flist in os.walk(dirname): 23 | for itm in flist: 24 | if not itm.endswith('.pyc'): 25 | rtn.add(os.path.join(dname,itm)) 26 | if len(dlist) == 0: 27 | return rtn 28 | else: 29 | for d in dlist: 30 | pth = os.path.join(dname,d) 31 | if not pth in seen: 32 | rtn.update(get_all_files(pth,seen)) 33 | 34 | remove_dir = lambda dirname,path: path.split(dirname)[-1] 35 | remove_bob = lambda x: x.replace('.bob','') 36 | 37 | 38 | def get_templates(): 39 | return [name for name in os.listdir(MRBOB_TEMPLATE_DIR) if not name.startswith('_') and not name.endswith('.pyc')] 40 | 41 | @manager.command 42 | def print_template_dir(): 43 | print MRBOB_TEMPLATE_DIR 44 | 45 | @manager.command 46 | def print_templates(): 47 | print ', '.join(map(str,get_templates())) 48 | 49 | @manager.option('template',nargs='?',default=None) 50 | def print_template_files(template=None): 51 | if template is None: 52 | print 'please provide a template name' 53 | return 54 | else: 55 | if not template in get_templates(): 56 | print 'Error! {} is not installed'.format(template) 57 | return 58 | print '\n'.join( 59 | map( 60 | str, 61 | [remove_bob(remove_dir( 62 | MRBOB_TEMPLATE_DIR,x 63 | )) for x in get_all_files( 64 | os.path.join( 65 | MRBOB_TEMPLATE_DIR,template 66 | ) 67 | ) if not x is None and not x.endswith('.pyc') 68 | ] 69 | ) 70 | ) 71 | _template_dir = lambda name: os.path.join(MRBOB_TEMPLATE_DIR,name) 72 | 73 | @manager.option("-t","--testing",action='store_true') 74 | def start_project(testing=False): 75 | template_dir = _template_dir('project') 76 | if testing: 77 | target_dir = './testing' 78 | else: 79 | target_dir = os.curdir 80 | if target_dir == os.curdir: 81 | if not prompt_bool('not testing, are you sure you want to continue...'): 82 | sys.exit(0) 83 | list_questions = False 84 | non_interactive = False 85 | args = [template_dir,'-O',target_dir,'-v','-w'] 86 | cli.main(args) 87 | 88 | @manager.option("-t","--testing",action="store_true") 89 | def add_blueprint(testing=False): 90 | template_dir = _template_dir('blueprint') 91 | if testing: 92 | target_dir = './testing' 93 | else: 94 | target_dir = os.curdir 95 | if target_dir == os.curdir: 96 | if not prompt_bool('not testing, are you sure you want to continue...'): 97 | sys.exit(0) 98 | list_questions = False 99 | non_interactive = False 100 | args = [template_dir,'-O',target_dir,'-v','-w'] 101 | cli.main(args) 102 | 103 | @manager.command 104 | def angular_app(): 105 | template_dir = _template_dir('angular_app') 106 | target_dir = os.path.join(os.curdir,'static','js') 107 | if not os.path.exists(target_dir): 108 | os.makedirs(target_dir) 109 | list_questions = False 110 | non_interactive = False 111 | args = [template_dir,'-O',target_dir,'-v','-w'] 112 | cli.main(args) 113 | 114 | @manager.command 115 | def angular_service(): 116 | template_dir = _template_dir('angular_service') 117 | target_dir = os.path.join(os.curdir,'static','js') 118 | if not os.path.exists(target_dir): 119 | os.makedirs(target_dir) 120 | list_questions = False 121 | non_interactive = False 122 | args = [template_dir,'-O',target_dir,'-v','-w'] 123 | cli.main(args) 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/cfg.py: -------------------------------------------------------------------------------- 1 | from admin import admin 2 | from importlib import import_module 3 | from flask import current_app 4 | from .models import Setting,Type 5 | from flask.ext.wtf import Form 6 | 7 | 8 | class BaseSettings(object): 9 | _settings = [] 10 | _values = {} 11 | _form = None 12 | _defaults = {} 13 | _cfg = '' 14 | 15 | def __init__(self): 16 | self._cfg = current_app.config 17 | for itm in self._settings: 18 | if not itm in self._values: 19 | try: 20 | self.set_setting(itm,self._defaults[itm]) 21 | except KeyError: 22 | raise ValueError('Need to be given a value or default for {}'.format(itm)) 23 | self.set_setting(itm,self._cfg[itm]) 24 | 25 | def _get_setting_field(self,setting,field_type=None): 26 | pass 27 | 28 | def _get_form(self): 29 | raise NotImplementedError 30 | 31 | @property 32 | def form(self): 33 | return self._get_form() 34 | 35 | def set_setting(self,setting,value): 36 | if setting in self._settings: 37 | self._values[setting] = value 38 | 39 | def get_setting(self,setting,default=None): 40 | if setting in self._settings: 41 | return self._values[setting] 42 | return default 43 | 44 | def apply_settings(self,**kwargs): 45 | if len(kwargs) > 0: 46 | for k in kwargs: 47 | self.set_setting(k,kwargs[k]) 48 | for setting in self._settings: 49 | if not setting.startswith('_'): 50 | self._cfg[setting.upper()] = self.get_setting(setting) 51 | 52 | def _get_default_field(self,setting,fields): 53 | return fields.__dict__['StringField'] 54 | 55 | class SiteSettings(BaseSettings): 56 | _settings = [ 57 | 'RECAPTCHA_PUBLIC_KEY', 58 | 'RECAPTCHA_PRIVATE_KEY', 59 | 'ADMIN_PER_PAGE', 60 | 'CODEMIRROR_LANGUAGES', 61 | 'CODEMIRROR_THEME', 62 | 'BLUEPRINTS', 63 | 'EXTENSIONS', 64 | 'TEMPLATE_FILTERS', 65 | 'CONTEXT_PROCESSORS', 66 | ] 67 | _defaults = { 68 | 'RECAPTCHA_PUBLIC_KEY':'', 69 | 'RECAPTCHA_PRIVATE_KEY':'', 70 | 'ADMIN_PER_PAGE':'', 71 | 'CODEMIRROR_LANGUAGES':['python'], 72 | 'CODEMIRROR_THEME':'3024-year', 73 | 'BLUEPRINTS':'', 74 | 'EXTENSIONS':'', 75 | 'TEMPLATE_FILTERS':'', 76 | 'CONTEXT_PROCESSORS':'', 77 | } 78 | def _get_setting_field(self,setting,field_type=None): 79 | fields = import_module('wtforms.fields') 80 | if field_type is not None: 81 | field = fields.__dict__[field_type] 82 | else: 83 | from .models import Setting 84 | s = Setting.query.filter(Setting.name==setting).first() 85 | if s is None: 86 | field = self._get_default_field(setting,fields) 87 | else: 88 | field = fields.__dict__[s.type.field_type] 89 | return field 90 | 91 | def _get_form(self): 92 | form_args = {} 93 | for itm in self._settings: 94 | form_args[itm] = self._get_setting_field(itm)() 95 | self._form = type( 96 | 'EditSiteSettingsForm',(Form,),form_args 97 | ) 98 | return self._form 99 | 100 | 101 | 102 | 103 | 104 | 105 | @admin.before_app_request 106 | def add_settings(): 107 | from app import app 108 | settings = app.config.copy() 109 | CACHED_SETTINGS = [ 110 | 'RECAPTCHA_PUBLIC_KEY', 111 | 'RECAPTCHA_PRIVATE_KEY', 112 | 'ADMIN_PER_PAGE', 113 | 'CODEMIRROR_LANGUAGES', 114 | 'CODEMIRROR_THEME', 115 | 'BLUEPRINTS', 116 | 'EXTENSIONS', 117 | 'TEMPLATE_FILTERS', 118 | 'CONTEXT_PROCESSORS', 119 | ] 120 | for itm in CACHED_SETTINGS: 121 | setting = Setting.query.filter(Setting.name==itm).first() 122 | if setting is None: 123 | t = Type.query.filter(Type.name==type(settings[itm])).first() 124 | value = settings.get(itm,None) 125 | if value is None: 126 | value = '' 127 | if t is None: 128 | t = Type(type(settings[itm])) 129 | t.save() 130 | setting = Setting( 131 | name=itm,type=t,value=value 132 | ) 133 | setting.save() 134 | -------------------------------------------------------------------------------- /flask_xxl/templates/project/+project.name+/settings.py.bob: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | {{% macro add_blueprint(name) %}} 3 | '{{{ name }}}.{{{ name }}}', 4 | {{% endmacro %}} 5 | {{% macro render_dburi(driver,host,user,pw,db,port='3306') %}} 6 | SQLALCHEMY_DATABASE_URI = '{{{ driver }}}://{{{ user }}}:{{{ pw }}}@{{{ host }}}:{{{ port }}}/{{{ db }}}' 7 | {{% endmacro %}} 8 | {{% macro render_app_urls(app) %}} 9 | 'flask_xxl.apps.{{{ app }}}.urls.routes', 10 | {{% endmacro %}} 11 | {{% macro render_install_app(app) %}} 12 | 'flask_xxl.apps.{{{ app }}}.{{{ app }}}', 13 | {{% endmacro %}} 14 | 15 | """ 16 | settings 17 | ~~~~~~~~ 18 | Global settings for project. 19 | """ 20 | import os 21 | from local_settings import LocalConfig 22 | 23 | class BaseConfig(LocalConfig): 24 | ROOT_PATH = os.path.abspath(os.path.dirname(__file__)) 25 | CSRF_ENABLED = True 26 | #ADMIN_PER_PAGE = 5 27 | #CODEMIRROR_LANGUAGES = ['python','python2','python3','php','javascript','xml'] 28 | #CODEMIRROR_THEME = 'blackboard'#'vivid-chalk'#'3024-night' 29 | #SQLALCHEMY_ECHO = True 30 | #SQLALCHEMY_COMMIT_ON_TEARDOWN = True 31 | 32 | 33 | ##################################################### 34 | # Define url modules with routes defined inside 35 | # 36 | # in bp.urls.py file make routes 37 | # routes = [ 38 | # ((bp_name), 39 | # ('/url_rule',ViewClass.as_view('endpoint')), 40 | # ('/next_url',view_func)), 41 | # ] 42 | # 43 | ##################################################### 44 | URL_MODULES = [ 45 | '{{{project.name}}}.urls.routes', 46 | {{% if project.add_apps -%}} 47 | {{%- for app in ['admin','page','auth','menu'] -%}} 48 | {{%- if project.get('add_%s' % app) -%}} 49 | {{{ render_app_urls(app) }}} 50 | {{%- endif %}} 51 | {{%- endfor %}} 52 | {{%- endif %}} 53 | #'flask.ext.xxl.apps.admin.urls.routes', 54 | #'flask.ext.xxl.apps.auth.urls.routes', 55 | #'flask.ext.xxl.apps.page.urls.routes', 56 | ] 57 | 58 | ##################################################### 59 | # define sub dirs as blueprints 60 | # in __init__.py file define blueprint like this: 61 | # from flask import BluePrint 62 | # blueprint = BluePrint('blueprint',__name__, 63 | # url_prefix='/blueprint', 64 | # template_dir='templates') 65 | ##################################################### 66 | BLUEPRINTS = [ 67 | '{{{project.name}}}.{{{project.name}}}', 68 | {{%- if project.add_apps %}} 69 | {{%- for app in ['admin','page','menu','auth'] %}} 70 | {{%- if project.get('add_%s' % app) %}} 71 | {{{ render_install_app(app) }}} 72 | {{% endif -%}} 73 | {{% endfor -%}} 74 | {{% endif -%}} 75 | #'flask.ext.xxl.apps.admin.admin', 76 | #'flask.ext.xxl.apps.menu.menu', 77 | #'flask.ext.xxl.apps.page.page', 78 | #'flask.ext.xxl.apps.auth.auth', 79 | ] 80 | 81 | #################################################### 82 | # define flask extensions to use in your app 83 | # in ext.py import and create instance of extensions 84 | # from flask.ext.name import Extension 85 | # 86 | # extension = Extension() 87 | ##################################################### 88 | 89 | EXTENSIONS = [ 90 | 'ext.db', 91 | {{% if project.add_migration -%}} 92 | 'ext.alembic', 93 | {{% else %}} 94 | #'ext.alembic', 95 | {{%- endif %}} 96 | #'ext.toolbar', 97 | #'ext.pagedown', 98 | #'ext.codemirror', 99 | 100 | ] 101 | 102 | ################################################### 103 | # context processors add global variables or 104 | # functions to your templates 105 | # 106 | # define like this: 107 | # def my_processor_name(): 108 | # def the_func_to_add(): 109 | # #w/e you want to do 110 | # return {name:the_func_to_add} 111 | # then in templates name is available 112 | ###################################################### 113 | 114 | CONTEXT_PROCESSORS = [ 115 | 'flask.ext.xxl.context_processors.add_get_model', 116 | #'flask.ext.xxl.context_processors.add_is_page', 117 | #'flask.ext.xxl.context_processors.common_context', 118 | #'flask.ext.xxl.context_processors.common_forms', 119 | #'flask.ext.xxl.apps.menu.context_processors.frontend_nav', 120 | #'flask.ext.xxl.apps.menu.context_processors.admin_nav', 121 | #'flask.ext.xxl.apps.auth.context_processors.user_context', 122 | ] 123 | 124 | TEMPLATE_FILTERS = [ 125 | 'flask.ext.xxl.filters.date', 126 | 'flask.ext.xxl.filters.date_pretty', 127 | 'flask.ext.xxl.filters.datetime', 128 | 'flask.ext.xxl.filters.pluralize', 129 | 'flask.ext.xxl.filters.month_name', 130 | 'flask.ext.xxl.filters.markdown', 131 | ] 132 | 133 | 134 | def get_choices(option): 135 | return BaseConfig.DYNAMIC_SETTINGS[option] 136 | 137 | 138 | class DevelopmentConfig(BaseConfig): 139 | DEBUG = True 140 | DEBUG_TB_PROFILER_ENABLED = True 141 | DEBUG_TB_INTERCEPT_REDIRECTS = False 142 | 143 | 144 | class TestingConfig(BaseConfig): 145 | TESTING = True 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Flask-XXL 2 | #### - A best practices approach to creating larger web apps with Flask, in an attempt to make Flask feel like it is as capable, if not more, than __Django__. 3 | this is an amazing program best in the world 4 | [![PyPI version](https://badge.fury.io/py/flaskxxl.svg)](https://badge.fury.io/py/flaskxxl) 5 | 6 | [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/jstacoder) 7 | 8 | [![Join the chat at https://gitter.im/jstacoder/flask-xxl](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jstacoder/flask-xxl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 9 | 10 | _to see this in a real world example take a look at my other projects_ [Flask-Cms](https://github.com/jstacoder/flask-cms) or [Flask-Ide](https://github.com/jstacoder/flask-ide) 11 | 12 | 13 | 14 | ## What this provides: 15 | 16 | - Installable blueprints 17 | - any blueprints listed in your settings under `BLUEPRINTS` 18 | will be imported and registered on your app 19 | if that blueprint is a package, any files it contains named `models.py` or `views.py` will be imported as well, 20 | so no more need to manualy import your views and models 21 | giving odd errors if you dont do it in the exact correct order!! 22 | 23 | - basemodels.py 24 | - with a sqlalchemy compatible BaseMixin class 25 | - provides many useful CRUD operations, IE: model.save(), model.delete() 26 | - `BaseMixin` generates `__tablename__` automaticlly 27 | - `BaseMixin` adds an auto incrementing `id` field, as the primary_key to each model 28 | - `BaseMixin.session` is current model classes session 29 | - `BaseMixin.engine` is the current db engine 30 | - `BaseMixin.query` is the models sqlalchemy query from its session 31 | - `BaseMixin.get_all()` `->` function to return all of a model 32 | - `BaseMixin.get(*args,**kwargs)` `->` get single model by attr values, mainly for id=x 33 | 34 | - baseviews.py 35 | - with a BaseView class that is subclassed from Flask.views.MethodView to allow easy definition of view responses to get and post requests. 36 | - BaseView also has many builtin helpers/imports to speed development, ie: 37 | - BaseView.render() calls: 38 | `render_template(BaseView._template,**BaseView._context)` 39 | easily define either or both in the class variable 40 | section of the class and then add or change whatever you need to 41 | ie: maybe based on logic that happens during request processing. 42 | for example: 43 | ```python 44 | class ExampleView(BaseView): 45 | _context = { 46 | 'some_flag':True, 47 | } 48 | 49 | def get(self,new_flag=False): 50 | if new_flag: 51 | self._context['new_flag'] = new_flag 52 | self._context['some_flag'] = False 53 | return self.render() 54 | 55 | ``` 56 | 57 | - `BaseView.redirect(endpoint)` 58 | is a reimplementation of `flask.helpers.redirect` which allows you to directly enter the 59 | endpoint, so you dont have to run it through `url_for()` first. 60 | 61 | - `BaseView.get_env()` -> returns the current jinja2_env 62 | 63 | - `BaseView.form_validated()` -> returns true if all forms validate 64 | 65 | - __namespaces imported into BaseView__: 66 | `BaseView.flash == flask.flash` 67 | 68 | 69 | 70 | 71 | - many builtin template globals(context_processors) to use. 72 | ie: 73 | 74 | - `get_block(block_id)` <-- requires use of flask.ext.xxl.apps.blog 75 | * add blocks of html/jinja2/template helpers 76 | into the db and access from within templates 77 | great for things like header navs or sidebar widgets 78 | 79 | - `get_icon(icon_name,icon_lib)` <-- requires use of flask.ext.xxl.apps.blog 80 | - flask.ext.xxl.apps.blog comes with 8 icon librarys!!! 81 | * Glyphicon 82 | * Font Awesome 83 | * Mfg_Labs 84 | * Elusive icons 85 | * Genericons 86 | * and more ... 87 | 88 | access any icon anywhere in your templates! even from cms blocks!!! 89 | 90 | - `get_model(model_name,blueprint_name)` 91 | - access any model class from any template (currently only supports sqlalchemy models) 92 | 93 | - `get_button(name)` 94 | - create buttons in the cms and access from within templates 95 | 96 | - AppFactory class with many hooks into settings file (makes use of settings file similar to django) 97 | - settings like: 98 | - CONTEXT_PROCESSORS 99 | - TEMPLATE_FILTERS 100 | - URL_ROUTE_MODULES 101 | - INSTALLED_BLUEPRINTS etc.. 102 | 103 | - new revamped url routing scheme, use a urls.py file in each blueprint to 104 | define the url routes for the blueprint. reference the blueprint and the url 105 | route module in the settings file to registar onto the app upon instantiation. 106 | 107 | define routes like this: 108 | 109 | file: urls.py 110 | ```python 111 | from blueprint import blueprint 112 | from .views import ViewName,SecondView 113 | 114 | routes = [ 115 | ((blueprint_name,) 116 | ('/url',ViewName.as_View('view_name')), 117 | ('/another',SecondView.as_view('second_view')), 118 | ) 119 | ] 120 | ``` 121 | it basicly is like using `app.add_url_rule()` method, you 122 | just dont have to add `view_func=ViewName.as_view(endpoint)` 123 | or at least the `view_func=` part. 124 | 125 | 126 | - easily start a new project or extend an old one 127 | with the flaskxxl-manage.py command line helper tool 128 | - to start a project from scratch 129 | `$ flaskxxl-manage.py start-project` 130 | 131 | - to add to an existing project 132 | `$ flaskxxl-manage.py start-blueprint` 133 | 134 | 135 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/jstacoder/flask-xxl/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 136 | 137 | for more fun checkout the [wiki](https://github.com/jstacoder/flask-xxl/wiki) 138 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | settings 5 | ~~~~~~~~ 6 | 7 | Global settings for project. 8 | """ 9 | import os 10 | from .test_local_settings import LocalConfig 11 | 12 | class BaseConfig(LocalConfig): 13 | SYSTEM_MESSAGE_CATEGORIES = [ 14 | 'success' # 0 - GREEN 15 | 'info', # 1 - BLUE 16 | 'warning', # 2 - YELLOW 17 | 'danger', # 3 - RED 18 | ] 19 | 20 | ADMIN_PER_PAGE = 5 21 | CODEMIRROR_LANGUAGES = ['python','python2','python3','php','javascript','xml','jinja2'] 22 | CODEMIRROR_THEME = 'blackboard'#'vivid-chalk'#'3024-night' 23 | SQLALCHEMY_ECHO = True 24 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True 25 | CSRF_ENABLED = True 26 | ROOT_PATH = os.path.abspath(os.path.dirname(__file__)) 27 | 28 | URL_MODULES = [ 29 | #'core.urls.routes', 30 | #'admin.urls.routes', 31 | 'flask.ext.xxl.apps.auth.urls.routes', 32 | 'flask.ext.xxl.apps.page.urls.routes', 33 | 'flask.ext.xxl.apps.admin.urls.routes', 34 | #'blog.urls.routes', 35 | #'member.urls.routes', 36 | #'page.urls.routes', 37 | #'fileviewer.urls.routes', 38 | ] 39 | 40 | BLUEPRINTS = [ 41 | #'core.core', 42 | #'member.member', 43 | #'admin.admin', 44 | #'menu.menu', 45 | #'blog.blog', 46 | #'page.page', 47 | 'flask.ext.xxl.apps.auth.auth', 48 | 'flask.ext.xxl.page.auth.auth', 49 | 'flask.ext.xxl.admin.auth.auth', 50 | #'auth.auth', 51 | #'fileviewer.fileviewer', 52 | 53 | ] 54 | 55 | EXTENSIONS = [ 56 | #'ext.db', 57 | #'ext.toolbar', 58 | #'ext.pagedown', 59 | #'ext.codemirror', 60 | #'ext.alembic', 61 | ] 62 | 63 | CONTEXT_PROCESSORS = [ 64 | #'core.context_processors.common_context', 65 | #'core.context_processors.common_forms', 66 | #'menu.context_processors.frontend_nav', 67 | #'menu.context_processors.admin_nav', 68 | #'auth.context_processors.user_context', 69 | #'core.context_processors.add_is_page', 70 | #'core.context_processors.add_is_list', 71 | #'core.context_processors.add_get_model', 72 | #'core.context_processors.add_get_button', 73 | #'core.context_processors.add_get_icon', 74 | #'core.context_processors.get_context', 75 | #'core.context_processors.add_get_block', 76 | #'core.context_processors.add_urlfor', 77 | #'core.context_processors.add_layouts', 78 | #'core.context_processors.add_layout_mode', 79 | #'page.context_processors.add_page_self_context', 80 | #'menu.context_processors.get_navbar', 81 | #'menu.context_processors._add_navbar', 82 | #'menu.context_processors.sidebar', 83 | #'make_base.base', 84 | #'auth.context_processors.auth_context', 85 | #'blog.context_processors.add_admin_head', 86 | #'core.context_processors.add_size_converters', 87 | 88 | ] 89 | 90 | TEMPLATE_FILTERS = [ 91 | 'flask.ext.xxl.filters.date', 92 | 'flask.ext.xxl.filters.date_pretty', 93 | 'flask.ext.xxl.filters.datetime', 94 | 'flask.ext.xxl.filters.pluralize', 95 | 'flask.ext.xxl.filters.month_name', 96 | 'flask.ext.xxl.filters.markdown', 97 | #'core.context_processors.fix_body', 98 | #'core.filters.split', 99 | #'blog.filters.markdown', 100 | ] 101 | 102 | CONTACT_FORM_SETTINGS = { 103 | 'HEADING':'Send Us a message', 104 | 'SUBHEADING':'Or a Comment', 105 | 'OPTIONS':( 106 | ('test','opt1'), 107 | ('test2','opt2'), 108 | ('test3','opt3'), 109 | ('test4','opt4'), 110 | ('test5','opt5'), 111 | ('test6','opt6'), 112 | ), 113 | 'SUBMIT_TEXT':'Send to Us', 114 | 'COMPANY_TITLE':'Level2designs', 115 | 'COMPANY_ADDRESS':{ 116 | 'NAME':'level2designs', 117 | 'STREET':'1045 w katella', 118 | 'CITY':'Orange', 119 | 'STATE':'CA', 120 | 'ZIP':'92804', 121 | }, 122 | 'COMPANY_PHONE':'714-783-6369', 123 | 'CONTACT_NAME':'Roux', 124 | 'CONTACT_EMAIL':'kyle@level2designs.com', 125 | } 126 | 127 | 128 | # populates header option in creating a cms page 129 | # should be tuples of (name,file) 130 | NAVBAR_TEMPLATE_FILES = ( 131 | ('bootstrap-std','navbars/bs_std.html'), 132 | ('bootstrap-inverse','navbars/bs_inverse.html'), 133 | ('blog','navbars/blog.html'), 134 | ('clean','navbars/clean.html') 135 | ) 136 | 137 | DEFAULT_NAVBAR = 'clean' 138 | 139 | LAYOUT_FILES = { 140 | 'blog':'layouts/1col_leftsidebar.html', 141 | 'post_form':'layouts/1col_rightsidebar.html', 142 | 'one_col_left':'layouts/1col_leftsidebar.html', 143 | 'one_col_right':'layouts/1col_rightsidebar.html', 144 | 'two_col_left':'layouts/2col_leftsidebar.html', 145 | 'two_col_right':'layouts/2col_rightsidebar.html', 146 | 'three_col_left':'layouts/3col_leftsidebar.html', 147 | 148 | } 149 | 150 | BASE_TEMPLATE_FILES = [ 151 | ('one_col_left','1_col_left.html'), 152 | ('one_col_right','1_col_right.html'), 153 | ('two_col_left','2_col_left.html'), 154 | ('two_col_right','2_col_right.html'), 155 | ('three_col','3_col.html'), 156 | ] 157 | 158 | #BLOG_SIDEBAR_LEFT = False 159 | #BLOG_SIDEBAR_RIGHT = True 160 | BLOG_SIDEBAR_LEFT = True 161 | #BLOG_SIDEBAR_RIGHT = False 162 | BLOG_TITLE = 'Dynamic' 163 | BLOG_CONTENT = 'some text to put into my
    Blog' 164 | 165 | DEFAULT_ICON_LIBRARY = 'octicon' 166 | 167 | def get_choices(): 168 | return BaseConfig.CONTACT_FORM_SETTINGS['OPTIONS'] 169 | 170 | 171 | class DevelopmentConfig(BaseConfig): 172 | DEBUG = True 173 | DEBUG_TB_PROFILER_ENABLED = True 174 | DEBUG_TB_INTERCEPT_REDIRECTS = False 175 | 176 | 177 | class TestingConfig(BaseConfig): 178 | TESTING = True 179 | SQLALCHEMY_ECHO = False 180 | SQLALCHEMY_DATABASE_URI = 'mysql://test:test@174.140.227.137:3306/test_test5' 181 | 182 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | settings 5 | ~~~~~~~~ 6 | 7 | Global settings for project. 8 | """ 9 | import os 10 | from .local_settings import LocalConfig 11 | 12 | class BaseConfig(LocalConfig): 13 | SYSTEM_MESSAGE_CATEGORIES = [ 14 | 'success' # 0 - GREEN 15 | 'info', # 1 - BLUE 16 | 'warning', # 2 - YELLOW 17 | 'danger', # 3 - RED 18 | ] 19 | 20 | ADMIN_PER_PAGE = 5 21 | CODEMIRROR_LANGUAGES = ['python','python2','python3','php','javascript','xml','jinja2'] 22 | CODEMIRROR_THEME = 'blackboard'#'vivid-chalk'#'3024-night' 23 | SQLALCHEMY_ECHO = True 24 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True 25 | CSRF_ENABLED = True 26 | ROOT_PATH = os.path.abspath(os.path.dirname(__file__)) 27 | 28 | URL_MODULES = [ 29 | #'core.urls.routes', 30 | #'admin.urls.routes', 31 | 'flask.ext.xxl.apps.auth.urls.routes', 32 | 'flask.ext.xxl.apps.page.urls.routes', 33 | 'flask.ext.xxl.apps.admin.urls.routes', 34 | #'blog.urls.routes', 35 | #'member.urls.routes', 36 | #'page.urls.routes', 37 | #'fileviewer.urls.routes', 38 | ] 39 | 40 | BLUEPRINTS = [ 41 | #'core.core', 42 | #'member.member', 43 | #'admin.admin', 44 | #'menu.menu', 45 | #'blog.blog', 46 | #'page.page', 47 | 'flask.ext.xxl.apps.auth.auth', 48 | 'flask.ext.xxl.apps.page.page', 49 | 'flask.ext.xxl.apps.admin.admin', 50 | #'auth.auth', 51 | #'fileviewer.fileviewer', 52 | 53 | ] 54 | 55 | EXTENSIONS = [ 56 | #'ext.db', 57 | #'ext.toolbar', 58 | #'ext.pagedown', 59 | #'ext.codemirror', 60 | #'ext.alembic', 61 | ] 62 | 63 | CONTEXT_PROCESSORS = [ 64 | #'core.context_processors.common_context', 65 | #'core.context_processors.common_forms', 66 | #'menu.context_processors.frontend_nav', 67 | #'menu.context_processors.admin_nav', 68 | #'auth.context_processors.user_context', 69 | #'core.context_processors.add_is_page', 70 | #'core.context_processors.add_is_list', 71 | #'core.context_processors.add_get_model', 72 | #'core.context_processors.add_get_button', 73 | #'core.context_processors.add_get_icon', 74 | #'core.context_processors.get_context', 75 | #'core.context_processors.add_get_block', 76 | #'core.context_processors.add_urlfor', 77 | #'core.context_processors.add_layouts', 78 | #'core.context_processors.add_layout_mode', 79 | #'page.context_processors.add_page_self_context', 80 | #'menu.context_processors.get_navbar', 81 | #'menu.context_processors._add_navbar', 82 | #'menu.context_processors.sidebar', 83 | #'make_base.base', 84 | #'auth.context_processors.auth_context', 85 | #'blog.context_processors.add_admin_head', 86 | #'core.context_processors.add_size_converters', 87 | 88 | ] 89 | 90 | TEMPLATE_FILTERS = [ 91 | 'flask.ext.xxl.filters.date', 92 | 'flask.ext.xxl.filters.date_pretty', 93 | 'flask.ext.xxl.filters.datetime', 94 | 'flask.ext.xxl.filters.pluralize', 95 | 'flask.ext.xxl.filters.month_name', 96 | 'flask.ext.xxl.filters.markdown', 97 | #'core.context_processors.fix_body', 98 | #'core.filters.split', 99 | #'blog.filters.markdown', 100 | ] 101 | 102 | CONTACT_FORM_SETTINGS = { 103 | 'HEADING':'Send Us a message', 104 | 'SUBHEADING':'Or a Comment', 105 | 'OPTIONS':( 106 | ('test','opt1'), 107 | ('test2','opt2'), 108 | ('test3','opt3'), 109 | ('test4','opt4'), 110 | ('test5','opt5'), 111 | ('test6','opt6'), 112 | ), 113 | 'SUBMIT_TEXT':'Send to Us', 114 | 'COMPANY_TITLE':'Level2designs', 115 | 'COMPANY_ADDRESS':{ 116 | 'NAME':'level2designs', 117 | 'STREET':'1045 w katella', 118 | 'CITY':'Orange', 119 | 'STATE':'CA', 120 | 'ZIP':'92804', 121 | }, 122 | 'COMPANY_PHONE':'714-783-6369', 123 | 'CONTACT_NAME':'Roux', 124 | 'CONTACT_EMAIL':'kyle@level2designs.com', 125 | } 126 | 127 | 128 | # populates header option in creating a cms page 129 | # should be tuples of (name,file) 130 | NAVBAR_TEMPLATE_FILES = ( 131 | ('bootstrap-std','navbars/bs_std.html'), 132 | ('bootstrap-inverse','navbars/bs_inverse.html'), 133 | ('blog','navbars/blog.html'), 134 | ('clean','navbars/clean.html') 135 | ) 136 | 137 | DEFAULT_NAVBAR = 'clean' 138 | 139 | LAYOUT_FILES = { 140 | 'blog':'layouts/1col_leftsidebar.html', 141 | 'post_form':'layouts/1col_rightsidebar.html', 142 | 'one_col_left':'layouts/1col_leftsidebar.html', 143 | 'one_col_right':'layouts/1col_rightsidebar.html', 144 | 'two_col_left':'layouts/2col_leftsidebar.html', 145 | 'two_col_right':'layouts/2col_rightsidebar.html', 146 | 'three_col_left':'layouts/3col_leftsidebar.html', 147 | 148 | } 149 | 150 | BASE_TEMPLATE_FILES = [ 151 | ('one_col_left','1_col_left.html'), 152 | ('one_col_right','1_col_right.html'), 153 | ('two_col_left','2_col_left.html'), 154 | ('two_col_right','2_col_right.html'), 155 | ('three_col','3_col.html'), 156 | ] 157 | 158 | #BLOG_SIDEBAR_LEFT = False 159 | #BLOG_SIDEBAR_RIGHT = True 160 | BLOG_SIDEBAR_LEFT = True 161 | #BLOG_SIDEBAR_RIGHT = False 162 | BLOG_TITLE = 'Dynamic' 163 | BLOG_CONTENT = 'some text to put into my
    Blog' 164 | 165 | DEFAULT_ICON_LIBRARY = 'octicon' 166 | 167 | VERBOSE = True 168 | 169 | def get_choices(): 170 | return BaseConfig.CONTACT_FORM_SETTINGS['OPTIONS'] 171 | 172 | 173 | class DevelopmentConfig(BaseConfig): 174 | DEBUG = True 175 | DEBUG_TB_PROFILER_ENABLED = True 176 | DEBUG_TB_INTERCEPT_REDIRECTS = False 177 | 178 | 179 | class TestingConfig(BaseConfig): 180 | TESTING = True 181 | SQLALCHEMY_ECHO = False 182 | SQLALCHEMY_DATABASE_URI = 'mysql://test:test@174.140.227.137:3306/test_test5' 183 | 184 | -------------------------------------------------------------------------------- /flask_xxl/basemodels.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | basemodels.py 4 | ~~~~~~~~~~~ 5 | """ 6 | import os 7 | from functools import wraps 8 | from werkzeug.local import LocalProxy 9 | from flask import current_app,g 10 | try: 11 | from flask import _app_ctx_stack as stack 12 | except ImportError: 13 | from flask import _request_ctx_stack as stack 14 | 15 | from inflection import underscore, pluralize 16 | from sqlalchemy.ext.declarative import as_declarative, declarative_base, declared_attr 17 | from sqlalchemy.ext.declarative.api import DeclarativeMeta 18 | from sqlalchemy.orm import scoped_session, sessionmaker 19 | from sqlalchemy import UniqueConstraint,Column,Integer,Text,String,Date,DateTime,ForeignKey,func,create_engine 20 | 21 | # classproperty decorator 22 | class classproperty(object): 23 | def __init__(self, getter): 24 | self.getter = getter 25 | def __get__(self, instance, owner): 26 | return self.getter(owner) 27 | 28 | 29 | echo_sql = lambda: os.environ.get('DATABASE_URI') and current_app.config.get('SQLALCHEMY_ECHO',False) 30 | get_engine = lambda: create_engine(os.environ.get('DATABASE_URI') or current_app.config['DATABASE_URI'],echo=echo_sql()) 31 | 32 | Base = declarative_base() 33 | 34 | 35 | class SQLAlchemyMissingException(Exception): 36 | pass 37 | 38 | class BaseMixin(Base): 39 | __table_args__ = { 40 | 'extend_existing':True 41 | } 42 | __abstract__ = True 43 | _session = None 44 | _engine = None 45 | _query = None 46 | _meta = None 47 | 48 | def __init__(self,*args,**kwargs): 49 | super(BaseMixin,self).__init__(*args,**kwargs) 50 | metadata = BaseMixin.metadata or Base.metadata 51 | self.metadata = BaseMixin.metadata = metadata 52 | 53 | @classproperty 54 | def engine(cls): 55 | if BaseMixin._engine is None: 56 | BaseMixin._engine = get_engine() 57 | BaseMixin._engine.echo = echo_sql() 58 | return BaseMixin._engine 59 | 60 | @declared_attr 61 | def id(self): 62 | return Column(Integer,primary_key=True) 63 | 64 | @classproperty 65 | def session(cls): 66 | if BaseMixin._session is None: 67 | BaseMixin._session = scoped_session(sessionmaker(bind=cls.engine))() 68 | return BaseMixin._session 69 | 70 | @classproperty 71 | def query(cls): 72 | if cls._query is None: 73 | cls._query = cls.session.query(cls) 74 | return cls._query 75 | 76 | @declared_attr 77 | def __tablename__(self): 78 | return underscore(pluralize(self.__name__)) 79 | 80 | @classmethod 81 | def get_by_id(cls, id): 82 | if any( 83 | (isinstance(id, basestring) and id.isdigit(), 84 | isinstance(id, (int, float))), 85 | ): 86 | return cls.query.get(int(id)) 87 | return None 88 | 89 | @classmethod 90 | def get_all(cls): 91 | return cls.query.all() 92 | 93 | @classmethod 94 | def create(cls, **kwargs): 95 | instance = cls(**kwargs) 96 | return instance.save() 97 | 98 | def update(self, commit=True, **kwargs): 99 | for attr, value in kwargs.iteritems(): 100 | setattr(self, attr, value) 101 | return commit and self.save() or self 102 | 103 | def save(self,commit=True,testing=False): 104 | try: 105 | self.session.add(self) 106 | if commit: 107 | self.session.commit() 108 | except Exception, e: 109 | if not testing: 110 | raise e 111 | print e.message 112 | return False 113 | return self 114 | 115 | def delete(self, commit=True): 116 | self.session.delete(self) 117 | return commit and self.session.commit() 118 | 119 | @property 120 | def absolute_url(self): 121 | return self._get_absolute_url() 122 | 123 | def _get_absolute_url(self): 124 | raise NotImplementedError('need to define _get_absolute_url') 125 | 126 | @classmethod 127 | def get_all_columns(cls,exclude=['id']): 128 | if not 'id' in exclude: 129 | exclude.append('id') 130 | rtn = [] 131 | for col in cls.__table__.c._all_columns: 132 | if not col.name in exclude and not col.name.endswith('id'): 133 | rtn.append((col.name,_clean_name(col.name))) 134 | for attr in dir(cls): 135 | if not attr in exclude: 136 | if not attr in [x[0] for x in rtn]: 137 | if not attr.startswith('_') and not attr.endswith('id'): 138 | if not callable(getattr(cls,attr)): 139 | rtn.append((attr,_clean_name(attr))) 140 | return rtn 141 | 142 | 143 | class AuditMixin(object): 144 | __abstract__ = True 145 | 146 | @declared_attr 147 | def date_added(self): 148 | return sq.Column(sq.DateTime,default=dt.now) 149 | 150 | @declared_attr 151 | def date_modified(self): 152 | return sq.Column(sq.DateTime,onupdate=dt.now) 153 | 154 | def _clean_name(name): 155 | names = name.split('_') 156 | if len(names) > 1: 157 | if len(names) == 2: 158 | name = names[0].title() + ' ' + names[-1].title() 159 | else: 160 | name = names[0].title() + ' {} '.format(' '.join(map(str,names[1:-1]))) + names[-1].title() 161 | else: 162 | name = names[0].title() 163 | return name 164 | 165 | class DBObject(object): 166 | _session = None 167 | _engine = None 168 | _metadata = None 169 | 170 | @staticmethod 171 | def check_ctx(): 172 | return stack.top is not None 173 | 174 | @classproperty 175 | def session(cls): 176 | sess = cls._session 177 | if sess is None: 178 | sess = DBObject._session = scoped_session(sessionmaker(bind=DBObject.engine)) 179 | return sess 180 | 181 | @classproperty 182 | def engine(cls): 183 | engine = cls._engine 184 | if engine is None: 185 | engine = cls._engine = cls.check_ctx() and create_engine(current_app.config.get('DATABASE_URI') or os.environ.get('DATABASE_URI')) 186 | return engine 187 | 188 | @classproperty 189 | def metadata(cls): 190 | meta = cls._metadata 191 | if meta is None: 192 | meta = cls._metadata = BaseMixin.metadata 193 | meta.bind = meta.bind if meta.bind else cls.engine 194 | return meta 195 | 196 | db = LocalProxy(DBObject) 197 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/views.py: -------------------------------------------------------------------------------- 1 | from flask_xxl.baseviews import BaseView,ModelAPIView 2 | import flask 3 | from . import page 4 | from .forms import TestForm,ContactUsForm 5 | from .models import Page 6 | 7 | 8 | #class TestView(ModelAPIView): 9 | # _model = Page 10 | 11 | class ContactFormView(BaseView): 12 | _template = 'contact.html' 13 | _form = ContactUsForm 14 | _context = {} 15 | 16 | def get(self): 17 | return self.render() 18 | 19 | def post(self): 20 | return self.render() 21 | 22 | class PageSlugView(BaseView): 23 | _template = 'page.html' 24 | _context = {} 25 | 26 | def get(self,slug): 27 | file_name = None 28 | if slug.endswith('.html'): 29 | # first try to load the html file 30 | if slug in page.jinja_loader.list_templates(): 31 | file_name = slug 32 | else: 33 | slug = slug.split('.html')[0] 34 | try: 35 | url = flask.url_for('page.'+slug) 36 | return self.redirect(url) 37 | except: 38 | pass 39 | test = TestForm() 40 | content = '' 41 | page_name = '' 42 | if '.html' in slug: 43 | slug = slug.split('.html')[0] 44 | page = Page._session.query(Page).filter(Page.slug==slug).all() 45 | if page is not None: 46 | if type(page) == list and len(page) != 0: 47 | page = page[0] 48 | content = page.body_content 49 | template_file = page.template_file if getattr(page,'template_file',False) and os.path.exists(page.template_file) else page.DEFAULT_TEMPLATE 50 | page_name = page.name 51 | title = page.title 52 | meta_title = page.meta_title 53 | elif file_name is not None: 54 | template_file = file_name 55 | title = slug 56 | meta_title = slug 57 | else: 58 | template_file = slug + '.html' 59 | title = slug 60 | meta_title = slug 61 | self._template = template_file 62 | self._context['content'] = content 63 | self._context['page_title'] = title 64 | self._context['page_name'] = page_name 65 | self._context['test'] = test 66 | return self.render() 67 | 68 | 69 | class AddPageView(BaseView): 70 | pass 71 | 72 | class PagesView(BaseView): 73 | pass 74 | 75 | ''' 76 | admin_required = '' 77 | 78 | class BasePageView(BaseView): 79 | _template = '' 80 | _context = {} 81 | _base_template = 'aabout.html' 82 | _parent_template = None 83 | 84 | def render(self): 85 | return render_template_string(self._template,**self._context) 86 | 87 | def get(self,slug): 88 | from auth.models import User 89 | from page.models import Page 90 | self._page = Page.get_by_slug(slug) 91 | self.frontend_nav() 92 | self._context['content'] = self._page.content 93 | if self._page.use_base_template: 94 | if self._page.template is not None: 95 | self._base_template = self._page.body 96 | self._process_base_template() 97 | 98 | def post(self,slug): 99 | from auth.models import User 100 | from page.models import Page 101 | self._page = Page.get_by_slug(slug) 102 | self.frontend_nav() 103 | self._context['content'] = self._page.content 104 | if self._page.use_base_template: 105 | if self._page.template is not None: 106 | self._base_template = self._page.body 107 | self._process_base_template() 108 | 109 | def _process_base_template(self): 110 | self._template = "{{template}}" 111 | self._template = render_template_string(self._template,template=self._base_template) 112 | self._template = "{% extends '" + self._template + "' %}
    {% block body %}{% endblock body %}
    " 113 | 114 | def frontend_nav(self): 115 | self._setup_nav() 116 | 117 | def _setup_sidebar(self): 118 | if self._page.add_to_sidebar: 119 | sidebar_data = [] 120 | pages = self._page.query.all() 121 | for page in pages: 122 | if page.add_to_sidebar: 123 | sidebar_data.append( 124 | ((page.name,'pages.page',dict(slug=page.slug))) 125 | ) 126 | #self._context['sidebar_data'] = ('Title',sidebar_data) 127 | 128 | def _setup_nav(self): 129 | self._context['nav_links'] = [] 130 | pages = self._page.query.all() 131 | for page in pages: 132 | if page.add_to_nav: 133 | self._context['nav_links'].append( 134 | (('page.pages',dict(slug=page.slug)),'{}'.format(page.name)) 135 | ) 136 | self._context['nav_title'] = '{}'.format(self._page.title) 137 | #self._setup_sidebar() 138 | 139 | 140 | class PagesView(BasePageView): 141 | 142 | def get(self,slug): 143 | super(PagesView,self).get(slug) 144 | #blocks = self._page.blocks.all() 145 | #self._page.template.process_blocks(blocks) 146 | return self.render() 147 | 148 | def post(self,slug): 149 | super(PagesView,self).post(slug) 150 | data = request.form.get('content') 151 | if data != self._page.content: 152 | self._page.content = data 153 | self._page.update() 154 | return self.render() 155 | 156 | 157 | class AddPageView(BaseView): 158 | 159 | def get(self): 160 | from .models import Page 161 | from blog.models import Tag 162 | data = dict( 163 | date_added=request.args.get('date_added',None), 164 | date_end=request.args.get('date_end',None), 165 | name=request.args.get('name',None), 166 | description=request.args.get('description',None), 167 | slug=request.args.get('slug',None), 168 | short_url=request.args.get('short_url',None), 169 | title=request.args.get('title',None), 170 | add_to_nav=request.args.get('add_to_nav',None), 171 | add_sidebar=request.args.get('add_sidebar',None), 172 | visible=request.args.get('visible',None), 173 | meta_title=request.args.get('meta_title',None), 174 | content=request.args.get('content',None), 175 | template=request.args.get('template',None), 176 | category=request.args.get('category',None), 177 | tags=request.args.get('tags',None), 178 | use_base_template=request.args.get('use_base_template',None), 179 | ) 180 | p = Page.query.filter(Page.name==data['name']).first() 181 | if p is not None: 182 | res = 0 183 | else: 184 | tags = [x.name for x in Tag.query.all()] 185 | for tag in data['tags']: 186 | if not tag in tags: 187 | t = Tag() 188 | t.name = tag 189 | t.save() 190 | p = Page() 191 | p.name = data.get('name') 192 | p.date_added = data.get('date_added') 193 | p.date_end = data.get('date_end',None) 194 | p.description = data.get('description',None) 195 | p.slug = data.get('slug',None) 196 | p.short_url = data.get('short_url',None) 197 | p.title = data.get('title',None) 198 | nav = data.get('add_to_nav',1) 199 | if str(nav).lower() == 'y': 200 | nav = 1 201 | else: 202 | nav = 0 203 | p.add_to_nav = nav 204 | sidebar = data.get('add_sidebar',0) 205 | if str(sidebar).lower() == 'y': 206 | sidebar = 1 207 | else: 208 | sidebar = 0 209 | p.add_sidebar = sidebar 210 | p.visible = data.get('visible',None) 211 | p.meta_title = data.get('meta_title',None) 212 | p.content = data.get('content',None) 213 | p.template = data.get('template',None) 214 | p.category = data.get('category',None) 215 | p.tags = data.get('tags',None) 216 | p.use_base_template = data.get('use_base_template',None) 217 | p.save() 218 | res = 1 219 | return jsonify(result=res,content=data['content']) 220 | ''' 221 | -------------------------------------------------------------------------------- /flask_xxl/baseviews.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from flask.views import MethodView 4 | from flask.templating import render_template 5 | from flask.helpers import url_for 6 | from flask import redirect, flash, request 7 | from inflection import pluralize 8 | from wtforms.form import FormMeta 9 | from .basemodels import classproperty 10 | 11 | 12 | is_verbose = lambda: os.environ.get('VERBOSE') and True or False 13 | 14 | class Flasher(object): 15 | DEFAULT_CATEGORY = 'warning' 16 | WARNING_CLASS = 'warning' 17 | INFO_CLASS = 'info' 18 | SUCCESS_CLASS = 'success' 19 | ERROR_CLASS = 'danger' 20 | 21 | def __init__(self,default=None,class_map=None): 22 | if default: 23 | self.DEFAULT_CATEGORY = default 24 | if class_map: 25 | self._set_classes(class_map) 26 | 27 | def _set_classes(self,class_map): 28 | default_map = { 29 | 'success':self.SUCCESS_CLASS, 30 | 'warning':self.WARNING_CLASS, 31 | 'error':self.ERROR_CLASS, 32 | 'info':self.INFO_CLASS 33 | } 34 | for k in class_map: 35 | tmp = class_map.get(k) 36 | if tmp is not None: 37 | default_map[k] = tmp 38 | 39 | 40 | def flash(self,msg,cat=None): 41 | cat = cat or self.DEFAULT_CATEGORY 42 | return flash(msg,cat) 43 | 44 | def add_warning(self,msg): 45 | return self.flash(msg,self.WARNING_CLASS) 46 | 47 | def add_error(self,msg): 48 | return self.flash(msg,self.ERROR_CLASS) 49 | 50 | def add_info(self,msg): 51 | return self.flash(msg,self.INFO_CLASS) 52 | 53 | def add_success(self,msg): 54 | return self.flash(msg,self.SUCCESS_CLASS) 55 | 56 | 57 | class PostViewAddon(object): 58 | def _process_post(self): 59 | self.post_data = ((request.data and json.loads(request.data)) if not request.form else dict(request.form.items())) if not request.mimetype == 'application/json' else request.json 60 | 61 | 62 | class BaseView(MethodView): 63 | _template = None 64 | _form = None 65 | _context = {} 66 | _form_obj = None 67 | _obj_id = None 68 | _form_args = {} 69 | _default_view_routes = {} 70 | _flasher = None 71 | _flasher_class_map = None 72 | 73 | def __init__(self,*args,**kwargs): 74 | super(BaseView,self).__init__(*args,**kwargs) 75 | if self._flasher_class_map is not None: 76 | self._flasher = Flasher(class_map=self._flasher_class_map) 77 | else: 78 | self._flasher = Flasher() 79 | 80 | 81 | def flash(self,msg): 82 | return self.flasher.flash(msg) 83 | 84 | def success(self,msg): 85 | return self._flasher.add_success(msg) 86 | 87 | def warning(self,msg): 88 | return self._flasher.add_warning(msg) 89 | 90 | def info(self,msg): 91 | return self._flasher.add_info(msg) 92 | 93 | def error(self,msg): 94 | return self._flasher.add_danger(msg) 95 | 96 | @classmethod 97 | def _add_default_routes(cls,app=None): 98 | for route,endpoint in cls._default_view_routes.items(): 99 | if is_verbose(): 100 | print 'attaching',route,'to view func',endpoint 101 | app.add_url_rule(route,endpoint,view_func=cls.as_view(endpoint)) 102 | 103 | def render(self,**kwargs): 104 | if self._template is None: 105 | return NotImplemented 106 | if kwargs: 107 | self._context.update(kwargs) 108 | if self._form is not None: 109 | 110 | if type(self._form) == FormMeta: 111 | if self._form_obj is not None: 112 | self._context['form'] = self._form(obj=self._form_obj,**self._form_args) 113 | else: 114 | self._context['form'] = self._form(**self._form_args) 115 | if self._obj_id is not None: 116 | self._context['obj_id'] = self._obj_id 117 | else: 118 | self._context['form'] = self._form 119 | choices = self._context.get('choices') 120 | if choices: 121 | for field in self._context['form']: 122 | if hasattr(field,field.__name__) and hasattr(getattr(field,field.__name__),field.__name__): 123 | inner_field = getattr(getattr(field,field.__name__),getattr(field.__name__)) 124 | if hasattr(inner_field,'choices'): 125 | setattr(inner_field,'choices',choices) 126 | for f,v in self._form_args.items(): 127 | self._form.__dict__[f].data = v 128 | return render_template(self._template,**self._context) 129 | 130 | def redirect(self,endpoint,**kwargs): 131 | if not kwargs.pop('raw',False): 132 | return redirect(url_for(endpoint,**kwargs)) 133 | return redirect(endpoint,**kwargs) 134 | 135 | def form_validated(self): 136 | if self._form: 137 | return self._form().validate() 138 | return False 139 | 140 | def get_form_data(self): 141 | result = {} 142 | for field in self._form(): 143 | name = field.name 144 | if '_' in field.name: 145 | if not field.name.startswith('_'): 146 | if not field.name.endswith('_'): 147 | if field.name.split('_')[0] == field.name.split('_')[1]: 148 | name = field.name.split('_')[0] 149 | result[name] = field.data 150 | return result 151 | 152 | def get_env(self): 153 | from flask import current_app 154 | return current_app.create_jinja_environment() 155 | 156 | @property 157 | def flasher(self): 158 | return self._flasher 159 | 160 | 161 | class ModelView(BaseView): 162 | # ModelView is an abstract class 163 | # just an interface really 164 | # to use the ModelView create your own view class 165 | # and use this as the parent class, and 166 | # set its _model class attr to the class to wrap ie: 167 | # 168 | # class UserModelView(ModelView): 169 | # _model = User 170 | _model = None 171 | 172 | def render(self,**kwargs): 173 | alt_model_id = '{0}_id'.format(self._model.__name__.lower()) 174 | if self._model is not None: 175 | if 'model_id' in kwargs: 176 | model_id = kwargs.pop('model_id') 177 | elif alt_model_id in kwargs: 178 | model_id = kwargs.pop('alt_model_id') 179 | else: 180 | model_id = None 181 | if model_id is not None: 182 | self._context['object'] = self.get_by_id(model_id) 183 | else: 184 | self._context['object'] = self._model() 185 | self._context['model'] = self._model 186 | return super(ModelView,self).render(**kwargs) 187 | 188 | def add(self,**kwargs): 189 | tmp = self._model(**kwargs) 190 | tmp.save() 191 | return tmp 192 | 193 | def update(self,model_id,**kwargs): 194 | tmp = self._model.query.filter_by(self._model.id==model_id).first() 195 | if 'return' in kwargs: 196 | if kwargs.pop('return',None): 197 | rtn = True 198 | else: 199 | rtn = False 200 | for k in kwargs.keys(): 201 | tmp.__dict__[k] = kwargs[k] 202 | tmp.save() 203 | if rtn: return tmp 204 | 205 | def get_all(self): 206 | return self._model.get_all() 207 | 208 | def get_by_id(self,model_id): 209 | return self._model.get_by_id(model_id) 210 | 211 | class AddModelView(ModelView,PostViewAddon): 212 | _success_endpoint = None 213 | _success_message = 'You successfully added an item' 214 | 215 | def get(self): 216 | return self.render() 217 | 218 | def post(self): 219 | self._process_post() 220 | self._context['obj'] = self._model(**self.post_data).save() 221 | self.success(self._success_message) 222 | return self.redirect(self._success_endpoint or '.index') 223 | 224 | 225 | def AddModelApiView(ModelView,PostViewAddon): 226 | def post(self): 227 | self._process_post() 228 | return jsonify(**self.add(**self.post_data).to_json()) 229 | 230 | class ListModelView(ModelView): 231 | def get(self): 232 | name = pluralize(self._model.__name__) 233 | return jsonify(name=[m.to_json() for m in self.get_all()]) 234 | 235 | class ViewModelView(ModelView): 236 | def get(self,item_id): 237 | m = self.get_by_id(item_id) 238 | return jsonify(**m.to_json()) 239 | 240 | class ModelAPIView(ModelView): 241 | __abstract__ = True 242 | 243 | @classproperty 244 | def _default_view_routes(cls): 245 | if cls is ModelAPIView: 246 | return {} 247 | name = cls.__name__.lower() 248 | _default_view_routes = { 249 | '/{}/list'.format(name):'{}-list'.format(name), 250 | '/{}/detail'.format(name):'{}-detail'.format(name), 251 | '/{}/edit'.format(name):'{}-edit'.format(name), 252 | } 253 | return _default_view_routes 254 | 255 | 256 | def render(self,**kwargs): 257 | old_rtn = super(ModelAPIView,self).render(**kwargs) 258 | rtn = make_response(json.dumps(self._context)) 259 | rtn.headers['Content-Type'] = 'application/json' 260 | return rtn 261 | 262 | 263 | -------------------------------------------------------------------------------- /flask_xxl/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | main.py - where the magic happens 5 | ~~~~~~~~ 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | import jinja2_highlight 9 | import sys 10 | import os 11 | from flask import Flask,views 12 | from werkzeug.utils import import_string,find_modules 13 | import jinja2_highlight 14 | from .baseviews import is_verbose 15 | 16 | 17 | class MyFlask(Flask): 18 | jinja_options = dict(Flask.jinja_options) 19 | jinja_options.setdefault('extensions', 20 | []).append('jinja2_highlight.HighlightExtension') 21 | 22 | class NoRouteModuleException(Exception): 23 | pass 24 | 25 | class NoTemplateFilterException(Exception): 26 | pass 27 | 28 | class NoContextProcessorException(Exception): 29 | pass 30 | 31 | class NoBlueprintException(Exception): 32 | pass 33 | 34 | class NoExtensionException(Exception): 35 | pass 36 | 37 | class NoInstalledBlueprintsSettingException(Exception): 38 | pass 39 | 40 | class AppFactory(object): 41 | _routes_registered = None 42 | 43 | def __init__(self, config, envvar='PROJECT_SETTINGS', bind_db_object=True): 44 | self.app_config = config 45 | self.app_envvar = os.environ.get(envvar, False) 46 | self.bind_db_object = bind_db_object 47 | 48 | def get_app(self, app_module_name, **kwargs): 49 | self.app = MyFlask(app_module_name, **kwargs) 50 | self.app.config.from_object(self.app_config) 51 | self.app.config.from_envvar(self.app_envvar, silent=True) 52 | self.app.config['VERBOSE'] = is_verbose() 53 | 54 | 55 | self._set_path() 56 | self._bind_extensions() 57 | #self._register_blueprints() 58 | self._register_routes() 59 | self._load_models() 60 | self._load_views() 61 | self._register_context_processors() 62 | self._register_template_filters() 63 | self._register_template_extensions() 64 | self._register_middlewares() 65 | 66 | return self.app 67 | 68 | def _register_middlewares(self): 69 | for middleware_path in self.app.config.get('MIDDLEWARE', []): 70 | module_name, middleware_name = middleware_path.rsplit('.', 1) 71 | module_name, _ = self._get_imported_stuff_by_path(middleware_path) 72 | middleware = getattr(module_name, middleware_name, None) 73 | if middleware is not None: 74 | self.app.wsgi_app = middleware(self.app.wsgi_app) 75 | 76 | def _set_path(self): 77 | sys.path.append(self.app.config.get('ROOT_PATH','')) 78 | 79 | def _is_public_attr(self,name): 80 | return not name.startswith('_') 81 | 82 | def _get_imported_stuff_by_path(self, path): 83 | module_name, object_name = path.rsplit('.', 1) 84 | module = import_string(module_name) 85 | return module, object_name 86 | 87 | def _load_resource(self,typename): 88 | bp_settings_path = ((self.app.config.get('BLUEPRINTS',None) and 'BLUEPRINTS') or (self.app.config.get('INSTALLED_BLUEPRINTS',None) and 'INSTALLED_BLUEPRINTS') or False) 89 | if not bp_settings_path: 90 | raise NoInstalledBlueprintsSettingException('You must have a setting for either INSTALLED_BLUEPRINTS or BLUEPRINTS') 91 | for blueprint_path in self.app.config.get(bp_settings_path, []): 92 | module_name, object_name = blueprint_path.rsplit('.', 1) 93 | blueprint_module, bp_name = self._get_imported_stuff_by_path(blueprint_path) 94 | blueprint = getattr(blueprint_module,bp_name) 95 | modules = find_modules(module_name) 96 | for module in modules: 97 | if typename in module: 98 | mod = import_string(module) 99 | for itm in dir(mod): 100 | cls = getattr(mod,itm) 101 | if self._is_public_attr(itm) and\ 102 | itm[0] == str(itm[0]).upper() and\ 103 | 'class' in str(cls) and\ 104 | 'view' in str(itm).lower(): 105 | 106 | if hasattr(cls,'_add_default_routes') and\ 107 | getattr(cls,'_default_view_routes'): 108 | if is_verbose(): 109 | print 'getting default routes for ',cls.__name__ 110 | getattr(cls,'_add_default_routes')(app=blueprint or self.app) 111 | 112 | 113 | 114 | def _load_views(self): 115 | return self._load_resource('views') 116 | 117 | def _load_models(self): 118 | return self._load_resource('models') 119 | 120 | def _register_template_extensions(self): 121 | self.app.jinja_options = dict(Flask.jinja_options) 122 | exts = self.app.config.get('TEMPLATE_EXTENSIONS') or ['jinja2_highlight.HighlightExtension'] 123 | self.app.jinja_options.setdefault('extensions',[])\ 124 | .extend(exts) 125 | 126 | def _bind_extensions(self): 127 | if self.app.config.get('VERBOSE',False): 128 | print 'binding extensions' 129 | for ext_path in self.app.config.get('EXTENSIONS', []): 130 | module, e_name = self._get_imported_stuff_by_path(ext_path) 131 | if not hasattr(module, e_name): 132 | raise NoExtensionException('No {e_name} extension found'.format(e_name=e_name)) 133 | ext = getattr(module, e_name) 134 | if getattr(ext, 'init_app', False): 135 | ext.init_app(self.app) 136 | else: 137 | ext(self.app) 138 | 139 | def _register_template_filters(self): 140 | if self.app.config.get('VERBOSE',False): 141 | print 'registering template filters' 142 | for filter_path in self.app.config.get('TEMPLATE_FILTERS', []): 143 | module, f_name = self._get_imported_stuff_by_path(filter_path) 144 | if hasattr(module, f_name): 145 | self.app.jinja_env.filters[f_name] = getattr(module, f_name) 146 | else: 147 | raise NoTemplateFilterException('No {f_name} template filter found'.format(f_name=f_name)) 148 | 149 | def _register_context_processors(self): 150 | if self.app.config.get('VERBOSE',False): 151 | print 'registering template context processors' 152 | for processor_path in self.app.config.get('CONTEXT_PROCESSORS', []): 153 | module, p_name = self._get_imported_stuff_by_path(processor_path) 154 | if hasattr(module, p_name): 155 | self.app.context_processor(getattr(module, p_name)) 156 | else: 157 | raise NoContextProcessorException('No {cp_name} context processor found'.format(cp_name=p_name)) 158 | 159 | def _register_blueprints(self): 160 | if self.app.config.get('VERBOSE',False): 161 | print 'registering blueprints' 162 | self._bp = {} 163 | for blueprint_path in self.app.config.get('BLUEPRINTS', []): 164 | module, b_name = self._get_imported_stuff_by_path(blueprint_path) 165 | if hasattr(module, b_name): 166 | #self.app.register_blueprint(getattr(module, b_name)) 167 | self._bp[b_name] = getattr(module,b_name) 168 | if self.app.config.get('VERBOSE',False): 169 | print 'adding {} to bp'.format(b_name) 170 | else: 171 | raise NoBlueprintException('No {bp_name} blueprint found'.format(bp_name=b_name)) 172 | 173 | def _register_routes(self): 174 | if AppFactory._routes_registered is None: 175 | AppFactory._routes_registered = True 176 | if self.app.config.get('VERBOSE',False): 177 | print 'starting routing' 178 | for url_module in self.app.config.get('URL_MODULES',[]): 179 | if self.app.config.get('VERBOSE',False): 180 | pass 181 | module,r_name = self._get_imported_stuff_by_path(url_module) 182 | if self.app.config.get('VERBOSE',False): 183 | pass 184 | if r_name == 'routes' and hasattr(module,r_name): 185 | if self.app.config.get('VERBOSE',False): 186 | print '\tsetting up routing for {} with\n\troute module {}\n'.format(module.__package__,module.__name__) 187 | self._setup_routes(getattr(module,r_name)) 188 | else: 189 | raise NoRouteModuleException('No {r_name} url module found'.format(r_name=r_name)) 190 | if self.app.config.get('VERBOSE',False): 191 | print 'Finished registering blueprints and url routes' 192 | else: 193 | if self.app.config.get('VERBOSE',False): 194 | print 'skipped' 195 | 196 | def _check_for_registered_blueprint(self, bp): 197 | found = False 198 | for name in [str(x) for x in self.app.blueprints]: 199 | if bp.__class__.__name__.split('.')[-1] in name: 200 | found = True 201 | return found 202 | 203 | def _setup_routes(self,routes): 204 | for route in routes: 205 | blueprint,rules = route[0],route[1:] 206 | #for pattern,endpoint,view in rules: 207 | for itm in rules: 208 | if len(itm) == 3: 209 | pattern,endpoint,view = itm 210 | else: 211 | pattern,view = itm 212 | endpoint = None 213 | #for pattern,endpoint,view in rules: 214 | if self.app.config.get('VERBOSE',False): 215 | print '\t\tplugging url Pattern:',pattern 216 | print '\t\tinto View class/function:',hasattr(view,'func_name') and view.view_class.__name__ or view.__name__ 217 | print '\t\tat endpoint:',endpoint or view.func_name 218 | print 219 | if type(blueprint) == type(tuple()): 220 | blueprint = blueprint[0] 221 | blueprint.add_url_rule(pattern,endpoint or view.func_name,view_func=hasattr(view,'func_name') and view or view.as_view(endpoint)) 222 | if not self._check_for_registered_blueprint(blueprint): 223 | if self.app.config.get('VERBOSE',False): 224 | print '\n\t\t\tNow registering {} as blueprint\n\n'.format(str(blueprint.name)) 225 | self.app.register_blueprint(blueprint) 226 | 227 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | #__Flask-XXL__ 2 | 3 | ----------- 4 | 5 | ###__Contents__: 6 | 7 | 1. overview 8 | 2. getting started 9 | 3. the settings file 10 | * INSTALLED_BLUEPRINTS 11 | * TEMPLATE_FILTERS 12 | * CONTEXT_PROCESSORS 13 | * ROUTE_MODULES 14 | * TEMPLATE_LOADERS 15 | * EXTRA_TEMPLATE_DIRS_ 16 | 4. Flask, python and MTV 17 | 5. Views - _part1_ _begining_ 18 | 6. Templates - _part1_ _begining_ 19 | 7. models 20 | 8. Templates - _Advanced_ 21 | 9. Widgets 22 | 10. Extending 23 | 11. Etc..... 24 | 25 | ---------------- 26 | 27 | ##Ch.1 - Overview 28 | 29 | ==================== 30 | 31 | Flasks creator had the idea, that Django was somewhat of an elephant,(they do say so themselves). 32 | And Who needs an elephant? Well I say there are plenty of cases to use an elephant, but why does 33 | it have to be Django? I purpose to use flask for the same monumental tasks for which we would 34 | normally look to somthing bigger. Flask by design is a small framework, it is very good at 35 | being a light, agile framework to build upon. That is why i have created Flask-XXL. Using the 36 | Flask Factory pattern, specifically one borrowed from "Flask-Kit", that uses a few settings, 37 | very similar to Django, to automatically do some things for the user, i used to find this 38 | annoying in Django, it constantly doing things behind my back. Now, i find the freedom inspiring, 39 | there is so much boilerplate i can now forget about, now i understand what everyone likes about Django. 40 | But flask is so lightweight and transparent, the source easy to read. But im rambling, so let me show 41 | you what i mean in the first example. 42 | 43 | 44 | 45 | ##Ch.2 - Getting Started 46 | 47 | ============================ 48 | 49 | a typical small flask app might start like this 50 | 51 | from flask import Flask,url_for,redirect,session,g,flash 52 | 53 | app = Flask(__name__) 54 | 55 | @app.route('/') 56 | def index(): 57 | return "hello World" 58 | 59 | if __name__ == "__main__": 60 | app.run() 61 | 62 | 63 | but this is obviously a very small example. If you really want a system 64 | that can expand you should start using Blueprints 65 | 66 | from flask import Blueprint 67 | 68 | blog = Blueprint('blog',__name__, 69 | template_folder='templates', 70 | url_prefix='/blog') 71 | from .views import * 72 | 73 | now most tutorials say to keep going like the original 74 | example 75 | 76 | @blog.route('/') 77 | def blog(): 78 | return "my blog" 79 | 80 | 81 | but this is where we take our _turn_, 82 | 83 | so the first thing to look at in any big web system like this, id say is the settings file, or whichever 84 | config holds the database info. 85 | 86 | here thats settings.py a file that is very similar now to djangos settings.py 87 | 88 | 89 | 90 | 91 | ##Ch.3 Settings file 92 | 93 | ====================== 94 | 95 | the main settings here are 96 | 97 | #Django Equivilants 98 | #++++++++++++++++++++ 99 | INSTALLED_BLUEPRINTS = [ #<-- INSTALLED_APPS 100 | XXXXX 101 | ] 102 | TEMPLATE_FILTERS = [ [ #<-- Django dosent have a setting for this feature, just a clunky api 103 | XXXXXX 104 | ] 105 | CONTEXT_PROCESSORS = [ #<-- TEMPLATE_CONTEXT_PROCESSORS 106 | XXXXXXX 107 | ] 108 | ROUTE_MODULES = [ #<-- ROOT_URLCONF 109 | XXXXXX 110 | ] 111 | TEMPLATE_LOADERS = [ #<-- TEMPLATE_LOADERS 112 | xxxxxx 113 | ] 114 | EXTRA_TEMPLATE_DIRS = [ #<-- TEMPLATE_DIRS 115 | xxxxxxxx 116 | ] 117 | 118 | 119 | These 4 settings handle many things that normally take quite some time to get right, 120 | and boils them down to a few easy processes. 121 | 122 | ###INSTALLED_BLUEPRINTS 123 | 124 | +++++++++++++++++++++++ 125 | 126 | this takes the place of Djangos INSTALLED_APPS but for flask blueprints. 127 | It works the same way. just add the import path of a Blueprint, it can be anywhere, 128 | even in a seperate installed package, as long as its importable on your PYTHONPATH. 129 | Heres a an example of the setting 130 | 131 | INSTALLED_BLUEPRINTS = [ 132 | 'flask.ext.xxl.apps.auth.auth', #these are a few of the blueprints 133 | 'flask.ext.xxl.apps.page.page', #built into flask-xxl 134 | 'admin.admin', # these are blueprints inside the webapp 135 | 'blog.blog' 136 | ] 137 | 138 | ###TEMPLATE_FILTERS 139 | 140 | +++++++++++++++++++ 141 | 142 | now in Django you can write and register your own template filters, 143 | but i think its kind of a hassle. Flask has an easy way to do it that ive always 144 | loved, but this makes it much eaiser. 145 | 146 | 1. step1 - write the filter heres an example that will pull all of the x's from the input and return the result 147 | 148 | 149 | 150 | def filter_out_xs(data): 151 | rtn = '' 152 | for char in data: 153 | if char.lower() == 'x': 154 | pass 155 | else: 156 | rtn += char 157 | return rtn 158 | 159 | 160 | 2. step2 just add to the setting, if it was in myapp.template_filters i would put 161 | 162 | 163 | 164 | TEMPLATE_FILTERS = [ 165 | 'myapp.template_filters.filter_out_xs', 166 | ] 167 | 168 | 169 | then it could be used in a template like this: 170 | 171 | {{ someval|filter_out_xs }} 172 | 173 | and of the input was 174 | > 'xx55x76' 175 | 176 | the output would be 177 | > '5576' 178 | 179 | 180 | ###CONTEXT_PROCESSORS 181 | 182 | +++++++++++++++++++++ 183 | 184 | 185 | A better way to think of context_processors, is global template variables. 186 | Basicly they are functions you can define, that can return data and objects 187 | into the global template environment. Then those objects or that data, is avialiable 188 | upon rendering templates. This can be very powerful, and it is just as easy here as it is 189 | in Django. 190 | 191 | Heres a simple example: 192 | 193 | def simple_context(): 194 | return dict(somevar='someval') 195 | 196 | def extra_func(): 197 | return 'somthing i computed' 198 | 199 | def complicated_context(): 200 | return dict(complicated=extra_func) 201 | 202 | 203 | now this is in myapp/context_processors.py in the settings file id put 204 | 205 | CONTEXT_PROCESSORS = [ 206 | 'myapp.context_processors.simple_context', 207 | 'myapp.context_processors.complicated_context', 208 | ] 209 | 210 | then in my templates somevar and extra_func would be avialible 211 | 212 | 213 | 214 | 215 | 216 | ##Ch. 4 Flask, Python.. and MTV 217 | 218 | -------------------------------- 219 | 220 | 221 | Now most people, well at least most programmers, know about the MVC or Model, View, Controller pattern. In 222 | Python things tend to work a little differently, first lets look at the traditional __MVC__ pattern. 223 | 224 | * a model class mapping database tables to objects and associcated data within the program 225 | * M 226 | * a few view classes (with related latout,block or template files/classes) to process that data for display 227 | * V 228 | * finally You would normally have a some kind of front controller class that routes http requests to the views 229 | * C 230 | 231 | 232 | now python dosent handle the http requests as cleanly as some languages might, and as a consiquense of that 233 | the routing mainly happens behind the scenes, and is handeled on by the user via either a config style syntax, 234 | or a decoraotor syntax ie: 235 | 236 | config style: 237 | 238 | ```python 239 | routes = ( 240 | ((Bp), 241 | ('/',AView.as_view('view')), 242 | ) 243 | ``` 244 | 245 | decorator style: 246 | ```python 247 | @app.route('/') 248 | def view_func(): 249 | return a_view() 250 | ``` 251 | and this leads us to our next setting 252 | 253 | ###ROUTE_MODULES 254 | 255 | ++++++++++++++++ 256 | 257 | now flask and its many tutorials teach the decorator style from above, arguing that it makes debugging 258 | a crashed site eaiser because the function that crashed the site is defined right next to the url that called it. 259 | Which makes sense, until your project grows to 500+ lines, then those functions start getting harder to sift through. Not to mention that the decorator syntax makes it easy to assign different request methods to the same functions, then checking in the function which type of request was sent and basing the response on that. This to me seems like a lot for nothing. The great thing about it though is it uses simple string matching to line up incoming requests with the defined url rules, this isnt as powerful as some systems, but it is simple to understand and you can get up and running in a short matter of time. 260 | 261 | Django on the other hand uses the config style, not exactly like my example above, but close enough. In the Django world each app you add to your project you give a urls.py file that imports your view functions/classes and matchs them to url rules. I find this makes debugging actually eaiser. Anytime your site crashes, the url will tell you which urls.py file to look in, then just find that line and theres your offending function. The main downside with Django is the it uses a complex system of _"regular expressions"_ to parse and match the incoming url requests to the defined url rules. I must admit that no matter how trivial they may sound, regular expressions are anything but. 262 | 263 | So for my system i took the best of both of those worlds. 264 | * a _urls.py_ file 265 | * each app (blueprint in flask speak) gets its own urls.py for routing 266 | 267 | * string comparisons for matching requests to url rules 268 | 269 | 270 | Here is a _"small"_ example to show you how a view and its routing are defined: 271 | (dont mind the flask-xxl imports, ill explain them later) 272 | 273 | views.py 274 | ```python 275 | from flask.ext.xxl.core.baseviews import BaseView 276 | 277 | class ExampleView(BaseView): 278 | _template = 'example.html' 279 | 280 | def get(self): 281 | return self.render() 282 | ``` 283 | 284 | now we need a blueprint to plug it into 285 | 286 | example.py 287 | ```python 288 | from flask import Blueprint 289 | 290 | example = Blueprint('example',__file__) 291 | from .views import * 292 | ``` 293 | 294 | then we need to define our route 295 | 296 | urls.py 297 | ```python 298 | from .views import ExampleView 299 | from example import eaxmple 300 | 301 | routes = [ 302 | ((example), 303 | ('/',ExampleView.as_view('example')), 304 | )] 305 | ``` 306 | 307 | then just define the url module in the ROUTE_MODULE setting 308 | 309 | settings.py 310 | ```python 311 | 312 | ROUTE_MODULES = ( 313 | 'myapp.example.urls', 314 | ) 315 | ``` 316 | 317 | 318 | 319 | now i know this may seem like a bit much for such a small example app, 320 | but lets not forget that this is called _Flask-XXL_, so its really not intended 321 | for such small requirments, if you plan on having a small web-app, with few blueprints, 322 | that is what Flask is made for, but when you plan on your application growing 323 | and you want to handle it with clean system, that is what flask-xxl is for. 324 | 325 | 326 | ##Ch. 5. Views - _part1_ _begining_ 327 | 328 | ----------- 329 | from here on out we will dump our example app and start fresh, building what hopefully 330 | will grow to be a full-featured project management suite. But we will start from the start, or 331 | at least as far back as you need to when using flask-xxl. 332 | 333 | Now one thing i think almost everyone can agree Django did well was project / (module/app) setup. 334 | 335 | with a simple 336 | 337 | ```bash 338 | 339 | $ manage.py startproject 340 | 341 | ``` 342 | or 343 | ```bash 344 | 345 | $ manage.py startapp 346 | ``` 347 | you are asked a few simple questions about your intentions and 348 | wham boom, youve got a new project/app, or at least a number of the files youll 349 | want to start off with, as well as a few helpful imports / commented notes to 350 | help speed up development. 351 | 352 | Thats the way things should work, so here to start we use 353 | 354 | ```bash 355 | 356 | $ flaskxxl.py startproject 357 | 358 | ``` 359 | 360 | 361 | 362 | 363 | ##To Be Continued __.....__ 364 | 365 | ------------------ 366 | -------------------------------------------------------------------------------- /flask_xxl/apps/admin/templates/_macros.html: -------------------------------------------------------------------------------- 1 | {# 2 | render_nav_dropdown() 3 | @args: 4 | label(str) 5 | link_list(list) 6 | @notes: 7 | link_list s/b list of either (text,endpoint) or ('sep',None) tuples 8 | #} 9 | {% macro render_nav_dropdown(label,link_list) %} 10 | {{ label }} 11 | 22 | {% endmacro %} 23 | 24 | {# 25 | render_nav_link() 26 | @args: 27 | label(str) 28 | endpoint(str) 29 | @notes: 30 | highlights link with given endpoint 31 | #} 32 | {% macro render_nav_link(label,endpoint) %} 33 | {{ endpoint }} 34 | {% endmacro %} 35 | 36 | {# 37 | render_navbar() 38 | @args: 39 | brand(str) 40 | nav_links(list) 41 | dropdowns(list) 42 | inverse(bool) - False 43 | auth(bool) - False 44 | @notes: 45 | create navbar (mainly for admin interface) 46 | #} 47 | {% macro render_navbar(brand,nav_links,dropdowns,inverse=false,auth=false) %} 48 | {% if inverse %} 49 | {% set type = 'inverse' %} 50 | {% else %} 51 | {% set type = 'default' %} 52 | {% endif %} 53 | 88 | {% endmacro %} 89 | 90 | {# 91 | render_bs3_field() 92 | @args: 93 | field(wtforms.Field) 94 | label(bool) - True 95 | 96 | @notes: 97 | render field with bs3 styles 98 | #} 99 | {% macro render_field(field,label=true) %} 100 |
    101 | {% if (field.type != 'HiddenField' and field.type != 'CSRFTokenField') and label %} 102 | 103 | {% endif %} 104 | {{ field(class_='form-control',**kwargs) }} 105 | {% if field.errors %} 106 | {% for e in field.errors %} 107 |

    {{ e }}

    108 | {% endfor %} 109 | {% endif %} 110 |
    111 | {% endmacro %} 112 | 113 | {# 114 | render_checkbox() 115 | @args: 116 | field(wtforms.Field) 117 | @notes: 118 | renders bs3 compliant checkbox 119 | #} 120 | {% macro render_checkbox(field) %} 121 |
    122 | 125 |
    126 | {% endmacro %} 127 | 128 | {# 129 | render_radio() 130 | @args: 131 | field(wtforms.Field) 132 | @notes: 133 | renders bs3 compliant radio select 134 | #} 135 | {% macro render_radio(field) %} 136 | {% for value,label,_ in field.iter_choices() %} 137 |
    138 | 141 |
    142 | {% endfor %} 143 | {% endmacro %} 144 | 145 | {% macro render_content_link(field) %} 146 | {% if 'page' in request.url %} 147 | {% if 'edit' in request.url %} 148 | {% set link_url = 'admin.edit_page_content' %} 149 | {% else %} 150 | {% set link_url = 'admin.page_content' %} 151 | {% endif %} 152 | {% elif 'block' in request.url %} 153 | {% if 'edit' in request.url %} 154 | {% set link_url = 'admin.edit_block_content' %} 155 | {% else %} 156 | {% set link_url = 'admin.block_content' %} 157 | {% endif %} 158 | {% endif %} 159 | Edit Content 161 | {% endmacro %} 162 | 163 | 164 | {% macro render_form(form,action='',action_text='Submit',class_='',btn_class='btn btn-default') %} 165 |
    166 | {{ form.hidden_tag() if form.hidden_tag is defined}} 167 | {% if caller %} 168 | {{ caller() }} 169 | {% else %} 170 | {% set c = 0 %} 171 | {% for f in form %} 172 | {% set c = 1 + 1 %} 173 | {% endfor %} 174 | {% if c > 2 %} 175 |
    176 | {% endif %} 177 | {% set row = 1 %} 178 | {% for f in form %} 179 | {% if f == none %} 180 | {% else %} 181 | {% if row == 1 %} 182 | {% set row = 0 %} 183 | {% if c > 2 %} 184 |
    185 | {% endif %} 186 | {% else %} 187 | {% set row = 1 %} 188 |   189 | {% endif %} 190 | {% endif %} 191 | {% if f.flags.required %}*  192 | Required
    {% endif %} 193 | {% if f.type == 'FormField' %} 194 | {{ f.hidden_tag() }} 195 | {% for field in f %} 196 | {% if not field.type == 'CSRFTokenField' %} 197 | {{ field.label }}{{ field }} 198 | {% endif %} 199 | {% endfor %} 200 | {% elif form._has_pagedown %} 201 | {{ f(rows = 10, style = 'width:100%') }} 202 | {% else %} 203 | {% if f.name == 'content' %} 204 | {% if 'content' in request.endpoint %} 205 | {% if obj_id %} 206 | {{ render_content_link(f,obj_id) }} 207 | {% else %} 208 | {{ render_content_link(f) }} 209 | {% endif %} 210 | {% else %} 211 |
    212 | {{ render_field(f) }} 213 |
    214 | {% endif %} 215 | {% elif f.type == 'BooleanField' %} 216 | {{ render_checkbox(f) }} 217 | {% elif f.type == 'RadioField' %} 218 | {{ render_radio(f) }} 219 | {% elif f.type == 'QuerySelectField' %} 220 | {{ render_field(f) }} 221 | {% elif f.type == 'QuerySelectMultipleField' %} 222 | {{ render_field(f) }} 223 | {% elif f.type == 'TextField' or f.type == 'StringField' %} 224 | {{ f.label }}{{ f(class_='form-control') }} 225 | {% else %} 226 | {{ render_field(f) }} 227 | {% endif %} 228 | {% if row == 1 %} 229 |   230 | {% set row = 0 %} 231 | {% else %} 232 | {% if c > 2 %} 233 |
    234 | {% endif %} 235 | {% set row = 1 %} 236 | {% endif %} 237 | {% endif %} 238 | {% endfor %} 239 |
    240 | {% endif %} 241 | 242 |
    243 | {% endmacro %} 244 | 245 | {% macro render_panel_form(form,heading='',action='',action_text='Submit',class_='form',btn_class='btn btn-default') %} 246 |
    247 |
    248 |
    249 |
    250 |
    251 |

    {{ heading }}

    252 |
    253 |
    254 | {{ render_form(form=form,action='',action_text=action_text,btn_class=btn_class) }} 255 |
    256 |
    257 |
    258 |
    259 |
    260 | {% endmacro %} 261 | 262 | {% macro render_dual_panel_form(form_one_args,form_two_args) %} 263 | {# form_args should be (form, heading='',action='',action_text='Submit',class_='form',btn_class='btn btn-default') #} 264 | 265 |
    266 |
    267 |
    268 |
    269 |
    270 |

    {{ heading }}

    271 |
    272 |
    273 | {{ render_form(form=form,action='',action_text=action_text,btn_class=btn_class) }} 274 |
    275 |
    276 |
    277 |
    278 |
    279 | {% endmacro %} 280 | 281 | {% macro render_pagination(pag) %} 282 | 286 | {% endmacro %} 287 | 288 | 289 | {% macro render_centered_form(form,heading='',action='',action_text='Submit',class_='form',btn_class='btn btn-default') %} 290 |
    291 |
    292 |
    293 |
    294 |
    295 |

    {{ heading }}

    296 |
    297 |
    298 | {{ render_form(form=form,action='',action_text=action_text,btn_class=btn_class) }} 299 |
    300 |
    301 |
    302 |
    303 |
    304 | {% endmacro %} 305 | 306 | {% macro render_nav_sidebar(link_list,title) %} 307 | 317 | {% endmacro %} 318 | 319 | 320 | -------------------------------------------------------------------------------- /flask_xxl/apps/page/templates/new_post.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin.html' %} 2 | {% block extra_head %} 3 | {% if codemirror %} 4 | {{ codemirror.include_codemirror() }} 5 | {% endif %} 6 | 16 | {% endblock extra_head %} 17 | {% block body %} 18 | 21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |

    28 | 29 | Add New Page 30 | 31 |

    32 |
    33 |
    34 |
    35 |
    36 |
    37 |
    38 | {% if form.title and form.name %} 39 |
    40 | {{ form.title(class_="form-control",placeholder="Page Title",required=true,style="width:49%;") }} 41 | {{ form.name(class_="form-control",placeholder="Page Name",required=true,style="width:50%;") }} 42 |
    43 | {% else %} 44 | 45 | {% endif %} 46 |
    47 |
    48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 | {% if form.visible %} 56 | {{ form.visible(class_="form-control") }} 57 | {% else %} 58 | 62 | {% endif %} 63 |
    If you select draft your page will not be published
    64 |
    65 |
    66 |
    67 |
    68 |
    69 | {% if form.date_added %} 70 | {{ form.date_added.label.text }} 71 | {{ form.date_added(class_="form-control input-group", value="", placeholder="Enter Date",required=true) }} 72 | {% else %} 73 | Publish On: 74 | 75 | {% endif %} 76 |
    77 |
    78 | If publish is selected, the page will be public on this date 79 |
    80 |
    81 |
    82 |
    83 |
    84 |
    85 | {% if form.date_end %} 86 | {{ form.date_end.label.text }} 87 | {{ form.date_end(class_="form-control input-group",value="",placeholder="Enter Date") }} 88 | {% else %} 89 | Expire On: 90 | 91 | {% endif %} 92 |
    93 | If set, the page will no longer be public on this date 94 |
    95 |
    96 |
    97 |
    98 |
    99 |
    100 |
    101 |
    102 |
    103 |
    104 | {% if form.content %} 105 | {{ form.content(class_="form-control",placeholder="content",rows="5" ,required=true,style="height:250px;border:2px inset black;") }} 106 | {% else %} 107 | 108 | {% endif %} 109 |
    110 |
    111 |
    112 |
    113 |
    114 |
    115 |
    116 |
    117 | {% if form.short_url %} 118 | {{request.url_root}} 119 | {{ form.short_url(class_="form-control",placeholder="Page Url") }} 120 | {% else %} 121 | www.my-flask-cms.com/ 122 | 123 | {% endif %} 124 |
    125 |
    Will autogenerate based on slug value if nothing entered
    126 |
    127 |
    128 |
    129 |
    136 |
    137 |
    138 |
    139 |
    146 |
    147 |
    148 |
    149 |
    150 |
    151 |
    152 |
    153 | {% if form.category %} 154 | {{ form.category.label }} 155 | {{ form.category(class_="form-control", id="category") }} 156 | {% else %} 157 | 158 | 163 | {% endif %} 164 |
    165 |
    166 | {% if form.tags %} 167 | {{ form.tags.label }} 168 | {{ form.tags(class_="form-control pull-left", placeholder="Tags") }} 169 | {% else %} 170 | 171 | 172 | {% endif %} 173 |
    174 |
    175 |
    176 |
    177 |
    178 | 179 |
    180 |
    181 |
    182 |

    183 | 184 | META DATA 185 | 186 |

    187 |
    188 |
    189 |
    190 |
    191 |
    192 |
    193 |
    194 |
    195 | {% if form.slug %} 196 | {{ form.slug(class_="form-control",placeholder="slug", required=false) }} 197 | {% else %} 198 | 199 | {% endif %} 200 |
    201 |
    202 | {% if form.meta_title %} 203 | {{ form.meta_title(class_="form-control",placeholder="Meta Title") }} 204 | {% else %} 205 | 206 | {% endif %} 207 |
    208 |
    209 |
    210 |
    211 | {% if form.description %} 212 | {{ form.description(class_="form-control",placeholder="Description") }} 213 | {% else %} 214 | 215 | {% endif %} 216 |
    217 |
    218 |
    219 |
    220 | 240 |
    241 |
    242 |
    243 |
    244 |
    245 |
    246 |
    247 | {% endblock body %} 248 | {% block footer_js %} 249 | {{ super() }} 250 | 290 | {% endblock footer_js %} 291 | 292 | --------------------------------------------------------------------------------