Renaming the project to Pillar.

This commit is contained in:
2015-08-31 18:22:50 +02:00
parent b75482b0d6
commit 1830d04400
13 changed files with 11 additions and 29 deletions

View File

@@ -0,0 +1,295 @@
import os
import json
from eve import Eve
# import random
# import string
from eve.auth import TokenAuth
from eve.auth import BasicAuth
from eve.io.mongo import Validator
from eve.methods.post import post_internal
from bson import ObjectId
from flask import g
from flask import request
from pre_hooks import pre_GET
from pre_hooks import pre_PUT
from pre_hooks import pre_PATCH
from pre_hooks import pre_POST
from pre_hooks import pre_DELETE
from pre_hooks import check_permissions
from pre_hooks import compute_permissions
from datetime import datetime
from datetime import timedelta
RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
class SystemUtility():
def __new__(cls, *args, **kwargs):
raise TypeError("Base class may not be instantiated")
@staticmethod
def blender_id_endpoint():
"""Gets the endpoint for the authentication API. If the env variable
is defined, it's possible to override the (default) production address.
"""
return os.environ.get(
'BLENDER_ID_ENDPOINT', "https://www.blender.org/id")
def validate(token):
"""Validate a Token against Blender ID server
"""
import requests
payload = dict(
token=token)
try:
r = requests.post("{0}/u/validate_token".format(
SystemUtility.blender_id_endpoint()), data=payload)
except requests.exceptions.ConnectionError as e:
raise e
if r.status_code == 200:
message = r.json()['message']
valid = r.json()['valid']
user = r.json()['user']
else:
message = ""
valid = False
user = None
return dict(valid=valid, message=message, user=user)
def validate_token():
token = request.authorization.username
tokens = app.data.driver.db['tokens']
users = app.data.driver.db['users']
lookup = {'token': token, 'expire_time': {"$gt": datetime.now()}}
dbtoken = tokens.find_one(lookup)
if not dbtoken:
validation = validate(token)
if validation['valid']:
email = validation['user']['email']
dbuser = users.find_one({'email': email})
tmpname = email.split('@')[0]
if not dbuser:
user_data = {
'first_name': tmpname,
'last_name': tmpname,
'email': email,
'role': ['admin'],
}
r = post_internal('users', user_data)
user_id = r[0]["_id"]
else:
user_id = dbuser['_id']
token_data = {
'user': user_id,
'token': token,
'expire_time': datetime.now() + timedelta(hours=1)
}
post_internal('tokens', token_data)
return token_data
else:
return None
else:
token_data = {
'user': dbtoken['user'],
'token': dbtoken['token'],
'expire_time': dbtoken['expire_time']
}
return token_data
class TokensAuth(TokenAuth):
def check_auth(self, token, allowed_roles, resource, method):
if not token:
return False
validate_token()
# if dbtoken:
# check_permissions(dbtoken['user'])
# return True
# return validation['valid']
return True
"""
users = app.data.driver.db['users']
lookup = {'first_name': token['username']}
if allowed_roles:
lookup['role'] = {'$in': allowed_roles}
user = users.find_one(lookup)
if not user:
return False
return token
"""
class BasicsAuth(BasicAuth):
def check_auth(self, username, password, allowed_roles, resource, method):
# return username == 'admin' and password == 'secret'
return True
class CustomTokenAuth(BasicsAuth):
"""Switch between Basic and Token auth"""
def __init__(self):
self.token_auth = TokensAuth()
self.authorized_protected = BasicsAuth.authorized
def authorized(self, allowed_roles, resource, method):
# if resource == 'tokens':
if False:
return self.authorized_protected(
self, allowed_roles, resource, method)
else:
return self.token_auth.authorized(allowed_roles, resource, method)
def authorized_protected(self):
pass
def convert_properties(properties, node_schema):
for prop in node_schema:
if not prop in properties:
continue
schema_prop = node_schema[prop]
prop_type = schema_prop['type']
if prop_type == 'dict':
properties[prop] = convert_properties(
properties[prop], schema_prop['schema'])
if prop_type == 'list':
if properties[prop] in ['', '[]']:
properties[prop] = []
for k, val in enumerate(properties[prop]):
if not 'schema' in schema_prop:
continue
item_schema = {'item': schema_prop['schema']}
item_prop = {'item': properties[prop][k]}
properties[prop][k] = convert_properties(
item_prop, item_schema)['item']
# Convert datetime string to RFC1123 datetime
elif prop_type == 'datetime':
prop_val = properties[prop]
properties[prop] = datetime.strptime(prop_val, RFC1123_DATE_FORMAT)
elif prop_type == 'objectid':
prop_val = properties[prop]
if prop_val:
properties[prop] = ObjectId(prop_val)
else:
properties[prop] = None
return properties
class ValidateCustomFields(Validator):
def _validate_valid_properties(self, valid_properties, field, value):
node_types = app.data.driver.db['node_types']
lookup = {}
lookup['_id'] = ObjectId(self.document['node_type'])
node_type = node_types.find_one(lookup)
try:
value = convert_properties(value, node_type['dyn_schema'])
except Exception, e:
print ("Error converting: {0}".format(e))
#print (value)
v = Validator(node_type['dyn_schema'])
val = v.validate(value)
if val:
return True
else:
try:
print (val.errors)
except:
pass
self._error(
field, "Error validating properties")
def post_item(entry, data):
return post_internal(entry, data)
app = Eve(validator=ValidateCustomFields, auth=CustomTokenAuth)
def global_validation():
setattr(g, 'token_data', validate_token())
setattr(g, 'validate', validate(g.get('token_data')['token']))
check_permissions(g.get('token_data')['user'], app.data.driver)
def pre_GET_nodes(request, lookup):
# Only get allowed documents
global_validation()
# print ("Get")
# print ("Owner: {0}".format(g.get('owner_permissions')))
# print ("World: {0}".format(g.get('world_permissions')))
return pre_GET(request, lookup, app.data.driver)
def pre_PUT_nodes(request, lookup):
# Only Update allowed documents
global_validation()
# print ("Put")
# print ("Owner: {0}".format(g.get('owner_permissions')))
# print ("World: {0}".format(g.get('world_permissions')))
return pre_PUT(request, lookup, app.data.driver)
def pre_PATCH_nodes(request):
return pre_PATCH(request, app.data.driver)
def pre_POST_nodes(request):
global_validation()
# print ("Post")
# print ("World: {0}".format(g.get('world_permissions')))
# print ("Group: {0}".format(g.get('groups_permissions')))
return pre_POST(request, app.data.driver)
def pre_DELETE_nodes(request, lookup):
# Only Delete allowed documents
global_validation()
# print ("Delete")
# print ("Owner: {0}".format(type_owner_permissions))
# print ("World: {0}".format(type_world_permissions))
# print ("Groups: {0}".format(type_groups_permissions))
return pre_DELETE(request, lookup, app.data.driver)
app.on_pre_GET_nodes += pre_GET_nodes
app.on_pre_POST_nodes += pre_POST_nodes
app.on_pre_PATCH_nodes += pre_PATCH_nodes
app.on_pre_PUT_nodes += pre_PUT_nodes
app.on_pre_DELETE_nodes += pre_DELETE_nodes
def post_GET_user(request, payload):
json_data = json.loads(payload.data)
# Check if we are querying the users endpoint (instead of the single user)
if json_data.get('_id') is None:
return
json_data['computed_permissions'] = \
compute_permissions(json_data['_id'], app.data.driver)
payload.data = json.dumps(json_data)
app.on_post_GET_users += post_GET_user
# The file_server module needs app to be defined
from file_server import file_server
app.register_blueprint(file_server, url_prefix='/file_server')

View File

@@ -0,0 +1,179 @@
import os
import hashlib
from flask import Blueprint
from flask import request
from application import app
from application import post_item
from datetime import datetime
from PIL import Image
from bson import ObjectId
RFC1123_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
file_server = Blueprint('file_server', __name__,
template_folder='templates',
static_folder='static/storage')
def hashfile(afile, hasher, blocksize=65536):
buf = afile.read(blocksize)
while len(buf) > 0:
hasher.update(buf)
buf = afile.read(blocksize)
return hasher.hexdigest()
@file_server.route('/build_previews/<file_name>')
def build_previews(file_name=None):
from pymongo import MongoClient
# Get File
client = MongoClient()
db = client.eve
file_ = db.files.find({"path": "{0}".format(file_name)})
file_ = file_[0]
user = file_['user']
folder_name = file_name[:2]
file_folder_path = os.path.join(app.config['FILE_STORAGE'],
folder_name)
# The original file exists?
file_path = os.path.join(file_folder_path, file_name)
if not os.path.isfile(file_path):
return "", 404
sizes = ["xs", "s", "m", "l", "xl"]
size_dict = {
"xs": (32, 32),
"s": (64, 64),
"m": (128, 128),
"l": (640, 480),
"xl": (1024, 768)
}
# Generate
preview_list = []
for size in sizes:
resized_file_name = "{0}_{1}".format(size, file_name)
resized_file_path = os.path.join(
app.config['FILE_STORAGE'],
resized_file_name)
# Create thumbnail
#if not os.path.isfile(resized_file_path):
try:
im = Image.open(file_path)
except IOError:
return "", 500
im.thumbnail(size_dict[size])
width = im.size[0]
height = im.size[1]
format = im.format.lower()
try:
im.save(resized_file_path)
except IOError:
return "", 500
# file_static_path = os.path.join("", folder_name, size, file_name)
picture_file_file = open(resized_file_path, 'rb')
hash_ = hashfile(picture_file_file, hashlib.md5())
name = "{0}{1}".format(hash_,
os.path.splitext(file_name)[1])
picture_file_file.close()
description = "Thumbnail {0} for file {1}".format(
size, file_name)
prop = {}
prop['name'] = resized_file_name
prop['description'] = description
prop['user'] = user
# Preview properties:
prop['is_preview'] = True
prop['size'] = size
prop['format'] = format
prop['width'] = width
prop['height'] = height
# TODO set proper contentType and length
prop['contentType'] = 'image/png'
prop['length'] = 0
prop['uploadDate'] = datetime.strftime(
datetime.now(), RFC1123_DATE_FORMAT)
prop['md5'] = hash_
prop['filename'] = resized_file_name
prop['backend'] = 'attract'
prop['path'] = name
entry = post_item ('files', prop)
if entry[0]['_status'] == 'ERR':
entry = db.files.find({"path": name})
entry = entry[0]
prop['_id'] = entry['_id']
new_folder_name = name[:2]
new_folder_path = os.path.join(
app.config['FILE_STORAGE'],
new_folder_name)
new_file_path = os.path.join(
new_folder_path,
name)
if not os.path.exists(new_folder_path):
os.makedirs(new_folder_path)
# Clean up temporary file
os.rename(
resized_file_path,
new_file_path)
preview_list.append(str(prop['_id']))
#print (new_file_path)
# Add previews to file
previews = []
try:
previews = file_['previews']
except KeyError:
pass
preview_list = preview_list + previews
#print (previews)
#print (preview_list)
#print (file_['_id'])
file_ = db.files.update(
{"_id": ObjectId(file_['_id'])},
{"$set": {"previews": preview_list}}
)
#print (file_)
return "", 200
@file_server.route('/file', methods=['POST'])
@file_server.route('/file/<file_name>')
def index(file_name=None):
#GET file
if file_name:
folder_name = file_name[:2]
file_path = os.path.join("", folder_name, file_name)
return file_server.send_static_file(file_path)
#POST file
file_name = request.form['name']
folder_name = file_name[:2]
file_folder_path = os.path.join(app.config['FILE_STORAGE'],
folder_name)
if not os.path.exists(file_folder_path):
os.mkdir(file_folder_path)
file_path = os.path.join(file_folder_path, file_name)
request.files['data'].save(file_path)
return "{}", 200

View File

@@ -0,0 +1,214 @@
import json
from flask import g
from flask import abort
from flask import request
from bson import ObjectId
# from application import app
def permissions_lookup(action, lookup):
type_world_permissions = g.get('type_world_permissions')
type_owner_permissions = g.get('type_owner_permissions')
node_types = []
# Get all node_types allowed by world:
for per in type_world_permissions:
if action in type_world_permissions[per]:
node_types.append(str(per))
# Get all nodes with node_type allowed by owner if user == owner
owner_lookup = []
for per in type_owner_permissions:
if action in type_owner_permissions[per]:
if action not in type_world_permissions[per]:
# If one of the following is true
# If node_type==node_type and user==user
owner_lookup.append(
{'$and': [{'node_type': str(per)},
{'user': str(g.get('token_data')['user'])}]})
lookup['$or'] = [{'node_type': {'$in': node_types}}]
if len(owner_lookup) > 0:
lookup['$or'].append({'$or': owner_lookup})
return lookup
def pre_GET(request, lookup, data_driver):
action = 'GET'
if 'token_type' not in lookup and '_id' not in request.view_args:
# Is quering for all nodes (mixed types)
lookup = permissions_lookup(action, lookup)
else:
# Is quering for one specific node
if action not in g.get('world_permissions') and \
action not in g.get('groups_permissions'):
lookup['user'] = g.get('token_data')['user']
# token_data = validate_token()
# validate(token_data['token'])
# lookup["userr"] = "user"
# print ("Lookup")
# print (lookup)
def pre_PUT(request, lookup, data_driver):
action = 'UPDATE'
if 'token_type' not in lookup and '_id' not in request.view_args:
# Is updating all nodes (mixed types)
lookup = permissions_lookup(action, lookup)
else:
# Is updating one specific node
if action not in g.get('world_permissions') and \
action not in g.get('groups_permissions'):
lookup['user'] = g.get('token_data')['user']
# print ("Lookup")
# print (lookup)
def pre_PATCH(request, lookup, data_driver):
print ("Patch")
def pre_POST(request, data_driver):
# Only Post allowed documents
action = 'POST'
print (g.get('type_groups_permissions'))
# Is quering for one specific node
if action not in g.get('world_permissions') and \
action not in g.get('groups_permissions'):
abort(403)
def pre_DELETE(request, lookup, data_driver):
type_world_permissions = g.get('type_world_permissions')
type_owner_permissions = g.get('type_owner_permissions')
type_groups_permissions = g.get('type_groups_permissions')
action = 'DELETE'
if '_id' in lookup:
nodes = data_driver.db['nodes']
dbnode = nodes.find_one({'_id': ObjectId(lookup['_id'])})
# print (dbnode.count())
node_type = str(dbnode['node_type'])
if g.get('token_data')['user'] == dbnode['user']:
owner = True
else:
owner = False
if action not in type_world_permissions[node_type] and \
action not in type_groups_permissions[node_type]:
if action not in type_owner_permissions[node_type]:
print ("Abort1")
abort(403)
else:
if not owner:
print ("Abort2")
abort(403)
else:
print ("Abort3")
abort(403)
def compute_permissions(user, data_driver):
node_type = None
dbnode = None
owner_permissions = []
world_permissions = []
groups_permissions = []
groups = data_driver.db['groups']
users = data_driver.db['users']
owner_group = groups.find_one({'name': 'owner'})
world_group = groups.find_one({'name': 'world'})
user_data = users.find_one({'_id': ObjectId(user)})
# If is requesting a specific node
try:
uuid = request.path.split("/")[2]
nodes = data_driver.db['nodes']
lookup = {'_id': ObjectId(uuid)}
dbnode = nodes.find_one(lookup)
except IndexError:
pass
if dbnode:
node_type = str(dbnode['node_type'])
json_data = None
try:
json_data = json.loads(request.data)
except ValueError:
pass
if not node_type and json_data:
if 'node_type' in json_data:
node_type = json_data['node_type']
# Extract query lookup
# which node_type is asking for?
for arg in request.args:
if arg == 'where':
try:
where = json.loads(request.args[arg])
except ValueError:
raise
if where.get('node_type'):
node_type = where.get('node_type')
break
# Get and store permissions for that node_type
type_owner_permissions = {}
type_world_permissions = {}
type_groups_permissions = {}
type_mixed_permissions = {}
for per in owner_group['permissions']:
type_owner_permissions[str(per['node_type'])] = per['permissions']
if str(per['node_type']) == node_type:
owner_permissions = per['permissions']
for per in world_group['permissions']:
type_world_permissions[str(per['node_type'])] = per['permissions']
if str(per['node_type']) == node_type:
world_permissions = per['permissions']
# Adding empty permissions
if str(per['node_type']) not in type_groups_permissions:
type_groups_permissions[str(per['node_type'])] = []
type_mixed_permissions = type_world_permissions
groups_data = user_data.get('groups')
if groups_data:
for group in groups_data:
group_data = groups.find_one({'_id': ObjectId(group)})
for per in group_data['permissions']:
type_groups_permissions[str(per['node_type'])] += \
per['permissions']
type_mixed_permissions[str(per['node_type'])] += \
per['permissions']
if str(per['node_type']) == node_type:
groups_permissions = per['permissions']
return {
'owner_permissions': owner_permissions,
'world_permissions': world_permissions,
'groups_permissions': groups_permissions,
'type_owner_permissions': type_owner_permissions,
'type_world_permissions': type_world_permissions,
'type_groups_permissions': type_groups_permissions,
'type_mixed_permissions': type_mixed_permissions
}
def check_permissions(user, data_driver):
# Entry point should be nodes
entry_point = request.path.split("/")[1]
if entry_point != 'nodes':
return
permissions = compute_permissions(user, data_driver)
# Store permission properties on global
setattr(g, 'owner_permissions', permissions['owner_permissions'])
setattr(g, 'world_permissions', permissions['world_permissions'])
setattr(g, 'groups_permissions', permissions['groups_permissions'])
setattr(g, 'type_owner_permissions', permissions['type_owner_permissions'])
setattr(g, 'type_world_permissions', permissions['type_world_permissions'])
setattr(g, 'type_groups_permissions',
permissions['type_groups_permissions'])

View File

@@ -0,0 +1,3 @@
# Ignore everything but self
*
!.gitignore

View File

28
pillar/config.py.example Normal file
View File

@@ -0,0 +1,28 @@
import os
class Config(object):
# Configured for GMAIL
MAIL_SERVER = ''
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = ''
MAIL_PASSWORD = ''
DEFAULT_MAIL_SENDER = ''
# Flask-Security setup
SECURITY_LOGIN_WITHOUT_CONFIRMATION = True
SECURITY_REGISTERABLE = True
SECURITY_RECOVERABLE = True
SECURITY_CHANGEABLE = True
SECUIRTY_POST_LOGIN = '/'
SECURITY_PASSWORD_HASH = ''
SECURITY_PASSWORD_SALT = ''
SECURITY_EMAIL_SENDER = ''
class Development(Config):
SECRET_KEY = ''
HOST = '0.0.0.0'
PORT = 5000
DEBUG = True
FILE_STORAGE = '{0}/application/static/storage'.format(
os.path.join(os.path.dirname(__file__)))

662
pillar/manage.py Normal file
View File

@@ -0,0 +1,662 @@
import os
from application import app
from application import post_item
from flask.ext.script import Manager
manager = Manager(app)
@manager.command
def runserver():
try:
import config
PORT = config.Development.PORT
HOST = config.Development.HOST
DEBUG = config.Development.DEBUG
app.config['FILE_STORAGE'] = config.Development.FILE_STORAGE
except ImportError:
# Default settings
PORT = 5000
HOST = '0.0.0.0'
DEBUG = True
app.config['FILE_STORAGE'] = '{0}/application/static/storage'.format(
os.path.dirname(os.path.realpath(__file__)))
# Automatic creation of FILE_STORAGE path if it's missing
if not os.path.exists(app.config['FILE_STORAGE']):
os.makedirs(app.config['FILE_STORAGE'])
app.run(
port=PORT,
host=HOST,
debug=DEBUG)
@manager.command
def clear_db():
"""Wipes the database
"""
from pymongo import MongoClient
client = MongoClient()
db = client.eve
db.drop_collection('nodes')
db.drop_collection('node_types')
db.drop_collection('tokens')
db.drop_collection('users')
@manager.command
def remove_properties_order():
"""Removes properties.order
"""
from pymongo import MongoClient
client = MongoClient()
db = client.eve
nodes = db.nodes.find()
for node in nodes:
new_prop = {}
for prop in node['properties']:
if prop == 'order':
continue
else:
new_prop[prop] = node['properties'][prop]
db.nodes.update({"_id": node['_id']},
{"$set": {"properties": new_prop}})
@manager.command
def upgrade_node_types():
"""Wipes node_types collection
and populates it again
"""
from pymongo import MongoClient
client = MongoClient()
db = client.eve
node_types = db.node_types.find({})
old_ids = {}
for nt in node_types:
old_ids[nt['name']] = nt['_id']
populate_node_types(old_ids)
def get_id(collection, name):
"""Returns the _id of the given collection
and name."""
from pymongo import MongoClient
client = MongoClient()
db = client.eve
node = db[collection].find({'name': name})
print (node[0]['_id'])
return node[0]['_id']
@manager.command
def manage_groups():
"""Take user email and group name,
and add or remove the user from that group.
"""
from pymongo import MongoClient
client = MongoClient()
db = client.eve
print ("")
print ("Add or Remove user from group")
print ("leave empty to cancel")
print ("")
# Select Action
print ("Do you want to Add or Remove the user from the group?")
retry = True
while retry:
action = raw_input('add/remove: ')
if action == '':
return
elif action.lower() in ['add', 'a', 'insert']:
action == 'add'
retry = False
elif action.lower() in ['remove', 'r', 'rmv', 'rem', 'delete', 'del']:
action = 'remove'
retry = False
else:
print ("Incorrect action, press type 'add' or 'remove'")
# Select User
retry = True
while retry:
user_email = raw_input('User email: ')
if user_email == '':
return
user = db.users.find_one({'email': user_email})
if user:
retry = False
else:
print ("Incorrect user email, try again, or leave empty to cancel")
# Select group
retry = True
while retry:
group_name = raw_input('Group name: ')
if group_name == '':
return
group = db.groups.find_one({'name': group_name})
if group:
retry = False
else:
print ("Incorrect group name, try again, or leave empty to cancel")
# Do
current_groups = user.get('groups', [])
if action == 'add':
if group['_id'] in current_groups:
print "User {0} is already in group {1}".format(
user_email, group_name)
else:
current_groups.append(group['_id'])
db.users.update({'_id': user['_id']},
{"$set": {'groups': current_groups}})
print "User {0} added to group {1}".format(user_email, group_name)
elif action == 'remove':
if group['_id'] not in current_groups:
print "User {0} is not in group {1}".format(user_email, group_name)
else:
current_groups.remove(group['_id'])
db.users.update({'_id': user['_id']},
{"$set": {'groups': current_groups}})
print "User {0} removed from group {1}".format(
user_email, group_name)
@manager.command
def add_groups():
"""Add permisions
"""
admin_group = {
'name': 'admin',
'permissions': [
{'node_type': get_id('node_types', 'shot'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'task'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'scene'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'act'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'comment'),
'permissions': ['GET', 'POST', 'UPDATE', 'DELETE']
},
]
}
post_item('groups', admin_group)
owner_group = {
'name': 'owner',
'permissions': [
{'node_type': get_id('node_types', 'shot'),
'permissions': ['GET', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'task'),
'permissions': ['GET', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'scene'),
'permissions': ['GET', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'act'),
'permissions': ['GET', 'UPDATE', 'DELETE']
},
{'node_type': get_id('node_types', 'comment'),
'permissions': ['GET', 'UPDATE', 'DELETE']
},
]
}
post_item('groups', owner_group)
world_group = {
'name': 'world',
'permissions': [
{'node_type': get_id('node_types', 'shot'),
'permissions': ['GET']
},
{'node_type': get_id('node_types', 'task'),
'permissions': ['GET']
},
{'node_type': get_id('node_types', 'scene'),
'permissions': ['GET']
},
{'node_type': get_id('node_types', 'act'),
'permissions': ['GET']
},
{'node_type': get_id('node_types', 'comment'),
'permissions': ['GET', 'POST']
},
]
}
post_item('groups', world_group)
@manager.command
def populate_db_test():
"""Populate the db with sample data
"""
populate_node_types()
def populate_node_types(old_ids={}):
shot_node_type = {
'name': 'shot',
'description': 'Shot Node Type, for shots',
'dyn_schema': {
'url': {
'type': 'string',
},
'cut_in': {
'type': 'integer'
},
'cut_out': {
'type': 'integer'
},
'status': {
'type': 'string',
'allowed': [
'on_hold',
'todo',
'in_progress',
'review',
'final'
],
},
'notes': {
'type': 'string',
'maxlength': 256,
},
'shot_group': {
'type': 'string',
#'data_relation': {
# 'resource': 'nodes',
# 'field': '_id',
#},
},
},
'form_schema': {
'url': {},
'cut_in': {},
'cut_out': {},
'status': {},
'notes': {},
'shot_group': {}
},
'parent': {
'node_types': ['scene']
}
}
task_node_type = {
'name': 'task',
'description': 'Task Node Type, for tasks',
'dyn_schema': {
'status': {
'type': 'string',
'allowed': [
'todo',
'in_progress',
'on_hold',
'approved',
'cbb',
'final',
'review'
],
'required': True,
},
'filepath': {
'type': 'string',
},
'revision': {
'type': 'integer',
},
'owners': {
'type': 'dict',
'schema': {
'users': {
'type': 'list',
'schema': {
'type': 'objectid',
}
},
'groups': {
'type': 'list',
'schema': {
'type': 'objectid',
}
}
}
},
'time': {
'type': 'dict',
'schema': {
'start': {
'type': 'datetime'
},
'duration': {
'type': 'integer'
},
'chunks': {
'type': 'list',
'schema': {
'type': 'dict',
'schema': {
'start': {
'type': 'datetime',
},
'duration': {
'type': 'integer',
}
}
}
},
}
},
'is_conflicting' : {
'type': 'boolean'
},
'is_processing' : {
'type': 'boolean'
},
'is_open' : {
'type': 'boolean'
}
},
'form_schema': {
'status': {},
'filepath': {},
'revision': {},
'owners': {
'schema': {
'users':{
'items': [('User', 'first_name')],
},
'groups': {}
}
},
'time': {
'schema': {
'start': {},
'duration': {},
'chunks': {
'visible': False,
'schema': {
'start': {},
'duration': {}
}
}
}
},
'is_conflicting': {},
'is_open': {},
'is_processing': {},
},
'parent': {
'node_types': ['shot'],
}
}
scene_node_type = {
'name': 'scene',
'description': 'Scene node type',
'dyn_schema': {
'order': {
'type': 'integer',
}
},
'form_schema': {
'order': {},
},
'parent': {
"node_types": ["act"]
}
}
act_node_type = {
'name': 'act',
'description': 'Act node type',
'dyn_schema': {
'order': {
'type': 'integer',
}
},
'form_schema': {
'order': {},
},
'parent': {}
}
comment_node_type = {
'name': 'comment',
'description': 'Comment node type',
'dyn_schema': {
'text': {
'type': 'string',
'maxlength': 256
},
'attachments': {
'type': 'list',
'schema': {
'type': 'objectid',
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
}
}
}
},
'form_schema': {
'text': {},
'attachments': {
'items': [("File", "name")]
}
},
'parent': {
"node_types": ["shot", "task"]
}
}
project_node_type = {
'name': 'project',
'parent': {},
'description': 'The official project type',
'dyn_schema': {
'category': {
'type': 'string',
'allowed': [
'film',
'assets',
'software',
'game'
],
'required': True,
},
'is_private': {
'type': 'boolean'
},
'url': {
'type': 'string'
},
'organization': {
'type': 'objectid',
'nullable': True,
'data_relation': {
'resource': 'organizations',
'field': '_id',
'embeddable': True
},
},
'owners': {
'type': 'dict',
'schema': {
'users': {
'type': 'list',
'schema': {
'type': 'objectid',
}
},
'groups': {
'type': 'list',
'schema': {
'type': 'objectid',
'data_relation': {
'resource': 'groups',
'field': '_id',
'embeddable': True
}
}
}
}
},
# Logo
'picture_1': {
'type': 'objectid',
'nullable': True,
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
},
},
# Header
'picture_2': {
'type': 'objectid',
'nullable': True,
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
},
},
},
'form_schema': {
'is_private': {},
# TODO add group parsing
'category': {},
'url': {},
'organization': {},
'picture_1': {},
'picture_2': {},
'owners': {
'schema': {
'users':{
'items': [('User', 'first_name')],
},
'groups': {
'items': [('Group', 'name')],
},
}
},
},
}
group_node_type = {
'name': 'group',
'description': 'Generic group node type',
'dyn_schema': {
'url': {
'type': 'string',
},
'status': {
'type': 'string',
'allowed': [
'published',
'pending'
],
},
'notes': {
'type': 'string',
'maxlength': 256,
},
'parent': {}
},
'form_schema': {
'url': {},
'status': {},
'notes': {},
},
}
from pymongo import MongoClient
client = MongoClient()
db = client.eve
def mix_node_type(old_id, node_type_dict):
# Take eve parameters
node_type = db.node_types.find({'_id':old_id})
node_type = node_type[0]
for attr in node_type:
if attr[0]=='_':
# Mix with node type attributes
node_type_dict[attr]=node_type[attr]
return node_type_dict
def upgrade(node_type, old_ids):
node_name = node_type['name']
if node_name in old_ids:
node_type = mix_node_type(old_ids[node_name], node_type)
# Remove old node_type
db.node_types.remove({'_id': old_ids[node_name]})
# Insert new node_type
db.node_types.insert(node_type)
else:
print("Making the node")
print node_type
post_item('node_types', node_type)
# upgrade(shot_node_type, old_ids)
# upgrade(task_node_type, old_ids)
# upgrade(scene_node_type, old_ids)
# upgrade(act_node_type, old_ids)
# upgrade(comment_node_type, old_ids)
upgrade(project_node_type, old_ids)
@manager.command
def migrate_custom():
from pymongo import MongoClient
client = MongoClient()
db = client.eve
group_node_type = {
'name': 'group',
'description': 'Generic group node type',
'dyn_schema': {
'url': {
'type': 'string',
},
'status': {
'type': 'string',
'allowed': [
'published',
'pending'
],
},
'notes': {
'type': 'string',
'maxlength': 256,
},
'parent': {}
},
'form_schema': {
'url': {},
'status': {},
'notes': {},
},
}
db.node_types.insert(group_node_type)
if __name__ == '__main__':
manager.run()

427
pillar/settings.py Normal file
View File

@@ -0,0 +1,427 @@
import os
# Enable reads (GET), inserts (POST) and DELETE for resources/collections
# (if you omit this line, the API will default to ['GET'] and provide
# read-only access to the endpoint).
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
# Enable reads (GET), edits (PATCH), replacements (PUT) and deletes of
# individual items (defaults to read-only item access).
ITEM_METHODS = ['GET', 'PUT', 'DELETE', 'PATCH']
PAGINATION_LIMIT = 999
# To be implemented on Eve 0.6
# RETURN_MEDIA_AS_URL = True
users_schema = {
'first_name': {
'type': 'string',
'minlength': 1,
'maxlength': 60,
},
'last_name': {
'type': 'string',
'minlength': 1,
'maxlength': 60,
},
'username': {
'type': 'string',
'minlength': 1,
'maxlength': 60,
'required': True,
},
'email': {
'type': 'string',
'minlength': 1,
'maxlength': 60,
},
'role': {
'type': 'list',
'allowed': ["admin"],
'required': True,
},
'groups': {
'type': 'list',
'default': [],
'schema': {
'type': 'objectid',
'data_relation': {
'resource': 'groups',
'field': '_id',
'embeddable': True
}
}
}
}
organizations_schema = {
'name': {
'type': 'string',
'minlength': 1,
'maxlength': 128,
'required': True
},
'url': {
'type': 'string',
'minlength': 1,
'maxlength': 128,
'required': True
},
'description': {
'type': 'string',
'maxlength': 256,
},
'website': {
'type': 'string',
'maxlength': 256,
},
'location': {
'type': 'string',
'maxlength': 256,
},
'picture': {
'type': 'objectid',
'nullable': True,
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
},
},
'users': {
'type': 'list',
'default': [],
'schema': {
'type': 'objectid',
'data_relation': {
'resource': 'users',
'field': '_id',
'embeddable': True
}
}
},
'teams': {
'type': 'list',
'default': [],
'schema': {
'type': 'dict',
'schema': {
# Team name
'name': {
'type': 'string',
'minlength': 1,
'maxlength': 128,
'required': True
},
# List of user ids for the team
'users': {
'type': 'list',
'default': [],
'schema': {
'type': 'objectid',
'data_relation': {
'resource': 'users',
'field': '_id',
}
}
},
# List of groups assigned to the team (this will automatically
# update the groups property of each user in the team)
'groups': {
'type': 'list',
'default': [],
'schema': {
'type': 'objectid',
'data_relation': {
'resource': 'groups',
'field': '_id',
}
}
}
}
}
}
}
nodes_schema = {
'name': {
'type': 'string',
'minlength': 1,
'maxlength': 128,
'required': True,
},
'description': {
'type': 'string',
'minlength': 0,
'maxlength': 128,
},
'picture': {
'type': 'objectid',
'nullable': True,
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
},
},
'order': {
'type': 'integer',
'minlength': 0,
},
'parent': {
'type': 'objectid',
'data_relation': {
'resource': 'nodes',
'field': '_id',
'embeddable': True
},
},
'user': {
'type': 'objectid',
'required': True,
'data_relation': {
'resource': 'users',
'field': '_id',
'embeddable': True
},
},
'node_type': {
'type': 'objectid',
'required': True,
'data_relation': {
'resource': 'node_types',
'field': '_id',
'embeddable': True
},
},
'properties': {
'type' : 'dict',
'valid_properties' : True,
'required': True,
},
}
node_types_schema = {
'name': {
'type': 'string',
'minlength': 1,
'maxlength': 128,
'required': True,
},
'description': {
'type': 'string',
'maxlength': 256,
},
'dyn_schema': {
'type': 'dict',
'required': True,
},
'form_schema': {
'type': 'dict',
'required': True,
},
'parent': {
'type': 'dict',
'required': True,
}
}
tokens_schema = {
'user': {
'type': 'objectid',
'required': True,
},
'token': {
'type': 'string',
'required': True,
},
'expire_time': {
'type': 'datetime',
'required': True,
},
}
files_schema = {
'name': {
'type': 'string',
'required': True,
},
'description': {
'type': 'string',
'required': True,
},
# Preview parameters:
'is_preview': {
'type': 'boolean'
},
'size': {
'type': 'string'
},
'format': {
'type': 'string'
},
'width': {
'type': 'integer'
},
'height': {
'type': 'integer'
},
#
'user': {
'type': 'objectid',
'required': True,
},
'contentType': {
'type': 'string',
'required': True,
},
'length': {
'type': 'integer',
'required': True,
},
'uploadDate': {
'type': 'datetime',
'required': True,
},
'md5': {
'type': 'string',
'required': True,
},
'filename': {
'type': 'string',
'required': True,
},
'backend': {
'type': 'string',
'required': True,
'allowed': ["attract-web", "attract"]
},
'path': {
'type': 'string',
'required': True,
'unique': True,
},
'previews': {
'type': 'list',
'schema': {
'type': 'objectid',
'data_relation': {
'resource': 'files',
'field': '_id',
'embeddable': True
}
}
}
}
binary_files_schema = {
'data': {
'type': 'media',
'required': True
}
}
groups_schema = {
'name': {
'type': 'string',
'required': True
},
'permissions': {
'type': 'list',
'required': True,
'schema': {
'type': 'dict',
'schema': {
'node_type': {
'type': 'objectid',
'required': True,
'data_relation': {
'resource': 'node_types',
'field': '_id',
'embeddable': True
}
},
'permissions': {
'type': 'list',
'required': True,
'allowed': ['GET', 'POST', 'UPDATE', 'DELETE']
}
}
}
}
}
nodes = {
'schema': nodes_schema
}
node_types = {
'resource_methods': ['GET', 'POST'],
'schema': node_types_schema,
}
users = {
'item_title': 'user',
# We choose to override global cache-control directives for this resource.
'cache_control': 'max-age=10,must-revalidate',
'cache_expires': 10,
# most global settings can be overridden at resource level
'resource_methods': ['GET', 'POST'],
'public_methods': ['GET', 'POST'],
# 'public_item_methods': ['GET'],
'schema': users_schema
}
tokens = {
'resource_methods': ['GET', 'POST'],
# Allow 'token' to be returned with POST responses
#'extra_response_fields': ['token'],
'schema' : tokens_schema
}
files = {
'resource_methods': ['GET', 'POST'],
'schema': files_schema,
}
binary_files = {
'resource_methods': ['GET', 'POST'],
'schema': binary_files_schema,
}
groups = {
'resource_methods': ['GET', 'POST'],
'schema': groups_schema,
}
organizations = {
'schema': organizations_schema,
}
DOMAIN = {
'users': users,
'nodes': nodes,
'node_types': node_types,
'tokens': tokens,
'files': files,
'binary_files': binary_files,
'groups': groups,
'organizations': organizations
}
try:
os.environ['TEST_ATTRACT']
MONGO_DBNAME = 'attract_test'
except:
pass
if os.environ.get('MONGO_HOST'):
MONGO_HOST = os.environ.get('MONGO_HOST')

158
pillar/test_attract.py Normal file
View File

@@ -0,0 +1,158 @@
import os
import json
import unittest
from pymongo import MongoClient
from bson import ObjectId
from datetime import datetime
import base64
class AttractTestCase(unittest.TestCase):
def encodeUsrPass(self, user, password):
usrPass = "{0}:{1}".format(user, password)
b64Val = base64.b64encode(usrPass)
return b64Val
def addUser(self, first_name, last_name, role):
return self.app.post('/users', data=dict(
first_name=first_name,
last_name=last_name,
role=role,
), follow_redirects=True)
def addNodeType(self, name, schema):
data = {
'name': name,
'dyn_schema': schema,
}
headers = {
'content-type': 'application/json',
'Authorization': 'Basic QU5MR05TSUVaSjoxMjM0'
}
return self.app.post(
'/node_types',
data=json.dumps(data),
headers=headers,
follow_redirects=True)
def addNode(self, name, nodeType, properties):
data = {
'name': name,
'node_type': nodeType,
'properties': properties
}
headers = {
'content-type': 'application/json',
'Authorization': 'Basic QU5MR05TSUVaSjoxMjM0',
}
return self.app.post(
'/nodes',
data=json.dumps(data),
headers=headers,
follow_redirects=True)
def login(self, username, password):
headers = {
'content-type': 'application/json',
'Authorization': 'Basic {0}'.format(
self.encodeUsrPass(username, password))
}
data = {
'username': username,
}
return self.app.post(
'/tokens',
data=json.dumps(data),
headers=headers,
follow_redirects=True)
def logout(self):
return self.app.get('/logout', follow_redirects=True)
# Tests
def test_add_user(self):
rv = self.addUser('admin', 'default', 'author')
assert 201 == rv.status_code
def test_add_node_type(self):
schema = {
'frame_start': {
'type':'integer',
}
}
rv = self.addNodeType('NodeName', schema)
assert 201 == rv.status_code
def test_add_node(self):
properties = {
'frame_start': 123
}
rv = self.addNode('Shot01', '55016a52135d32466fc800be', properties)
assert 201 == rv.status_code
def test_login(self):
rv = self.login('admin', 'secret')
#print (rv.data)
assert 201 == rv.status_code
def test_empty_db(self):
rv = self.app.get('/')
assert 401 == rv.status_code
# Test Setup
def setUp(self):
# Setup DB
client = MongoClient()
db = client.attract_test
for col in db.collection_names():
try:
db[col].remove({})
except:
pass
test_user = {
"_id": ObjectId("550171c8135d3248e477f288"),
"_updated": datetime.now(),
"firs_tname": "TestFirstname",
"last_name": "TestLastname",
"role": "author",
"_created": datetime.now(),
"_etag": "302236e27f51d2e26041ae9de49505d77332b260"
}
test_node_type = {
"_id": ObjectId("55016a52135d32466fc800be"),
"_updated": datetime.now(),
"name": "NodeName",
"dyn_schema": {"frame_start": {"type": "integer"}},
"_created": datetime.now(),
"_etag": "0ea3c4f684a0cda85525184d5606c4f4ce6ac5f5"
}
test_token = {
"-id": ObjectId("5502f289135d3274cb658ba7"),
"username": "TestFirstname",
"token": "ANLGNSIEZJ",
"_etag": "1e96ed46b133b7ede5ce6ef0d6d4fc53edd9f2ba"
}
db.users.insert(test_user)
db.node_types.insert(test_node_type)
db.tokens.insert(test_token)
# Initialize Attract
os.environ['TEST_ATTRACT'] = '1'
import application
application.app.config['TESTING'] = True
self.app = application.app.test_client()
def tearDown(self):
pass
if __name__ == '__main__':
unittest.main()