Merge remote-tracking branch 'origin/Database' into submission

# Conflicts:
#	app/__init__.py
This commit is contained in:
Jordan Stremming
2019-03-23 12:11:33 -05:00
18 changed files with 670 additions and 199 deletions
+3
View File
@@ -220,6 +220,9 @@ dmypy.json
config\.py
config\.ini
config\.ini\.backup
app/static/site\.css
app.wsgi
config\.ini\.backup
+74 -8
View File
@@ -1,13 +1,16 @@
from flask import Flask
from flask_assets import Environment
from webassets import Bundle
from app.models import db, migrate
from app.models import db, migrate, User
from app.schemas import ma
from flask_mail import Mail
from flask_user import UserManager
from flask_wtf.csrf import CSRFProtect
from app.configuration import AppConfiguration
# load the configuration
config = AppConfiguration()
# Instantiate Flask extensions
mail = Mail()
csrf_protect = CSRFProtect()
# db and migrate is instantiated in models.py
def create_app():
@@ -16,13 +19,30 @@ def create_app():
Returns:
Flask: the configured app
"""
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(config.flask_config)
# Load common settings
app.config.from_object('app.settings')
# Load environment specific settings
app.config.from_object('app.local_settings')
register_extensions(app)
register_blueprints(app)
# Setup an error-logger to send emails to app.config.ADMINS
init_email_error_handler(app)
# Define bootstrap_is_hidden_field for flask-bootstrap's bootstrap_wtf.html
from wtforms.fields import HiddenField
def is_hidden_field_filter(field):
return isinstance(field, HiddenField)
app.jinja_env.globals['bootstrap_is_hidden_field'] = is_hidden_field_filter
return app
@@ -33,8 +53,14 @@ def register_extensions(app):
app (Flask): Flask app to register for
"""
db.init_app(app)
migrate.init_app(app=app, db=db)
migrate.init_app(app, db)
ma.init_app(app)
mail.init_app(app)
csrf_protect.init_app(app)
user_manager = UserManager(app, db, User)
@app.context_processor
def context_processor():
return dict(user_manager=user_manager)
assets = Environment(app)
assets.url = app.static_url_path
@@ -53,3 +79,43 @@ def register_blueprints(app):
from .tad import tad_blueprint
app.register_blueprint(tad_blueprint)
from .auth import auth_blueprint
app.register_blueprint(auth_blueprint)
def init_email_error_handler(app):
"""
Initialize a logger to send emails on error-level messages.
Unhandled exceptions will now send an email message to app.config.ADMINS.
"""
if app.debug: return # Do not send error emails while developing
# Retrieve email settings from app.config
host = app.config['MAIL_SERVER']
port = app.config['MAIL_PORT']
from_addr = app.config['MAIL_DEFAULT_SENDER']
username = app.config['MAIL_USERNAME']
password = app.config['MAIL_PASSWORD']
secure = () if app.config.get('MAIL_USE_TLS') else None
# Retrieve app settings from app.config
to_addr_list = app.config['ADMINS']
subject = app.config.get('APP_SYSTEM_ERROR_SUBJECT_LINE', 'System Error')
# Setup an SMTP mail handler for error-level messages
import logging
from logging.handlers import SMTPHandler
mail_handler = SMTPHandler(
mailhost=(host, port), # Mail host and port
fromaddr=from_addr, # From address
toaddrs=to_addr_list, # To address
subject=subject, # Subject line
credentials=(username, password), # Credentials
secure=secure,
)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
# Log errors using: app.logger.error('Some error message')
+21
View File
@@ -0,0 +1,21 @@
from flask import render_template, Blueprint
auth_blueprint = Blueprint('auth', __name__)
@auth_blueprint.route('/auth/login', methods=['GET', 'POST'])
def login():
"""Auth: Login Page"""
return render_template('auth/login.jinja2')
@auth_blueprint.route('/auth/logout')
def logout():
"""Auth: Logout Page"""
return render_template('auth/logout.jinja2')
@auth_blueprint.route('/auth/register')
def register():
"""Auth: Login Page"""
return render_template('auth/register.jinja2')
-118
View File
@@ -1,118 +0,0 @@
import configparser
import os
from distutils.version import LooseVersion
class AppConfiguration(object):
"""Configuration Version"""
CONFIG_VERSION = '2.0'
def __init__(self):
"""Creates/Parses the config.ini"""
# create/read the config.ini
self.config_parser = self.reload_config()
self.__update_config()
# setup configuration
self.mode = self.config_parser.get('FLASK', 'mode')
# setup flask configuration
self.flask_config = self.__FLASK_CONFIGS.get(self.mode, self.__DefaultFlaskConfig)
self.flask_config.SECRET_KEY = self.__FLASK_CONFIGS.get('secret_key', 'SECRET_KEY_NOT_SET')
# setup postgres
self.flask_config.SQLALCHEMY_DATABASE_URI = self.config_parser.get('SQLALCHEMY', 'sqlalchemy_database_uri')
def _create_config(self):
"""Creates a default configuration (*.ini) file
Returns:
config (ConfigParser): the config.ini parser
"""
config = configparser.ConfigParser()
# create the config
config['CONFIG'] = {
'version': self.CONFIG_VERSION
}
config['SQLALCHEMY'] = {
'SQLALCHEMY_DATABASE_URI': 'postgresql://user:password@host:port/database',
'SQLALCHEMY_TRACK_MODIFICATIONS': 'False',
}
config['FLASK'] = {
'mode': 'default',
'secret_key': 'SECRET_KEY_NOT_SET'
}
config['FLASK-MAIL'] = {
'MAIL_SERVER': 'smtp.gmail.com',
'MAIL_PORT': '587',
'MAIL_USE_SSL': 'False',
'MAIL_USE_TLS': 'True',
'MAIL_USERNAME': 'yourname@gmail.com',
'MAIL_PASSWORD': 'password',
}
config['FLASK-USER'] = {
'USER_APP_NAME': 'MSState Library ETD',
'USER_EMAIL_SENDER_NAME': 'MSState',
'USER_EMAIL_SENDER_EMAIL': 'yourname@gmail.com',
}
# write the config.
config_path = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(os.path.dirname(config_path), 'config.ini')
with open(config_path, 'w') as configfile:
config.write(configfile)
return config
def reload_config(self):
"""Create/read the config.ini into ConfigParser
Returns:
the ConfigParser from config.ini
"""
if not os.path.exists('config.ini'):
print('creating new config...')
config = self._create_config()
else:
print('loading config...')
config = configparser.ConfigParser()
config.read('config.ini')
return config
def get(self, *args):
"""Returns ConfigParser.get(*args)"""
return self.config_parser.get(*args)
def __update_config(self):
"""Checks if config.ini needs an update"""
if LooseVersion(self.config_parser.get('CONFIG', 'version')) < LooseVersion(self.CONFIG_VERSION):
print('updating config...')
os.rename('config.ini', 'config.ini.backup')
self.config_parser = self.reload_config()
class __DefaultFlaskConfig(object):
"""Default/Production Flask Configuration"""
DEBUG = False
TESTING = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
class __DevelopmentFlaskConfig(__DefaultFlaskConfig):
"""Development/Debug Configuration"""
DEBUG = True
ASSETS_DEBUG = True
SQLALCHEMY_ECHO = True
class __TestingFlaskConfig(__DefaultFlaskConfig):
"""Testing Configuration"""
TESTING = True
# config map
__FLASK_CONFIGS = {
'dev': __DevelopmentFlaskConfig,
'prod': __DefaultFlaskConfig,
'test': __TestingFlaskConfig,
'default': __DevelopmentFlaskConfig
}
+40
View File
@@ -0,0 +1,40 @@
import os
# *****************************
# Environment specific settings
# *****************************
# DO NOT use "DEBUG = True" in production environments
DEBUG = True
# DO NOT use Unsecure Secrets in production environments
# Generate a safe one with:
# python -c "import os; print repr(os.urandom(24));"
SECRET_KEY = 'This is an UNSECURE Secret. CHANGE THIS for production environments.'
# SQLAlchemy settings
SQLALCHEMY_DATABASE_URI = 'postgresql://user:password@host:port/database'
SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids a SQLAlchemy Warning
# Flask-Mail settings
# For smtp.gmail.com to work, you MUST set "Allow less secure apps" to ON in Google Accounts.
# Change it in https://myaccount.google.com/security#connectedapps (near the bottom).
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 587
MAIL_USE_SSL = False
MAIL_USE_TLS = True
MAIL_USERNAME = 'yourname@gmail.com'
MAIL_PASSWORD = 'password'
# Sendgrid settings
SENDGRID_API_KEY='place-your-sendgrid-api-key-here'
# Flask-User settings
USER_APP_NAME = 'Flask-User starter app'
USER_EMAIL_SENDER_NAME = 'Your name'
USER_EMAIL_SENDER_EMAIL = 'yourname@gmail.com'
ADMINS = [
'"Admin One" <admin1@gmail.com>',
]
+117 -51
View File
@@ -1,92 +1,158 @@
from sqlalchemy.sql import func
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import enum
from sqlalchemy_utils import ArrowType
import arrow
import random
from flask_user import UserMixin
db = SQLAlchemy()
migrate = Migrate()
class User(db.Model):
__tablename__ = 'user'
class User(db.Model, UserMixin):
__tablename__ = 'users'
msuid = db.Column(db.Text, primary_key=True, nullable=False, unique=True)
netid = db.Column(db.Text, nullable=False)
department = db.Column(db.Text, nullable=False)
professor = db.Column(db.Text, nullable=False)
id = db.Column(db.Integer, primary_key=True)
role = db.Column(db.Text, nullable=False)
# User authentication information (required for Flask-User)
email = db.Column(db.Unicode(255), nullable=False, server_default=u'', unique=True)
email_confirmed_at = db.Column(db.DateTime())
password = db.Column(db.String(255), nullable=False, server_default='')
# reset_password_token = db.Column(db.String(100), nullable=False, server_default='')
active = db.Column(db.Boolean(), nullable=False, server_default='0')
first_name = db.Column(db.Text, nullable=False)
middle_name = db.Column(db.Text, nullable=False)
last_name = db.Column(db.Text, nullable=False)
# User information
first_name = db.Column(db.Unicode(50), nullable=False, server_default=u'')
middle_name = db.Column(db.Unicode(50), nullable=False, server_default=u'')
last_name = db.Column(db.Unicode(50), nullable=False, server_default=u'')
prim_email = db.Column(db.Text, nullable=False)
sec_email = db.Column(db.Text, nullable=False)
msu_id = db.Column(db.Unicode(9), unique=True)
net_id = db.Column(db.Unicode(8), unique=True)
department = db.Column(db.Unicode(50), server_default=u'')
prim_phone = db.Column(db.Text, nullable=False)
sec_phone = db.Column(db.Text, nullable=False)
prim_phone = db.Column(db.Unicode(50), nullable=False, server_default=u'')
sec_phone = db.Column(db.Unicode(50), server_default=u'')
country = db.Column(db.Text, nullable=False)
administrative_area = db.Column(db.Text, nullable=False)
locality = db.Column(db.Text, nullable=False)
postal_code = db.Column(db.Text, nullable=False)
thoroughfare = db.Column(db.Text, nullable=False)
premise = db.Column(db.Text, nullable=False)
country = db.Column(db.Unicode(50), nullable=False, server_default=u'')
administrative_area = db.Column(db.Unicode(50), nullable=False, server_default=u'')
locality = db.Column(db.Unicode(50), nullable=False, server_default=u'')
postal_code = db.Column(db.Unicode(50), nullable=False, server_default=u'')
thoroughfare = db.Column(db.Unicode(50), nullable=False, server_default=u'')
premise = db.Column(db.Unicode(50), nullable=False, server_default=u'')
preffirst_name = db.Column(db.Text)
prefmiddle_name = db.Column(db.Text)
preflast_name = db.Column(db.Text)
pref_first_name = db.Column(db.Unicode(50), server_default=u'')
pref_middle_name = db.Column(db.Unicode(50), server_default=u'')
pref_last_name = db.Column(db.Unicode(50), server_default=u'')
maiden_name = db.Column(db.Text, nullable=False)
maiden_name = db.Column(db.Unicode(50), server_default=u'')
birth_date = db.Column(db.Unicode(50), nullable=False, server_default=u'')
birth_date = db.Column(db.Text, nullable=False)
# Relationships
roles = db.relationship('Role', secondary='users_roles',
backref=db.backref('users', lazy='dynamic'))
class Notifications(db.Model):
# Define the Role data model
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), nullable=False, server_default=u'', unique=True) # for @roles_accepted()
label = db.Column(db.Unicode(255), server_default=u'') # for display purposes
# Define the UserRoles association model
class UsersRoles(db.Model):
__tablename__ = 'users_roles'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))
class UsersProfessors(db.Model):
__tablename__ = 'users_professors'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
Professor_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
class Notification(db.Model):
__tablename__ = 'notifications'
id = db.Column(db.Integer, primary_key=True)
netID = db.Column(db.Text, db.ForeignKey('user.netid'), nullable=False)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
text = db.Column(db.Text, nullable=False)
state = db.Column(db.Boolean, default=False)
class Submission(db.Model):
__tablename__ = 'submission'
__tablename__ = 'submissions'
id = db.Column(db.Integer, primary_key=True)
netid = db.Column(db.Text, db.ForeignKey('user.netid'), nullable=False)
title = db.Column(db.Text)
abstract = db.Column(db.Text)
type = db.Column(db.Text)
release_type = db.Column(db.Integer)
ww_length = db.Column(db.Text)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
title = db.Column(db.Unicode(), nullable=False, server_default=u'')
abstract = db.Column(db.Unicode(), nullable=False, server_default=u'')
# TODO: Make Enum type for this
type = db.Column(db.Integer, nullable=False)
# TODO: Make Enum type for this
release_type = db.Column(db.Integer, nullable=False)
ww_length = db.Column(db.Integer, nullable=False)
# TODO: figure out files for this
signature_file = db.Column(db.Text)
started = db.Column(db.DateTime(timezone=True), server_default=func.now(), nullable=False)
state = db.Column(db.Boolean, default=False)
approved_date = db.Column(db.Text)
state = db.Column(db.Boolean, default=False, nullable=False)
approved_date = db.Column(db.DateTime(timezone=True))
class Revision(db.Model):
__tablename__ = 'revision'
__tablename__ = 'revisions'
id = db.Column(db.Integer, primary_key=True)
sub_id = db.Column(db.Text)
sub_id = db.Column(db.Integer(), db.ForeignKey('submissions.id', ondelete='CASCADE'))
submitted = db.Column(db.DateTime(timezone=True), server_default=func.now())
# TODO: figure out files for this
file = db.Column(db.Text)
rev_num = db.Column(db.Text)
state = db.Column(db.Boolean, default=False)
class Review(db.Model):
__tablename__ = 'review'
__tablename__ = 'reviews'
id = db.Column(db.Integer, primary_key=True)
rev_id = db.Column(db.Integer)
reviewer_netID = db.Column(db.Text, db.ForeignKey('user.netid'), nullable=False)
# put the checklist for it and figure out how to store checklist in a not dumb way
reviewed = db.Column(db.DateTime(timezone=True), server_default=func.now())
review_ID = db.Column(db.Integer(), db.ForeignKey('reviews.id', ondelete='CASCADE'))
reviewer_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
# 32 check boxes + comment box
# check boxes will refer to the ones in the review form
# new check boxes must be added at the end
comments = db.Column(db.Unicode(), server_default=u'')
check_1 = db.Column(db.Boolean, default=False, nullable=False)
check_2 = db.Column(db.Boolean, default=False, nullable=False)
check_3 = db.Column(db.Boolean, default=False, nullable=False)
check_4 = db.Column(db.Boolean, default=False, nullable=False)
check_5 = db.Column(db.Boolean, default=False, nullable=False)
check_6 = db.Column(db.Boolean, default=False, nullable=False)
check_7 = db.Column(db.Boolean, default=False, nullable=False)
check_8 = db.Column(db.Boolean, default=False, nullable=False)
check_9 = db.Column(db.Boolean, default=False, nullable=False)
check_10 = db.Column(db.Boolean, default=False, nullable=False)
check_11 = db.Column(db.Boolean, default=False, nullable=False)
check_12 = db.Column(db.Boolean, default=False, nullable=False)
check_13 = db.Column(db.Boolean, default=False, nullable=False)
check_14 = db.Column(db.Boolean, default=False, nullable=False)
check_15 = db.Column(db.Boolean, default=False, nullable=False)
check_16 = db.Column(db.Boolean, default=False, nullable=False)
check_17 = db.Column(db.Boolean, default=False, nullable=False)
check_18 = db.Column(db.Boolean, default=False, nullable=False)
check_19 = db.Column(db.Boolean, default=False, nullable=False)
check_20 = db.Column(db.Boolean, default=False, nullable=False)
check_21 = db.Column(db.Boolean, default=False, nullable=False)
check_22 = db.Column(db.Boolean, default=False, nullable=False)
check_23 = db.Column(db.Boolean, default=False, nullable=False)
check_24 = db.Column(db.Boolean, default=False, nullable=False)
check_25 = db.Column(db.Boolean, default=False, nullable=False)
check_26 = db.Column(db.Boolean, default=False, nullable=False)
check_27 = db.Column(db.Boolean, default=False, nullable=False)
check_28 = db.Column(db.Boolean, default=False, nullable=False)
check_29 = db.Column(db.Boolean, default=False, nullable=False)
check_30 = db.Column(db.Boolean, default=False, nullable=False)
check_31 = db.Column(db.Boolean, default=False, nullable=False)
check_32 = db.Column(db.Boolean, default=False, nullable=False)
+9 -7
View File
@@ -1,29 +1,31 @@
from flask_marshmallow import Marshmallow
from .models import User, Notifications, Submission, Revision, Review
from flask_marshmallow.sqla import ModelSchema
from .models import User, Notification, Submission, Revision, Review
ma = Marshmallow()
class UserSchema(ma.ModelSchema):
class UserSchema(ModelSchema):
class Meta:
model = User
class NotificationsSchema(ma.ModelSchema):
class NotificationsSchema(ModelSchema):
class Meta:
model = Notifications
model = Notification
class SubmissionSchema(ma.ModelSchema):
class SubmissionSchema(ModelSchema):
class Meta:
model = Submission
class RevisionSchema(ma.ModelSchema):
class RevisionSchema(ModelSchema):
class Meta:
model = Revision
class ReviewSchema(ma.ModelSchema):
class ReviewSchema(ModelSchema):
class Meta:
model = Review
+28
View File
@@ -0,0 +1,28 @@
# Settings common to all environments (development|staging|production)
# Place environment specific settings in env_settings.py
# An example file (env_settings_example.py) can be used as a starting point
import os
# Application settings
APP_NAME = "MSState Library ETD System"
APP_SYSTEM_ERROR_SUBJECT_LINE = APP_NAME + " system error"
# Flask settings
CSRF_ENABLED = True
# Flask-SQLAlchemy settings
SQLALCHEMY_TRACK_MODIFICATIONS = False
# Flask-User settings
USER_APP_NAME = APP_NAME
USER_ENABLE_CHANGE_PASSWORD = True # Allow users to change their password
USER_ENABLE_CHANGE_USERNAME = False # Allow users to change their username
USER_ENABLE_CONFIRM_EMAIL = True # Force users to confirm their email
USER_ENABLE_FORGOT_PASSWORD = True # Allow users to reset their passwords
USER_ENABLE_EMAIL = True # Register with Email
USER_ENABLE_REGISTRATION = True # Allow new users to register
USER_REQUIRE_RETYPE_PASSWORD = True # Prompt for `retype password` in:
USER_ENABLE_USERNAME = False # Register and Login with username
USER_AFTER_LOGIN_ENDPOINT = 'main'
USER_AFTER_LOGOUT_ENDPOINT = 'main'
+67
View File
@@ -0,0 +1,67 @@
{% extends 'base.jinja2' %}
{% set navbar_shadow = True %}
{% block title %}Login{% endblock %}
{% block header %}{% endblock %}
{% block css %}
{{ super() }}
{# override the body and html css to force center #}
<style>
html, body {
height: 100%;
background-color: #777777;
}
</style>
{% endblock %}
{% block content_before %}
<div class="container-fluid h-100">
<div class="row h-100 justify-content-center align-items-center">
<div class="mx-auto" style="max-width: 30em;">
{# Splash card #}
<div class="card shadow rounded border-0 h-100">
<div class="card-header bg-primary pt-4 text-center text-white">
<img class="img-fluid mb-3" src="http://lib.msstate.edu/_assets/img/2015-header-logo-msstate.png"
alt=""/>
<h5>Electronic Thesis and Dissertation System</h5>
</div>
<div class="card-body d-flex flex-column m-2">
<form>
<h3>
Login
</h3>
<hr class="mb-4"/>
<div class="alert alert-warning" role="alert" hidden>
<b>Invalid username or password</b>
</div>
<div class="form-group">
<label class="font-weight-bold">NetID</label>
<input name="netid" class="form-control" id="inputNetID" placeholder="Enter NetID">
</div>
<div class="form-group">
<label class="font-weight-bold">Password</label>
<input name="password" class="form-control" id="inputPassword" type="password" placeholder="Enter password">
</div>
<button type="submit" class="btn btn-primary">Login</button>
<hr class="mt-4"/>
<a href="#" class="text-secondary">Forgot Password</a> |
<a href="{{ url_for("auth.register") }}" class="text-secondary">Register</a>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+43
View File
@@ -0,0 +1,43 @@
{% extends 'base.jinja2' %}
{% block title %}Logout{% endblock %}
{% block header %}{% endblock %}
{% block css %}
{{ super() }}
{# override the body and html css to force center #}
<style>
html, body {
height: 100%;
background-color: #777777;
}
</style>
{% endblock %}
{% block content_before %}
<div class="container-fluid h-100">
<div class="row h-100 justify-content-center align-items-center">
<div class="mx-auto" style="max-width: 30em;">
{# Splash card #}
<div class="card shadow rounded border-0 h-100">
<div class="card-header bg-primary pt-4 text-center text-white mb-4">
<img class="img-fluid mb-3" src="http://lib.msstate.edu/_assets/img/2015-header-logo-msstate.png"
alt=""/>
<h5>Electronic Thesis and Dissertation System</h5>
</div>
<div class="card-body d-flex flex-column text-center mb-2">
<h3>
You are now logged out
</h3>
<p class="mb-5">
Please close your browser.
</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+97
View File
@@ -0,0 +1,97 @@
{% extends 'base.jinja2' %}
{% set navbar_shadow = True %}
{% block title %}Register{% endblock %}
{% block header %}{% endblock %}
{% block css %}
{{ super() }}
{# override the body and html css to force center #}
<style>
html, body {
height: 100%;
background-color: #777777;
}
</style>
{% endblock %}
{% block content_before %}
<div class="container-fluid h-100">
<div class="row h-100 justify-content-center align-items-center">
<div class="mx-auto" style="max-width: 30em;">
{# Splash card #}
<div class="card shadow rounded border-0 h-100">
<div class="card-header bg-primary pt-4 text-center text-white">
<img class="img-fluid mb-3" src="http://lib.msstate.edu/_assets/img/2015-header-logo-msstate.png"
alt=""/>
<h5>Electronic Thesis and Dissertation System</h5>
</div>
<div class="card-body d-flex flex-column m-2">
<form>
<h3>
Register
</h3>
<hr class="mb-4"/>
<div class="form-group">
<label class="font-weight-bold">First name</label>
<input class="form-control" id="inputFirstName" placeholder="Enter first name">
</div>
<div class="form-group">
<label class="font-weight-bold">Last name</label>
<input class="form-control" id="inputFirstName" placeholder="Enter last name">
</div>
<hr/>
<div class="form-group">
<label class="font-weight-bold">MSU ID (9-digit, no dashes)</label>
<input class="form-control" id="inputNetID" placeholder="Enter MSU ID">
</div>
<div class="form-group">
<label class="font-weight-bold">NetID</label>
<input class="form-control" id="inputNetID" placeholder="Enter NetID">
</div>
<div class="form-group">
<label class="font-weight-bold">Classification</label>
<select id="inputClass" class="form-control">
<option>Undergraduate, Freshman</option>
<option>Undergraduate, Sophomore</option>
<option>Undergraduate, Junior</option>
<option selected>Undergraduate, Senior</option>
<option>Graduate</option>
</select>
</div>
<div class="form-group">
<label class="font-weight-bold">Major</label>
<input class="form-control" id="inputNetID" placeholder="Enter Major">
</div>
<hr/>
<div class="form-group">
<label class="font-weight-bold">Password</label>
<input class="form-control" id="inputPassword" type="password" placeholder="Enter password">
</div>
<div class="form-group">
<label class="font-weight-bold">Password (again)</label>
<input class="form-control" id="inputPasswordConfirm" type="password" placeholder="Confirm password">
</div>
<button type="submit" class="btn btn-primary">Create account</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+1 -1
View File
@@ -36,7 +36,7 @@
</p>
{# TODO: link to CAS sign in #}
<a href="{{ url_for("main.index") }}" class="btn btn-primary btn-lg mt-auto align-self-center px-4">
<a href="{{ url_for("auth.login") }}" class="btn btn-primary btn-lg mt-auto align-self-center px-4">
Sign in with CAS
</a>
</div>
-14
View File
@@ -1,14 +0,0 @@
[CONFIG]
version = 1.0
[DATABASE]
host = localhost
name = msstate_etd
user = msstate_etd
pass = password
type = postgresql
[FLASK]
mode = default
secret_key = SECRET_KEY_NOT_SET
+1
View File
@@ -0,0 +1 @@
Generic single-database configuration.
+45
View File
@@ -0,0 +1,45 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
+95
View File
@@ -0,0 +1,95 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
+24
View File
@@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
+5
View File
@@ -1,6 +1,7 @@
alembic==1.0.8
arrow==0.13.1
asn1crypto==0.24.0
bcrypt==3.1.6
blinker==1.4
cffi==1.12.2
Click==7.0
@@ -11,6 +12,7 @@ Flask-Login==0.4.1
Flask-Mail==0.9.1
flask-marshmallow==0.10.0
Flask-Migrate==2.4.0
Flask-Script==2.0.6
Flask-SQLAlchemy==2.3.2
Flask-User==1.0.1.5
Flask-WTF==0.14.2
@@ -22,6 +24,8 @@ MarkupSafe==1.1.0
marshmallow==2.19.1
marshmallow-sqlalchemy==0.16.1
passlib==1.7.1
psycopg2==2.7.7
psycopg2-binary==2.7.7
pycparser==2.19
python-dateutil==2.8.0
python-editor==1.0.4
@@ -31,3 +35,4 @@ SQLAlchemy-Utils==0.33.11
webassets==0.12.1
Werkzeug==0.14.1
WTForms==2.2.1
xmltodict==0.12.0