diff --git a/.gitignore b/.gitignore index 61378cc..7ee00af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,19 @@ +# Vagrant stuff +acceptance_config.yml +boxes/* +/.vagrant +/website/.vagrant +/website/build +/vagrant-spec.config.rb +test/vagrant-spec/.vagrant/ -#Build result - +# Logs *.log -_pycache_/ \ No newline at end of file + +# IDE +_pycache_/ +.idea/* + +# Python +*.pyc +*.orig diff --git a/Vagrantfile b/Vagrantfile index c3fd6bf..ca11435 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,11 +1,7 @@ -FLASK_PORT=5000 -POSTGRESQL_PORT=5432 - Vagrant.configure("2") do |config| config.vm.box = "ubuntu/bionic64" config.vm.provision :shell, path: "setup.sh" - config.vm.network :forwarded_port, guest: FLASK_PORT, host: FLASK_PORT - config.vm.network :forwarded_port, guest: POSTGRESQL_PORT, host: POSTGRESQL_PORT + config.vm.network "private_network", type: "dhcp" config.vm.provider "virtualbox" do |v| v.gui = false v.name = "Edison_test" @@ -14,3 +10,11 @@ Vagrant.configure("2") do |config| end end + +# Fixes a dhcp configuration conflict of the private network. +# Issue: https://github.com/hashicorp/vagrant/issues/8878 +class VagrantPlugins::ProviderVirtualBox::Action::Network + def dhcp_server_matches_config?(dhcp_server, config) + true + end +end diff --git a/edison/__init__.py b/edison/__init__.py new file mode 100644 index 0000000..d4ff709 --- /dev/null +++ b/edison/__init__.py @@ -0,0 +1,12 @@ +import os + +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from edison.config import get_config_object + + +# Put app here so the entire app could import it. +app = Flask(__name__) +app.config.from_object(get_config_object(app.config["ENV"])) +basedir = os.path.abspath(os.path.dirname(__file__)) +db = SQLAlchemy(app) diff --git a/edison/app.py b/edison/app.py new file mode 100644 index 0000000..7e675e2 --- /dev/null +++ b/edison/app.py @@ -0,0 +1,26 @@ +import edison +import edison.models + +from flask import render_template +from flask_migrate import Migrate + + +app = edison.app +db = edison.db +migrate = Migrate(app, db) + +# Creates all tables defined in the database models and the only ones that are not created yet. +# If there's any change in the database models you should perform a migration to apply this change in the database itself. +# More about database migrations can be found in /edison/migrations/README. +db.create_all() + +@app.route("/policy") +def policy(): + return render_template('policy.html') + +@app.route("/") +def home(): + return render_template('home.html') + +if __name__ == "__main__": + app.run(host='0.0.0.0') diff --git a/edison/config.py b/edison/config.py new file mode 100644 index 0000000..284caae --- /dev/null +++ b/edison/config.py @@ -0,0 +1,39 @@ +import inspect +import sys + + +def get_config_object(env_keyword: str): + """ + Returns the the desired config class path. + + The function iterates through a dictionary returned by inspect. + The dictionary contains details about all of the file members. + Its key is the name of the member and value is obj which contains all the details about the member. + The desired config path is being picked by the ENV_KEYWORD field defined in the config class. + + Parameters: + env_keyword (str): Should be equals to one of the config classes ENV_KEYWORD field. + + Returns: + str: module_name.class_name, which is the full path of the config class. + """ + for name, obj in inspect.getmembers(sys.modules[__name__]): + if issubclass(obj, Config) and obj.ENV_KEYWORD == env_keyword: + return ".".join([obj.__module__, name]) + + +class Config: + ENV_KEYWORD = "" + DEBUG = False + # Turns off the Flask-SQLAlchemy event system + SQLALCHEMY_TRACK_MODIFICATIONS = False + SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:edison@127.0.0.1/edison' + +# PostgreSQL connection string should be updated once an actual production environment is established. +class ProductionConfig(Config): + ENV_KEYWORD = "production" + + +class DevelopmentConfig(Config): + ENV_KEYWORD = "development" + DEBUG = True diff --git a/edison/migrations/README b/edison/migrations/README new file mode 100644 index 0000000..3ef13df --- /dev/null +++ b/edison/migrations/README @@ -0,0 +1,43 @@ +This information and more can be found at - https://flask-migrate.readthedocs.io/ + +The content of edison/migrations folder is auto-created by Flask-Migrate extension. +Flask-Migrate is an extension that configures Alembic in the proper way to work with Flask and Flask-SQLAlchemy application. +In terms of the actual database migrations, everything is handled by Alembic so you get exactly the same functionality. + +FAQ + +1. Why to use database migrations ? + + Database migrations basically track granular changes to our database schema. + These granular changes are typically reflected as separate scripted files. + Every time we make a change to our database schema we should perform a migration. + This migration creates a new file that describes our current database schema inside edison/migrations/versions. + That way we could downgrade or upgrade our database schema to different versions if needed. + +2. When to use database migration ? + + You should perform a database migration on any change you make to the database models. + When a migration is performed it applies those changes to the database itself. + +3. How to perform a database migration ? + + - First, we need to initialize our migrations folder (If it's not already created). + To do so you should first make sure to define the Flask app path inside FLASK_APP environment variable, + and then execute the following command: + flask db init + Executing this command will create the migrations folder inside /home/vagrant/migrations, + which is not good for us because we want it to be created in our project directory. + So we simply need to add the directory flag: + flask db init --directory /vagrant/edison/migrations + + - Then, we should perform the migration as follow: + flask db migrate -m "" --directory /vagrant/edison/migrations + It will create a new version file of the database schema inside edison/migrations/versions. + + - Last, we need to apply this migration on the database itself by executing: + flask db upgrade --directory /vagrant/edison/migrations. + +4. How to downgrade the database schema ? + + Simply execute the following command: + flask db downgrade --directory /vagrant/edison/migrations. diff --git a/edison/migrations/alembic.ini b/edison/migrations/alembic.ini new file mode 100644 index 0000000..f8ed480 --- /dev/null +++ b/edison/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/edison/migrations/env.py b/edison/migrations/env.py new file mode 100644 index 0000000..9452179 --- /dev/null +++ b/edison/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +from flask import current_app +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/edison/migrations/script.py.mako b/edison/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/edison/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/edison/models/__init__.py b/edison/models/__init__.py new file mode 100644 index 0000000..ee4c00b --- /dev/null +++ b/edison/models/__init__.py @@ -0,0 +1 @@ +from .user import User diff --git a/edison/models/user.py b/edison/models/user.py new file mode 100644 index 0000000..7349cd0 --- /dev/null +++ b/edison/models/user.py @@ -0,0 +1,13 @@ +from edison import db + + +class User(db.Model): + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(50), nullable=False, unique=True) + + def to_json(self): + return { + "username": self.username + } diff --git a/edison/static/Pictures/Eric_Schmit.png b/edison/static/Pictures/Eric_Schmit.png new file mode 100644 index 0000000..ab4e01d Binary files /dev/null and b/edison/static/Pictures/Eric_Schmit.png differ diff --git a/edison/static/Pictures/back.jpg b/edison/static/Pictures/back.jpg new file mode 100644 index 0000000..e14243c Binary files /dev/null and b/edison/static/Pictures/back.jpg differ diff --git a/edison/static/Pictures/logo.png b/edison/static/Pictures/logo.png new file mode 100644 index 0000000..81c860a Binary files /dev/null and b/edison/static/Pictures/logo.png differ diff --git a/edison/static/css/footer.css b/edison/static/css/footer.css new file mode 100644 index 0000000..e9a9e0e --- /dev/null +++ b/edison/static/css/footer.css @@ -0,0 +1,15 @@ +.page-footer { + background-color: #222; + color: #ccc; + padding: 60px 30px; + bottom:0; +} + +.footer-copyright { + color: #666; + padding: 60px; +} + +.block { + display: inline-block; +} diff --git a/edison/static/css/header.css b/edison/static/css/header.css new file mode 100644 index 0000000..32ab625 --- /dev/null +++ b/edison/static/css/header.css @@ -0,0 +1,8 @@ +.header { + background-image: url('../Pictures/back.jpg'); + background-position: center; + position: relative; + opacity: 0.8; + background-size: cover; + height: 900px; +} diff --git a/edison/static/css/layout.css b/edison/static/css/layout.css new file mode 100644 index 0000000..040b055 --- /dev/null +++ b/edison/static/css/layout.css @@ -0,0 +1,5 @@ +body { + padding: 0; + margin: 0; + background: #f2f6e9; +} diff --git a/edison/static/css/navgar.css b/edison/static/css/navgar.css new file mode 100644 index 0000000..05f7d2a --- /dev/null +++ b/edison/static/css/navgar.css @@ -0,0 +1,30 @@ +.navbar { + background: #ffffff; +} + +.nav-link, +.navbar-brand { + color: #fff; + cursor: pointer; +} + +.nav-link { + margin-right: 1em !important; +} + +.nav-link:hover { + color: #000; +} + +.navbar-collapse { + justify-content: flex-end; + margin-right: -90mm; +} + +.nav-link { + color: black; +} + +.Logo { + margin-left: -90mm; +} diff --git a/edison/static/css/policy.css b/edison/static/css/policy.css new file mode 100644 index 0000000..3744fa8 --- /dev/null +++ b/edison/static/css/policy.css @@ -0,0 +1,35 @@ +.policy { + padding: 0.4em; + top: 5rem; + position: fixed; +} + +.active { + font-size: 120%; +} + +.card-header { + font-size: 100%; +} + +.btn-link { + font-size: 120%; + height: 1.6rem; +} + +.btn { + font-size: 120%; + margin: 1rem; +} + +.modal-body { + font-size: 120%; +} + +.condition-term { + display: none; +} + +.card{ + width: 50rem; +} diff --git a/static/hello-world-image.png b/edison/static/hello-world-image.png similarity index 100% rename from static/hello-world-image.png rename to edison/static/hello-world-image.png diff --git a/edison/static/js/policiesExampleArray.js b/edison/static/js/policiesExampleArray.js new file mode 100644 index 0000000..b89523d --- /dev/null +++ b/edison/static/js/policiesExampleArray.js @@ -0,0 +1,25 @@ +import { Policy } from './policy.model.js' +export class PoliciesExampleArray { + + constructor() { + this.jsonPolicesExample = [{ + 'id': '1', + 'name': 'Test Policy One', + 'room': 'living room', + 'command': 'light On, shutters Off', + 'condition': 'humidity < 40, temperature < 25' + }, { + 'id': '2', + 'name': 'Test Policy Two', + 'room': 'kitchen', + 'command': 'shutters Off', + 'condition': 'humidity > 80, temperature between 30 50' + },{ + 'id': '3', + 'name': 'Test Policy Three', + 'room': 'bedroom', + 'command': 'shutters Off', + 'condition': 'humidity > 80, temperature between 30 50' + }]; + } +} diff --git a/edison/static/js/policy.controller.js b/edison/static/js/policy.controller.js new file mode 100644 index 0000000..036743a --- /dev/null +++ b/edison/static/js/policy.controller.js @@ -0,0 +1,162 @@ + +import { Policy } from './policy.model.js' +import { PolicyElementsModifier } from './policyElementsModifier.js' +import { PoliciesExampleArray } from './policiesExampleArray.js' + +var policy = new Policy(); + +function setCardBody(policy) { + var cardBody = createInitElement('div', 'card-body'); + var cardTitle = createInitElement('h5', 'card_title'); + var cardText = createInitElement('h5'); + + cardBody.appendChild(cardTitle); + cardBody.appendChild(cardText); + cardText.innerHTML = `This policy is associated with ${policy.room} room and responsible for ${policy.command} + with these conditions: ${policy.condition}`; + + return cardBody; +} + +function createCard(policy) { + var card = createInitElement('div', 'card'); + var headerCard = createInitElement('div', 'card-header'); + var buttonTextSize = createInitElement('h5', 'mb-0'); + var headerCardButton = createInitElement('button', 'btn btn-link'); + + card.appendChild(headerCard); + headerCard.appendChild(buttonTextSize); + headerCardButton.setAttribute("data-toggle", "collapse"); + headerCardButton.setAttribute("data-target", `#collapse-policy${policy.id}`); + headerCardButton.setAttribute("aria-expanded", "true"); + headerCardButton.setAttribute("aria-controls", `collapse-policy${policy.id}`); + headerCardButton.innerHTML = policy.name; + buttonTextSize.appendChild(headerCardButton); + + return card; +} + +function getFromBackend() { + //once the route will be ready this function will be modifiy to get the policies from the backend and convert them to array + var jsonPolicyExample = new PoliciesExampleArray().jsonPolicesExample; + var policiesArray = []; + + for (var i in jsonPolicyExample) { + var newPolicy = new Policy(jsonPolicyExample[i].name, jsonPolicyExample[i].room, jsonPolicyExample[i].command, jsonPolicyExample[i].condition, jsonPolicyExample[i].id) + policiesArray.push(newPolicy); + } + + return policiesArray; +} + +function showPolicies() { + var policiesArray = getFromBackend(); + var len = policiesArray.length; + var showPolicies = document.getElementById('show-policies'); + for (var i = 0; i < len; i++) { + var card = createCard(policiesArray[i]); + var collapseClassPolicy = createInitElement('div', 'collapse', `collapse-policy${policiesArray[i].id}`); + var cardBody = setCardBody(policiesArray[i]); + + collapseClassPolicy.setAttribute('aria-labelledby', `head-policy${policiesArray[i].id}`); + collapseClassPolicy.setAttribute('data-parent', '#show-policies'); + showPolicies.appendChild(card); + card.appendChild(collapseClassPolicy); + collapseClassPolicy.appendChild(cardBody); + } +} + +function createInitElement(type, className = '', id = '') { + var element = document.createElement(type); + + if (className !== '') { + element.setAttribute("class", className); + } + + if (id !== '') { + element.setAttribute("id", id); + } + return element; +} + +function setCondition() { + var policyElementsModifier = new PolicyElementsModifier(); + var conditionValue = document.getElementById('choose-condition').value; + var equalOrUnderOrAbove = document.getElementById('show-equal-under-above'); + var from = document.getElementById('show-from'); + var to = document.getElementById('show-to'); + var labelEqualOrUnderOrAbove = document.getElementById('label-equal-under-above'); + + policyElementsModifier.modify(conditionValue, from, to, labelEqualOrUnderOrAbove, equalOrUnderOrAbove); +} + +function addCondition(policy) { + var conditionValue = document.getElementById('choose-condition').value; + var sensor = `${document.getElementById('sensor').value} `; + + var condition = `${sensor + conditionValue} `; + + if (conditionValue === 'Between') { + condition = `${condition + document.getElementById('from').value} `; + condition = `${condition + document.getElementById('to').value}`; + } else { + condition = `${condition + document.getElementById('equal-under-above').value}`; + } + + policy.addCondition(condition); +} + +function setCommand(policy) { + var light = document.getElementById('light').value; + var airConditioner = document.getElementById('air-conditioner').value; + var shutters = document.getElementById('shutters').value; + + policy.addCommandToPolicy(airConditioner, light, shutters); +} + +function showCondition(policy, countCondition, elementID) { + var element = document.getElementById(elementID); + var elementCurrCondition = createInitElement('option', '', `option${countCondition}`); + var arrCondition = policy.condition.split(', '); + var currCondition = arrCondition[countCondition - 1]; + + elementCurrCondition.innerHTML = `${countCondition}: ${currCondition} `; + element.appendChild(elementCurrCondition); +} + +function initSettingToNewPolicy() { + var len = policy.getCountCondition(); + + for (var i = 1; i <= len; i++) { + var element = document.getElementById(`option${i}`); + element.remove(); + } + + policy.reset(); +} + +function addPolicy() { + policy.name = document.getElementById('policy-name').value; + policy.room = document.getElementById('policy-room').value; + setCommand(policy); + //this function will be enable when the route wiil be ready + //$.post('/policy', policy, function(){ }); + initSettingToNewPolicy(); +} + +function saveCondition() { + addCondition(policy); + var countCondition = policy.getCountCondition(); + showCondition(policy, countCondition, 'condition-list'); +} + +function mainFunctionPolicy() { + document.getElementById('choose-condition').addEventListener('click', setCondition); + document.getElementById('save-condition').addEventListener('click', saveCondition); + document.getElementById('save-policy').addEventListener('click', addPolicy); +} + +$(function () { + mainFunctionPolicy(); + showPolicies(); +}); diff --git a/edison/static/js/policy.model.js b/edison/static/js/policy.model.js new file mode 100644 index 0000000..cfc924b --- /dev/null +++ b/edison/static/js/policy.model.js @@ -0,0 +1,50 @@ +export class Policy { + #countCondition; + + constructor(name = '', room = '', command = '', condition = '', id = '') { + this.id = id; + this.name = name; + this.room = room; + this.command = command; + this.condition = condition; + this.#countCondition = 0; + } + + reset() { + this.id = ''; + this.name = ''; + this.room = ''; + this.command = ''; + this.condition = ''; + this.#countCondition = 0; + } + + addCondition(add_condition) { + if (this.condition !== '') { + this.condition = this.condition + ', '; + } + + this.condition = this.condition + add_condition; + this.#countCondition++ + } + + addCommandToPolicy(airConditioner, light, shutters) { + var sensors = []; + if (light) { + sensors.push('light ' + light); + } + + if (airConditioner) { + sensors.push('air conditioner ' + airConditioner);; + } + + if (shutters) { + sensors.push('shutters ' + shutters); + } + this.command = sensors.join(' ,'); + } + + getCountCondition() { + return this.#countCondition; + } +} diff --git a/edison/static/js/policyElementsModifier.js b/edison/static/js/policyElementsModifier.js new file mode 100644 index 0000000..1695c0c --- /dev/null +++ b/edison/static/js/policyElementsModifier.js @@ -0,0 +1,24 @@ + +export class PolicyElementsModifier { + #configByCondition; + + constructor() { + this.#configByCondition = { + '=': { displayTo: 'none', displayFrom: 'none', labelEqualOrUnderOrAbove: 'Equal', displayEqualOrUnderOrAbove: 'inline' }, + '<': { displayTo: 'none', displayFrom: 'none', labelEqualOrUnderOrAbove: 'Under', displayEqualOrUnderOrAbove: 'inline' }, + '>': { displayTo: 'none', displayFrom: 'none', labelEqualOrUnderOrAbove: 'Above', displayEqualOrUnderOrAbove: 'inline' }, + 'Between': { displayTo: 'inline', displayFrom: 'inline', labelEqualOrUnderOrAbove: '', displayEqualOrUnderOrAbove: 'none' } + } + } + + modify(condition, from, to, labelEqualOrUnderOrAbove, equalOrUnderOrAbove) { + if (this.#configByCondition.hasOwnProperty(condition)) { + var config = this.#configByCondition[condition]; + + to.style.display = config.displayTo; + from.style.display = config.displayFrom; + equalOrUnderOrAbove.style.display = config.displayEqualOrUnderOrAbove; + labelEqualOrUnderOrAbove.innerHTML = config.labelEqualOrUnderOrAbove; + } + } +} diff --git a/edison/templates/home.html b/edison/templates/home.html new file mode 100644 index 0000000..4077890 --- /dev/null +++ b/edison/templates/home.html @@ -0,0 +1,4 @@ +{% extends 'layout.html' %} + +{% block body %}{% endblock %} + diff --git a/edison/templates/include/footer.html b/edison/templates/include/footer.html new file mode 100644 index 0000000..97aac0b --- /dev/null +++ b/edison/templates/include/footer.html @@ -0,0 +1,28 @@ + diff --git a/edison/templates/include/header.html b/edison/templates/include/header.html new file mode 100644 index 0000000..fbff895 --- /dev/null +++ b/edison/templates/include/header.html @@ -0,0 +1,4 @@ + diff --git a/edison/templates/include/navgar.html b/edison/templates/include/navgar.html new file mode 100644 index 0000000..8fa5893 --- /dev/null +++ b/edison/templates/include/navgar.html @@ -0,0 +1,22 @@ + diff --git a/templates/index.html b/edison/templates/index.html similarity index 100% rename from templates/index.html rename to edison/templates/index.html diff --git a/edison/templates/layout.html b/edison/templates/layout.html new file mode 100644 index 0000000..f78c176 --- /dev/null +++ b/edison/templates/layout.html @@ -0,0 +1,26 @@ + + + + + Edison Project + + + + + + + + + + {% include 'include/navgar.html' %} + {% include 'include/header.html' %} + {% include 'include/footer.html' %} + {% block body %}{% endblock %} + + + diff --git a/edison/templates/policy.html b/edison/templates/policy.html new file mode 100644 index 0000000..4d33e50 --- /dev/null +++ b/edison/templates/policy.html @@ -0,0 +1,113 @@ +{% extends 'layout.html' %} +{% block body %} +
+ +
+ +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+

+ + +
+
+
+
+
+
+
+
+ +
+{% endblock %} diff --git a/flask_init.py b/flask_init.py deleted file mode 100644 index 14ad216..0000000 --- a/flask_init.py +++ /dev/null @@ -1,10 +0,0 @@ -from flask import Flask, render_template - -app = Flask(__name__) - -@app.route("/") -def index(): - return render_template('index.html') - -if __name__ == "__main__": - app.run(host='0.0.0.0') diff --git a/requirements.txt b/requirements.txt index 32e8968..62fa738 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,5 @@ Flask==1.1.1 +flask-sqlalchemy==2.4.1 +psycopg2-binary==2.8.5 +Flask-Migrate==2.5.3 +pytest==3.3.2 diff --git a/setup.sh b/setup.sh index e344782..147887d 100644 --- a/setup.sh +++ b/setup.sh @@ -1,5 +1,7 @@ #!/bin/bash +FLASK_PORT=5000 + echo "updating apt before installation" sudo apt-get update @@ -15,6 +17,12 @@ sudo apt-get install -y postgresql postgresql-contrib echo "install requirements" pip3 install -r /vagrant/requirements.txt -echo "running flask_init.py" -export FLASK_APP=/vagrant/flask_init.py -python3 -m flask run --host=0.0.0.0 >> /vagrant/log.log 2>&1 & +echo "configuring database" +sudo -u postgres createdb edison +sudo -u postgres psql -c "ALTER ROLE postgres WITH PASSWORD 'edison';" + +export FLASK_ENV=development + +echo "running app.py" +export FLASK_APP=/vagrant/edison/app.py +flask run -h 0.0.0.0 -p $FLASK_PORT >> /vagrant/edison/app.log 2>&1 & diff --git a/test/init_test.py b/tests/init_test.py similarity index 100% rename from test/init_test.py rename to tests/init_test.py