Webservice side projects
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
*.db
|
||||||
|
|
||||||
webservice/venv/
|
webservice/venv/
|
||||||
docs/venv/
|
docs/venv/
|
||||||
|
@@ -27,7 +27,6 @@ if path not in sys.path:
|
|||||||
del os, sys, path
|
del os, sys, path
|
||||||
# --------
|
# --------
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import svn.local
|
import svn.local
|
||||||
@@ -37,12 +36,19 @@ from flask import Flask, jsonify, abort, request, make_response, url_for, Respon
|
|||||||
from flask.views import MethodView
|
from flask.views import MethodView
|
||||||
from flask.ext.restful import Api, Resource, reqparse, fields, marshal
|
from flask.ext.restful import Api, Resource, reqparse, fields, marshal
|
||||||
from flask.ext.httpauth import HTTPBasicAuth
|
from flask.ext.httpauth import HTTPBasicAuth
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
api = Api(app)
|
api = Api(app)
|
||||||
auth = HTTPBasicAuth()
|
auth = HTTPBasicAuth()
|
||||||
import config
|
import config
|
||||||
app.config.from_object(config.Development)
|
app.config.from_object(config.Development)
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
from application.modules.admin import backend
|
||||||
|
from application.modules.admin import settings
|
||||||
|
from application.modules.projects import admin
|
||||||
|
from application.modules.projects.model import Project
|
||||||
|
|
||||||
|
|
||||||
@auth.get_password
|
@auth.get_password
|
||||||
@@ -73,11 +79,13 @@ class DirectoryAPI(Resource):
|
|||||||
|
|
||||||
def get(self, project_name):
|
def get(self, project_name):
|
||||||
|
|
||||||
|
project = Project.query.filter_by(name=project_name).first()
|
||||||
|
|
||||||
path = request.args['path']
|
path = request.args['path']
|
||||||
if not path:
|
if not path:
|
||||||
path = ''
|
path = ''
|
||||||
|
|
||||||
absolute_path_root = app.config['STORAGE_PATH']
|
absolute_path_root = project.repository_path
|
||||||
parent_path = ''
|
parent_path = ''
|
||||||
|
|
||||||
if path != '':
|
if path != '':
|
||||||
@@ -134,8 +142,10 @@ class FileAPI(Resource):
|
|||||||
filepath = request.args['filepath']
|
filepath = request.args['filepath']
|
||||||
command = request.args['command']
|
command = request.args['command']
|
||||||
|
|
||||||
|
project = Project.query.filter_by(name=project_name).first()
|
||||||
|
|
||||||
if command == 'info':
|
if command == 'info':
|
||||||
r = svn.local.LocalClient(app.config['STORAGE_PATH'])
|
r = svn.local.LocalClient(project.repository_path)
|
||||||
|
|
||||||
log = r.log_default(None, None, 5, filepath)
|
log = r.log_default(None, None, 5, filepath)
|
||||||
log = [l for l in log]
|
log = [l for l in log]
|
||||||
@@ -145,7 +155,7 @@ class FileAPI(Resource):
|
|||||||
log=log)
|
log=log)
|
||||||
|
|
||||||
elif command == 'checkout':
|
elif command == 'checkout':
|
||||||
filepath = os.path.join(app.config['STORAGE_PATH'], filepath)
|
filepath = os.path.join(project.repository_path, filepath)
|
||||||
|
|
||||||
if not os.path.exists(filepath):
|
if not os.path.exists(filepath):
|
||||||
return jsonify(message="Path not found %r" % filepath)
|
return jsonify(message="Path not found %r" % filepath)
|
||||||
@@ -192,6 +202,7 @@ class FileAPI(Resource):
|
|||||||
return jsonify(message="Command unknown")
|
return jsonify(message="Command unknown")
|
||||||
|
|
||||||
def put(self, project_name):
|
def put(self, project_name):
|
||||||
|
project = Project.query.filter_by(name=project_name).first()
|
||||||
command = request.args['command']
|
command = request.args['command']
|
||||||
arguments = ''
|
arguments = ''
|
||||||
if 'arguments' in request.args:
|
if 'arguments' in request.args:
|
||||||
@@ -199,12 +210,12 @@ class FileAPI(Resource):
|
|||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
|
|
||||||
if file and self.allowed_file(file.filename):
|
if file and self.allowed_file(file.filename):
|
||||||
local_client = svn.local.LocalClient(app.config['STORAGE_PATH'])
|
local_client = svn.local.LocalClient(project.repository_path)
|
||||||
# TODO, add the merge operation to a queue. Later on, the request could stop here
|
# TODO, add the merge operation to a queue. Later on, the request could stop here
|
||||||
# and all the next steps could be done in another loop, or triggered again via
|
# and all the next steps could be done in another loop, or triggered again via
|
||||||
# another request
|
# another request
|
||||||
filename = werkzeug.secure_filename(file.filename)
|
filename = werkzeug.secure_filename(file.filename)
|
||||||
tmp_filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
tmp_filepath = os.path.join(project.upload_path, filename)
|
||||||
file.save(tmp_filepath)
|
file.save(tmp_filepath)
|
||||||
|
|
||||||
# TODO, once all files are uploaded, unpack and run the tasklist (copy, add, remove
|
# TODO, once all files are uploaded, unpack and run the tasklist (copy, add, remove
|
||||||
|
0
webservice/bam/application/modules/__init__.py
Normal file
0
webservice/bam/application/modules/__init__.py
Normal file
115
webservice/bam/application/modules/admin/__init__.py
Normal file
115
webservice/bam/application/modules/admin/__init__.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
from application import app, db
|
||||||
|
#from application import thumb
|
||||||
|
|
||||||
|
from flask import render_template, redirect, url_for
|
||||||
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
from flask.ext import admin, login
|
||||||
|
from flask.ext.admin import Admin, expose
|
||||||
|
from flask.ext.admin import form
|
||||||
|
from flask.ext.admin.contrib import sqla
|
||||||
|
from flask.ext.admin.contrib.sqla import ModelView
|
||||||
|
from flask.ext.admin.base import BaseView
|
||||||
|
from flask.ext.security import current_user
|
||||||
|
|
||||||
|
from werkzeug import secure_filename
|
||||||
|
from jinja2 import Markup
|
||||||
|
from wtforms import fields, validators, widgets
|
||||||
|
from wtforms.fields import SelectField, TextField
|
||||||
|
import os, hashlib, time
|
||||||
|
import os.path as op
|
||||||
|
|
||||||
|
|
||||||
|
def _list_items(view, context, model, name):
|
||||||
|
"""Utilities to upload and present images
|
||||||
|
"""
|
||||||
|
if not model.name:
|
||||||
|
return ''
|
||||||
|
return Markup(
|
||||||
|
'<div class="select2-container-multi">'
|
||||||
|
'<ul class="select2-choices" style="border:0;cursor:default;background:none;">%s</ul></div>' % (
|
||||||
|
''.join( ['<li class="select2-search-choice" style="padding:3px 5px;">'
|
||||||
|
'<div>'+item.name+'</div></li>' for item in getattr(model,name)] )))
|
||||||
|
|
||||||
|
|
||||||
|
def _list_thumbnail(view, context, model, name):
|
||||||
|
if not getattr(model,name): #model.name only does not work because name is a string
|
||||||
|
return ''
|
||||||
|
return ''
|
||||||
|
# return Markup('<img src="%s">' % url_for('static',
|
||||||
|
# filename=thumb.thumbnail(getattr(model,name), '50x50', crop='fit')))
|
||||||
|
|
||||||
|
# Create directory for file fields to use
|
||||||
|
file_path = op.join(op.dirname(__file__), '../../static/files',)
|
||||||
|
try:
|
||||||
|
os.mkdir(file_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def prefix_name(obj, file_data):
|
||||||
|
# Collect name and extension
|
||||||
|
parts = op.splitext(file_data.filename)
|
||||||
|
# Get current time (for unique hash)
|
||||||
|
timestamp = str(round(time.time()))
|
||||||
|
# Has filename only (not extension)
|
||||||
|
file_name = secure_filename(timestamp + '%s' % parts[0])
|
||||||
|
# Put them together
|
||||||
|
full_name = hashlib.md5(file_name).hexdigest() + parts[1]
|
||||||
|
return full_name
|
||||||
|
|
||||||
|
|
||||||
|
def image_upload_field(label):
|
||||||
|
return form.ImageUploadField(label,
|
||||||
|
base_path=file_path,
|
||||||
|
thumbnail_size=(100, 100, True),
|
||||||
|
namegen=prefix_name,
|
||||||
|
endpoint='filemanager.static')
|
||||||
|
|
||||||
|
|
||||||
|
# Define wtforms widget and field
|
||||||
|
class CKTextAreaWidget(widgets.TextArea):
|
||||||
|
def __call__(self, field, **kwargs):
|
||||||
|
kwargs.setdefault('class_', 'ckeditor')
|
||||||
|
return super(CKTextAreaWidget, self).__call__(field, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CKTextAreaField(fields.TextAreaField):
|
||||||
|
widget = CKTextAreaWidget()
|
||||||
|
|
||||||
|
|
||||||
|
# Create customized views with access restriction
|
||||||
|
class CustomModelView(ModelView):
|
||||||
|
def is_accessible(self):
|
||||||
|
return True
|
||||||
|
#return login.current_user.has_role('admin')
|
||||||
|
|
||||||
|
class CustomBaseView(BaseView):
|
||||||
|
def is_accessible(self):
|
||||||
|
return True
|
||||||
|
#return login.current_user.has_role('admin')
|
||||||
|
|
||||||
|
|
||||||
|
# Create customized index view class that handles login & registration
|
||||||
|
class CustomAdminIndexView(admin.AdminIndexView):
|
||||||
|
def is_accessible(self):
|
||||||
|
return True
|
||||||
|
#return login.current_user.has_role('admin')
|
||||||
|
|
||||||
|
@expose('/')
|
||||||
|
def index(self):
|
||||||
|
return super(CustomAdminIndexView, self).index()
|
||||||
|
|
||||||
|
@expose('/logout/')
|
||||||
|
def logout_view(self):
|
||||||
|
login.logout_user()
|
||||||
|
return redirect(url_for('homepage'))
|
||||||
|
|
||||||
|
|
||||||
|
# Create admin
|
||||||
|
backend = Admin(
|
||||||
|
app,
|
||||||
|
'BAM',
|
||||||
|
index_view=CustomAdminIndexView(),
|
||||||
|
base_template='admin/layout_admin.html'
|
||||||
|
)
|
||||||
|
|
11
webservice/bam/application/modules/admin/model.py
Normal file
11
webservice/bam/application/modules/admin/model.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from application import db
|
||||||
|
|
||||||
|
class Setting(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(256), unique=True, nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
value = db.Column(db.String(100), nullable=False)
|
||||||
|
data_type = db.Column(db.String(128), nullable=False)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
9
webservice/bam/application/modules/admin/settings.py
Normal file
9
webservice/bam/application/modules/admin/settings.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from application import app
|
||||||
|
from application import db
|
||||||
|
|
||||||
|
from application.modules.admin.model import Setting
|
||||||
|
from application.modules.admin import *
|
||||||
|
|
||||||
|
|
||||||
|
# Add views
|
||||||
|
backend.add_view(CustomModelView(Setting, db.session, name='Settings', url='settings'))
|
17
webservice/bam/application/modules/projects/admin.py
Normal file
17
webservice/bam/application/modules/projects/admin.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from application import app
|
||||||
|
from application import db
|
||||||
|
|
||||||
|
from application.modules.projects.model import Project
|
||||||
|
|
||||||
|
from application.modules.admin import *
|
||||||
|
from application.modules.admin import _list_thumbnail
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectView(CustomModelView):
|
||||||
|
column_searchable_list = ('name',)
|
||||||
|
column_list = ('name', 'picture', 'creation_date')
|
||||||
|
#column_formatters = { 'picture': _list_thumbnail }
|
||||||
|
#form_extra_fields = {'picture': image_upload_field('Header')}
|
||||||
|
|
||||||
|
# Add views
|
||||||
|
backend.add_view(ProjectView(Project, db.session, name='Projects', url='projects'))
|
14
webservice/bam/application/modules/projects/model.py
Normal file
14
webservice/bam/application/modules/projects/model.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import datetime
|
||||||
|
from application import db
|
||||||
|
|
||||||
|
class Project(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(255), nullable=False)
|
||||||
|
repository_path = db.Column(db.Text, nullable=False)
|
||||||
|
upload_path = db.Column(db.Text, nullable=False)
|
||||||
|
picture = db.Column(db.String(80))
|
||||||
|
creation_date = db.Column(db.DateTime(), default=datetime.datetime.now)
|
||||||
|
status = db.Column(db.String(80)) #pending #active #inactive
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
20
webservice/bam/application/templates/admin/layout_admin.html
Normal file
20
webservice/bam/application/templates/admin/layout_admin.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{% extends 'admin/base.html' %}
|
||||||
|
|
||||||
|
{% block brand %}
|
||||||
|
<span class="brand"><a href="#">{{ admin_view.admin.name }}</a></span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{#
|
||||||
|
{% block access_control %}
|
||||||
|
{% if current_user.is_authenticated() %}
|
||||||
|
<div class="btn-group pull-right">
|
||||||
|
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
|
||||||
|
<i class="icon-user"></i> {{ current_user.login }} <span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="{{ url_for('admin.logout_view') }}">Log out</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
#}
|
14
webservice/bam/manage.py
Executable file
14
webservice/bam/manage.py
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from application import app, db
|
||||||
|
from flask.ext.script import Manager
|
||||||
|
from flask.ext.migrate import Migrate, MigrateCommand
|
||||||
|
|
||||||
|
migrate = Migrate(app, db)
|
||||||
|
manager = Manager(app)
|
||||||
|
manager.add_command('db', MigrateCommand)
|
||||||
|
|
||||||
|
@manager.command
|
||||||
|
def create_all_tables():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
manager.run()
|
45
webservice/bam/migrations/alembic.ini
Normal file
45
webservice/bam/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
|
73
webservice/bam/migrations/env.py
Normal file
73
webservice/bam/migrations/env.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
|
from alembic import context
|
||||||
|
from sqlalchemy import engine_from_config, pool
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
engine = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section),
|
||||||
|
prefix='sqlalchemy.',
|
||||||
|
poolclass=pool.NullPool)
|
||||||
|
|
||||||
|
connection = engine.connect()
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
|
|
22
webservice/bam/migrations/script.py.mako
Executable file
22
webservice/bam/migrations/script.py.mako
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
@@ -0,0 +1,45 @@
|
|||||||
|
"""initial_tables
|
||||||
|
|
||||||
|
Revision ID: 4918c57ece7
|
||||||
|
Revises: None
|
||||||
|
Create Date: 2014-11-05 18:26:17.841382
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4918c57ece7'
|
||||||
|
down_revision = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('setting',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=256), nullable=False),
|
||||||
|
sa.Column('description', sa.Text(), nullable=True),
|
||||||
|
sa.Column('value', sa.String(length=100), nullable=False),
|
||||||
|
sa.Column('data_type', sa.String(length=128), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('name')
|
||||||
|
)
|
||||||
|
op.create_table('project',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('repository_path', sa.Text(), nullable=False),
|
||||||
|
sa.Column('upload_path', sa.Text(), nullable=False),
|
||||||
|
sa.Column('picture', sa.String(length=80), nullable=True),
|
||||||
|
sa.Column('creation_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('status', sa.String(length=80), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('project')
|
||||||
|
op.drop_table('setting')
|
||||||
|
### end Alembic commands ###
|
@@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from application import app
|
|
||||||
app.run(debug=True)
|
|
@@ -1,13 +1,29 @@
|
|||||||
Flask==0.10.1
|
Flask==0.10.1
|
||||||
|
Flask-Admin==1.0.8
|
||||||
Flask-HTTPAuth==2.3.0
|
Flask-HTTPAuth==2.3.0
|
||||||
|
Flask-Login==0.2.11
|
||||||
|
Flask-Mail==0.9.1
|
||||||
|
Flask-Migrate==1.2.0
|
||||||
|
Flask-Principal==0.4.0
|
||||||
Flask-RESTful==0.2.12
|
Flask-RESTful==0.2.12
|
||||||
|
Flask-SQLAlchemy==2.0
|
||||||
|
Flask-Script==2.0.5
|
||||||
|
Flask-Security==1.7.4
|
||||||
|
Flask-WTF==0.10.2
|
||||||
Jinja2==2.7.3
|
Jinja2==2.7.3
|
||||||
|
Mako==1.0.0
|
||||||
MarkupSafe==0.23
|
MarkupSafe==0.23
|
||||||
|
Pillow==2.6.1
|
||||||
|
SQLAlchemy==0.9.8
|
||||||
|
WTForms==2.0.1
|
||||||
Werkzeug==0.9.6
|
Werkzeug==0.9.6
|
||||||
|
alembic==0.6.7
|
||||||
aniso8601==0.83
|
aniso8601==0.83
|
||||||
|
blinker==1.3
|
||||||
gnureadline==6.3.3
|
gnureadline==6.3.3
|
||||||
ipython==2.3.0
|
ipython==2.3.0
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
|
passlib==1.6.2
|
||||||
python-dateutil==2.2
|
python-dateutil==2.2
|
||||||
pytz==2014.7
|
pytz==2014.7
|
||||||
six==1.7.2
|
six==1.7.2
|
||||||
|
Reference in New Issue
Block a user