").append( jQuery.parseHTML( responseText ) ).find( selector ) :
63 |
64 | // Otherwise use the full result
65 | responseText );
66 |
67 | }).complete( callback && function( jqXHR, status ) {
68 | self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
69 | });
70 | }
71 |
72 | return this;
73 | };
74 |
75 | });
76 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/static/libs/jQuery/src/ajax/parseJSON.js:
--------------------------------------------------------------------------------
1 | define([
2 | "../core"
3 | ], function( jQuery ) {
4 |
5 | // Support: Android 2.3
6 | // Workaround failure to string-cast null input
7 | jQuery.parseJSON = function( data ) {
8 | return JSON.parse( data + "" );
9 | };
10 |
11 | return jQuery.parseJSON;
12 |
13 | });
14 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/static/libs/jQuery/src/ajax/parseXML.js:
--------------------------------------------------------------------------------
1 | define([
2 | "../core"
3 | ], function( jQuery ) {
4 |
5 | // Cross-browser xml parsing
6 | jQuery.parseXML = function( data ) {
7 | var xml, tmp;
8 | if ( !data || typeof data !== "string" ) {
9 | return null;
10 | }
11 |
12 | // Support: IE9
13 | try {
14 | tmp = new DOMParser();
15 | xml = tmp.parseFromString( data, "text/xml" );
16 | } catch ( e ) {
17 | xml = undefined;
18 | }
19 |
20 | if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
21 | jQuery.error( "Invalid XML: " + data );
22 | }
23 | return xml;
24 | };
25 |
26 | return jQuery.parseXML;
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/static/libs/jQuery/src/ajax/script.js:
--------------------------------------------------------------------------------
1 | define([
2 | "../core",
3 | "../ajax"
4 | ], function( jQuery ) {
5 |
6 | // Install script dataType
7 | jQuery.ajaxSetup({
8 | accepts: {
9 | script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
10 | },
11 | contents: {
12 | script: /(?:java|ecma)script/
13 | },
14 | converters: {
15 | "text script": function( text ) {
16 | jQuery.globalEval( text );
17 | return text;
18 | }
19 | }
20 | });
21 |
22 | // Handle cache's special case and crossDomain
23 | jQuery.ajaxPrefilter( "script", function( s ) {
24 | if ( s.cache === undefined ) {
25 | s.cache = false;
26 | }
27 | if ( s.crossDomain ) {
28 | s.type = "GET";
29 | }
30 | });
31 |
32 | // Bind script tag hack transport
33 | jQuery.ajaxTransport( "script", function( s ) {
34 | // This transport only deals with cross domain requests
35 | if ( s.crossDomain ) {
36 | var script, callback;
37 | return {
38 | send: function( _, complete ) {
39 | script = jQuery("
40 |
41 |
42 | {% endblock %}
43 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/admin/new_blog.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 |
4 | {% block page_title %}New Blog{% endblock %}
5 |
6 | {% block css %}
7 |
8 | {% endblock %}
9 |
10 | {% block content %}
11 |
12 |
13 |
New Blog Post
14 |
15 |
35 |
36 | {% endblock %}
37 |
38 | {% block js %}
39 |
40 |
41 |
42 | {% endblock %}
43 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/blog/blog_detail.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 |
4 | {% block page_title %}{{ post.title }}{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
{{ post.title }}
11 |
12 |
13 | by {{ post.author }}
14 |
15 |
16 |
Posted: {{ post.date }}
17 |
18 |
{{ post.body }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
About
26 |
27 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore, perspiciatis adipisci
28 | accusamus
29 | laudantium odit aliquam repellat tempore quos aspernatur vero.
30 |
31 |
32 |
Top Tags
33 |
34 |
50 |
51 |
52 |
53 |
54 |
55 | {% endblock %}
56 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/blog/blog_page.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 |
4 | {% block page_title %}Blog{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
13 | {% for post in posts %}
14 |
17 |
18 | by {{ post.author }}
19 |
20 |
Posted: {{ post.date }}
21 |
22 |
{{ post.slug }}
23 |
Read More
24 |
25 | {% endfor %}
26 |
36 |
37 |
38 |
39 |
40 |
41 |
About
42 |
43 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Inventore, perspiciatis adipisci
44 | accusamus laudantium odit aliquam repellat tempore quos aspernatur vero.
45 |
46 |
47 |
Top Tags
48 |
49 |
65 |
66 |
67 |
68 |
69 |
70 | {% endblock %}
71 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/footer.html:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/layout.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
{% block page_title %}
12 | {% endraw %}
13 | {{ cookiecutter.project_name }}
14 | {% raw %}
15 | {% endblock %}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {% assets "css_all" %}
25 |
26 | {% endassets %}
27 |
28 |
29 |
30 | {% block css %}{% endblock %}
31 |
32 |
33 |
34 | {% block body %}
35 | {% with form=form %}
36 | {% include "nav.html" %}
37 | {% endwith %}
38 |
39 |
{% block header %}{% endblock %}
40 |
41 |
42 |
43 | {% with messages = get_flashed_messages(with_categories=true) %}
44 | {% if messages %}
45 |
46 |
47 | {% for category, message in messages %}
48 |
49 |
×
50 | {{message}}
51 |
52 | {% endfor %}
53 |
54 |
55 | {% endif %}
56 | {% endwith %}
57 |
58 | {% block content %}{% endblock %}
59 |
60 |
61 |
62 |
63 | {% include "footer.html" %}
64 |
65 |
66 |
67 | {% block js %}{% endblock %}
68 |
69 | {% endblock %}
70 |
71 |
72 | {% endraw %}
73 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/nav.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 |
60 | {% endraw %}
61 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/public/about.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "../layout.html" %}
3 |
4 | {% block content %}
5 |
11 | {% endblock %}
12 | {% endraw %}
13 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/public/home.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
5 |
6 | {% endraw %}
7 |
Welcome to {{ cookiecutter.project_name }}
8 | {% raw %}
9 |
This is a starter Flask template. It includes Bootstrap 3, jQuery 2, Flask-SQLAlchemy, WTForms, and various testing utilities out of the box.
10 |
Learn more »
11 |
12 |
13 |
14 |
15 |
16 |
Bootstrap 3
17 |
Sleek, intuitive, and powerful mobile-first front-end framework for faster and easier web development.
18 |
Official website »
19 |
20 |
21 |
SQLAlchemy
22 |
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.
23 |
Official website »
24 |
25 |
26 |
WTForms
27 |
WTForms is a flexible forms validation and rendering library for python web development.
28 |
Official website »
29 |
30 |
31 |
32 | {% endblock %}
33 | {% endraw %}
34 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/public/register.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
5 |
Register
6 |
7 |
35 |
Already registered? Click here to login.
36 |
37 | {% endblock %}
38 | {% endraw %}
39 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/public/sitemap_template.xml:
--------------------------------------------------------------------------------
1 | {% raw %}
2 |
3 |
4 | {% for page in pages %}
5 |
6 | {{page[0]|safe}}
7 | {{page[1]}}
8 |
9 | {% endfor %}
10 |
11 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/users/change_password.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
5 |
Change Password
6 |
Use the form below to change your password.
7 |
8 |
22 |
23 |
24 | {% endblock %}
25 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/users/change_username.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
5 |
Change Username
6 |
Use the form below to change your username.
7 |
8 |
22 |
23 |
24 | {% endblock %}
25 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/users/profile.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
Welcome {{ session.username }}
5 |
This is the members-only page.
6 | {% endblock %}
7 | {% endraw%}
8 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/users/reset.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
5 |
14 |
15 | {% endblock %}
16 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/users/reset_with_token.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
5 |
19 |
20 | {% endblock %}
21 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/templates/users/unsubscribe.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "layout.html" %}
3 | {% block content %}
4 |
5 |
Unsubscribe
6 |
We hope you don't really want to unsubscribe from {{ cookiecutter.app_name }}, but if you insist, click below.
7 |
Unsubscribe
8 |
9 | {% endblock %}
10 | {% endraw %}
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """Helper utilities and decorators."""
3 | from flask import flash, render_template, current_app
4 |
5 | def flash_errors(form, category="warning"):
6 | """Flash all errors for a form."""
7 | for field, errors in form.errors.items():
8 | for error in errors:
9 | flash("{0} - {1}"
10 | .format(getattr(form, field).label.text, error), category)
11 |
12 |
13 | def render_extensions(template_path, **kwargs):
14 | """
15 | Wraps around the standard render template method and shoves in some other stuff out of the config.
16 |
17 | :param template_path:
18 | :param kwargs:
19 | :return:
20 | """
21 |
22 | return render_template(template_path,
23 | _GOOGLE_ANALYTICS=current_app.config['GOOGLE_ANALYTICS'],
24 | **kwargs)
25 |
26 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/views/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'willmcginnis'
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/views/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """Public section, including homepage and signup."""
3 | from flask import Blueprint, request
4 |
5 | from flask_login import login_required, current_user
6 | from {{cookiecutter.app_name}}.utils import flash_errors, render_extensions
7 | from {{cookiecutter.app_name}}.models.post import Post
8 |
9 | blueprint = Blueprint('admin', __name__, static_folder="../static")
10 |
11 |
12 | @blueprint.route("/new_blog", methods=["GET", "POST"])
13 | @login_required
14 | def new_blog():
15 | if not current_user.is_admin:
16 | return render_extensions('401.html')
17 |
18 | if request.method == 'POST':
19 | try:
20 | content = str(request.form['content'])
21 | except Exception:
22 | content = ''
23 |
24 | try:
25 | slug = str(request.form['slug'])
26 | except Exception:
27 | slug = ''
28 |
29 | try:
30 | title = str(request.form['title'])
31 | except Exception:
32 | title = ''
33 |
34 | post = Post(title=title, body=content, slug=slug)
35 | post.save()
36 |
37 | current_user.posts.append(post)
38 | current_user.save()
39 |
40 | return render_extensions('admin/new_blog.html')
41 |
42 | @blueprint.route("/edit_blog/
/", methods=["GET", "POST"])
43 | @login_required
44 | def edit_blog(blog_id):
45 | if not current_user.is_admin:
46 | return render_extensions('401.html')
47 |
48 | if request.method == 'POST':
49 | try:
50 | content = str(request.form['content'])
51 | except Exception:
52 | content = ''
53 |
54 | try:
55 | slug = str(request.form['slug'])
56 | except Exception:
57 | slug = ''
58 |
59 | try:
60 | title = str(request.form['title'])
61 | except Exception:
62 | title = ''
63 |
64 | post = Post(title=title, body=content, slug=slug)
65 | post.save()
66 |
67 | current_user.posts.append(post)
68 | current_user.save()
69 |
70 | post_obj = Post.query.filter_by(id=int(blog_id)).first()
71 | post_content = {
72 | 'title': str(post_obj.title),
73 | 'slug': str(post_obj.slug),
74 | 'body': str(post_obj.body),
75 | }
76 |
77 | return render_extensions('admin/edit_blog.html', post=post_content)
78 |
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/views/blog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """Public section, including homepage and signup."""
3 | from flask import Blueprint
4 |
5 | from {{cookiecutter.app_name}}.services.blog import get_page, get_post_detail
6 | from {{cookiecutter.app_name}}.utils import flash_errors, render_extensions
7 |
8 | blueprint = Blueprint('blog', __name__, static_folder="../static")
9 |
10 |
11 | @blueprint.route("/blog//", methods=["GET"])
12 | def blog_page(page=None):
13 | """
14 |
15 | :param page:
16 | :return:
17 | """
18 |
19 | page = int(page)
20 | _page_size = 3 # TODO: move into settings
21 |
22 | if page is None or page <= 0:
23 | next_page = 0
24 | prev_page = 1
25 | current = True
26 | else:
27 | next_page = page - 1
28 | prev_page = page + 1
29 | current = False
30 |
31 | posts = get_page(_page_size, page)
32 |
33 | return render_extensions("blog/blog_page.html", posts=posts, next_page=next_page, prev_page=prev_page, current=current)
34 |
35 |
36 | @blueprint.route("/post_detail//", methods=["GET"])
37 | def blog_detail(pk):
38 | """
39 |
40 | :param pk:
41 | :return:
42 | """
43 |
44 | post = get_post_detail(int(pk))
45 | return render_extensions("blog/blog_detail.html", post=post)
--------------------------------------------------------------------------------
/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/views/public.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """Public section, including homepage and signup."""
3 | from flask import (Blueprint, request, render_template, flash, url_for, send_from_directory, make_response,
4 | redirect, current_app)
5 | import datetime
6 |
7 | from flask_login import login_user, login_required, logout_user
8 |
9 | from {{cookiecutter.app_name}}.extensions import login_manager
10 | from {{cookiecutter.app_name}}.models.user import User
11 | from {{cookiecutter.app_name}}.forms.public import LoginForm
12 | from {{cookiecutter.app_name}}.forms.user import RegisterForm
13 | from {{cookiecutter.app_name}}.utils import flash_errors, render_extensions
14 | from {{cookiecutter.app_name}}.database import db
15 |
16 | blueprint = Blueprint('public', __name__, static_folder="../static")
17 |
18 |
19 | @login_manager.user_loader
20 | def load_user(id):
21 | return User.get_by_id(int(id))
22 |
23 |
24 | @blueprint.route("/", methods=["GET", "POST"])
25 | def home():
26 | form = LoginForm(request.form)
27 | # Handle logging in
28 | if request.method == 'POST':
29 | if form.validate_on_submit():
30 | login_user(form.user)
31 | flash("You are logged in.", 'success')
32 | redirect_url = request.args.get("next") or url_for("user.profile")
33 | return redirect(redirect_url)
34 | else:
35 | flash_errors(form)
36 | return render_extensions("public/home.html", form=form)
37 |
38 |
39 | @blueprint.route('/logout/')
40 | @login_required
41 | def logout():
42 | logout_user()
43 | flash('You are logged out.', 'info')
44 | return redirect(url_for('public.home'))
45 |
46 |
47 | @blueprint.route("/register/", methods=['GET', 'POST'])
48 | def register():
49 | form = RegisterForm(request.form, csrf_enabled=False)
50 | if form.validate_on_submit():
51 | new_user = User.create(username=form.username.data,
52 | first_name=form.first_name.data,
53 | last_name=form.last_name.data,
54 | email=form.email.data,
55 | password=form.password.data,
56 | active=True)
57 | flash("Thank you for registering. You can now log in.", 'success')
58 | return redirect(url_for('public.home'))
59 | else:
60 | flash_errors(form)
61 | return render_extensions('public/register.html', form=form)
62 |
63 |
64 | @blueprint.route("/about/")
65 | def about():
66 | form = LoginForm(request.form)
67 | return render_extensions("public/about.html", form=form)
68 |
69 | @blueprint.route('/robots.txt')
70 | @blueprint.route('/favicon.ico')
71 | def static_from_root():
72 | return send_from_directory(current_app.static_folder, request.path[1:])
73 |
74 | @blueprint.route('/sitemap.xml', methods=['GET'])
75 | def sitemap():
76 | """
77 | Generate sitemap.xml. Makes a list of urls and date modified.
78 | """
79 | pages = []
80 | ten_days_ago = datetime.datetime.now() - datetime.timedelta(days=10)
81 | ten_days_ago = ten_days_ago.date().isoformat()
82 | # static pages
83 | for rule in current_app.url_map.iter_rules():
84 | if "GET" in rule.methods and len(rule.arguments) == 0:
85 | pages.append([rule.rule, ten_days_ago])
86 |
87 | sitemap_xml = render_template('public/sitemap_template.xml', pages=pages)
88 | response = make_response(sitemap_xml)
89 | response.headers["Content-Type"] = "application/xml"
90 |
91 | return response
--------------------------------------------------------------------------------