Webservice side projects
This commit is contained in:
@@ -27,7 +27,6 @@ if path not in sys.path:
|
||||
del os, sys, path
|
||||
# --------
|
||||
|
||||
|
||||
import os
|
||||
import json
|
||||
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.ext.restful import Api, Resource, reqparse, fields, marshal
|
||||
from flask.ext.httpauth import HTTPBasicAuth
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
|
||||
app = Flask(__name__)
|
||||
api = Api(app)
|
||||
auth = HTTPBasicAuth()
|
||||
import config
|
||||
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
|
||||
@@ -73,11 +79,13 @@ class DirectoryAPI(Resource):
|
||||
|
||||
def get(self, project_name):
|
||||
|
||||
project = Project.query.filter_by(name=project_name).first()
|
||||
|
||||
path = request.args['path']
|
||||
if not path:
|
||||
path = ''
|
||||
|
||||
absolute_path_root = app.config['STORAGE_PATH']
|
||||
absolute_path_root = project.repository_path
|
||||
parent_path = ''
|
||||
|
||||
if path != '':
|
||||
@@ -134,8 +142,10 @@ class FileAPI(Resource):
|
||||
filepath = request.args['filepath']
|
||||
command = request.args['command']
|
||||
|
||||
project = Project.query.filter_by(name=project_name).first()
|
||||
|
||||
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 = [l for l in log]
|
||||
@@ -145,7 +155,7 @@ class FileAPI(Resource):
|
||||
log=log)
|
||||
|
||||
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):
|
||||
return jsonify(message="Path not found %r" % filepath)
|
||||
@@ -192,6 +202,7 @@ class FileAPI(Resource):
|
||||
return jsonify(message="Command unknown")
|
||||
|
||||
def put(self, project_name):
|
||||
project = Project.query.filter_by(name=project_name).first()
|
||||
command = request.args['command']
|
||||
arguments = ''
|
||||
if 'arguments' in request.args:
|
||||
@@ -199,12 +210,12 @@ class FileAPI(Resource):
|
||||
file = request.files['file']
|
||||
|
||||
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
|
||||
# and all the next steps could be done in another loop, or triggered again via
|
||||
# another request
|
||||
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)
|
||||
|
||||
# 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-Admin==1.0.8
|
||||
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-SQLAlchemy==2.0
|
||||
Flask-Script==2.0.5
|
||||
Flask-Security==1.7.4
|
||||
Flask-WTF==0.10.2
|
||||
Jinja2==2.7.3
|
||||
Mako==1.0.0
|
||||
MarkupSafe==0.23
|
||||
Pillow==2.6.1
|
||||
SQLAlchemy==0.9.8
|
||||
WTForms==2.0.1
|
||||
Werkzeug==0.9.6
|
||||
alembic==0.6.7
|
||||
aniso8601==0.83
|
||||
blinker==1.3
|
||||
gnureadline==6.3.3
|
||||
ipython==2.3.0
|
||||
itsdangerous==0.24
|
||||
passlib==1.6.2
|
||||
python-dateutil==2.2
|
||||
pytz==2014.7
|
||||
six==1.7.2
|
||||
|
Reference in New Issue
Block a user