mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-10-31 02:32:45 +00:00 
			
		
		
		
	Merge base templates. Add database support. Add blueprint for main.
This commit is contained in:
		| @@ -1,5 +1,13 @@ | |||||||
| from config import config | from config import config | ||||||
| from flask import Flask, render_template | from flask import Flask | ||||||
|  | from flask_login import LoginManager | ||||||
|  | from flask_sqlalchemy import SQLAlchemy | ||||||
|  |  | ||||||
|  |  | ||||||
|  | db = SQLAlchemy() | ||||||
|  |  | ||||||
|  | login_manager = LoginManager() | ||||||
|  | login_manager.login_view = 'auth.login' | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_app(config_name): | def create_app(config_name): | ||||||
| @@ -7,11 +15,13 @@ def create_app(config_name): | |||||||
|     app.config.from_object(config[config_name]) |     app.config.from_object(config[config_name]) | ||||||
|     config[config_name].init_app(app) |     config[config_name].init_app(app) | ||||||
|  |  | ||||||
|     @app.route('/') |     db.init_app(app) | ||||||
|     def index(): |     login_manager.init_app(app) | ||||||
|         return render_template('base.html.j2') |  | ||||||
|  |  | ||||||
|     from .auth import auth as auth_blueprint |     from .auth import auth as auth_blueprint | ||||||
|     app.register_blueprint(auth_blueprint, url_prefix='/auth') |     app.register_blueprint(auth_blueprint, url_prefix='/auth') | ||||||
|  |  | ||||||
|  |     from .main import main as main_blueprint | ||||||
|  |     app.register_blueprint(main_blueprint) | ||||||
|  |  | ||||||
|     return app |     return app | ||||||
|   | |||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | from flask_wtf import FlaskForm | ||||||
|  | from wtforms import StringField, PasswordField, BooleanField, SubmitField | ||||||
|  | from wtforms.validators import DataRequired, Length, Email | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LoginForm(FlaskForm): | ||||||
|  |     email = StringField('Email', validators=[DataRequired(), Length(1, 64), | ||||||
|  |                                              Email()]) | ||||||
|  |     password = PasswordField('Password', validators=[DataRequired()]) | ||||||
|  |     remember_me = BooleanField('Keep me logged in') | ||||||
|  |     submit = SubmitField('Log In') | ||||||
|   | |||||||
| @@ -1,10 +1,31 @@ | |||||||
| from flask import render_template | from flask import flash, redirect, render_template, request, url_for | ||||||
|  | from flask_login import login_required, login_user, logout_user | ||||||
| from . import auth | from . import auth | ||||||
|  | from .forms import LoginForm | ||||||
|  | from ..models import User | ||||||
|  |  | ||||||
|  |  | ||||||
| @auth.route('/login', methods=['GET', 'POST']) | @auth.route('/login', methods=['GET', 'POST']) | ||||||
| def login(): | def login(): | ||||||
|     return render_template('auth/login.html.j2', title='Log in') |     form = LoginForm() | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         user = User.query.filter_by(email=form.email.data).first() | ||||||
|  |         if user is not None and user.verify_password(form.password.data): | ||||||
|  |             login_user(user, form.remember_me.data) | ||||||
|  |             next = request.args.get('next') | ||||||
|  |             if next is None or not next.startswith('/'): | ||||||
|  |                 next = url_for('main.index') | ||||||
|  |             return redirect(next) | ||||||
|  |         flash('Invalid username or password.') | ||||||
|  |     return render_template('auth/login.html.j2', form=form, title='Log in') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @auth.route('/logout') | ||||||
|  | @login_required | ||||||
|  | def logout(): | ||||||
|  |     logout_user() | ||||||
|  |     flash('You have been logged out.') | ||||||
|  |     return redirect(url_for('main.index')) | ||||||
|  |  | ||||||
|  |  | ||||||
| @auth.route('/register', methods=['GET', 'POST']) | @auth.route('/register', methods=['GET', 'POST']) | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | from flask import Blueprint | ||||||
|  |  | ||||||
|  | main = Blueprint('main', __name__) | ||||||
|  |  | ||||||
|  | from . import views | ||||||
|   | |||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | from flask import render_template | ||||||
|  | from . import main | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @main.route('/') | ||||||
|  | def index(): | ||||||
|  |     return render_template('main/index.html.j2') | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								app/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								app/models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | from flask_login import UserMixin | ||||||
|  | from werkzeug.security import generate_password_hash, check_password_hash | ||||||
|  | from . import db | ||||||
|  | from . import login_manager | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Role(db.Model): | ||||||
|  |     __tablename__ = 'roles' | ||||||
|  |     id = db.Column(db.Integer, primary_key=True) | ||||||
|  |     name = db.Column(db.String(64), unique=True) | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '<Role %r>' % self.name | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class User(UserMixin, db.Model): | ||||||
|  |     __tablename__ = 'users' | ||||||
|  |     id = db.Column(db.Integer, primary_key=True) | ||||||
|  |     email = db.Column(db.String(64), unique=True, index=True) | ||||||
|  |     username = db.Column(db.String(64), unique=True, index=True) | ||||||
|  |     password_hash = db.Column(db.String(128)) | ||||||
|  |     role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) | ||||||
|  |  | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '<User %r>' % self.username | ||||||
|  |  | ||||||
|  |     password_hash = db.Column(db.String(128)) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def password(self): | ||||||
|  |         raise AttributeError('password is not a readable attribute') | ||||||
|  |  | ||||||
|  |     @password.setter | ||||||
|  |     def password(self, password): | ||||||
|  |         self.password_hash = generate_password_hash(password) | ||||||
|  |  | ||||||
|  |     def verify_password(self, password): | ||||||
|  |         return check_password_hash(self.password_hash, password) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @login_manager.user_loader | ||||||
|  | def load_user(user_id): | ||||||
|  |     return User.query.get(int(user_id)) | ||||||
| @@ -16,6 +16,7 @@ | |||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div class="col s12 m6"> | <div class="col s12 m6"> | ||||||
|   <div class="card medium"> |   <div class="card medium"> | ||||||
|     <div class="card-content"> |     <div class="card-content"> | ||||||
|   | |||||||
| @@ -14,7 +14,35 @@ | |||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |     <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     {% include 'header.html.j2' %} |     <header> | ||||||
|  |       <div id="nav-notifications-dropdown" class="dropdown-content"> | ||||||
|  |         <p>Notifications</p> | ||||||
|  |       </div> | ||||||
|  |       <div id="nav-settings-dropdown" class="dropdown-content"> | ||||||
|  |         <p>Settings</p> | ||||||
|  |       </div> | ||||||
|  |       <nav> | ||||||
|  |         <div class="nav-wrapper"> | ||||||
|  |           <a href="#!" class="brand-logo"> | ||||||
|  |             {% if title %}{{ title }}{% else %}Opaque{% endif %} | ||||||
|  |           </a> | ||||||
|  |           <a href="#" data-target="slide-out" class="sidenav-trigger"><i class="material-icons">menu</i></a> | ||||||
|  |           <ul class="right hide-on-med-and-down"> | ||||||
|  |             <li><a id="nav-notifications" class="dropdown-trigger" href="#!" data-target="nav-notifications-dropdown"><i class="material-icons">notifications</i></a></li> | ||||||
|  |             <li><a id="nav-settings" class="dropdown-trigger" href="#!" data-target="nav-settings-dropdown"><i class="material-icons">settings</i></a></li> | ||||||
|  |           </ul> | ||||||
|  |         </div> | ||||||
|  |       </nav> | ||||||
|  |  | ||||||
|  |       <ul id="slide-out" class="sidenav sidenav-fixed"> | ||||||
|  |         <li><a href="{{ url_for('main.index') }}">Opaque</a></li> | ||||||
|  |         {% if current_user.is_authenticated %} | ||||||
|  |         <li><a href="{{ url_for('auth.logout') }}">Log out</a></li> | ||||||
|  |         {% else %} | ||||||
|  |         <li><a href="{{ url_for('auth.login') }}">Log in</a></li> | ||||||
|  |         {% endif %} | ||||||
|  |       </ul> | ||||||
|  |     </header> | ||||||
|  |  | ||||||
|     <main class="grey lighten-5"> |     <main class="grey lighten-5"> | ||||||
|       <div class="container"> |       <div class="container"> | ||||||
| @@ -25,7 +53,45 @@ | |||||||
|       </div> |       </div> | ||||||
|     </main> |     </main> | ||||||
|  |  | ||||||
|     {% include 'footer.html.j2' %} |     <footer class="page-footer"> | ||||||
|  |       <div class="container"> | ||||||
|  |         <div class="row"> | ||||||
|  |           <div class="col s12 l3"> | ||||||
|  |             <img src="{{ url_for('static', filename='images/logo_sfb_1288.png') }}" class="responsive-img" style="max-height: 140px;"> | ||||||
|  |           </div> | ||||||
|  |           <div class="col s12 l3"> | ||||||
|  |             <h5 class="white-text">About</h5> | ||||||
|  |             <ul> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 1</a></li> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 2</a></li> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 3</a></li> | ||||||
|  |             </ul> | ||||||
|  |           </div> | ||||||
|  |           <div class="col s12 l3"> | ||||||
|  |             <h5 class="white-text">Connect</h5> | ||||||
|  |             <ul> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 1</a></li> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 2</a></li> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 3</a></li> | ||||||
|  |             </ul> | ||||||
|  |           </div> | ||||||
|  |           <div class="col s12 l3"> | ||||||
|  |             <h5 class="white-text">Contact</h5> | ||||||
|  |             <ul> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 1</a></li> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 2</a></li> | ||||||
|  |               <li><a class="grey-text text-lighten-3" href="#!">Link 3</a></li> | ||||||
|  |             </ul> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="footer-copyright"> | ||||||
|  |         <div class="container"> | ||||||
|  |         © 2019 Bielefeld University | ||||||
|  |         <a class="grey-text text-lighten-4 right" href="#!">Impress</a> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </footer> | ||||||
|  |  | ||||||
|     <!--JavaScript at end of body for optimized loading--> |     <!--JavaScript at end of body for optimized loading--> | ||||||
|     <script type="text/javascript" src="{{ url_for('static', filename='js/materialize.min.js') }}"></script> |     <script type="text/javascript" src="{{ url_for('static', filename='js/materialize.min.js') }}"></script> | ||||||
|   | |||||||
| @@ -1,39 +0,0 @@ | |||||||
| <footer class="page-footer"> |  | ||||||
|   <div class="container"> |  | ||||||
|     <div class="row"> |  | ||||||
|       <div class="col s12 l3"> |  | ||||||
|         <img src="{{ url_for('static', filename='images/logo_sfb_1288.png') }}" class="responsive-img" style="max-height: 140px;"> |  | ||||||
|       </div> |  | ||||||
|       <div class="col s12 l3"> |  | ||||||
|         <h5 class="white-text">About</h5> |  | ||||||
|         <ul> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 1</a></li> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 2</a></li> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 3</a></li> |  | ||||||
|         </ul> |  | ||||||
|       </div> |  | ||||||
|       <div class="col s12 l3"> |  | ||||||
|         <h5 class="white-text">Connect</h5> |  | ||||||
|         <ul> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 1</a></li> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 2</a></li> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 3</a></li> |  | ||||||
|         </ul> |  | ||||||
|       </div> |  | ||||||
|       <div class="col s12 l3"> |  | ||||||
|         <h5 class="white-text">Contact</h5> |  | ||||||
|         <ul> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 1</a></li> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 2</a></li> |  | ||||||
|           <li><a class="grey-text text-lighten-3" href="#!">Link 3</a></li> |  | ||||||
|         </ul> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
|   <div class="footer-copyright"> |  | ||||||
|     <div class="container"> |  | ||||||
|     © 2019 Bielefeld University |  | ||||||
|     <a class="grey-text text-lighten-4 right" href="#!">Impress</a> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </footer> |  | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| <header> |  | ||||||
|   <div id="nav-notifications-dropdown" class="dropdown-content"> |  | ||||||
|     <p>Notifications</p> |  | ||||||
|   </div> |  | ||||||
|   <div id="nav-settings-dropdown" class="dropdown-content"> |  | ||||||
|     <p>Settings</p> |  | ||||||
|   </div> |  | ||||||
|   <nav> |  | ||||||
|     <div class="nav-wrapper"> |  | ||||||
|       <a href="#!" class="brand-logo"> |  | ||||||
|         {% if title %}{{ title }}{% else %}Opaque{% endif %} |  | ||||||
|       </a> |  | ||||||
|       <a href="#" data-target="slide-out" class="sidenav-trigger"><i class="material-icons">menu</i></a> |  | ||||||
|       <ul class="right hide-on-med-and-down"> |  | ||||||
|         <li><a id="nav-notifications" class="dropdown-trigger" href="#!" data-target="nav-notifications-dropdown"><i class="material-icons">notifications</i></a></li> |  | ||||||
|         <li><a id="nav-settings" class="dropdown-trigger" href="#!" data-target="nav-settings-dropdown"><i class="material-icons">settings</i></a></li> |  | ||||||
|       </ul> |  | ||||||
|     </div> |  | ||||||
|   </nav> |  | ||||||
|  |  | ||||||
|   <ul id="slide-out" class="sidenav sidenav-fixed"> |  | ||||||
|     <li><a href="{{ url_for('index') }}">Opaque</a></li> |  | ||||||
|     <li><a href="{{ url_for('auth.login') }}">Login</a></li> |  | ||||||
|   </ul> |  | ||||||
| </header> |  | ||||||
| @@ -2,6 +2,19 @@ | |||||||
| 
 | 
 | ||||||
| {% block page_content %} | {% block page_content %} | ||||||
| <div class="col s12"> | <div class="col s12"> | ||||||
|  |   <div class="card"> | ||||||
|  |     <div class="card-content"> | ||||||
|  |       <span class="card-title"> | ||||||
|  |         Hello, | ||||||
|  |         {% if current_user.is_authenticated %} | ||||||
|  |         {{ current_user.username }} | ||||||
|  |         {% else %} | ||||||
|  |         Stranger | ||||||
|  |         {% endif %}! | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|   <h2>Services</h2> |   <h2>Services</h2> | ||||||
| 
 | 
 | ||||||
|   <div class="row"> |   <div class="row"> | ||||||
| @@ -1,8 +1,12 @@ | |||||||
| import os | import os | ||||||
|  |  | ||||||
|  |  | ||||||
|  | basedir = os.path.abspath(os.path.dirname(__file__)) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Config: | class Config: | ||||||
|     SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string' |     SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string' | ||||||
|  |     SQLALCHEMY_TRACK_MODIFICATIONS = False | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def init_app(app): |     def init_app(app): | ||||||
| @@ -11,6 +15,7 @@ class Config: | |||||||
|  |  | ||||||
| class DevelopmentConfig(Config): | class DevelopmentConfig(Config): | ||||||
|     DEBUG = True |     DEBUG = True | ||||||
|  |     SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data_dev.sqlite') | ||||||
|  |  | ||||||
|  |  | ||||||
| # class TestingConfig(Config): | # class TestingConfig(Config): | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								data_dev.sqlite
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								data_dev.sqlite
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								migrations/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								migrations/README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | Generic single-database configuration. | ||||||
							
								
								
									
										45
									
								
								migrations/alembic.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								migrations/alembic.ini
									
									
									
									
									
										Normal 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 | ||||||
							
								
								
									
										96
									
								
								migrations/env.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								migrations/env.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | 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').replace('%', '%%')) | ||||||
|  | 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
									
								
								migrations/script.py.mako
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								migrations/script.py.mako
									
									
									
									
									
										Normal 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"} | ||||||
							
								
								
									
										47
									
								
								migrations/versions/0d4e0dde8ae4_initial_migration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								migrations/versions/0d4e0dde8ae4_initial_migration.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | """initial migration | ||||||
|  |  | ||||||
|  | Revision ID: 0d4e0dde8ae4 | ||||||
|  | Revises:  | ||||||
|  | Create Date: 2019-07-05 14:43:36.246455 | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | from alembic import op | ||||||
|  | import sqlalchemy as sa | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # revision identifiers, used by Alembic. | ||||||
|  | revision = '0d4e0dde8ae4' | ||||||
|  | down_revision = None | ||||||
|  | branch_labels = None | ||||||
|  | depends_on = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def upgrade(): | ||||||
|  |     # ### commands auto generated by Alembic - please adjust! ### | ||||||
|  |     op.create_table('roles', | ||||||
|  |     sa.Column('id', sa.Integer(), nullable=False), | ||||||
|  |     sa.Column('name', sa.String(length=64), nullable=True), | ||||||
|  |     sa.PrimaryKeyConstraint('id'), | ||||||
|  |     sa.UniqueConstraint('name') | ||||||
|  |     ) | ||||||
|  |     op.create_table('users', | ||||||
|  |     sa.Column('id', sa.Integer(), nullable=False), | ||||||
|  |     sa.Column('email', sa.String(length=64), nullable=True), | ||||||
|  |     sa.Column('username', sa.String(length=64), nullable=True), | ||||||
|  |     sa.Column('role_id', sa.Integer(), nullable=True), | ||||||
|  |     sa.Column('password_hash', sa.String(length=128), nullable=True), | ||||||
|  |     sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ), | ||||||
|  |     sa.PrimaryKeyConstraint('id') | ||||||
|  |     ) | ||||||
|  |     op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) | ||||||
|  |     op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True) | ||||||
|  |     # ### end Alembic commands ### | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def downgrade(): | ||||||
|  |     # ### commands auto generated by Alembic - please adjust! ### | ||||||
|  |     op.drop_index(op.f('ix_users_username'), table_name='users') | ||||||
|  |     op.drop_index(op.f('ix_users_email'), table_name='users') | ||||||
|  |     op.drop_table('users') | ||||||
|  |     op.drop_table('roles') | ||||||
|  |     # ### end Alembic commands ### | ||||||
							
								
								
									
										10
									
								
								opaque.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								opaque.py
									
									
									
									
									
								
							| @@ -1,5 +1,13 @@ | |||||||
| from app import create_app | from app import create_app, db | ||||||
|  | from app.models import User, Role | ||||||
|  | from flask_migrate import Migrate | ||||||
| import os | import os | ||||||
|  |  | ||||||
|  |  | ||||||
| app = create_app(os.getenv('FLASK_CONFIG') or 'default') | app = create_app(os.getenv('FLASK_CONFIG') or 'default') | ||||||
|  | migrate = Migrate(app, db) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.shell_context_processor | ||||||
|  | def make_shell_context(): | ||||||
|  |     return dict(db=db, User=User, Role=Role) | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| Flask==1.0.3 | Flask==1.0.3 | ||||||
| Flask-Login==0.4.1 | Flask-Login==0.4.1 | ||||||
|  | Flask-Migrate==2.5.2 | ||||||
| Flask-SQLAlchemy==2.4.0 | Flask-SQLAlchemy==2.4.0 | ||||||
|  | Flask-WTF==0.14.2 | ||||||
| python-dotenv==0.10.3 | python-dotenv==0.10.3 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user