<commit_before>#!/usr/bin/env python

# simple pyCan Example for can4linux
import time
import datetime
import signal
import sys
import subprocess
import struct
import json
import os

sys.path.insert(0, '/root/can4linux-code/can4linux-examples')
import pyCan

import upload

# the power expressed in (mW)
# default is around 660
# it must be an Integer greater then 45
# 45mW allows to detect tags at aroud 50cm distance
antennaPower = 2000

datafolder = '/root/slope-data/'
logFolder = '/root/slope-log/'

def get_incremental_number():
	maxcounter = 0
	listFiles = os.listdir(datafolder)
	for file in listFiles:
		if os.path.isfile(datafolder + file):
			name = file.split('_')
			if len(name) > 0:
				counter = name[0]
				if counter.isdigit():
					counter = int(counter)
					if counter > maxcounter:
				 		maxcounter = counter
	return maxcounter + 1

incremental = get_incremental_number()
msgFilePath = datafolder + str(incremental) + '_canbus-messages.txt'
logFilePath = logFolder + 'slope-canbus.log'

def get_lifting_status(status):
	if status == 0:
		return "stop"
	elif status == 1:
		return "going up"
	elif status == 2:
		return "going down"
	else:
		return "error"

def get_translation_status(status):
	if status == 0:
		return "stop"
	elif status == 1:
		return "going to load"
	elif status == 2:
		return "going to release"
	elif status == 3:
		return "antenna down"
	else:
		return "error"

def write_log(text):
	logfile = open(logFilePath, 'a', 1)
	now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
	logfile.write('[' + now + '] ' + text + '\n')
	logfile.close()

def write_msg(text):
	msgfile = open(msgFilePath, 'a+', 1)
	# now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
	msgfile.write(text + '\n')
	msgfile.close()

def get_timestamp():
	return str(int(round(time.time() * 1000)))

def sigterm_handler(_signo, _stack_frame):
	# Raises SystemExit(0):
	sys.exit(0)

signal.signal(signal.SIGTERM, sigterm_handler)

#print 'Python wrapper loaded'
#print datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
write_log('Slope-Canbus Version 1.0')
write_log('Python wrapper loaded')
write_log('Incremental Log Number: ' + str(incremental))

# setting the device number
device = 0
defaultBaudrate = '250'

# open the can interface /dev/can0
try:
	fileBaud = open('/proc/sys/dev/Can/Baud', 'r+')
	baudRates = fileBaud.read().split()
	baudRates[device] = defaultBaudrate
	fileBaud.write(' '.join(baudRates))
	fileBaud.close()

except IOError:
	write_log('Could not set the new bitrate')
	exit()

except IndexError:
	write_log('No proper entry for bitrate of can' + str(device) + ' found')
	exit()

# print 'open can'+ str(device) + ' none blocking'
can_fd = pyCan.open(device)

if can_fd == -1:
	write_log('Error opening CAN device /dev/can' + str(device))
	exit()

try:
	write_log('Wait for message...')
	count = 0
	flip = '0'
	lastMsg30 = ""
	lastMsg31 = ""
	lastLifting = 0
	while True:
		data = pyCan.read(can_fd)
		arrMess = data.split(':', 3)
		messId = arrMess[0][:len(arrMess[0]) - 1]
		if messId.isdigit():
			messId = int(messId)
		if messId == 30:
			# print messId
			start = arrMess[1].find("(")
			end = arrMess[1].find(")")
			bytesNum = arrMess[1][start + 1:end]
			# print bytesNum
			messBytes = arrMess[2][1:]
			if messBytes != lastMsg30:  # discard new message if is equal to last one
				lastMsg30 = messBytes
				arrBytes = messBytes.split()
				if bytesNum.isdigit():
					bytesNum = int(bytesNum)
				if bytesNum == len(arrBytes):
					weight = int(struct.unpack('<h', "".join(map(chr, [int(arrBytes[0]), int(arrBytes[1])])))[0])
					# (kg) kilogrammes
					position = int(struct.unpack('<h', "".join(map(chr, [int(arrBytes[2]), int(arrBytes[3])])))[0])
					# (m) meters
					speed = float(struct.unpack('<f', "".join(map(chr, [int(arrBytes[4]), int(arrBytes[5]), int(arrBytes[6]), int(arrBytes[7])])))[0])
					# (m/s) meters at second

					# decide what to do...
					# strOutput = str(weight) + "kg " + str(position) + "m " + str(speed) + "m/s "
					# write_msg("%d:%s" % (messId, strOutput))
					objOutput = {"weight": weight, "position": position, "speed": speed, "timestamp": get_timestamp()}
					write_msg(json.dumps(objOutput))

		if messId == 31:
			# print messId
			start = arrMess[1].find("(")
			end = arrMess[1].find(")")
			bytesNum = arrMess[1][start + 1:end]
			# print bytesNum
			messBytes = arrMess[2][1:]
			if messBytes != lastMsg31:  # discard new message if is equal to last one
				lastMsg31 = messBytes
				arrBytes = messBytes.split()
				if bytesNum.isdigit():
					bytesNum = int(bytesNum)
				if bytesNum == len(arrBytes):
					axleX = int(arrBytes[0])
					# (%) percentage of X axle grade (frontal)
					axleY = int(arrBytes[1])
					# (%) percentage of Y axle grade (lateral)
					consumption = int(struct.unpack('<h', "".join(map(chr, [int(arrBytes[2]), int(arrBytes[3])])))[0])
					# (l/h) liters at hour
					lifting = get_lifting_status(int(arrBytes[4]))
					# lifting status (0='stop', 1='going up', 2='going down', 3='antenna down')
					translation = get_translation_status(int(arrBytes[5]))
					# translation status (0='stop', 1='going to load', 2='going to release')

					# decide what to do...
					# strOutput = str(axleX) + "% " + str(axleY) + "% " + str(consumption) + "l/h " + lifting + " " + translation
					# write_msg("%d:%s" % (messId, strOutput))
					objOutput = {"axleX": axleX, "axleY": axleY, "consumption": consumption, "lifting": lifting, "translation": translation, "timestamp": get_timestamp()}
					write_msg(json.dumps(objOutput))
					lastLifting = int(arrBytes[4])

		count += 1

		#every 0.5 sec see below (10 msg per second)
		if count % 5 == 0:
			try:
				if lastLifting == 3:
					subprocess.check_call("java -jar /root/slope-bananapro/TagsReader.jar " + str(antennaPower) + " "  + str(incremental), shell=True)
					lastLifting = 0
			except subprocess.CalledProcessError, e:
				write_log('Error executing lib TagsReader.jar: ' + str(e))

		#every 5 sec upload data and send watchdog
		if count == 50:
			count = 0
			pyCan.send(can_fd, 1, '60:' + flip)
			if flip == '0':
				flip = '1'
			if flip == '1':
				flip = '0'

			upload.upload()
			"""try:
				subprocess.check_call("python upload-data.py", shell=True)
			except subprocess.CalledProcessError, e:
				write_log('Error executing upload-data.py: ' + str(e))	"""

		time.sleep(0.1)

except KeyboardInterrupt:
	write_log('Program exits with ctrl+c')

finally:
	pyCan.close(can_fd)
	write_log('/dev/can' + str(device) + ' closed in finally')

# print 'Wait ' + str(timeout) + ' sec for an message.....'
# print pyCan.read1(can_fd, timeout * 1000000)
# print 'Wait default timeout for an message.....'
# print pyCan.read1(can_fd)

# pyCan.close(can_fd)
# write_log('/dev/can' + str(device) + ' closed at the end')
# logFile.close()
# msgFile.close()
# exit()
<commit_msg>Thread version 3.0<commit_after>#!/usr/bin/env python

# simple pyCan Example for can4linux
import time
import datetime
import signal
import sys
import subprocess
import struct
import json
import os
import thread

sys.path.insert(0, '/root/can4linux-code/can4linux-examples')
import pyCan

import upload

# the power expressed in (mW)
# default is around 660
# it must be an Integer greater then 45
# 45mW allows to detect tags at aroud 50cm distance
antennaPower = 2000

datafolder = '/root/slope-data/'
logFolder = '/root/slope-log/'

def get_incremental_number():
	maxcounter = 0
	listFiles = os.listdir(datafolder)
	for file in listFiles:
		if os.path.isfile(datafolder + file):
			name = file.split('_')
			if len(name) > 0:
				counter = name[0]
				if counter.isdigit():
					counter = int(counter)
					if counter > maxcounter:
				 		maxcounter = counter
	return maxcounter + 1

incremental = get_incremental_number()
msgFilePath = datafolder + str(incremental) + '_canbus-messages.txt'
logFilePath = logFolder + 'slope-canbus.log'

def get_lifting_status(status):
	if status == 0:
		return "stop"
	elif status == 1:
		return "going up"
	elif status == 2:
		return "going down"
	else:
		return "error"

def get_translation_status(status):
	if status == 0:
		return "stop"
	elif status == 1:
		return "going to load"
	elif status == 2:
		return "going to release"
	elif status == 3:
		return "antenna down"
	else:
		return "error"

def write_log(text):
	logfile = open(logFilePath, 'a', 1)
	now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
	logfile.write('[' + now + '] ' + text + '\n')
	logfile.close()

def write_msg(text):
	msgfile = open(msgFilePath, 'a+', 1)
	# now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
	msgfile.write(text + '\n')
	msgfile.close()

def get_timestamp():
	return str(int(round(time.time() * 1000)))

def sigterm_handler(_signo, _stack_frame):
	# Raises SystemExit(0):
	sys.exit(0)

signal.signal(signal.SIGTERM, sigterm_handler)

#print 'Python wrapper loaded'
#print datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
write_log('Slope-Canbus Version 1.0')
write_log('Python wrapper loaded')
write_log('Incremental Log Number: ' + str(incremental))

# setting the device number
device = 0
defaultBaudrate = '250'

# open the can interface /dev/can0
try:
	fileBaud = open('/proc/sys/dev/Can/Baud', 'r+')
	baudRates = fileBaud.read().split()
	baudRates[device] = defaultBaudrate
	fileBaud.write(' '.join(baudRates))
	fileBaud.close()

except IOError:
	write_log('Could not set the new bitrate')
	exit()

except IndexError:
	write_log('No proper entry for bitrate of can' + str(device) + ' found')
	exit()

# print 'open can'+ str(device) + ' none blocking'
can_fd = pyCan.open(device)

if can_fd == -1:
	write_log('Error opening CAN device /dev/can' + str(device))
	exit()

try:
	write_log('Wait for message...')
	count = 0
	flip = '0'
	lastMsg30 = ""
	lastMsg31 = ""
	lastLifting = 0

	upload_th = threading.Thread(upload.upload, ())

	while True:
		data = pyCan.read(can_fd)
		arrMess = data.split(':', 3)
		messId = arrMess[0][:len(arrMess[0]) - 1]
		if messId.isdigit():
			messId = int(messId)
		if messId == 30:
			# print messId
			start = arrMess[1].find("(")
			end = arrMess[1].find(")")
			bytesNum = arrMess[1][start + 1:end]
			# print bytesNum
			messBytes = arrMess[2][1:]
			if messBytes != lastMsg30:  # discard new message if is equal to last one
				lastMsg30 = messBytes
				arrBytes = messBytes.split()
				if bytesNum.isdigit():
					bytesNum = int(bytesNum)
				if bytesNum == len(arrBytes):
					weight = int(struct.unpack('<h', "".join(map(chr, [int(arrBytes[0]), int(arrBytes[1])])))[0])
					# (kg) kilogrammes
					position = int(struct.unpack('<h', "".join(map(chr, [int(arrBytes[2]), int(arrBytes[3])])))[0])
					# (m) meters
					speed = float(struct.unpack('<f', "".join(map(chr, [int(arrBytes[4]), int(arrBytes[5]), int(arrBytes[6]), int(arrBytes[7])])))[0])
					# (m/s) meters at second

					# decide what to do...
					# strOutput = str(weight) + "kg " + str(position) + "m " + str(speed) + "m/s "
					# write_msg("%d:%s" % (messId, strOutput))
					objOutput = {"weight": weight, "position": position, "speed": speed, "timestamp": get_timestamp()}
					write_msg(json.dumps(objOutput))

		if messId == 31:
			# print messId
			start = arrMess[1].find("(")
			end = arrMess[1].find(")")
			bytesNum = arrMess[1][start + 1:end]
			# print bytesNum
			messBytes = arrMess[2][1:]
			if messBytes != lastMsg31:  # discard new message if is equal to last one
				lastMsg31 = messBytes
				arrBytes = messBytes.split()
				if bytesNum.isdigit():
					bytesNum = int(bytesNum)
				if bytesNum == len(arrBytes):
					axleX = int(arrBytes[0])
					# (%) percentage of X axle grade (frontal)
					axleY = int(arrBytes[1])
					# (%) percentage of Y axle grade (lateral)
					consumption = int(struct.unpack('<h', "".join(map(chr, [int(arrBytes[2]), int(arrBytes[3])])))[0])
					# (l/h) liters at hour
					lifting = get_lifting_status(int(arrBytes[4]))
					# lifting status (0='stop', 1='going up', 2='going down', 3='antenna down')
					translation = get_translation_status(int(arrBytes[5]))
					# translation status (0='stop', 1='going to load', 2='going to release')

					# decide what to do...
					# strOutput = str(axleX) + "% " + str(axleY) + "% " + str(consumption) + "l/h " + lifting + " " + translation
					# write_msg("%d:%s" % (messId, strOutput))
					objOutput = {"axleX": axleX, "axleY": axleY, "consumption": consumption, "lifting": lifting, "translation": translation, "timestamp": get_timestamp()}
					write_msg(json.dumps(objOutput))
					lastLifting = int(arrBytes[4])

		count += 1

		#every 0.5 sec see below (10 msg per second)
		if count % 5 == 0:
			try:
				if lastLifting == 3:
					subprocess.check_call("java -jar /root/slope-bananapro/TagsReader.jar " + str(antennaPower) + " "  + str(incremental), shell=True)
					lastLifting = 0
			except subprocess.CalledProcessError, e:
				write_log('Error executing lib TagsReader.jar: ' + str(e))

		#every 5 sec upload data and send watchdog
		if count == 50:
			count = 0
			pyCan.send(can_fd, 1, '60:' + flip)
			if flip == '0':
				flip = '1'
			if flip == '1':
				flip = '0'

			if(!upload_th.isAlive())
				upload_th.start()
			"""try:
				subprocess.check_call("python upload-data.py", shell=True)
			except subprocess.CalledProcessError, e:
				write_log('Error executing upload-data.py: ' + str(e))	"""

		time.sleep(0.1)

except KeyboardInterrupt:
	write_log('Program exits with ctrl+c')

finally:
	pyCan.close(can_fd)
	write_log('/dev/can' + str(device) + ' closed in finally')

# print 'Wait ' + str(timeout) + ' sec for an message.....'
# print pyCan.read1(can_fd, timeout * 1000000)
# print 'Wait default timeout for an message.....'
# print pyCan.read1(can_fd)

# pyCan.close(can_fd)
# write_log('/dev/can' + str(device) + ' closed at the end')
# logFile.close()
# msgFile.close()
# exit()
<|endoftext|>


<commit_before>from functools import wraps
from flask import flash, session, render_template, redirect, \
    request, abort
from admin_ui_helper import url_for
from backdrop.core.bucket import Bucket
from backdrop.core.errors import ParseError, ValidationError
from backdrop.core.upload import create_parser
from backdrop.core.upload.filters import first_sheet_filter
from backdrop.write.signonotron2 import Signonotron2
from backdrop.write.uploaded_file import UploadedFile, FileUploadException
from ..core import cache_control

def setup(app, db):
    USER_SCOPE = app.config['USER_SCOPE']
    ADMIN_UI_HOST = app.config["BACKDROP_ADMIN_UI_HOST"]
    MAX_UPLOAD_SIZE = 1000000

    app.oauth_service = Signonotron2(
        client_id=app.config['OAUTH_CLIENT_ID'],
        client_secret=app.config['OAUTH_CLIENT_SECRET'],
        base_url=app.config['OAUTH_BASE_URL'],
        backdrop_admin_ui_host=ADMIN_UI_HOST
    )

    @app.after_request
    def prevent_clickjacking(response):
        response.headers["X-Frame-Options"] = "SAMEORIGIN"
        return response

    def protected(f):
        @wraps(f)
        def verify_user_logged_in(*args, **kwargs):
            if not "user" in session:
                return redirect(
                    url_for(ADMIN_UI_HOST, 'oauth_sign_in'))
            return f(*args, **kwargs)
        return verify_user_logged_in

    @app.route(USER_SCOPE)
    @cache_control.set("private, must-revalidate")
    def user_route():
        """
        This representation is private to the logged-in user
        (with their own buckets)
        """
        if use_single_sign_on(app):
            buckets_available = app.permissions.buckets_in_session(session)
            return render_template("index.html",
                                   buckets_available=buckets_available)
        else:
            return "Backdrop is running."

    @app.route(USER_SCOPE + "/sign_in")
    @cache_control.nocache
    def oauth_sign_in():
        """
        This returns a redirect to the OAuth provider, so we shouldn't
        allow this response to be cached.
        """
        return app.oauth_service.authorize()

    @app.route(USER_SCOPE + "/sign_out")
    @cache_control.set("private, must-revalidate")
    def oauth_sign_out():
        session.clear()
        flash("You have been signed out of Backdrop", category="success")
        return render_template("signon/signout.html",
                               oauth_base_url=app.config['OAUTH_BASE_URL'])

    @app.route(USER_SCOPE + "/authorized")
    @cache_control.nocache
    def oauth_authorized():
        """
        The result of this is a redirect, which shouldn't be cached in
        case their permissions get changed, etc.
        """
        auth_code = request.args.get('code')
        if not auth_code:
            abort(400)
        access_token = app.oauth_service.exchange(auth_code)

        user_details, can_see_backdrop = \
            app.oauth_service.user_details(access_token)
        if can_see_backdrop is None:
            flash("Could not authenticate with single sign on.",
                  category="error")
            return redirect(url_for(ADMIN_UI_HOST, "not_authorized"))
        if can_see_backdrop is False:
            flash("You are signed in to your GOV.UK account, "
                  "but you don't have permissions to use this application.")
            return redirect(url_for(ADMIN_UI_HOST, "not_authorized"))
        _create_session_user(user_details["user"]["name"],
                             user_details["user"]["email"])
        flash("You were successfully signed in", category="success")
        return redirect(url_for(ADMIN_UI_HOST, "user_route"))

    @app.route(USER_SCOPE + "/not_authorized")
    @cache_control.nocache
    def not_authorized():
        return render_template("signon/not_authorized.html")

    @app.route(USER_SCOPE + "/protected", methods=['GET'])
    @protected
    def upload_buckets():
        return "hello"

    if allow_test_signin(app):
        @app.route(USER_SCOPE + "/sign_in/test", methods=['GET'])
        def test_signin():
            _create_session_user(request.args.get('user'),
                                 request.args.get('email'))
            return "logged in as %s" % session.get('user'), 200

    def _create_session_user(name, email):
        session.update(
            {"user": {
                "name": name,
                "email": email
            }})

    @app.route('/<bucket:bucket_name>/upload', methods=['GET', 'POST'])
    @protected
    @cache_control.set("private, must-revalidate")
    def upload(bucket_name):
        current_user_email = session.get("user").get("email")
        if not app.permissions.allowed(current_user_email, bucket_name):
            return abort(404)

        upload_format = _upload_format_for(bucket_name)
        upload_filters = _upload_filters_for(bucket_name)

        if request.method == 'GET':
            return render_template("upload_%s.html" % upload_format,
                                   bucket_name=bucket_name)

        parser = create_parser(upload_format, upload_filters)

        return _store_data(bucket_name, parser)

    def _store_data(bucket_name, parser):
        upload = UploadedFile(request.files['file'])
        try:
            id_keys = _auto_id_keys_for(bucket_name)
            bucket = Bucket(db, bucket_name,
                            generate_id_from=id_keys)
            upload.save(bucket, parser)
            return render_template('upload_ok.html')
        except (FileUploadException, ParseError, ValidationError) as e:
            message = e.message
            app.logger.error(message)
            return _invalid_upload(message)

    def _upload_format_for(bucket_name):
        return app.config.get("BUCKET_UPLOAD_FORMAT", {})\
                         .get(bucket_name, "csv")

    def _upload_filters_for(bucket_name):
        return app.config.get("BUCKET_UPLOAD_FILTERS", {})\
                         .get(bucket_name, [first_sheet_filter])

    def _auto_id_keys_for(bucket_name):
        return app.config.get("BUCKET_AUTO_ID_KEYS", {}).get(bucket_name)

    def _invalid_upload(msg):
        app.logger.error("Upload error: %s" % msg)
        return render_template("upload_error.html", message=msg), 400

def allow_test_signin(app):
    return bool(app.config.get("ALLOW_TEST_SIGNIN"))

def use_single_sign_on(app):
    return bool(app.config.get("SINGLE_SIGN_ON"))
<commit_msg>Create parser closer to where it's used<commit_after>from functools import wraps
from flask import flash, session, render_template, redirect, \
    request, abort
from admin_ui_helper import url_for
from backdrop.core.bucket import Bucket
from backdrop.core.errors import ParseError, ValidationError
from backdrop.core.upload import create_parser
from backdrop.core.upload.filters import first_sheet_filter
from backdrop.write.signonotron2 import Signonotron2
from backdrop.write.uploaded_file import UploadedFile, FileUploadException
from ..core import cache_control

def setup(app, db):
    USER_SCOPE = app.config['USER_SCOPE']
    ADMIN_UI_HOST = app.config["BACKDROP_ADMIN_UI_HOST"]
    MAX_UPLOAD_SIZE = 1000000

    app.oauth_service = Signonotron2(
        client_id=app.config['OAUTH_CLIENT_ID'],
        client_secret=app.config['OAUTH_CLIENT_SECRET'],
        base_url=app.config['OAUTH_BASE_URL'],
        backdrop_admin_ui_host=ADMIN_UI_HOST
    )

    @app.after_request
    def prevent_clickjacking(response):
        response.headers["X-Frame-Options"] = "SAMEORIGIN"
        return response

    def protected(f):
        @wraps(f)
        def verify_user_logged_in(*args, **kwargs):
            if not "user" in session:
                return redirect(
                    url_for(ADMIN_UI_HOST, 'oauth_sign_in'))
            return f(*args, **kwargs)
        return verify_user_logged_in

    @app.route(USER_SCOPE)
    @cache_control.set("private, must-revalidate")
    def user_route():
        """
        This representation is private to the logged-in user
        (with their own buckets)
        """
        if use_single_sign_on(app):
            buckets_available = app.permissions.buckets_in_session(session)
            return render_template("index.html",
                                   buckets_available=buckets_available)
        else:
            return "Backdrop is running."

    @app.route(USER_SCOPE + "/sign_in")
    @cache_control.nocache
    def oauth_sign_in():
        """
        This returns a redirect to the OAuth provider, so we shouldn't
        allow this response to be cached.
        """
        return app.oauth_service.authorize()

    @app.route(USER_SCOPE + "/sign_out")
    @cache_control.set("private, must-revalidate")
    def oauth_sign_out():
        session.clear()
        flash("You have been signed out of Backdrop", category="success")
        return render_template("signon/signout.html",
                               oauth_base_url=app.config['OAUTH_BASE_URL'])

    @app.route(USER_SCOPE + "/authorized")
    @cache_control.nocache
    def oauth_authorized():
        """
        The result of this is a redirect, which shouldn't be cached in
        case their permissions get changed, etc.
        """
        auth_code = request.args.get('code')
        if not auth_code:
            abort(400)
        access_token = app.oauth_service.exchange(auth_code)

        user_details, can_see_backdrop = \
            app.oauth_service.user_details(access_token)
        if can_see_backdrop is None:
            flash("Could not authenticate with single sign on.",
                  category="error")
            return redirect(url_for(ADMIN_UI_HOST, "not_authorized"))
        if can_see_backdrop is False:
            flash("You are signed in to your GOV.UK account, "
                  "but you don't have permissions to use this application.")
            return redirect(url_for(ADMIN_UI_HOST, "not_authorized"))
        _create_session_user(user_details["user"]["name"],
                             user_details["user"]["email"])
        flash("You were successfully signed in", category="success")
        return redirect(url_for(ADMIN_UI_HOST, "user_route"))

    @app.route(USER_SCOPE + "/not_authorized")
    @cache_control.nocache
    def not_authorized():
        return render_template("signon/not_authorized.html")

    @app.route(USER_SCOPE + "/protected", methods=['GET'])
    @protected
    def upload_buckets():
        return "hello"

    if allow_test_signin(app):
        @app.route(USER_SCOPE + "/sign_in/test", methods=['GET'])
        def test_signin():
            _create_session_user(request.args.get('user'),
                                 request.args.get('email'))
            return "logged in as %s" % session.get('user'), 200

    def _create_session_user(name, email):
        session.update(
            {"user": {
                "name": name,
                "email": email
            }})

    @app.route('/<bucket:bucket_name>/upload', methods=['GET', 'POST'])
    @protected
    @cache_control.set("private, must-revalidate")
    def upload(bucket_name):
        current_user_email = session.get("user").get("email")
        if not app.permissions.allowed(current_user_email, bucket_name):
            return abort(404)

        upload_format = _upload_format_for(bucket_name)

        if request.method == 'GET':
            return render_template("upload_%s.html" % upload_format,
                                   bucket_name=bucket_name)

        return _store_data(bucket_name, upload_format)

    def _store_data(bucket_name, upload_format):
        upload_filters = _upload_filters_for(bucket_name)
        parser = create_parser(upload_format, upload_filters)
        upload = UploadedFile(request.files['file'])

        try:
            id_keys = _auto_id_keys_for(bucket_name)
            bucket = Bucket(db, bucket_name,
                            generate_id_from=id_keys)
            upload.save(bucket, parser)
            return render_template('upload_ok.html')
        except (FileUploadException, ParseError, ValidationError) as e:
            message = e.message
            app.logger.error(message)
            return _invalid_upload(message)

    def _upload_format_for(bucket_name):
        return app.config.get("BUCKET_UPLOAD_FORMAT", {})\
                         .get(bucket_name, "csv")

    def _upload_filters_for(bucket_name):
        return app.config.get("BUCKET_UPLOAD_FILTERS", {})\
                         .get(bucket_name, [first_sheet_filter])

    def _auto_id_keys_for(bucket_name):
        return app.config.get("BUCKET_AUTO_ID_KEYS", {}).get(bucket_name)

    def _invalid_upload(msg):
        app.logger.error("Upload error: %s" % msg)
        return render_template("upload_error.html", message=msg), 400

def allow_test_signin(app):
    return bool(app.config.get("ALLOW_TEST_SIGNIN"))

def use_single_sign_on(app):
    return bool(app.config.get("SINGLE_SIGN_ON"))
<|endoftext|>


<commit_before>from __future__ import absolute_import

from .document import Document

class Application(object):
    """An Application is a factory for Document instances"""

    def __init__(self):
        self._handlers = []

    # TODO (havocp) should this potentially create multiple documents?
    # or does multiple docs mean multiple Application?
    def create_document(self):
        """Loads a new document using the Application's handlers to fill it in."""
        doc = Document()
        for h in self._handlers:
            # TODO (havocp) we need to check the 'failed' flag on each handler
            # and build a composite error display.
            h.modify_document(doc)
        return doc

    def add(self, handler):
        """Add a handler to the pipeline used to initialize new documents."""
        self._handlers.append(handler)
<commit_msg>Add basic reporting of handler errors<commit_after>from __future__ import absolute_import

from .document import Document

import logging

log = logging.getLogger(__name__)

class Application(object):
    """An Application is a factory for Document instances"""

    def __init__(self):
        self._handlers = []

    # TODO (havocp) should this potentially create multiple documents?
    # or does multiple docs mean multiple Application?
    def create_document(self):
        """Loads a new document using the Application's handlers to fill it in."""
        doc = Document()
        for h in self._handlers:
            # TODO (havocp) we need to check the 'failed' flag on each handler
            # and build a composite error display.
            h.modify_document(doc)
            if h.failed:
                # TODO (havocp) this could be formatted a lot more nicely
                log.error("Error running application handler %r: %r %r ", h, h.error, h.error_detail)
        return doc

    def add(self, handler):
        """Add a handler to the pipeline used to initialize new documents."""
        self._handlers.append(handler)
<|endoftext|>


<commit_before>from django.test import TestCase

class CatalogViewsTestCase(TestCase):
    fixtures = ['catalog_test_data.yaml']

    def test_index_view(self):
        resp = self.client.get('/')
        self.assertEqual(resp.status_code, 200)

    def test_category_view_invalid_category(self):
        resp = self.client.get('/strobe-light/')
        self.assertEqual(resp.status_code, 404)

    def test_category_view_valid_category(self):
        resp = self.client.get('/the-fragile/')
        self.assertEqual(resp.status_code, 200)
        self.assertTrue('category' in resp.context)

        category = resp.context['category']
        self.assertEqual(category.pk, 1)
        self.assertEqual(category.name, 'The Fragile')

    def test_item_view_invalid_category_invalid_item(self):
        resp = self.client.get('/strobe-light/2/')
        self.assertEqual(resp.status_code, 404)

    def test_item_view_valid_category_invalid_item(self):
        resp = self.client.get('/the-fragile/2/')
        self.assertEqual(resp.status_code, 404)

    def test_item_view_invalid_category_valid_item(self):
        resp = self.client.get('/strobe-light/1/')
        self.assertEqual(resp.status_code, 404)

    def test_item_view_valid_category_valid_item(self):
        resp = self.client.get('/the-fragile/1/')
        self.assertTrue(resp.status_code, 200)
        self.assertTrue('item' in resp.context)

        item = resp.context['item']
        self.assertEqual(item.pk, 1)
        self.assertEqual(item.name, "The Fragile")<commit_msg>test category item set<commit_after>from django.test import TestCase

class CatalogViewsTestCase(TestCase):
    fixtures = ['catalog_test_data.yaml']

    def test_index_view(self):
        resp = self.client.get('/')
        self.assertEqual(resp.status_code, 200)

    def test_category_view_invalid_category(self):
        resp = self.client.get('/strobe-light/')
        self.assertEqual(resp.status_code, 404)

    def test_category_view_valid_category(self):
        resp = self.client.get('/the-fragile/')
        self.assertEqual(resp.status_code, 200)
        self.assertTrue('category' in resp.context)

        category = resp.context['category']
        self.assertEqual(category.pk, 1)
        self.assertEqual(category.name, 'The Fragile')

    def test_category_view_item_list(self):
        resp = self.client.get('/the-fragile/')
        category = resp.context['category']
        self.assertEqual(category.item_set.count(), 1)

        item = category.item_set.first()
        self.assertEqual(item.pk, 1)
        self.assertEqual(item.name, "The Fragile")

    def test_item_view_invalid_category_invalid_item(self):
        resp = self.client.get('/strobe-light/2/')
        self.assertEqual(resp.status_code, 404)

    def test_item_view_valid_category_invalid_item(self):
        resp = self.client.get('/the-fragile/2/')
        self.assertEqual(resp.status_code, 404)

    def test_item_view_invalid_category_valid_item(self):
        resp = self.client.get('/strobe-light/1/')
        self.assertEqual(resp.status_code, 404)

    def test_item_view_valid_category_valid_item(self):
        resp = self.client.get('/the-fragile/1/')
        self.assertTrue(resp.status_code, 200)
        self.assertTrue('item' in resp.context)

        item = resp.context['item']
        self.assertEqual(item.pk, 1)
        self.assertEqual(item.name, "The Fragile")<|endoftext|>


<commit_before># Copyright (c) 2012 Roberto Alsina y otros.

# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the
# Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice
# shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from __future__ import print_function
from optparse import OptionParser, OptionGroup
import os
import shutil
import codecs

from mako.template import Template

import nikola
from nikola.plugin_categories import Command

class CommandInit(Command):
    """Create a new site."""

    name = "init"

    doc_usage = "nikola init folder [--demo]."
    doc_description = """Create a Nikola site in the specified folder.
The destination folder must not exist.
"""
    cmd_options = [
        {'name': 'demo',
         'long': 'demo',
         'default': False,
         'type': bool,
         'help': "Create a site filled with example data.",
        }
    ]

    SAMPLE_CONF = {
        'BLOG_AUTHOR': "Your Name",
        'BLOG_TITLE': "Demo Site",
        'BLOG_URL': "http://nikola.ralsina.com.ar",
        'BLOG_EMAIL': "joe@demo.site",
        'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
        'DEFAULT_LANG': "en",

        'POST_PAGES': """(
    ("posts/*.txt", "posts", "post.tmpl", True),
    ("stories/*.txt", "stories", "story.tmpl", False),
)""",

        'POST_COMPILERS': """{
    "rest": ('.txt', '.rst'),
    "markdown": ('.md', '.mdown', '.markdown'),
    "textile": ('.textile',),
    "txt2tags": ('.t2t',),
    "bbcode": ('.bb',),
    "wiki": ('.wiki',),
    "ipynb": ('.ipynb',),
    "html": ('.html', '.htm')
    }""",
        'REDIRECTIONS': '[]',
    }

    @classmethod
    def copy_sample_site(cls, target):
        lib_path = cls.get_path_to_nikola_modules()
        src = os.path.join(lib_path, 'data', 'samplesite')
        shutil.copytree(src, target)

    @classmethod
    def create_configuration(cls, target):
        lib_path = cls.get_path_to_nikola_modules()
        template_path = os.path.join(lib_path, 'conf.py.in')
        conf_template = Template(filename=template_path)
        conf_path = os.path.join(target, 'conf.py')
        with codecs.open(conf_path, 'w+', 'utf8') as fd:
            fd.write(conf_template.render(**cls.SAMPLE_CONF))

    @classmethod
    def create_empty_site(cls, target):
        for folder in ('files', 'galleries', 'listings', 'posts', 'stories'):
            os.makedirs(os.path.join(target, folder))

    @staticmethod
    def get_path_to_nikola_modules():
        return os.path.dirname(nikola.__file__)

    def execute(self, options, args):
        """Create a new site."""
        if not args:
            print("Usage: nikola init folder [options]")
            return
        target = args[0]
        if target is None:
            print(self.usage)
        else:
            if not options['demo']:
                self.create_empty_site(target)
                print('Created empty site at %s.' % target)
            else:
                self.copy_sample_site(target)
                print("A new site with example data has been created at %s."
                      % target)
                print("See README.txt in that folder for more information.")

            self.create_configuration(target)
<commit_msg>fix doc<commit_after># Copyright (c) 2012 Roberto Alsina y otros.

# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the
# Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice
# shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from __future__ import print_function
from optparse import OptionParser, OptionGroup
import os
import shutil
import codecs

from mako.template import Template

import nikola
from nikola.plugin_categories import Command

class CommandInit(Command):
    """Create a new site."""

    name = "init"

    doc_usage = "folder [--demo]."
    doc_description = """Create a Nikola site in the specified folder.
The destination folder must not exist.
"""
    cmd_options = [
        {'name': 'demo',
         'long': 'demo',
         'default': False,
         'type': bool,
         'help': "Create a site filled with example data.",
        }
    ]

    SAMPLE_CONF = {
        'BLOG_AUTHOR': "Your Name",
        'BLOG_TITLE': "Demo Site",
        'BLOG_URL': "http://nikola.ralsina.com.ar",
        'BLOG_EMAIL': "joe@demo.site",
        'BLOG_DESCRIPTION': "This is a demo site for Nikola.",
        'DEFAULT_LANG': "en",

        'POST_PAGES': """(
    ("posts/*.txt", "posts", "post.tmpl", True),
    ("stories/*.txt", "stories", "story.tmpl", False),
)""",

        'POST_COMPILERS': """{
    "rest": ('.txt', '.rst'),
    "markdown": ('.md', '.mdown', '.markdown'),
    "textile": ('.textile',),
    "txt2tags": ('.t2t',),
    "bbcode": ('.bb',),
    "wiki": ('.wiki',),
    "ipynb": ('.ipynb',),
    "html": ('.html', '.htm')
    }""",
        'REDIRECTIONS': '[]',
    }

    @classmethod
    def copy_sample_site(cls, target):
        lib_path = cls.get_path_to_nikola_modules()
        src = os.path.join(lib_path, 'data', 'samplesite')
        shutil.copytree(src, target)

    @classmethod
    def create_configuration(cls, target):
        lib_path = cls.get_path_to_nikola_modules()
        template_path = os.path.join(lib_path, 'conf.py.in')
        conf_template = Template(filename=template_path)
        conf_path = os.path.join(target, 'conf.py')
        with codecs.open(conf_path, 'w+', 'utf8') as fd:
            fd.write(conf_template.render(**cls.SAMPLE_CONF))

    @classmethod
    def create_empty_site(cls, target):
        for folder in ('files', 'galleries', 'listings', 'posts', 'stories'):
            os.makedirs(os.path.join(target, folder))

    @staticmethod
    def get_path_to_nikola_modules():
        return os.path.dirname(nikola.__file__)

    def execute(self, options, args):
        """Create a new site."""
        if not args:
            print("Usage: nikola init folder [options]")
            return
        target = args[0]
        if target is None:
            print(self.usage)
        else:
            if not options['demo']:
                self.create_empty_site(target)
                print('Created empty site at %s.' % target)
            else:
                self.copy_sample_site(target)
                print("A new site with example data has been created at %s."
                      % target)
                print("See README.txt in that folder for more information.")

            self.create_configuration(target)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

"""Deletion functions to supplement :mod:`pybel.struct.mutation.expansion`."""

import itertools as itt
import logging
import typing
from collections import Counter, defaultdict
from typing import Collection, Iterable, Optional, Tuple

from pybel import BELGraph
from pybel.constants import ANNOTATIONS
from pybel.dsl import BaseEntity, CentralDogma, ComplexAbundance, CompositeAbundance, Reaction
from pybel.struct.filters import and_edge_predicates, concatenate_node_predicates
from pybel.struct.filters.edge_predicates import edge_has_annotation, is_causal_relation
from pybel.struct.filters.node_predicates import true_node_predicate
from pybel.struct.filters.typing import EdgeIterator, EdgePredicates, NodePredicates
from pybel.struct.pipeline import uni_in_place_transformation
from pybel.typing import EdgeData

__all__ = [
    'get_peripheral_successor_edges',
    'get_peripheral_predecessor_edges',
    'count_sources',
    'count_targets',
    'count_peripheral_successors',
    'count_peripheral_predecessors',
    'get_subgraph_edges',
    'get_subgraph_peripheral_nodes',
    'expand_periphery',
    'enrich_complexes',
    'enrich_composites',
    'enrich_reactions',
    'enrich_variants',
    'enrich_unqualified',
    'expand_internal',
    'expand_internal_causal',
]

logger = logging.getLogger(__name__)

def get_peripheral_successor_edges(graph: BELGraph, subgraph: Collection[BaseEntity]) -> EdgeIterator:
    """Get the set of possible successor edges peripheral to the sub-graph.

    The source nodes in this iterable are all inside the sub-graph, while the targets are outside.
    """
    for u in subgraph:
        for _, v, k in graph.out_edges(u, keys=True):
            if v not in subgraph:
                yield u, v, k

def get_peripheral_predecessor_edges(graph: BELGraph, subgraph: Collection[BaseEntity]) -> EdgeIterator:
    """Get the set of possible predecessor edges peripheral to the sub-graph.

    The target nodes in this iterable are all inside the sub-graph, while the sources are outside.
    """
    for v in subgraph:
        for u, _, k in graph.in_edges(v, keys=True):
            if u not in subgraph:
                yield u, v, k

def count_sources(edge_iter: EdgeIterator) -> Counter:
    """Count the source nodes in an edge iterator with keys and data.

    :return: A counter of source nodes in the iterable
    """
    return Counter(u for u, _, _ in edge_iter)

def count_targets(edge_iter: EdgeIterator) -> Counter:
    """Count the target nodes in an edge iterator with keys and data.

    :return: A counter of target nodes in the iterable
    """
    return Counter(v for _, v, _ in edge_iter)

def count_peripheral_successors(graph: BELGraph, subgraph: BELGraph) -> typing.Counter[BaseEntity]:
    """Count all peripheral successors of the subgraph.

    :param graph: A BEL graph
    :param subgraph: An iterator of BEL nodes
    :return: A counter of possible successor nodes
    """
    return count_targets(get_peripheral_successor_edges(graph, subgraph))

def count_peripheral_predecessors(graph: BELGraph, subgraph: BELGraph) -> typing.Counter[BaseEntity]:
    """Count all peripheral predecessors of the subgraph.

    :param graph: A BEL graph
    :param subgraph: An iterator of BEL nodes
    :return: A counter of possible predecessor nodes
    """
    return count_sources(get_peripheral_predecessor_edges(graph, subgraph))

def get_subgraph_edges(
    graph: BELGraph,
    annotation: str,
    value: str,
    source_filter: Optional[NodePredicates] = None,
    target_filter: Optional[NodePredicates] = None,
) -> Iterable[Tuple[BaseEntity, BaseEntity, str, EdgeData]]:
    """Get all edges from a given subgraph whose source and target nodes pass all of the given filters.

    :param graph: A BEL graph
    :param annotation:  The annotation to search
    :param value: The annotation value to search by
    :param source_filter: Optional filter for source nodes (graph, node) -> bool
    :param target_filter: Optional filter for target nodes (graph, node) -> bool
    :return: An iterable of (source node, target node, key, data) for all edges that match the annotation/value and
             node filters
    """
    if source_filter is None:
        source_filter = true_node_predicate

    if target_filter is None:
        target_filter = true_node_predicate

    for u, v, k, data in graph.edges(keys=True, data=True):
        if not edge_has_annotation(data, annotation):
            continue
        if data[ANNOTATIONS][annotation] == value and source_filter(graph, u) and target_filter(graph, v):
            yield u, v, k, data

def get_subgraph_peripheral_nodes(
    graph: BELGraph,
    subgraph: Collection[BaseEntity],
    node_predicates: Optional[NodePredicates] = None,
    edge_predicates: Optional[EdgePredicates] = None,
):
    """Get a summary dictionary of all peripheral nodes to a given sub-graph.

    :return: A dictionary of {external node: {'successor': {internal node: list of (key, dict)},
                                            'predecessor': {internal node: list of (key, dict)}}}
    :rtype: dict

    For example, it might be useful to quantify the number of predecessors and successors:

    >>> from pybel.struct.filters.node_predicates import not_pathology
    >>> value = 'Blood vessel dilation subgraph'
    >>> sg = get_subgraph_by_annotation_value(graph, annotation='Subgraph', value=value)
    >>> p = get_subgraph_peripheral_nodes(graph, sg, node_predicates=not_pathology)
    >>> for node in sorted(p, key=lambda n: len(set(p[n]['successor']) | set(p[n]['predecessor'])), reverse=True):
    >>>     if 1 == len(p[value][node]['successor']) or 1 == len(p[value][node]['predecessor']):
    >>>         continue
    >>>     print(node,
    >>>           len(p[node]['successor']),
    >>>           len(p[node]['predecessor']),
    >>>           len(set(p[node]['successor']) | set(p[node]['predecessor'])))
    """
    node_filter = concatenate_node_predicates(node_predicates=node_predicates)
    edge_filter = and_edge_predicates(edge_predicates=edge_predicates)

    result = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))

    for u, v, k, d in get_peripheral_successor_edges(graph, subgraph):
        if not node_filter(graph, v) or not node_filter(graph, u) or not edge_filter(graph, u, v, k):
            continue
        result[v]['predecessor'][u].append((k, d))

    for u, v, k, d in get_peripheral_predecessor_edges(graph, subgraph):
        if not node_filter(graph, v) or not node_filter(graph, u) or not edge_filter(graph, u, v, k):
            continue
        result[u]['successor'][v].append((k, d))

    return result

@uni_in_place_transformation
def expand_periphery(
    universe: BELGraph,
    graph: BELGraph,
    node_predicates: Optional[NodePredicates] = None,
    edge_predicates: Optional[EdgePredicates] = None,
    threshold: int = 2,
) -> None:
    """Iterate over all possible edges, peripheral to a given subgraph, that could be added from the given graph.

    Edges could be added if they go to nodes that are involved in relationships that occur with more than the
    threshold (default 2) number of nodes in the subgraph.

    :param universe: The universe of BEL knowledge
    :param graph: The (sub)graph to expand
    :param threshold: Minimum frequency of betweenness occurrence to add a gap node

    A reasonable edge filter to use is :func:`pybel_tools.filters.keep_causal_edges` because this function can allow
    for huge expansions if there happen to be hub nodes.
    """
    nd = get_subgraph_peripheral_nodes(
        universe, graph,
        node_predicates=node_predicates,
        edge_predicates=edge_predicates,
    )

    for node, dd in nd.items():
        pred_d = dd['predecessor']
        succ_d = dd['successor']

        in_subgraph_connections = set(pred_d) | set(succ_d)

        if threshold > len(in_subgraph_connections):
            continue

        graph.add_node(node, attr_dict=universe[node])

        for u, edges in pred_d.items():
            for key, data in edges:
                graph.add_edge(u, node, key=key, **data)

        for v, edges in succ_d.items():
            for key, data in edges:
                graph.add_edge(node, v, key=key, **data)

@uni_in_place_transformation
def enrich_complexes(graph: BELGraph) -> None:
    """Add all of the members of the complex abundances to the graph."""
    for u in list(graph):
        if not isinstance(u, ComplexAbundance):
            continue
        for v in u.members:
            graph.add_part_of(v, u)

@uni_in_place_transformation
def enrich_composites(graph: BELGraph) -> None:
    """Add all of the members of the composite abundances to the graph."""
    for u in list(graph):
        if not isinstance(u, CompositeAbundance):
            continue
        for v in u.members:
            graph.add_part_of(v, u)

@uni_in_place_transformation
def enrich_reactions(graph: BELGraph) -> None:
    """Add all of the reactants and products of reactions to the graph."""
    for u in list(graph):
        if not isinstance(u, Reaction):
            continue
        for v in u.reactants:
            graph.add_has_reactant(u, v)
        for v in u.products:
            graph.add_has_product(u, v)

@uni_in_place_transformation
def enrich_variants(graph: BELGraph) -> None:
    """Add the reference nodes for all variants of the given function."""
    for u in list(graph):
        if not isinstance(u, CentralDogma):
            continue

        parent = u.get_parent()
        if parent is None:
            continue

        if parent not in graph:
            graph.add_has_variant(parent, u)

@uni_in_place_transformation
def enrich_unqualified(graph: BELGraph) -> None:
    """Enrich the sub-graph with the unqualified edges from the graph.

    The reason you might want to do this is you induce a sub-graph from the original graph based on an annotation
    filter, but the unqualified edges that don't have annotations that most likely connect elements within your graph
    are not included.

    .. seealso::

        This function thinly wraps the successive application of the following functions:

        - :func:`enrich_complexes`
        - :func:`enrich_composites`
        - :func:`enrich_reactions`
        - :func:`enrich_variants`

    Equivalent to:

    >>> enrich_complexes(graph)
    >>> enrich_composites(graph)
    >>> enrich_reactions(graph)
    >>> enrich_variants(graph)
    """
    enrich_complexes(graph)
    enrich_composites(graph)
    enrich_reactions(graph)
    enrich_variants(graph)

@uni_in_place_transformation
def expand_internal(
    universe: BELGraph,
    graph: BELGraph,
) -> None:
    """Expand on edges between entities in the sub-graph that pass the given filters.

    :param universe: The full graph
    :param graph: A sub-graph to find the upstream information
    """
    for u, v, key, data in _iterate_internal(universe, graph):
        graph.add_edge(u, v, key=key, **data)

@uni_in_place_transformation
def expand_internal_causal(universe: BELGraph, graph: BELGraph) -> None:
    """Add causal edges between entities in the sub-graph.

    Is an extremely thin wrapper around :func:`expand_internal`.

    :param universe: A BEL graph representing the universe of all knowledge
    :param graph: The target BEL graph to enrich with causal relations between contained nodes

    Equivalent to:

    >>> from pybel_tools.mutation import expand_internal
    >>> from pybel.struct.filters.edge_predicates import is_causal_relation
    >>> expand_internal(universe, graph, edge_predicates=is_causal_relation)
    """
    for u, v, key in _iterate_internal(universe, graph):
        data = universe.edges[u][v][key]
        if is_causal_relation(data):
            graph.add_edge(u, v, key=key, **data)

def _iterate_internal(universe: BELGraph, graph: BELGraph) -> EdgeIterator:
    for u, v in itt.product(graph, repeat=2):
        if graph.has_edge(u, v):
            continue
        if not universe.has_edge(u, v):
            continue
        for key in universe[u][v]:
            yield u, v, key
<commit_msg>Fix bug in expansion function<commit_after># -*- coding: utf-8 -*-

"""Deletion functions to supplement :mod:`pybel.struct.mutation.expansion`."""

import itertools as itt
import logging
import typing
from collections import Counter, defaultdict
from typing import Collection, Iterable, Optional, Tuple

from pybel import BELGraph
from pybel.constants import ANNOTATIONS
from pybel.dsl import BaseEntity, CentralDogma, ComplexAbundance, CompositeAbundance, Reaction
from pybel.struct.filters import and_edge_predicates, concatenate_node_predicates
from pybel.struct.filters.edge_predicates import edge_has_annotation, is_causal_relation
from pybel.struct.filters.node_predicates import true_node_predicate
from pybel.struct.filters.typing import EdgeIterator, EdgePredicates, NodePredicates
from pybel.struct.pipeline import uni_in_place_transformation
from pybel.typing import EdgeData

__all__ = [
    'get_peripheral_successor_edges',
    'get_peripheral_predecessor_edges',
    'count_sources',
    'count_targets',
    'count_peripheral_successors',
    'count_peripheral_predecessors',
    'get_subgraph_edges',
    'get_subgraph_peripheral_nodes',
    'expand_periphery',
    'enrich_complexes',
    'enrich_composites',
    'enrich_reactions',
    'enrich_variants',
    'enrich_unqualified',
    'expand_internal',
    'expand_internal_causal',
]

logger = logging.getLogger(__name__)

def get_peripheral_successor_edges(graph: BELGraph, subgraph: Collection[BaseEntity]) -> EdgeIterator:
    """Get the set of possible successor edges peripheral to the sub-graph.

    The source nodes in this iterable are all inside the sub-graph, while the targets are outside.
    """
    for u in subgraph:
        for _, v, k in graph.out_edges(u, keys=True):
            if v not in subgraph:
                yield u, v, k

def get_peripheral_predecessor_edges(graph: BELGraph, subgraph: Collection[BaseEntity]) -> EdgeIterator:
    """Get the set of possible predecessor edges peripheral to the sub-graph.

    The target nodes in this iterable are all inside the sub-graph, while the sources are outside.
    """
    for v in subgraph:
        for u, _, k in graph.in_edges(v, keys=True):
            if u not in subgraph:
                yield u, v, k

def count_sources(edge_iter: EdgeIterator) -> Counter:
    """Count the source nodes in an edge iterator with keys and data.

    :return: A counter of source nodes in the iterable
    """
    return Counter(u for u, _, _ in edge_iter)

def count_targets(edge_iter: EdgeIterator) -> Counter:
    """Count the target nodes in an edge iterator with keys and data.

    :return: A counter of target nodes in the iterable
    """
    return Counter(v for _, v, _ in edge_iter)

def count_peripheral_successors(graph: BELGraph, subgraph: BELGraph) -> typing.Counter[BaseEntity]:
    """Count all peripheral successors of the subgraph.

    :param graph: A BEL graph
    :param subgraph: An iterator of BEL nodes
    :return: A counter of possible successor nodes
    """
    return count_targets(get_peripheral_successor_edges(graph, subgraph))

def count_peripheral_predecessors(graph: BELGraph, subgraph: BELGraph) -> typing.Counter[BaseEntity]:
    """Count all peripheral predecessors of the subgraph.

    :param graph: A BEL graph
    :param subgraph: An iterator of BEL nodes
    :return: A counter of possible predecessor nodes
    """
    return count_sources(get_peripheral_predecessor_edges(graph, subgraph))

def get_subgraph_edges(
    graph: BELGraph,
    annotation: str,
    value: str,
    source_filter: Optional[NodePredicates] = None,
    target_filter: Optional[NodePredicates] = None,
) -> Iterable[Tuple[BaseEntity, BaseEntity, str, EdgeData]]:
    """Get all edges from a given subgraph whose source and target nodes pass all of the given filters.

    :param graph: A BEL graph
    :param annotation:  The annotation to search
    :param value: The annotation value to search by
    :param source_filter: Optional filter for source nodes (graph, node) -> bool
    :param target_filter: Optional filter for target nodes (graph, node) -> bool
    :return: An iterable of (source node, target node, key, data) for all edges that match the annotation/value and
             node filters
    """
    if source_filter is None:
        source_filter = true_node_predicate

    if target_filter is None:
        target_filter = true_node_predicate

    for u, v, k, data in graph.edges(keys=True, data=True):
        if not edge_has_annotation(data, annotation):
            continue
        if data[ANNOTATIONS][annotation] == value and source_filter(graph, u) and target_filter(graph, v):
            yield u, v, k, data

def get_subgraph_peripheral_nodes(
    graph: BELGraph,
    subgraph: Collection[BaseEntity],
    node_predicates: Optional[NodePredicates] = None,
    edge_predicates: Optional[EdgePredicates] = None,
):
    """Get a summary dictionary of all peripheral nodes to a given sub-graph.

    :return: A dictionary of {external node: {'successor': {internal node: list of (key, dict)},
                                            'predecessor': {internal node: list of (key, dict)}}}
    :rtype: dict

    For example, it might be useful to quantify the number of predecessors and successors:

    >>> from pybel.struct.filters.node_predicates import not_pathology
    >>> value = 'Blood vessel dilation subgraph'
    >>> sg = get_subgraph_by_annotation_value(graph, annotation='Subgraph', value=value)
    >>> p = get_subgraph_peripheral_nodes(graph, sg, node_predicates=not_pathology)
    >>> for node in sorted(p, key=lambda n: len(set(p[n]['successor']) | set(p[n]['predecessor'])), reverse=True):
    >>>     if 1 == len(p[value][node]['successor']) or 1 == len(p[value][node]['predecessor']):
    >>>         continue
    >>>     print(node,
    >>>           len(p[node]['successor']),
    >>>           len(p[node]['predecessor']),
    >>>           len(set(p[node]['successor']) | set(p[node]['predecessor'])))
    """
    node_filter = concatenate_node_predicates(node_predicates=node_predicates)
    edge_filter = and_edge_predicates(edge_predicates=edge_predicates)

    result = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))

    for u, v, k, d in get_peripheral_successor_edges(graph, subgraph):
        if not node_filter(graph, v) or not node_filter(graph, u) or not edge_filter(graph, u, v, k):
            continue
        result[v]['predecessor'][u].append((k, d))

    for u, v, k, d in get_peripheral_predecessor_edges(graph, subgraph):
        if not node_filter(graph, v) or not node_filter(graph, u) or not edge_filter(graph, u, v, k):
            continue
        result[u]['successor'][v].append((k, d))

    return result

@uni_in_place_transformation
def expand_periphery(
    universe: BELGraph,
    graph: BELGraph,
    node_predicates: Optional[NodePredicates] = None,
    edge_predicates: Optional[EdgePredicates] = None,
    threshold: int = 2,
) -> None:
    """Iterate over all possible edges, peripheral to a given subgraph, that could be added from the given graph.

    Edges could be added if they go to nodes that are involved in relationships that occur with more than the
    threshold (default 2) number of nodes in the subgraph.

    :param universe: The universe of BEL knowledge
    :param graph: The (sub)graph to expand
    :param threshold: Minimum frequency of betweenness occurrence to add a gap node

    A reasonable edge filter to use is :func:`pybel_tools.filters.keep_causal_edges` because this function can allow
    for huge expansions if there happen to be hub nodes.
    """
    nd = get_subgraph_peripheral_nodes(
        universe, graph,
        node_predicates=node_predicates,
        edge_predicates=edge_predicates,
    )

    for node, dd in nd.items():
        pred_d = dd['predecessor']
        succ_d = dd['successor']

        in_subgraph_connections = set(pred_d) | set(succ_d)

        if threshold > len(in_subgraph_connections):
            continue

        graph.add_node(node, attr_dict=universe[node])

        for u, edges in pred_d.items():
            for key, data in edges:
                graph.add_edge(u, node, key=key, **data)

        for v, edges in succ_d.items():
            for key, data in edges:
                graph.add_edge(node, v, key=key, **data)

@uni_in_place_transformation
def enrich_complexes(graph: BELGraph) -> None:
    """Add all of the members of the complex abundances to the graph."""
    for u in list(graph):
        if not isinstance(u, ComplexAbundance):
            continue
        for v in u.members:
            graph.add_part_of(v, u)

@uni_in_place_transformation
def enrich_composites(graph: BELGraph) -> None:
    """Add all of the members of the composite abundances to the graph."""
    for u in list(graph):
        if not isinstance(u, CompositeAbundance):
            continue
        for v in u.members:
            graph.add_part_of(v, u)

@uni_in_place_transformation
def enrich_reactions(graph: BELGraph) -> None:
    """Add all of the reactants and products of reactions to the graph."""
    for u in list(graph):
        if not isinstance(u, Reaction):
            continue
        for v in u.reactants:
            graph.add_has_reactant(u, v)
        for v in u.products:
            graph.add_has_product(u, v)

@uni_in_place_transformation
def enrich_variants(graph: BELGraph) -> None:
    """Add the reference nodes for all variants of the given function."""
    for u in list(graph):
        if not isinstance(u, CentralDogma):
            continue

        parent = u.get_parent()
        if parent is None:
            continue

        if parent not in graph:
            graph.add_has_variant(parent, u)

@uni_in_place_transformation
def enrich_unqualified(graph: BELGraph) -> None:
    """Enrich the sub-graph with the unqualified edges from the graph.

    The reason you might want to do this is you induce a sub-graph from the original graph based on an annotation
    filter, but the unqualified edges that don't have annotations that most likely connect elements within your graph
    are not included.

    .. seealso::

        This function thinly wraps the successive application of the following functions:

        - :func:`enrich_complexes`
        - :func:`enrich_composites`
        - :func:`enrich_reactions`
        - :func:`enrich_variants`

    Equivalent to:

    >>> enrich_complexes(graph)
    >>> enrich_composites(graph)
    >>> enrich_reactions(graph)
    >>> enrich_variants(graph)
    """
    enrich_complexes(graph)
    enrich_composites(graph)
    enrich_reactions(graph)
    enrich_variants(graph)

@uni_in_place_transformation
def expand_internal(
    universe: BELGraph,
    graph: BELGraph,
) -> None:
    """Expand on edges between entities in the sub-graph that pass the given filters, in place.

    :param universe: The full graph
    :param graph: A sub-graph to find the upstream information
    """
    for u, v, key in iterate_internal(universe, graph):
        graph.add_edge(u, v, key=key, **universe[u][v][key])

@uni_in_place_transformation
def expand_internal_causal(universe: BELGraph, graph: BELGraph) -> None:
    """Add causal edges between entities in the sub-graph.

    Is an extremely thin wrapper around :func:`expand_internal`.

    :param universe: A BEL graph representing the universe of all knowledge
    :param graph: The target BEL graph to enrich with causal relations between contained nodes

    Equivalent to:

    >>> from pybel_tools.mutation import expand_internal
    >>> from pybel.struct.filters.edge_predicates import is_causal_relation
    >>> expand_internal(universe, graph, edge_predicates=is_causal_relation)
    """
    for u, v, key in iterate_internal(universe, graph):
        data = universe.edges[u][v][key]
        if is_causal_relation(data):
            graph.add_edge(u, v, key=key, **data)

def iterate_internal(universe: BELGraph, graph: BELGraph) -> EdgeIterator:
    """Iterate over edges that are in the universe but not the target graph."""
    for u, v in itt.product(graph, repeat=2):
        if graph.has_edge(u, v):
            continue
        if not universe.has_edge(u, v):
            continue
        for key in universe[u][v]:
            yield u, v, key
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.db.models import Sum
from django.db.models.signals import pre_save, post_save, post_init, pre_init
from django.contrib.auth.models import User, UserManager
from django.core.exceptions import ValidationError
import datetime, calendar
# Create your models here.

def get_image_path(instance, filename):
	return os.path.join('users', instance.id, filename)

class User(User):
	"""Custom User model, extending Django's default User"""
	USER_TYPES = (
		('MEM', 'Member'),
		('SPO', 'Sponsor'),
		('DON', 'Donation')
	)

	profile_image = models.ImageField(upload_to=get_image_path, blank=True)
	utype = models.CharField(max_length=3, choices=USER_TYPES, default='MEM')

	objects = UserManager()

	@property
	def most_recent_payment(self):
		p = self.payments_made.all().order_by('-date_paid')
		return p[0] if p else None

	def total_paid(self, ptype=None):
		'''Returns the total amount the User has paid either in total, or for a specified Contract type'''

		# Construct the appropriate Queryset
		if ptype is not None:
			payments = self.payments_made.filter(contract__ctype__desc=ptype)
		else:
			payments = self.payments_made

		return payments.aggregate(Sum('amount'))['amount__sum'] or 0.0

	def membership_status(self, pretty=False):
		'''Returns string (see Contract::CONTRACT_STATUSES) indicating latest Membership status of this User'''
		if pretty:
			return self.contracts.filter(ctype__desc='Membership').latest('start').get_status_display()
		else:
			return self.contracts.filter(ctype__desc='Membership').latest('start').status

	def __unicode__(self):
		if self.first_name and self.last_name:
			return self.get_full_name()
		else:
			return self.username

class ContractType(models.Model):
	desc = models.CharField(max_length=128, blank=False, null=True)

	def __unicode__(self):
		return self.desc

class Contract(models.Model):
	CONTRACT_STATUSES = (
		('ACT', 'Active'),
		('LAP', 'Lapsed'),
		('TER', 'Terminated'),
		('PEN', 'Pending')
	)

	start = models.DateField()
	end = models.DateField(blank=True)
	valid_till = models.DateField(editable=False)
	ctype = models.ForeignKey(ContractType, blank=False, null=True, verbose_name="Contract type", help_text="Locker and Address Use Contracts must use their respective Tiers. Membership contracts can accept all other Tiers")
	tier = models.ForeignKey("Tier", blank=False, null=True)
	user = models.ForeignKey(User, blank=False, null=True, related_name="contracts")
	status = models.CharField(max_length=3, choices=CONTRACT_STATUSES)

	def __extend_by(self, num_months):
		'''Extends the validity of this Contract by specified number of months (Assume 1 month = 28 days). THIS METHOD DOES NOT save() AUTOMATICALLY'''

# 		year = s.year + ((s.month + num_months) / 12)
# 		month = ((s.month + num_months) % 12)
# 		last_day = calendar.monthrange(self.end.year, self.end.month)[1]

		#self.end = datetime.date(year, month, last_day)
		self.valid_till = self.valid_till + datetime.timedelta(days=(28*num_months))

		# Normalise date to end of that month
		self.valid_till = datetime.date(self.valid_till.year, self.valid_till.month, calendar.monthrange(self.valid_till.year, self.valid_till.month)[1])

	@property
	def total_paid(self):
		'''Returns total amount paid due to this Contract'''

		return self.payments.aggregate(Sum('amount'))['amount__sum'] or 0.0

	def sync(self):
		'''Looks at the total amount paid to this Contract and recalculates its proper expiry (end) date, taking a month's deposit into account'''

		# Reset the clock
		self.valid_till = self.start

		months_paid = self.total_paid / self.tier.fee

		if months_paid > 0:
			# Take into account 1 month's deposit
			months_paid -= 1
			self.__extend_by(int(months_paid))

		self.save()

	def balance(self, in_months=False):
		'''Looks at how much has been paid for this Contract and determines if there is any balance owed by (-ve) / owed to (+ve) the Member'''
		balance = 0
		duration_in_months = 1

		if self.status == 'TER':
			duration_in_months = (self.end - self.start).days / 30 # Naive month calculation
		else:
			duration_in_months = (datetime.date.today() - self.start).days / 30 # Naive month calculation

		balance = self.total_paid - (self.tier.fee * duration_in_months)

		if in_months:
			return balance / self.tier.fee

		else:
			return balance

	def update_with_payment(self, p):
		# Takes a Payment object, calculates how many month's worth it is, and extends the contract end date accordingly
		if isinstance(p, Payment):
			# Get number of multiples of Contract for this Payment
			multiples = int(p.amount / self.tier.fee)

			self.__extend_by(multiples)
			self.save()

		else:
			return False

	def save(self):
		# Overridden save() forces the date of self.end to be the last day of that given month.
		# Eg. if self.end is initially declared as 5 May 2010, we now force it to become 31 May 2010 before actually save()'ing the object.

		# But first, is self.end even specified?
		if not self.valid_till:
			self.valid_till = self.start

		last_day = calendar.monthrange(self.valid_till.year, self.valid_till.month)[1]
		self.valid_till = datetime.date(self.valid_till.year, self.valid_till.month, last_day)

		#force start date to be normalised as 1st day of the month
		self.start = datetime.date(self.start.year, self.start.month, 1)
		super(Contract, self).save()

	def clean(self):
		# Model validation to ensure that validates that contract and tier are allowed
		if self.ctype != self.tier.ctype:
			raise ValidationError(_("Contract type and tier mismatched"))

	def __unicode__(self):
		return self.user.__unicode__() + u": " + self.ctype.__unicode__() + u" @ $" + unicode(self.tier.fee) + "/mth Start: " + u" " + unicode(self.start.strftime('%d %b %Y')) +  u" End: " + unicode(self.end.strftime('%d %b %Y'))

class Tier(models.Model):
	fee = models.FloatField(default=0.0)
	desc = models.CharField(max_length=255)
	ctype = models.ForeignKey("ContractType", blank=False, null=True)

	def __unicode__(self):
		return self.desc + u": " + unicode(self.fee)

class Payment(models.Model):
	PAYMENT_METHODS = (
		('EFT', 'Electronic Fund Transfer'),
		('CHK', 'Cheque'),
		('CSH', 'Cash'),
		('OTH', 'Others')
	)

#	PAYMENT_TYPES = (
#		('DPT', 'Deposit'),
#		('FEE', 'Membership Fees'),
#		('DNT', 'Donation')
#	)

#	MONTHS = (
#		('1', 'Jan'),
#		('2', 'Feb'),
#		('3', 'Mar'),
#		('4', 'Apr'),
#		('5', 'May'),
#		('6', 'Jun'),
#		('7', 'Jul'),
#		('8', 'Aug'),
#		('9', 'Sep'),
#		('10', 'Oct'),
#		('11', 'Nov'),
#		('12', 'Dec')
#	)

#	@property
#	def year_range(self):
#		this_year = datetime.today().year
#		years = ( (unicode(this_year), unicode(this_year)) )
#
#		for i in xrange(0, 10):
#			years.insert(0, (unicode(this_year-i), unicode(this_year-i)))
#			years.append((unicode(this_year+i), unicode(this_year+i)))
#
#		return years

#	YEARS = (
#		('2010', '2010'),
#	)

	date_paid = models.DateField()
	amount = models.FloatField(default=0.0)
	method = models.CharField(max_length=3, choices=PAYMENT_METHODS, default='EFT')
	contract = models.ForeignKey(Contract, blank=False, null=True, related_name="payments")
	desc = models.CharField(max_length=255, blank=True, help_text="Eg. Cheque or transaction number, if applicable")
	user = models.ForeignKey(User, blank=False, null=True, related_name="payments_made")
	verified = models.BooleanField(default=False, blank=False, help_text="Has this Payment been verified/approved by an Admin?")

	def __unicode__(self):
		return self.contract.__unicode__() + u" Paid: " + unicode(self.date_paid)

class Locker(models.Model):
	user = models.ForeignKey(User, blank=False, null=True, related_name="locker")
	num = models.IntegerField()

# Attaching a post_save signal handler to the Payment model to update the appropriate Contract
def update_contract_with_payments(sender, **kwargs):
	payment = kwargs['instance']
	c = payment.contract
	c.update_with_payment(payment)

post_save.connect(update_contract_with_payments, sender=Payment)

def lapsed_check(sender, **kwargs):
	'''Checks the end date of active contract and compares it with today. If contract is lapsed, update the contract status to lapsed.'''

	contract = kwargs['instance']
	if contract.status == u'ACT':
		if contract.valid_till < datetime.date.today():
			contract.status = u'LAP'
			contract.save()

	elif contract.status == u'LAP' and contract.valid_till > datetime.date.today():
		contract.status = u'ACT'
		contract.save()

post_init.connect(lapsed_check, sender=Contract)
<commit_msg>Adding additional checks to ensure we don't check on a fresh Contract<commit_after># -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.db.models import Sum
from django.db.models.signals import pre_save, post_save, post_init, pre_init
from django.contrib.auth.models import User, UserManager
from django.core.exceptions import ValidationError
import datetime, calendar
# Create your models here.

def get_image_path(instance, filename):
	return os.path.join('users', instance.id, filename)

class User(User):
	"""Custom User model, extending Django's default User"""
	USER_TYPES = (
		('MEM', 'Member'),
		('SPO', 'Sponsor'),
		('DON', 'Donation')
	)

	profile_image = models.ImageField(upload_to=get_image_path, blank=True)
	utype = models.CharField(max_length=3, choices=USER_TYPES, default='MEM')

	objects = UserManager()

	@property
	def most_recent_payment(self):
		p = self.payments_made.all().order_by('-date_paid')
		return p[0] if p else None

	def total_paid(self, ptype=None):
		'''Returns the total amount the User has paid either in total, or for a specified Contract type'''

		# Construct the appropriate Queryset
		if ptype is not None:
			payments = self.payments_made.filter(contract__ctype__desc=ptype)
		else:
			payments = self.payments_made

		return payments.aggregate(Sum('amount'))['amount__sum'] or 0.0

	def membership_status(self, pretty=False):
		'''Returns string (see Contract::CONTRACT_STATUSES) indicating latest Membership status of this User'''
		if pretty:
			return self.contracts.filter(ctype__desc='Membership').latest('start').get_status_display()
		else:
			return self.contracts.filter(ctype__desc='Membership').latest('start').status

	def __unicode__(self):
		if self.first_name and self.last_name:
			return self.get_full_name()
		else:
			return self.username

class ContractType(models.Model):
	desc = models.CharField(max_length=128, blank=False, null=True)

	def __unicode__(self):
		return self.desc

class Contract(models.Model):
	CONTRACT_STATUSES = (
		('ACT', 'Active'),
		('LAP', 'Lapsed'),
		('TER', 'Terminated'),
		('PEN', 'Pending')
	)

	start = models.DateField()
	end = models.DateField(blank=True)
	valid_till = models.DateField(editable=False)
	ctype = models.ForeignKey(ContractType, blank=False, null=True, verbose_name="Contract type", help_text="Locker and Address Use Contracts must use their respective Tiers. Membership contracts can accept all other Tiers")
	tier = models.ForeignKey("Tier", blank=False, null=True)
	user = models.ForeignKey(User, blank=False, null=True, related_name="contracts")
	status = models.CharField(max_length=3, choices=CONTRACT_STATUSES)

	def __extend_by(self, num_months):
		'''Extends the validity of this Contract by specified number of months (Assume 1 month = 28 days). THIS METHOD DOES NOT save() AUTOMATICALLY'''

# 		year = s.year + ((s.month + num_months) / 12)
# 		month = ((s.month + num_months) % 12)
# 		last_day = calendar.monthrange(self.end.year, self.end.month)[1]

		#self.end = datetime.date(year, month, last_day)
		self.valid_till = self.valid_till + datetime.timedelta(days=(28*num_months))

		# Normalise date to end of that month
		self.valid_till = datetime.date(self.valid_till.year, self.valid_till.month, calendar.monthrange(self.valid_till.year, self.valid_till.month)[1])

	@property
	def total_paid(self):
		'''Returns total amount paid due to this Contract'''

		return self.payments.aggregate(Sum('amount'))['amount__sum'] or 0.0

	def sync(self):
		'''Looks at the total amount paid to this Contract and recalculates its proper expiry (end) date, taking a month's deposit into account'''

		# Reset the clock
		self.valid_till = self.start

		months_paid = self.total_paid / self.tier.fee

		if months_paid > 0:
			# Take into account 1 month's deposit
			months_paid -= 1
			self.__extend_by(int(months_paid))

		self.save()

	def balance(self, in_months=False):
		'''Looks at how much has been paid for this Contract and determines if there is any balance owed by (-ve) / owed to (+ve) the Member'''
		balance = 0
		duration_in_months = 1

		if self.status == 'TER':
			duration_in_months = (self.end - self.start).days / 30 # Naive month calculation
		else:
			duration_in_months = (datetime.date.today() - self.start).days / 30 # Naive month calculation

		balance = self.total_paid - (self.tier.fee * duration_in_months)

		if in_months:
			return balance / self.tier.fee

		else:
			return balance

	def update_with_payment(self, p):
		# Takes a Payment object, calculates how many month's worth it is, and extends the contract end date accordingly
		if isinstance(p, Payment):
			# Get number of multiples of Contract for this Payment
			multiples = int(p.amount / self.tier.fee)

			self.__extend_by(multiples)
			self.save()

		else:
			return False

	def save(self):
		# Overridden save() forces the date of self.end to be the last day of that given month.
		# Eg. if self.end is initially declared as 5 May 2010, we now force it to become 31 May 2010 before actually save()'ing the object.

		# But first, is self.end even specified?
		if not self.valid_till:
			self.valid_till = self.start

		last_day = calendar.monthrange(self.valid_till.year, self.valid_till.month)[1]
		self.valid_till = datetime.date(self.valid_till.year, self.valid_till.month, last_day)

		#force start date to be normalised as 1st day of the month
		self.start = datetime.date(self.start.year, self.start.month, 1)
		super(Contract, self).save()

	def clean(self):
		# Model validation to ensure that validates that contract and tier are allowed
		if self.ctype != self.tier.ctype:
			raise ValidationError(_("Contract type and tier mismatched"))

	def __unicode__(self):
		return self.user.__unicode__() + u": " + self.ctype.__unicode__() + u" @ $" + unicode(self.tier.fee) + "/mth Start: " + u" " + unicode(self.start.strftime('%d %b %Y')) +  u" End: " + unicode(self.end.strftime('%d %b %Y'))

class Tier(models.Model):
	fee = models.FloatField(default=0.0)
	desc = models.CharField(max_length=255)
	ctype = models.ForeignKey("ContractType", blank=False, null=True)

	def __unicode__(self):
		return self.desc + u": " + unicode(self.fee)

class Payment(models.Model):
	PAYMENT_METHODS = (
		('EFT', 'Electronic Fund Transfer'),
		('CHK', 'Cheque'),
		('CSH', 'Cash'),
		('OTH', 'Others')
	)

#	PAYMENT_TYPES = (
#		('DPT', 'Deposit'),
#		('FEE', 'Membership Fees'),
#		('DNT', 'Donation')
#	)

#	MONTHS = (
#		('1', 'Jan'),
#		('2', 'Feb'),
#		('3', 'Mar'),
#		('4', 'Apr'),
#		('5', 'May'),
#		('6', 'Jun'),
#		('7', 'Jul'),
#		('8', 'Aug'),
#		('9', 'Sep'),
#		('10', 'Oct'),
#		('11', 'Nov'),
#		('12', 'Dec')
#	)

#	@property
#	def year_range(self):
#		this_year = datetime.today().year
#		years = ( (unicode(this_year), unicode(this_year)) )
#
#		for i in xrange(0, 10):
#			years.insert(0, (unicode(this_year-i), unicode(this_year-i)))
#			years.append((unicode(this_year+i), unicode(this_year+i)))
#
#		return years

#	YEARS = (
#		('2010', '2010'),
#	)

	date_paid = models.DateField()
	amount = models.FloatField(default=0.0)
	method = models.CharField(max_length=3, choices=PAYMENT_METHODS, default='EFT')
	contract = models.ForeignKey(Contract, blank=False, null=True, related_name="payments")
	desc = models.CharField(max_length=255, blank=True, help_text="Eg. Cheque or transaction number, if applicable")
	user = models.ForeignKey(User, blank=False, null=True, related_name="payments_made")
	verified = models.BooleanField(default=False, blank=False, help_text="Has this Payment been verified/approved by an Admin?")

	def __unicode__(self):
		return self.contract.__unicode__() + u" Paid: " + unicode(self.date_paid)

class Locker(models.Model):
	user = models.ForeignKey(User, blank=False, null=True, related_name="locker")
	num = models.IntegerField()

# Attaching a post_save signal handler to the Payment model to update the appropriate Contract
def update_contract_with_payments(sender, **kwargs):
	payment = kwargs['instance']
	c = payment.contract
	c.update_with_payment(payment)

post_save.connect(update_contract_with_payments, sender=Payment)

def lapsed_check(sender, **kwargs):
	'''Checks the end date of active contract and compares it with today. If contract is lapsed, update the contract status to lapsed.'''

	contract = kwargs['instance']

	# If this is a new Contract, check if we have a valid_till date set
	if not contract.id and not contract.valid_till:
		contract.valid_till = contract.start

	if contract.status == u'ACT':
		if contract.valid_till < datetime.date.today():
			contract.status = u'LAP'
			contract.save()

	elif contract.status == u'LAP' and contract.valid_till > datetime.date.today():
		contract.status = u'ACT'
		contract.save()

post_init.connect(lapsed_check, sender=Contract)
<|endoftext|>


<commit_before>from __future__ import print_function

from six import string_types

try:
    import numpy as np

except ImportError:
    print("bokeh.charts needs numpy installed to work properly!")
    raise

from ._chartobject import ChartObject, DataAdapter
from ..objects import ColumnDataSource, Range1d, DataRange1d

class Area(ChartObject):
    def __init__(self, values,
                 index=None,
                 title=None, xlabel=None, ylabel=None, legend=False,
                 xscale="linear", yscale="linear", width=800, height=600,
                 tools=True, filename=False, server=False, notebook=False,
                 facet=False, stacked=False):
        self.values = values
        self.source = None
        self.xdr = None
        self.ydr = None
        self.__stacked = stacked

        # list to save all the groups available in the incomming input
        self.groups = []
        self.data = dict()
        self.attr = []
        self.index = index

        super(Area, self).__init__(title, xlabel, ylabel, legend,
                                         xscale, yscale, width, height,
                                         tools, filename, server, notebook, facet)

    def stacked(self, stacked=True):
        """Set the areas stacked on your chart.

        Args:
            stacked (bool, optional): whether to stack the areas
                in your plot (default: True).

        Returns:
            self: the chart object being configured.
        """
        self._stacked = stacked
        return self

    def get_source(self):
        """
        Push the Line data into the ColumnDataSource and calculate the proper ranges.
        """
        self.source = ColumnDataSource(self.data)
        self.xdr = DataRange1d(sources=[self.source.columns("x")])

        y_names = self.attr[1:]

        endy = max(max(self.data[i]) for i in y_names)
        starty = min(min(self.data[i]) for i in y_names)
        self.ydr = Range1d(
            start=starty - 0.1 * (endy - starty),
            end=endy + 0.1 * (endy - starty)
        )

    def draw(self):
        """Use the patch glyphs to fill the area connecting the xy points
         in the series taken from the data added with area.get_data.

        Takes reference points from the data loaded at the ColumnDataSource.
        """
        colors = self._set_colors(self.attr)

        # parse all series. We exclude the first attr as it's the x values
        # added for the index
        for i, series_name in enumerate(self.attr[1:]):
            self.chart.make_patch(self.source, 'x', series_name, colors[i])

            if i < len(self.attr) - 1:
                self.create_plot_if_facet()

    def get_data(self):
        """Calculate the chart properties accordingly from area.values.
        Then build a dict containing references to all the points to be used by
        the patch glyph inside the ``draw`` method.

        """
        self.data = dict()

        # list to save all the attributes we are going to create
        self.attr = []

        xs, self.values = DataAdapter.get_index_and_data(self.values, self.index)
        last = np.zeros(len(xs))

        x2 = np.hstack((xs[::-1], xs))
        self.set_and_get("x", "", x2)

        for grp in self.values.keys():
            # TODO: This condition may be removed or changed depending on
            # the validation of self.index
            if isinstance(self.index, string_types) \
                and grp == self.index:
                continue

            # get single series values
            col_values = self.values[grp]
            _values = [col_values[x] for indx, x in enumerate(xs)]

            # to draw area we need 2 coordinates. The lower values will always
            # be:
            # - 0 in case of non stacked area
            # - the previous series top value in case of stacked charts
            next = last + _values
            values = np.hstack((last[::-1], next))

            # only update when stacked, otherwise we always want to start from 0
            if self.stacked:
                last = next

            # save values and new group
            self.set_and_get("y_", grp, values)
            self.groups.append(grp)
<commit_msg>update Area.stacked attribute to be chainable as other Chart attributes<commit_after>from __future__ import print_function

from six import string_types

try:
    import numpy as np

except ImportError:
    print("bokeh.charts needs numpy installed to work properly!")
    raise

from ._chartobject import ChartObject, DataAdapter
from ..objects import ColumnDataSource, Range1d, DataRange1d

class Area(ChartObject):
    def __init__(self, values,
                 index=None,
                 title=None, xlabel=None, ylabel=None, legend=False,
                 xscale="linear", yscale="linear", width=800, height=600,
                 tools=True, filename=False, server=False, notebook=False,
                 facet=False, stacked=False):
        self.values = values
        self.source = None
        self.xdr = None
        self.ydr = None
        self.__stacked = stacked

        # list to save all the groups available in the incomming input
        self.groups = []
        self.data = dict()
        self.attr = []
        self.index = index

        super(Area, self).__init__(title, xlabel, ylabel, legend,
                                         xscale, yscale, width, height,
                                         tools, filename, server, notebook, facet)

    def stacked(self, stacked=True):
        """Set the areas stacked on your chart.

        Args:
            stacked (bool, optional): whether to stack the areas
                in your plot (default: True).

        Returns:
            self: the chart object being configured.
        """
        self._stacked = stacked
        return self

    def check_attr(self):
        """Check if any of the chained method were used.

        If they were not used, it assign the init parameters content by default.
        """
        super(Area, self).check_attr()

        # add specific chained method
        if not hasattr(self, '_stacked'):
            self._stacked = self.__stacked

    def get_source(self):
        """
        Push the Line data into the ColumnDataSource and calculate the proper ranges.
        """
        self.source = ColumnDataSource(self.data)
        self.xdr = DataRange1d(sources=[self.source.columns("x")])

        y_names = self.attr[1:]

        endy = max(max(self.data[i]) for i in y_names)
        starty = min(min(self.data[i]) for i in y_names)
        self.ydr = Range1d(
            start=starty - 0.1 * (endy - starty),
            end=endy + 0.1 * (endy - starty)
        )

    def draw(self):
        """Use the patch glyphs to fill the area connecting the xy points
         in the series taken from the data added with area.get_data.

        Takes reference points from the data loaded at the ColumnDataSource.
        """
        colors = self._set_colors(self.attr)

        # parse all series. We exclude the first attr as it's the x values
        # added for the index
        for i, series_name in enumerate(self.attr[1:]):
            self.chart.make_patch(self.source, 'x', series_name, colors[i])

            if i < len(self.attr) - 1:
                self.create_plot_if_facet()

    def get_data(self):
        """Calculate the chart properties accordingly from area.values.
        Then build a dict containing references to all the points to be used by
        the patch glyph inside the ``draw`` method.

        """
        self.data = dict()

        # list to save all the attributes we are going to create
        self.attr = []

        xs, self.values = DataAdapter.get_index_and_data(self.values, self.index)
        last = np.zeros(len(xs))

        x2 = np.hstack((xs[::-1], xs))
        self.set_and_get("x", "", x2)

        for grp in self.values.keys():
            # TODO: This condition may be removed or changed depending on
            # the validation of self.index
            if isinstance(self.index, string_types) \
                and grp == self.index:
                continue

            # get single series values
            col_values = self.values[grp]
            _values = [col_values[x] for indx, x in enumerate(xs)]

            # to draw area we need 2 coordinates. The lower values will always
            # be:
            # - 0 in case of non stacked area
            # - the previous series top value in case of stacked charts
            next = last + _values
            values = np.hstack((last[::-1], next))

            # only update when stacked, otherwise we always want to start from 0
            if self._stacked:
                last = next

            # save values and new group
            self.set_and_get("y_", grp, values)
            self.groups.append(grp)
<|endoftext|>