mirror of
https://github.com/aronwk-aaron/MSState-Library-ETD.git
synced 2026-04-25 13:48:13 -05:00
Merge remote-tracking branch 'origin/Database' into submission
# Conflicts: # app/__init__.py
This commit is contained in:
@@ -220,6 +220,9 @@ dmypy.json
|
||||
config\.py
|
||||
|
||||
config\.ini
|
||||
config\.ini\.backup
|
||||
|
||||
app/static/site\.css
|
||||
app.wsgi
|
||||
|
||||
config\.ini\.backup
|
||||
|
||||
+74
-8
@@ -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
@@ -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')
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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"}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user