diff --git a/data_handler.py b/data_handler.py
index 0d8d9086..7fed4f96 100644
--- a/data_handler.py
+++ b/data_handler.py
@@ -19,6 +19,10 @@ def get_boards():
return persistence.get_boards(force=True)
+def add_new_board(title):
+ return persistence.add_new_board(title)
+
+
def get_cards_for_board(board_id):
persistence.clear_cache()
all_cards = persistence.get_cards()
diff --git a/database_connection.py b/database_connection.py
new file mode 100644
index 00000000..7925d19b
--- /dev/null
+++ b/database_connection.py
@@ -0,0 +1,105 @@
+import os
+import psycopg2
+import psycopg2.extras
+
+
+def establish_connection(connection_data=None):
+ """
+ Create a database connection based on the :connection_data: parameter
+
+ :connection_data: Connection string attributes
+
+ :returns: psycopg2.connection
+ """
+ if connection_data is None:
+ connection_data = get_connection_data()
+ try:
+ connect_str = "dbname={} user={} host={} password={}".format(connection_data['dbname'],
+ connection_data['user'],
+ connection_data['host'],
+ connection_data['password'])
+ conn = psycopg2.connect(connect_str)
+ conn.autocommit = True
+ except psycopg2.DatabaseError as e:
+ print("Cannot connect to database.")
+ print(e)
+ else:
+ return conn
+
+
+def get_connection_data(db_name=None):
+ """
+ Give back a properly formatted dictionary based on the environment variables values which are started
+ with :MY__PSQL_: prefix
+
+ :db_name: optional parameter. By default it uses the environment variable value.
+ """
+ if db_name is None:
+ db_name = os.environ.get('MY_PSQL_DBNAME')
+
+ return {
+ 'dbname': db_name,
+ 'user': os.environ.get('MY_PSQL_USER'),
+ 'host': os.environ.get('MY_PSQL_HOST'),
+ 'password': os.environ.get('MY_PSQL_PASSWORD')
+ }
+
+
+def execute_script_file(file_path):
+ """
+ Execute script file based on the given file path.
+ Print the result of the execution to console.
+
+ Example:
+ > execute_script_file('db_schema/01_create_schema.sql')
+
+ :file_path: Relative path of the file to be executed.
+ """
+ package_directory = os.path.dirname(os.path.abspath(__file__))
+ full_path = os.path.join(package_directory, file_path)
+ with open(full_path) as script_file:
+ with establish_connection() as conn, \
+ conn.cursor() as cursor:
+ try:
+ sql_to_run = script_file.read()
+ cursor.execute(sql_to_run)
+ print("{} script executed successfully.".format(file_path))
+ except Exception as ex:
+ print("Execution of {} failed".format(file_path))
+ print(ex.args)
+
+
+def execute_select(statement, variables=None):
+ """
+ Execute SELECT statement optionally parameterized
+
+ Example:
+ > execute_select('SELECT %(title)s; FROM shows', variables={'title': 'Codecool'})
+
+ :statement: SELECT statement
+
+ :variables: optional parameter dict"""
+ result_set = []
+ with establish_connection() as conn:
+ with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cursor:
+ cursor.execute(statement, variables)
+ result_set = cursor.fetchall()
+ return result_set
+
+
+def execute_dml_statement(statement, variables=None):
+ """
+ Execute data manipulation query statement (optionally parameterized)
+
+ :statment: SQL statement
+
+ :variables: optional parameter dict"""
+ result = None
+ with establish_connection() as conn:
+ with conn.cursor() as cursor:
+ cursor.execute(statement, variables)
+ try:
+ result = cursor.fetchone()
+ except psycopg2.ProgrammingError as pe:
+ pass
+ return result
diff --git a/design-materials/design.html b/design-materials/design.html
index 60ece6a0..3a5e36d2 100644
--- a/design-materials/design.html
+++ b/design-materials/design.html
@@ -8,7 +8,7 @@
-
+
ProMan
@@ -18,7 +18,7 @@ ProMan
diff --git a/main.py b/main.py
index 9e25a1ac..5a5305d1 100644
--- a/main.py
+++ b/main.py
@@ -1,7 +1,9 @@
-from flask import Flask, render_template, url_for
+from flask import Flask, render_template, url_for, request
from util import json_response
-
+import persistence
import data_handler
+import json
+
app = Flask(__name__)
@@ -20,7 +22,8 @@ def get_boards():
"""
All the boards
"""
- return data_handler.get_boards()
+
+ return persistence.get_sql_boards()
@app.route("/get-cards/
")
@@ -33,6 +36,14 @@ def get_cards_for_board(board_id: int):
return data_handler.get_cards_for_board(board_id)
+@app.route('/add-new-board', methods=["GET", "POST"])
+@json_response
+def add_new_board():
+ title = request.get_json()['title']
+ return data_handler.add_new_board(title)
+
+
+
def main():
app.run(debug=True)
diff --git a/persistence.py b/persistence.py
index 1e1f1e3d..9e1c9929 100644
--- a/persistence.py
+++ b/persistence.py
@@ -1,51 +1,9 @@
-import csv
+import database_connection
-STATUSES_FILE = './data/statuses.csv'
-BOARDS_FILE = './data/boards.csv'
-CARDS_FILE = './data/cards.csv'
+def get_sql_boards():
+ return database_connection.execute_select('SELECT * FROM boards;')
-_cache = {} # We store cached data in this dict to avoid multiple file readings
-
-def _read_csv(file_name):
- """
- Reads content of a .csv file
- :param file_name: relative path to data file
- :return: OrderedDict
- """
- with open(file_name) as boards:
- rows = csv.DictReader(boards, delimiter=',', quotechar='"')
- formatted_data = []
- for row in rows:
- formatted_data.append(dict(row))
- return formatted_data
-
-
-def _get_data(data_type, file, force):
- """
- Reads defined type of data from file or cache
- :param data_type: key where the data is stored in cache
- :param file: relative path to data file
- :param force: if set to True, cache will be ignored
- :return: OrderedDict
- """
- if force or data_type not in _cache:
- _cache[data_type] = _read_csv(file)
- return _cache[data_type]
-
-
-def clear_cache():
- for k in list(_cache.keys()):
- _cache.pop(k)
-
-
-def get_statuses(force=False):
- return _get_data('statuses', STATUSES_FILE, force)
-
-
-def get_boards(force=False):
- return _get_data('boards', BOARDS_FILE, force)
-
-
-def get_cards(force=False):
- return _get_data('cards', CARDS_FILE, force)
+def add_new_board(title):
+ return database_connection.execute_dml_statement("""INSERT INTO boards (title)
+ VALUES (%(title)s) RETURNING *""", dict(title=title))
diff --git a/design-materials/design.css b/static/css/design.css
similarity index 94%
rename from design-materials/design.css
rename to static/css/design.css
index a8b8f2f2..177c538b 100644
--- a/design-materials/design.css
+++ b/static/css/design.css
@@ -37,6 +37,15 @@ button{
margin: 0 auto;
}
+.board-template {
+ background: #ffffff90;
+}
+
+.add-new-board-button {
+ text-align: center;
+ margin-bottom: 5vh;
+}
+
section.board{
margin: 20px;
border: aliceblue;
diff --git a/static/js/data_handler.js b/static/js/data_handler.js
index 66df0ba8..67c17f21 100644
--- a/static/js/data_handler.js
+++ b/static/js/data_handler.js
@@ -8,7 +8,7 @@ export let dataHandler = {
_api_get: function (url, callback) {
// it is not called from outside
// loads data from API, parses it and calls the callback with it
-
+ const json = JSON.parse('[{"id": "1", "title": "Board 1"}, {"id": "2", "title": "Board 2"}]');
fetch(url, {
method: 'GET',
credentials: 'same-origin'
@@ -19,6 +19,17 @@ export let dataHandler = {
_api_post: function (url, data, callback) {
// it is not called from outside
// sends the data to the API, and calls callback function
+ fetch(url, {
+ method: 'POST',
+ credentials: 'same-origin',
+ headers: {
+ "Content-Type": "application/json",
+ // "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: JSON.stringify(data)
+ })
+ .then(response => response.json()) // parse the response as JSON
+ .then(json_response => callback(json_response));
},
init: function () {
},
@@ -32,6 +43,17 @@ export let dataHandler = {
callback(response);
});
},
+
+ addNewBoard: function (callback) {
+ // the boards are retrieved and then the callback function is called with the boards
+
+ // Here we use an arrow function to keep the value of 'this' on dataHandler.
+ // if we would use function(){...} here, the value of 'this' would change.
+ this._api_post('/add-new-board', {title: 'New Board'}, (response) => {
+ this._data = response;
+ callback(response);
+ });
+ },
getBoard: function (boardId, callback) {
// the board is retrieved and then the callback function is called with the board
},
diff --git a/static/js/dom.js b/static/js/dom.js
index 0092afb7..a12e7d76 100644
--- a/static/js/dom.js
+++ b/static/js/dom.js
@@ -26,26 +26,65 @@ export let dom = {
dom.showBoards(boards);
});
},
+
+
+ getBoardTitle: function (title) {
+ const template = document.querySelector('#board');
+ const clone = document.importNode(template.content, true);
+
+ if (title[1] == 'New Board') {
+ clone.querySelector('.board-title').textContent = title[1];
+ console.log("yes")
+ } else {
+ clone.querySelector('.board-title').textContent = title;
+ }
+
+
+ return clone;
+ },
+
+
showBoards: function (boards) {
// shows boards appending them to #boards div
// it adds necessary event listeners also
+ //console.log(clone);
+ let boardList = document.createElement("section");
+ boardList.id ="board";
+ for (let board of boards) {
+ boardList.appendChild(this.getBoardTitle(board.title))
+ }
- let boardList = '';
+ let container = document.querySelector('.board-container');
+ container.appendChild(boardList);
- for(let board of boards){
- boardList += `
- ${board.title}
- `;
- }
+ },
- const outerHtml = `
-
- `;
- this._appendToElement(document.querySelector('#boards'), outerHtml);
+ addNewBoardEventListener: function () {
+ let addNewBoardButton = document.getElementsByClassName("add-new-board-button");
+ addNewBoardButton[0].addEventListener("click", this.addNewBoardClickHandler)
},
+
+
+ addNewBoardClickHandler: function () {
+ dataHandler.addNewBoard(function (newCardTitle) {
+ dom.showBoard(newCardTitle);
+ })
+
+ },
+
+ showBoard: function (newCardTitle) {
+ let boardList = document.createElement("section");
+ boardList.id ="board";
+
+ boardList.appendChild(this.getBoardTitle(newCardTitle));
+
+
+
+ let container = document.querySelector('.board-container');
+ container.appendChild(boardList);
+ },
+
loadCards: function (boardId) {
// retrieves cards and makes showCards called
},
diff --git a/static/js/main.js b/static/js/main.js
index f9298ae2..4311419e 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -6,6 +6,8 @@ function init() {
dom.init();
// loads the boards to the screen
dom.loadBoards();
+ // add event listener on 'add new board' button
+ dom.addNewBoardEventListener()
}
diff --git a/templates/board_template.html b/templates/board_template.html
new file mode 100644
index 00000000..909f40c8
--- /dev/null
+++ b/templates/board_template.html
@@ -0,0 +1,23 @@
+
+
+
+
\ No newline at end of file
diff --git a/templates/index.html b/templates/index.html
index 6f42f26e..3d5bf657 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -11,11 +11,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ProMan
- Boards are loading...
+
+
+
+
+
+
+
+
+ {% include "board_template.html" %}
+
-