Initial code commit
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.pyc
|
||||
venv
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
blender-bfct/config.py
|
||||
blender-bfct/runserver.wsgi
|
16
blender-bfct/application/__init__.py
Normal file
16
blender-bfct/application/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from flask import Flask
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
|
||||
# Create app
|
||||
app = Flask(__name__)
|
||||
import config
|
||||
app.config.from_object(config.Development)
|
||||
|
||||
# Create database connection object
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
from controllers import main
|
||||
from controllers import admin
|
||||
from controllers.applications import applications
|
||||
|
||||
app.register_blueprint(applications, url_prefix='/applications')
|
0
blender-bfct/application/controllers/__init__.py
Normal file
0
blender-bfct/application/controllers/__init__.py
Normal file
64
blender-bfct/application/controllers/admin.py
Normal file
64
blender-bfct/application/controllers/admin.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from application import app, db
|
||||
from application.models.users import user_datastore
|
||||
from application.models.applications import Application
|
||||
|
||||
from flask import render_template, redirect, url_for
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
from flask.ext import admin, login
|
||||
from flask.ext.admin import Admin, expose, form
|
||||
from flask.ext.admin.contrib import sqla
|
||||
from flask.ext.admin.contrib.sqla import ModelView
|
||||
from flask.ext.admin.base import BaseView
|
||||
from flask.ext.security import login_required, current_user, roles_accepted
|
||||
|
||||
from werkzeug import secure_filename
|
||||
from jinja2 import Markup
|
||||
import os, hashlib, time
|
||||
import os.path as op
|
||||
|
||||
|
||||
# Create customized views with access restriction
|
||||
class CustomModelView(ModelView):
|
||||
def is_accessible(self):
|
||||
default_role = user_datastore.find_role("admin")
|
||||
#return login.current_user.is_authenticated()
|
||||
return login.current_user.has_role(default_role)
|
||||
|
||||
class CustomBaseView(BaseView):
|
||||
def is_accessible(self):
|
||||
return True
|
||||
|
||||
|
||||
# Create customized index view class that handles login & registration
|
||||
class MyAdminIndexView(admin.AdminIndexView):
|
||||
|
||||
@expose('/')
|
||||
def index(self):
|
||||
default_role = user_datastore.find_role("admin")
|
||||
if login.current_user.is_authenticated() and login.current_user.has_role(default_role):
|
||||
return super(MyAdminIndexView, self).index()
|
||||
else:
|
||||
return redirect(url_for('homepage'))
|
||||
|
||||
@expose('/logout/')
|
||||
def logout_view(self):
|
||||
login.logout_user()
|
||||
return redirect(url_for('homepage'))
|
||||
|
||||
|
||||
class ApplicationView(CustomModelView):
|
||||
column_list = ('user.first_name', 'user.last_name', 'city_country', 'submission_date', 'status')
|
||||
column_labels = {'user.first_name' : 'First Name', 'user.last_name' : 'Last Name'}
|
||||
column_searchable_list = ('website', 'status')
|
||||
can_create = False
|
||||
|
||||
|
||||
# Create admin
|
||||
backend = admin.Admin(
|
||||
app,
|
||||
'BFCT management',
|
||||
index_view=MyAdminIndexView(),
|
||||
base_template='admin/layout_admin.html'
|
||||
)
|
||||
|
||||
backend.add_view(ApplicationView(Application, db.session, name='Applications', url='applications'))
|
94
blender-bfct/application/controllers/applications.py
Normal file
94
blender-bfct/application/controllers/applications.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import datetime
|
||||
from application import db
|
||||
from application.models.users import *
|
||||
from application.models.applications import Application, Skill, ReviewersApplications
|
||||
|
||||
from flask import render_template, redirect, url_for, request, Blueprint
|
||||
from flask.ext.security import login_required, roles_accepted
|
||||
from flask.ext.security.core import current_user
|
||||
from flask_wtf import Form
|
||||
from wtforms import TextField, TextAreaField, BooleanField, SelectMultipleField
|
||||
from wtforms.validators import DataRequired
|
||||
from wtforms.fields.html5 import URLField
|
||||
from wtforms.validators import url
|
||||
|
||||
applications = Blueprint('applications', __name__)
|
||||
|
||||
@applications.route('/')
|
||||
@roles_accepted('bfct_manager', 'admin')
|
||||
def index():
|
||||
return render_template('applications/index.html',
|
||||
title='applications',
|
||||
applications=Application.query.all())
|
||||
|
||||
@applications.route('/view/<int:id>')
|
||||
@roles_accepted('bfct_manager', 'admin')
|
||||
def view(id):
|
||||
review = ReviewersApplications.query.\
|
||||
filter_by(application_id=id).\
|
||||
filter_by(reviewer_blender_id=current_user.id).\
|
||||
first()
|
||||
|
||||
reviews = ReviewersApplications.query.\
|
||||
filter_by(application_id=id).\
|
||||
all()
|
||||
|
||||
return render_template('applications/view.html',
|
||||
title='applications',
|
||||
application=Application.query.get_or_404(id),
|
||||
review=review,
|
||||
reviews=reviews)
|
||||
|
||||
@applications.route('/vote/<int:approved>/<int:id>')
|
||||
@roles_accepted('bfct_manager', 'admin')
|
||||
def vote(approved, id):
|
||||
application = Application.query.get_or_404(id)
|
||||
review = ReviewersApplications.query.\
|
||||
filter_by(application_id=id).\
|
||||
filter_by(reviewer_blender_id=current_user.id).\
|
||||
first()
|
||||
|
||||
if approved:
|
||||
if review:
|
||||
application.reject -= 1
|
||||
application.approve += 1
|
||||
else:
|
||||
if review:
|
||||
application.approve -= 1
|
||||
application.reject += 1
|
||||
|
||||
if application.status == 'submitted':
|
||||
application.status = 'under_review'
|
||||
application.review_start_date = datetime.datetime.now()
|
||||
db.session.add(application)
|
||||
|
||||
if not review:
|
||||
review = ReviewersApplications(
|
||||
application_id=id,
|
||||
reviewer_blender_id=current_user.id,
|
||||
approved=approved)
|
||||
else:
|
||||
review.approved = approved
|
||||
|
||||
db.session.add(review)
|
||||
|
||||
db.session.commit()
|
||||
return redirect(url_for('.view', id=id))
|
||||
|
||||
@applications.route('/final-review/<int:approved>/<int:id>')
|
||||
@roles_accepted('admin')
|
||||
def final_review(approved, id):
|
||||
application = Application.query.get_or_404(id)
|
||||
|
||||
if application.status != 'under_review':
|
||||
return 'error'
|
||||
else:
|
||||
if approved:
|
||||
application.status = 'approved'
|
||||
else:
|
||||
application.status = 'rejected'
|
||||
application.review_end_date = datetime.datetime.now()
|
||||
db.session.add(application)
|
||||
|
||||
db.session.commit()
|
||||
return redirect(url_for('.view', id=id))
|
79
blender-bfct/application/controllers/main.py
Normal file
79
blender-bfct/application/controllers/main.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from application import app, db
|
||||
from application.models.users import *
|
||||
from application.models.applications import Application, Skill
|
||||
|
||||
from flask import render_template, redirect, url_for, request, flash
|
||||
from flask.ext.security import login_required
|
||||
from flask.ext.security.core import current_user
|
||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
||||
from flask_wtf import Form
|
||||
from wtforms import TextField, TextAreaField, BooleanField, SelectMultipleField
|
||||
from wtforms.validators import DataRequired
|
||||
from wtforms.fields.html5 import URLField
|
||||
from wtforms.validators import url
|
||||
|
||||
|
||||
class ApplicationForm(Form):
|
||||
network_profile = TextField('Blender Network Profile', validators=[DataRequired()])
|
||||
website = URLField(validators=[url()])
|
||||
city_country = TextField('City and Country', validators=[DataRequired()])
|
||||
teaching = BooleanField('Teaching')
|
||||
skills = SelectMultipleField('Areas of expertise', coerce=int)
|
||||
video_example = URLField(validators=[url()])
|
||||
written_example = URLField(validators=[url()])
|
||||
portfolio_cv = URLField(validators=[url()])
|
||||
bio_message = TextAreaField('Your message for the board')
|
||||
|
||||
|
||||
# Views
|
||||
@app.route('/')
|
||||
def homepage():
|
||||
return render_template('index.html', title='home')
|
||||
|
||||
|
||||
@app.route('/apply', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def apply():
|
||||
application = Application.query.filter_by(blender_id=current_user.id).first()
|
||||
if application:
|
||||
return redirect(url_for('my_application'))
|
||||
else:
|
||||
form = ApplicationForm()
|
||||
form.skills.choices = [(s.id, s.name) for s in Skill.query.all()]
|
||||
if form.validate_on_submit():
|
||||
print 'validating'
|
||||
application = Application(
|
||||
blender_id=current_user.id,
|
||||
website=form.website.data,
|
||||
city_country=form.city_country.data,
|
||||
teaching=form.teaching.data,
|
||||
video_example=form.video_example.data,
|
||||
written_example=form.written_example.data,
|
||||
portfolio_cv=form.portfolio_cv.data,
|
||||
bio_message=form.bio_message.data,
|
||||
status='submitted')
|
||||
for skill in form.skills.data:
|
||||
s = Skill.query.get(skill)
|
||||
application.skills.append(s)
|
||||
db.session.add(application)
|
||||
db.session.commit()
|
||||
return redirect(url_for('my_application'))
|
||||
# print form.errors
|
||||
return render_template('apply.html',
|
||||
title='apply',
|
||||
form=form)
|
||||
|
||||
|
||||
@app.route('/my-application')
|
||||
@login_required
|
||||
def my_application():
|
||||
try:
|
||||
application = Application.query.filter_by(blender_id=current_user.id).one()
|
||||
except MultipleResultsFound:
|
||||
flash('You have submitted more than one application. Get in touch with support@blendernetwork.org.')
|
||||
return render_template('index.html')
|
||||
if not application:
|
||||
return redirect(url_for('apply'))
|
||||
else:
|
||||
return render_template('my_application.html',
|
||||
application=application)
|
42
blender-bfct/application/helpers.py
Normal file
42
blender-bfct/application/helpers.py
Normal file
@@ -0,0 +1,42 @@
|
||||
def pretty_date(time=False):
|
||||
"""
|
||||
Get a datetime object or a int() Epoch timestamp and return a
|
||||
pretty string like 'an hour ago', 'Yesterday', '3 months ago',
|
||||
'just now', etc
|
||||
"""
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
if type(time) is int:
|
||||
diff = now - datetime.fromtimestamp(time)
|
||||
elif isinstance(time,datetime):
|
||||
diff = now - time
|
||||
elif not time:
|
||||
diff = now - now
|
||||
second_diff = diff.seconds
|
||||
day_diff = diff.days
|
||||
|
||||
if day_diff < 0:
|
||||
return ''
|
||||
|
||||
if day_diff == 0:
|
||||
if second_diff < 10:
|
||||
return "just now"
|
||||
if second_diff < 60:
|
||||
return str(second_diff) + " seconds ago"
|
||||
if second_diff < 120:
|
||||
return "a minute ago"
|
||||
if second_diff < 3600:
|
||||
return str( second_diff / 60 ) + " minutes ago"
|
||||
if second_diff < 7200:
|
||||
return "an hour ago"
|
||||
if second_diff < 86400:
|
||||
return str( second_diff / 3600 ) + " hours ago"
|
||||
if day_diff == 1:
|
||||
return "Yesterday"
|
||||
if day_diff <= 7:
|
||||
return str(day_diff) + " days ago"
|
||||
if day_diff <= 31:
|
||||
return str(day_diff/7) + " weeks ago"
|
||||
if day_diff <= 365:
|
||||
return str(day_diff/30) + " months ago"
|
||||
return str(day_diff/365) + " years ago"
|
0
blender-bfct/application/models/__init__.py
Normal file
0
blender-bfct/application/models/__init__.py
Normal file
61
blender-bfct/application/models/applications.py
Normal file
61
blender-bfct/application/models/applications.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import datetime
|
||||
from application import app
|
||||
from application import db
|
||||
from application.helpers import pretty_date
|
||||
from users import User
|
||||
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
|
||||
class Application(db.Model):
|
||||
__table_args__ = {'schema': 'blender-bfct'}
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
blender_id = db.Column(db.Integer(), db.ForeignKey(User.id), nullable=False)
|
||||
user = db.relationship('User')
|
||||
network_profile = db.Column(db.String(255))
|
||||
website = db.Column(db.String(255))
|
||||
city_country = db.Column(db.String(255))
|
||||
teaching = db.Column(db.Boolean())
|
||||
skills = db.relationship('Skill', secondary='skills_applications',
|
||||
backref=db.backref('applications', lazy='dynamic'))
|
||||
video_example = db.Column(db.String(255))
|
||||
written_example = db.Column(db.String(255))
|
||||
portfolio_cv = db.Column(db.String(255))
|
||||
bio_message = db.Column(db.Text())
|
||||
submission_date = db.Column(db.DateTime(), default=datetime.datetime.now)
|
||||
status = db.Column(db.String(255))
|
||||
approve = db.Column(db.Integer(), default=0)
|
||||
reject = db.Column(db.Integer(), default=0)
|
||||
review_start_date = db.Column(db.DateTime())
|
||||
review_end_date = db.Column(db.DateTime())
|
||||
renewal_date = db.Column(db.DateTime())
|
||||
|
||||
def show_pretty_date(self, stage_date):
|
||||
if stage_date == 'submission':
|
||||
return pretty_date(self.submission_date)
|
||||
elif stage_date == 'review_start':
|
||||
return pretty_date(self.review_start_date)
|
||||
elif stage_date == 'review_end':
|
||||
return pretty_date(self.review_end_date)
|
||||
else:
|
||||
return '--'
|
||||
|
||||
|
||||
class Skill(db.Model):
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
name = db.Column(db.String(80), unique=True)
|
||||
description = db.Column(db.String(255))
|
||||
|
||||
|
||||
skills_applications = db.Table('skills_applications',
|
||||
db.Column('application_id', db.Integer(), db.ForeignKey(Application.id)),
|
||||
db.Column('skill_id', db.Integer(), db.ForeignKey('skill.id')))
|
||||
|
||||
|
||||
class ReviewersApplications(db.Model):
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
application_id = db.Column(db.Integer(), db.ForeignKey(Application.id), nullable=False)
|
||||
application = db.relationship('Application')
|
||||
reviewer_blender_id = db.Column(db.Integer(), db.ForeignKey(User.id), nullable=False)
|
||||
reviewer = db.relationship('User')
|
||||
approved = db.Column(db.Boolean(), nullable=False)
|
35
blender-bfct/application/models/users.py
Normal file
35
blender-bfct/application/models/users.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from flask.ext.security import Security, SQLAlchemyUserDatastore, \
|
||||
UserMixin, RoleMixin
|
||||
|
||||
from application import app
|
||||
from application import db
|
||||
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
__bind_key__ = 'users'
|
||||
__table_args__ = {'schema': 'blender-id'}
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
first_name = db.Column(db.String(255))
|
||||
last_name = db.Column(db.String(255))
|
||||
email = db.Column(db.String(255), unique=True)
|
||||
password = db.Column(db.String(255))
|
||||
active = db.Column(db.Boolean())
|
||||
confirmed_at = db.Column(db.DateTime())
|
||||
roles = db.relationship('Role', secondary='roles_users',
|
||||
backref=db.backref('users', lazy='dynamic'))
|
||||
|
||||
class Role(db.Model, RoleMixin):
|
||||
__bind_key__ = 'users'
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
name = db.Column(db.String(80), unique=True)
|
||||
description = db.Column(db.String(255))
|
||||
|
||||
# Define models
|
||||
roles_users = db.Table('roles_users',
|
||||
db.Column('user_id', db.Integer(), db.ForeignKey(User.id)),
|
||||
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')),
|
||||
info={'bind_key': 'users'})
|
||||
|
||||
# Setup Flask-Security
|
||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||
security = Security(app, user_datastore)
|
14
blender-bfct/application/templates/admin/layout_admin.html
Normal file
14
blender-bfct/application/templates/admin/layout_admin.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends 'admin/base.html' %}
|
||||
|
||||
{% block access_control %}
|
||||
{% if current_user.is_authenticated() %}
|
||||
<div class="btn-group pull-right">
|
||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="icon-user"></i> {{ current_user.login }} <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{ url_for('admin.logout_view') }}">Log out</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
33
blender-bfct/application/templates/applications/index.html
Executable file
33
blender-bfct/application/templates/applications/index.html
Executable file
@@ -0,0 +1,33 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Applicant</th>
|
||||
<th>Country</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th>Approvals</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for application in applications %}
|
||||
<tr>
|
||||
<td>{{application.user.first_name}} {{application.user.last_name}}</td>
|
||||
<td>{{application.city_country}}</td>
|
||||
<td>{{application.pretty_submission_date}}</td>
|
||||
<td>{{application.status}}</td>
|
||||
<td>{{application.approve}} / {{application.approve + application.reject}}</td>
|
||||
<td><a href="{{url_for('applications.view', id=application.id)}}" class="btn btn-default btn-xs">View</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
77
blender-bfct/application/templates/applications/view.html
Executable file
77
blender-bfct/application/templates/applications/view.html
Executable file
@@ -0,0 +1,77 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2>{{application.user.first_name}} {{application.user.last_name}} <small>{{application.city_country}}</small></h2>
|
||||
<p>{{application.bio_message}}</p>
|
||||
<ul>
|
||||
<li><a href="{{application.website}}">{{application.website}}</a></li>
|
||||
<li><a href="{{application.video_example}}">{{application.video_example}}</a></li>
|
||||
<li><a href="{{application.written_example}}">{{application.written_example}}</a></li>
|
||||
<li><a href="{{application.portfolio_cv}}">{{application.portfolio_cv}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h2>Status: {{application.status}}</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Reviewer</th>
|
||||
<th>Vote</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for review in reviews%}
|
||||
<tr>
|
||||
<td>
|
||||
{% if review.reviewer.id == current_user.id %}
|
||||
<strong>You</strong>
|
||||
{% endif %}
|
||||
{{review.reviewer.first_name}} {{review.reviewer.last_name}}
|
||||
</td>
|
||||
<td>
|
||||
{% if review.approved %}
|
||||
√
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% if not review %}
|
||||
<div class="col-md-6">
|
||||
<a href="{{url_for('applications.vote', approved=1, id=application.id)}}" class="btn btn-default">Approve</a>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<a href="{{url_for('applications.vote', approved=0, id=application.id)}}" class="btn btn-default">Reject</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-md-6">
|
||||
<p>You {% if review.approved %} approved {% else %} rejected {% endif %} this candidate.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% if current_user.has_role('admin') %}
|
||||
{% if not application.review_end_date %}
|
||||
<div class="col-md-6">
|
||||
<a href="{{url_for('applications.final_review', approved=1, id=application.id)}}" class="btn btn-default">Final Approve</a>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<a href="{{url_for('applications.final_review', approved=0, id=application.id)}}" class="btn btn-default">Final Reject</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
77
blender-bfct/application/templates/apply.html
Executable file
77
blender-bfct/application/templates/apply.html
Executable file
@@ -0,0 +1,77 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form class="form-horizontal" method="POST" action="{{url_for('apply')}}">
|
||||
<fieldset>
|
||||
{{ form.hidden_tag() }}
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Blender Network Profile link</span>
|
||||
{{ form.network_profile(class='form-control', placeholder='First Name') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Website</span>
|
||||
{{ form.website(class='form-control', placeholder='Your website') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">City and Country</span>
|
||||
{{ form.city_country(class='form-control', placeholder='Your city and country') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Teaching at university</span>
|
||||
{{ form.teaching(class='form-control') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Areas of expertise</span>
|
||||
{{ form.skills(class='form-control') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Video Example</span>
|
||||
{{ form.video_example(class='form-control', placeholder='Video Example') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Written Example</span>
|
||||
{{ form.written_example(class='form-control', placeholder='Written Example') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Portfolio and CV</span>
|
||||
{{ form.portfolio_cv(class='form-control', placeholder='Porftolio and CV') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="control-label">
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-addon">Bio and message</span>
|
||||
{{ form.bio_message(class='form-control', placeholder='Bio and message') }}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<input class="btn btn-default btn-block" type="submit" value="Go">
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
18
blender-bfct/application/templates/index.html
Executable file
18
blender-bfct/application/templates/index.html
Executable file
@@ -0,0 +1,18 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2>BFCT program</h2>
|
||||
<p>BFCT is the Blender Foundation’s official certified trainers program.This membership is renewed annually, along with updated knowledge on new topics Blender might cover.</p>
|
||||
|
||||
<p>The goals of the BFCT program are:</p>
|
||||
|
||||
<ul>
|
||||
<li>Provide a standard for Certification for everyone who is interested in teaching Blender professionally</li>
|
||||
<li>Help experienced Blender artists and developers to get into training business</li>
|
||||
<li>Increase the quality and quantity of Blender training worldwide</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
104
blender-bfct/application/templates/layout.html
Normal file
104
blender-bfct/application/templates/layout.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Blender Cloud</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
|
||||
<!-- <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,300' rel='stylesheet' type='text/css'> -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- <link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css"> -->
|
||||
|
||||
<!-- IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<!-- Le fav and touch icons -->
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='ico/favicon.ico') }}">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/apple-touch-icon-144-precomposed.png">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="/apple-touch-icon-114-precomposed.png">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="/apple-touch-icon-72-precomposed.png">
|
||||
<link rel="apple-touch-icon-precomposed" href="apple-touch-icon-57-precomposed.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% block modal %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
<header class="navbar navbar-default navbar-static-top" role="navigation">
|
||||
<div class="container">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{{ url_for('homepage') }}">blender-BFCT</a>
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="http://blendernetwork.org/BFCT">Certified Trainers</a></li>
|
||||
<li><a href="http://www.blender.org/certification/become-a-trainer/">Become a Trainer</a></li>
|
||||
{% if current_user.has_role('admin') %}
|
||||
<li><a href="{{url_for('applications.index')}}">Review Applications</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<nav class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if current_user.is_authenticated() %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{current_user.email}} <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li {% if title == 'home': %} class="active"{% endif %}>
|
||||
<a href="{{ url_for('homepage') }}">Home</a>
|
||||
</li>
|
||||
{% if not current_user.has_role('bfct') %}
|
||||
<li><a href="{{url_for('apply')}}">Apply/View application</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{{url_for_security('logout')}}">Log out</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="{{url_for_security('login')}}">Log in</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav><!-- /.navbar-collapse -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="alert alert-warning">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<footer>
|
||||
<p>Blender-BFCT is part of the blender.org project</p>
|
||||
</footer>
|
||||
|
||||
</div> <!-- /container -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
|
||||
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
||||
{% block footer_scripts %}{% endblock %}
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
36
blender-bfct/application/templates/my_application.html
Executable file
36
blender-bfct/application/templates/my_application.html
Executable file
@@ -0,0 +1,36 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2>Your BFCT status dashboard</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Stage</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{application.show_pretty_date('submission')}}</td>
|
||||
<td>You submitted your application</td>
|
||||
</tr>
|
||||
{% if application.show_pretty_date('review_start') %}
|
||||
<tr>
|
||||
<td>{{application.show_pretty_date('review_start')}}</td>
|
||||
<td>Application review started</td>
|
||||
</tr>
|
||||
{% endif%}
|
||||
{% if application.show_pretty_date('review_end') %}
|
||||
<tr>
|
||||
<td>{{application.show_pretty_date('review_end')}}</td>
|
||||
<td>Application review ended</td>
|
||||
</tr>
|
||||
{% endif%}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
46
blender-bfct/config.py.example
Normal file
46
blender-bfct/config.py.example
Normal file
@@ -0,0 +1,46 @@
|
||||
class Config(object):
|
||||
DEBUG=False
|
||||
|
||||
# Configured for GMAIL
|
||||
MAIL_SERVER = 'smtp.gmail.com'
|
||||
MAIL_PORT = 465
|
||||
MAIL_USE_SSL = True
|
||||
MAIL_USERNAME = ''
|
||||
MAIL_PASSWORD = ''
|
||||
DEFAULT_MAIL_SENDER = ''
|
||||
|
||||
# Flask-Security setup
|
||||
SECURITY_LOGIN_WITHOUT_CONFIRMATION = True
|
||||
SECURITY_REGISTERABLE = True
|
||||
SECURITY_RECOVERABLE = True
|
||||
SECURITY_CHANGEABLE = True
|
||||
SECUIRTY_POST_LOGIN = '/'
|
||||
SECURITY_PASSWORD_HASH = 'bcrypt'
|
||||
SECURITY_PASSWORD_SALT = 'YOURSALT'
|
||||
SECURITY_EMAIL_SENDER = ''
|
||||
SECURITY_POST_REGISTER_VIEW = '/welcome'
|
||||
GOOGLE_ANALYTICS_TRACKING_ID = ''
|
||||
GOOGLE_ANALYTICS_DOMAIN = ''
|
||||
|
||||
ADMIN_EMAIL = ['']
|
||||
|
||||
CDN_USE_URL_SIGNING = False
|
||||
CDN_SERVICE_DOMAIN_PROTOCOL = 'http'
|
||||
CDN_SERVICE_DOMAIN = ''
|
||||
CDN_CONTENT_SUBFOLDER = ''
|
||||
CDN_URL_SIGNING_KEY = ''
|
||||
|
||||
class Development(Config):
|
||||
SECRET_KEY='YOURSECRET'
|
||||
SERVER_NAME='cloud.blender.local'
|
||||
DEBUG=True
|
||||
SQLALCHEMY_DATABASE_URI='mysql://root:root@localhost/blender-bfct'
|
||||
SQLALCHEMY_BINDS = {
|
||||
'users': 'mysql://root:root@localhost/blender-id',
|
||||
}
|
||||
SECURITY_REGISTERABLE=True
|
||||
SECURITY_LOGIN_USER_TEMPLATE = 'security/login_user.html'
|
||||
ASSETS_DEBUG = False
|
||||
CACHE_TYPE = ''
|
||||
CACHE_DEFAULT_TIMEOUT = 60
|
||||
CACHE_DIR = ''
|
5
blender-bfct/runserver.py
Normal file
5
blender-bfct/runserver.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from application import app
|
||||
from application import db
|
||||
db.create_all(bind=['users'])
|
||||
db.create_all(bind=None)
|
||||
app.run()
|
24
requirements.txt
Normal file
24
requirements.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
Flask==0.10.1
|
||||
Flask-Login==0.2.9
|
||||
Flask-Mail==0.9.0
|
||||
Flask-OAuth==0.12
|
||||
Flask-Principal==0.4.0
|
||||
Flask-SQLAlchemy==1.0
|
||||
Flask-Security==1.7.1
|
||||
Flask-Social==1.6.2
|
||||
Flask-WTF==0.9.4
|
||||
Jinja2==2.7.2
|
||||
MarkupSafe==0.18
|
||||
MySQL-python==1.2.5
|
||||
SQLAlchemy==0.9.1
|
||||
WTForms==1.0.5
|
||||
Werkzeug==0.9.4
|
||||
blinker==1.3
|
||||
braintree==2.29.1
|
||||
httplib2==0.8
|
||||
itsdangerous==0.23
|
||||
oauth2==1.5.211
|
||||
passlib==1.6.2
|
||||
py-bcrypt==0.4
|
||||
requests==2.3.0
|
||||
wsgiref==0.1.2
|
Reference in New Issue
Block a user