From aab1eb77b2f2bcd646159a46de5548eca65112ad Mon Sep 17 00:00:00 2001 From: Kolos Gergely Date: Mon, 29 Apr 2019 14:02:41 +0200 Subject: [PATCH 1/8] inital commit --- static/js/dom.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/js/dom.js b/static/js/dom.js index 0092afb7..3526239d 100644 --- a/static/js/dom.js +++ b/static/js/dom.js @@ -18,6 +18,8 @@ export let dom = { return elementToExtend.lastChild; }, init: function () { + let board = document.getElementById('boards'); + board.innerHTML = ''; // This function should run once, when the page is loaded. }, loadBoards: function () { From 975671267df96d014c2f74ad2db58596fa7f67d0 Mon Sep 17 00:00:00 2001 From: Kolos Gergely Date: Mon, 29 Apr 2019 14:04:40 +0200 Subject: [PATCH 2/8] development added --- static/js/dom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/dom.js b/static/js/dom.js index 3526239d..bf7ee347 100644 --- a/static/js/dom.js +++ b/static/js/dom.js @@ -20,6 +20,7 @@ export let dom = { init: function () { let board = document.getElementById('boards'); board.innerHTML = ''; + // This function should run once, when the page is loaded. }, loadBoards: function () { From c521acd1dbf2663bf9756baeffc6600b1519bfab Mon Sep 17 00:00:00 2001 From: Kolos Gergely Date: Mon, 29 Apr 2019 14:29:38 +0200 Subject: [PATCH 3/8] first commit on this branch --- static/js/dom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/js/dom.js b/static/js/dom.js index bf7ee347..3526239d 100644 --- a/static/js/dom.js +++ b/static/js/dom.js @@ -20,7 +20,6 @@ export let dom = { init: function () { let board = document.getElementById('boards'); board.innerHTML = ''; - // This function should run once, when the page is loaded. }, loadBoards: function () { From 83c2b882144a2283a8c4309d3db6207c39fb1d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mil=C3=A1n?= Date: Tue, 30 Apr 2019 13:38:36 +0200 Subject: [PATCH 4/8] database connection set up. need to set env variables --- database_connection.py | 105 ++++++++++++++++++++++++++++++++++++++ main.py | 5 +- persistence.py | 53 ++----------------- static/js/data_handler.js | 3 +- templates/index.html | 2 +- 5 files changed, 114 insertions(+), 54 deletions(-) create mode 100644 database_connection.py 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/main.py b/main.py index 9e25a1ac..0ea4bc7e 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ from flask import Flask, render_template, url_for from util import json_response - +import persistence import data_handler app = Flask(__name__) @@ -20,7 +20,8 @@ def get_boards(): """ All the boards """ - return data_handler.get_boards() + print(persistence.get_sql_boards()) + return persistence.get_sql_boards() @app.route("/get-cards/") diff --git a/persistence.py b/persistence.py index 1e1f1e3d..8672ea0c 100644 --- a/persistence.py +++ b/persistence.py @@ -1,51 +1,4 @@ -import csv +import database_connection -STATUSES_FILE = './data/statuses.csv' -BOARDS_FILE = './data/boards.csv' -CARDS_FILE = './data/cards.csv' - -_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 get_sql_boards(): + return database_connection.execute_select('SELECT * FROM boards;') diff --git a/static/js/data_handler.js b/static/js/data_handler.js index 66df0ba8..56e65ea3 100644 --- a/static/js/data_handler.js +++ b/static/js/data_handler.js @@ -8,7 +8,8 @@ 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"}]'); + console.log(json); fetch(url, { method: 'GET', credentials: 'same-origin' diff --git a/templates/index.html b/templates/index.html index 6f42f26e..5e37957c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -18,4 +18,4 @@

ProMan

Boards are loading...
- \ No newline at end of file + From e701cc8ca79e27f4893663d698063135eddbd797 Mon Sep 17 00:00:00 2001 From: Vitya Date: Tue, 30 Apr 2019 14:24:50 +0200 Subject: [PATCH 5/8] board_template done --- design-materials/design.html | 4 ++-- {design-materials => static/css}/design.css | 0 templates/board_template.html | 22 +++++++++++++++++++++ templates/index.html | 11 +++++++++-- 4 files changed, 33 insertions(+), 4 deletions(-) rename {design-materials => static/css}/design.css (100%) create mode 100644 templates/board_template.html 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

Board 1 - +
diff --git a/design-materials/design.css b/static/css/design.css similarity index 100% rename from design-materials/design.css rename to static/css/design.css diff --git a/templates/board_template.html b/templates/board_template.html new file mode 100644 index 00000000..b547f92a --- /dev/null +++ b/templates/board_template.html @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 5e37957c..8376569d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -11,11 +11,18 @@ - + + + + + +

ProMan

-
Boards are loading...
+
+ {% include "board_template.html" %} +
From a429f5ff5b4a5a963088163d1f48e93bd428523e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mil=C3=A1n?= Date: Thu, 2 May 2019 10:49:31 +0200 Subject: [PATCH 6/8] existing tables can be printed out --- static/js/data_handler.js | 1 - static/js/dom.js | 39 +++++++++++++++++++++-------------- templates/board_template.html | 5 +++-- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/static/js/data_handler.js b/static/js/data_handler.js index 56e65ea3..add66053 100644 --- a/static/js/data_handler.js +++ b/static/js/data_handler.js @@ -9,7 +9,6 @@ export let dataHandler = { // 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"}]'); - console.log(json); fetch(url, { method: 'GET', credentials: 'same-origin' diff --git a/static/js/dom.js b/static/js/dom.js index 3526239d..4793e65e 100644 --- a/static/js/dom.js +++ b/static/js/dom.js @@ -18,8 +18,6 @@ export let dom = { return elementToExtend.lastChild; }, init: function () { - let board = document.getElementById('boards'); - board.innerHTML = ''; // This function should run once, when the page is loaded. }, loadBoards: function () { @@ -28,25 +26,34 @@ export let dom = { dom.showBoards(boards); }); }, + getBoardTitle: function (title) { + const template = document.querySelector('#board'); + const clone = document.importNode(template.content, true); + + clone.querySelector('.board-title').textContent = title; + + return clone; + }, + + /* const cardElement = createCard( + 'Card title', + 'Some quick example text to build on the card title and make up the bulk of the card\'s content.', + 'https://cdn-images-1.medium.com/max/653/1*wMZnVAEei1xbY1v6sAbYxQ.png'); +document.querySelector('#container').appendChild(cardElement);*/ + + showBoards: function (boards) { // shows boards appending them to #boards div // it adds necessary event listeners also - - let boardList = ''; - - for(let board of boards){ - boardList += ` -
  • ${board.title}
  • - `; + //console.log(clone); + let boardList = document.createElement("section"); + boardList.id ="board"; + for (let board of boards) { + boardList.appendChild(this.getBoardTitle(board.title)) } - const outerHtml = ` -
      - ${boardList} -
    - `; - - this._appendToElement(document.querySelector('#boards'), outerHtml); + let container = document.querySelector('.board-container'); + container.appendChild(boardList); }, loadCards: function (boardId) { // retrieves cards and makes showCards called diff --git a/templates/board_template.html b/templates/board_template.html index b547f92a..909f40c8 100644 --- a/templates/board_template.html +++ b/templates/board_template.html @@ -1,5 +1,5 @@ \ No newline at end of file From 0aa1d4ed5a8e1c1e84c0e7a626ede69139afcb62 Mon Sep 17 00:00:00 2001 From: Vitya Date: Thu, 2 May 2019 17:08:25 +0200 Subject: [PATCH 7/8] add board button works fine --- data_handler.py | 4 ++++ main.py | 14 +++++++++++-- persistence.py | 5 +++++ static/css/design.css | 9 ++++++++ static/js/data_handler.js | 22 ++++++++++++++++++++ static/js/dom.js | 44 ++++++++++++++++++++++++++++++++------- static/js/main.js | 2 ++ templates/index.html | 7 +++++++ 8 files changed, 98 insertions(+), 9 deletions(-) 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/main.py b/main.py index 0ea4bc7e..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,7 @@ def get_boards(): """ All the boards """ - print(persistence.get_sql_boards()) + return persistence.get_sql_boards() @@ -34,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 8672ea0c..9e1c9929 100644 --- a/persistence.py +++ b/persistence.py @@ -2,3 +2,8 @@ def get_sql_boards(): return database_connection.execute_select('SELECT * FROM boards;') + + +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/static/css/design.css b/static/css/design.css index a8b8f2f2..177c538b 100644 --- a/static/css/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 add66053..67c17f21 100644 --- a/static/js/data_handler.js +++ b/static/js/data_handler.js @@ -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 4793e65e..143aa90d 100644 --- a/static/js/dom.js +++ b/static/js/dom.js @@ -26,21 +26,23 @@ export let dom = { dom.showBoards(boards); }); }, + + getBoardTitle: function (title) { const template = document.querySelector('#board'); const clone = document.importNode(template.content, true); - clone.querySelector('.board-title').textContent = title; + if (title[1] == 'New Board') { + console.log("YES") + clone.querySelector('.board-title').textContent = title[1]; + } else { + clone.querySelector('.board-title').textContent = title; + } + return clone; }, - /* const cardElement = createCard( - 'Card title', - 'Some quick example text to build on the card title and make up the bulk of the card\'s content.', - 'https://cdn-images-1.medium.com/max/653/1*wMZnVAEei1xbY1v6sAbYxQ.png'); -document.querySelector('#container').appendChild(cardElement);*/ - showBoards: function (boards) { // shows boards appending them to #boards div @@ -52,9 +54,37 @@ document.querySelector('#container').appendChild(cardElement);*/ boardList.appendChild(this.getBoardTitle(board.title)) } + let container = document.querySelector('.board-container'); + container.appendChild(boardList); + + }, + + + 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/index.html b/templates/index.html index 8376569d..3f0785b7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -21,6 +21,13 @@

    ProMan

    + +
    + +
    + +
    +
    {% include "board_template.html" %}
    From f3cf072eb9dc9b024ad9bc34ca60aeb27e7c2b72 Mon Sep 17 00:00:00 2001 From: Vitya Date: Thu, 2 May 2019 19:29:06 +0200 Subject: [PATCH 8/8] working --- static/js/dom.js | 2 +- templates/index.html | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/static/js/dom.js b/static/js/dom.js index 143aa90d..a12e7d76 100644 --- a/static/js/dom.js +++ b/static/js/dom.js @@ -33,8 +33,8 @@ export let dom = { const clone = document.importNode(template.content, true); if (title[1] == 'New Board') { - console.log("YES") clone.querySelector('.board-title').textContent = title[1]; + console.log("yes") } else { clone.querySelector('.board-title').textContent = title; } diff --git a/templates/index.html b/templates/index.html index 3f0785b7..3d5bf657 100644 --- a/templates/index.html +++ b/templates/index.html @@ -16,6 +16,16 @@ + + + + + + + + + +