Skip to content

Commit a2fa50f

Browse files
committed
documentation on mkdocs
1 parent 4edd82b commit a2fa50f

File tree

11 files changed

+1047
-0
lines changed

11 files changed

+1047
-0
lines changed

docs/about.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

docs/account.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
Routing (Account Routes)
2+
3+
This guide will be explaining the concept of routing by going through a file. We will be using `app/account/views.py`
4+
5+
## Login
6+
7+
```
8+
@account.route('/login', methods=['GET', 'POST'])
9+
def login():
10+
"""Log in an existing user."""
11+
form = LoginForm()
12+
if form.validate_on_submit():
13+
user = User.query.filter_by(email=form.email.data).first()
14+
if user is not None and user.password_hash is not None and \
15+
user.verify_password(form.password.data):
16+
login_user(user, form.remember_me.data)
17+
flash('You are now logged in. Welcome back!', 'success')
18+
return redirect(request.args.get('next') or url_for('main.index'))
19+
else:
20+
flash('Invalid email or password.', 'form-error')
21+
return render_template('account/login.html', form=form)
22+
```
23+
All routes are decorated with the name of the associated Blueprint along
24+
with the .route prop with attributes of (name, methods=[]). For example
25+
`@account.route('/login', method=['GET', 'POST'])` creates a route accessible
26+
at `yourdomain.com/account/login`.
27+
28+
This route can accept either `POST` or `GET`
29+
requests which is appropriate since there is a form associated with the
30+
login process. This form is loaded from the forms.py file (in this case
31+
the `LoginForm()` is loaded) and we then check if the form is valid
32+
(`validate_on_submit`) in that it is a valid POST request.
33+
We grab the form field named 'email' and query the User database for the
34+
user that has that email. Then we call the `verify_password` method
35+
from the User class for this specific user instance and check the hashed
36+
password in the database against the password provided by the user which
37+
is hashed with the SECRET_KEY. If everything is fine, the Flask-login
38+
extendion performs a login_user action and sets the `SESSION['user_id']`
39+
equivalent to the user id provided from the user instance. If the
40+
form has remember_me set to True (ie checked) then that is passed along
41+
as a parameter in login_user.
42+
43+
If it was redirected to this /login page, their URL will have a parameter
44+
called `next` containing the URL they need to be directed to after they
45+
login. Otherwise, they will just be sent to the main.index route
46+
This is true for the admin as well. It is best to edit this functionality
47+
since index pages should differ by user type. There is a flash sent as well
48+
if the request is successful.
49+
50+
If there is an error in the user checking process, then the user is kicked
51+
back to the account/login page with a flashed form error.
52+
53+
If this is a GET request, only the account/login page is rendered
54+
55+
## Logout
56+
57+
```
58+
@account.route('/logout')
59+
@login_required
60+
def logout():
61+
logout_user()
62+
flash('You have been logged out.', 'info')
63+
return redirect(url_for('main.index'))
64+
```
65+
66+
The Flask-login Manager has a built in logout_user function that
67+
removes the SESSION variables from the user's browser and logs out
68+
the user completely
69+

docs/assets.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Assets
2+
3+
(refer to `flask-base/app/assets.py`)
4+
5+
See app/__init__.py for details on this
6+
7+
context is set as the assets/styles and
8+
assetts/scripts folders
9+
10+
filter = scss -> convers .scss to css
11+
filter = jsmin -> converts to minified
12+
javascript
13+
14+
Bundle is just the plugin that helps us
15+
do this task.
16+
17+
# Decorators
18+
19+
```python
20+
def permission_required(permission):
21+
"""Restrict a view to users with the given permission."""
22+
def decorator(f):
23+
@wraps(f)
24+
def decorated_function(*args, **kwargs):
25+
if not current_user.can(permission):
26+
abort(403)
27+
return f(*args, **kwargs)
28+
return decorated_function
29+
return decorator
30+
31+
```
32+
33+
This is a rather complicated function, but the general idea
34+
is that it will allow is to create a decorator that will
35+
kick users to a 403 page if they dont have a certain permission
36+
or let them continue. First there is a permission_required
37+
method which takes in a permission e.g. Permission.ADMINISTER
38+
Then it create a function called 'decorator' which performs
39+
the check in a separate function itself decorates called
40+
'decorated_function'. It returns the result from
41+
'decorate_function' as well as the results from a specified
42+
parameter f that serves as an extra function call. The
43+
@wraps(f) decorator is itself used to give context for the
44+
decorated function and actually point that context towards
45+
the fully decorated function when the permission_required()
46+
decorator is invoked. Tl;dr it does some complicated stuff
47+
you don't really need to know about
48+
49+
## `@admin_required`
50+
51+
```
52+
def admin_required(f):
53+
return permission_required(Permission.ADMINISTER)(f)
54+
```
55+
56+
This is a decorator created by the permission required decorator
57+
It checks if the current_user is an admin or not. It takes in a
58+
function f as the next action to occur after the check happens
59+
however in practice, we only use the decorator @admin_required
60+
on routes.
61+
62+
#

docs/config.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Configuration Commands and `config.py`
2+
3+
So lets go through each of the configuring variables.
4+
5+
APP_NAME is the name of the app. This is used in templating
6+
to make sure that all the pages at least have the same html
7+
title
8+
9+
SECRET_KEY is a alpha-numeric string that is used for crypto
10+
related things in some parts of the application. Set it as an
11+
environment variable or default to our insecure one. This is
12+
used in password hashing see app/models/user.py for more info.
13+
YOU SHOULD SET THIS AS A CONFIG VAR IN PRODUCTION!!!!
14+
15+
SQLALCHEMY_COMMIT_ON_TEARDOWN is used to auto-commit any sessions
16+
that are open at the end of the 'app context' or basically the
17+
current request on the application. But it is best practice
18+
to go ahead and commit after any db.session is created
19+
20+
SSL_DISABLE I unfortunately do not know much about ;(. But something
21+
realated to https
22+
23+
MAIL_... is used for basic mailing server connectivity throug the
24+
SMTP protocol. This is further described in email.py.

docs/index.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Welcome to Flask-Base Documentation
2+
3+
## Source Code
4+
5+
See [the Github repository](http://github.com/hack4impact/flask-base)
6+
7+
## Purpose
8+
9+
This guide is meant to be a mix between formal and explanatory documentation.
10+
11+
## Synopsis
12+
13+
A Flask application template with the boilerplate code already done for you.
14+
15+
## What's included?
16+
17+
* Blueprints
18+
* User and permissions management
19+
* Flask-SQLAlchemy for databases
20+
* Flask-WTF for forms
21+
* Flask-Assets for asset management and SCSS compilation
22+
* Flask-Mail for sending emails
23+
* gzip compression
24+
* gulp autoreload for quick static page debugging
25+
26+
## Formatting code
27+
28+
Before you submit changes to flask-base, you may want to auto format your code with `python manage.py format`.
29+
30+
31+
## Contributing
32+
33+
See [the Github repository](http://github.com/hack4impact/flask-base)
34+
35+
## License
36+
[MIT License](http://github.com/hack4impact/flask-base/blob/master/LICENSE.md)

docs/init.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# `__init__.py`
2+
3+
## CSRF Protection
4+
5+
Note about CSRF protection. This basically prevents hackers
6+
from being able to post to our POST routes without having actually
7+
loaded a form on our website. E.g. they could potentially create
8+
users if they found out the URL for our register routes and
9+
the params we expect (its fairly easy to do). But with
10+
CSRF protection, all forms have a hidden field that is verified on
11+
our end. This is a bit low level, but there is a SESSION object
12+
stored on the flask server in memory. Each user has their
13+
own session containing things like their username, password, etc
14+
When a form created, a random string called a CSRF token is
15+
created and is sent along with the form in a hidden field.
16+
Simultaneously, this string is added to the user session
17+
stored on the server. When the user submits a form, then
18+
the server will check to see if the hidden form field with the
19+
CSRF token matches the CSRF token stored in the user's session
20+
on the server. If it does, then everything is fine and the
21+
POST request can proceed normally. If not, then the POST request
22+
is aborted as a 403 (i think) error is thrown...basically
23+
the user is not able to POST. This is great for forms, but
24+
if you want to create a public API that does not require a session,
25+
then you'll want to include a decorator on your route `@csrf.exempt`
26+
27+
## Flask-Login
28+
29+
30+
```python
31+
login_manager = LoginManager()
32+
login_manager.session_protection = 'strong'
33+
login_manager.login_view = 'account.login'
34+
```
35+
36+
Flask-login provides us with a bunch of easy ways to do secure and
37+
simple login techniques. LoginManager() is the main class that
38+
will handle all of this. Session protection makes sure the
39+
user session is very secure and login_manager.login_view
40+
Is the view that the a non-authenticated user will get redirected
41+
to. Otherwise it is a 401 error.
42+
43+
## `init_app(app)`
44+
45+
```python
46+
mail.init_app(app)
47+
db.init_app(app)
48+
login_manager.init_app(app)
49+
csrf.init_app(app)
50+
compress.init_app(app)
51+
RQ(app)
52+
```
53+
54+
init_app(app) are methods in each of these packages
55+
More on init_app. It binds each instance of the respective
56+
application to the flask app. However, we do need to specify
57+
an application context while using things like db, mail,
58+
login_manager, and compress since they are not bound to our
59+
application _exclusively_.
60+
61+
## Set up Asset Pipeline
62+
63+
This one is a bit complex. First an Environment instance is created
64+
that holds references to a single path to the 'static' folder. We don't
65+
really care about that since the url_for() method allows us to specify
66+
access to resources in the static/ directory. But we then append all the
67+
folders and files within the 'dirs' array to the environment. This
68+
action provides context for the subsequence set of register actions.
69+
Looking in app/assets.py there are some Bundle instances created with
70+
3 parameters mainly: what type of file(s) to bundle, a type of filter/
71+
transpiler to apply, and then a final output file. E.g. for the
72+
app_css bundle, it looks within assets/styles, assets/scripts for any
73+
*.scss files, converts them to css with the scss transpiler and then
74+
outputs it to the styles/app.css file.
75+
See the templates/partials/_head.html
76+
file for more information on how to actually include the file.
77+
78+
## Blueprints
79+
80+
```python
81+
from account import account as account_blueprint
82+
from admin import admin as admin_blueprint
83+
from main import main as main_blueprint
84+
85+
app.register_blueprint(main_blueprint)
86+
app.register_blueprint(account_blueprint, url_prefix='/account')
87+
app.register_blueprint(admin_blueprint, url_prefix='/admin')
88+
```
89+
90+
Blueprints allow us to set up url prefixes for routes contained
91+
within the views file of each of the divisions we specify to be
92+
registered with a blueprint. Blueprints are meant to distinguish between
93+
the variable different bodies within a large application.
94+
In the case of flask-base, we have 'main', 'account', and 'admin'
95+
sections. The 'main' section contains error handling and views.
96+
The other sections contain mainly just views. The folders for each of
97+
these sections also contain an __init__ file which actually creates the
98+
Blueprint itself with a name and a default __name__ param as well.
99+
After that, the views file and any other files that depend upon the
100+
blueprint are imported and can use the variable name assigned to the
101+
blueprint to reference things like decorators for routes. e.g. if my
102+
blueprint is name 'first_component', I would use the following as
103+
a decorator for my routes '@first_component.route'. By specifying
104+
the url_prefix, all of the functions and routes etc of the blueprint
105+
will be read with the base url_prefix specified. E.g. if I wanted
106+
to access the '/blah' route within the 'acount' blueprint, I need only
107+
specify @account.router('/blah') def ... as my method in views.py under
108+
the account/ directory. But I would be able to access it in the
109+
browser with yourdomain.com/accounts/blah
110+
#
111+
A note on why we are importing here: Because stuff will break...and for
112+
a good reason! The account import in turn imports the views.py file under
113+
the account/ directory. The views.py in turn references db
114+
db is the database instance which was created after the import statements
115+
If we had included these import statements at the very top, views.py
116+
under account would have refered to a db instance which was not created!
117+
hence errors...all the errors (at least in files relying upon a created
118+
db instance...and any instance created beyond that.
119+

0 commit comments

Comments
 (0)