From ddb67593f60305eaf3ca940241cec39557e3b4e9 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Mon, 9 Sep 2019 14:26:05 +0200 Subject: [PATCH 001/182] Add connection.py template as data_handler --- data_handler.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 data_handler.py diff --git a/data_handler.py b/data_handler.py new file mode 100644 index 000000000..fce2de444 --- /dev/null +++ b/data_handler.py @@ -0,0 +1,38 @@ +import csv +import os + +DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data.csv' +DATA_HEADER = ['id', 'title', 'user_story', 'acceptance_criteria', 'business_value', 'estimation', 'status'] +STATUSES = ['planning', 'todo', 'in progress', 'review', 'done'] + + +def get_all_user_story(): + with open(DATA_FILE_PATH, 'r', newline='') as f: + reader = csv.DictReader(f) + database = [dict(row) for row in reader] + return database + + +def write_all_user_story(stories): + with open(DATA_FILE_PATH, 'w', newline='') as f: + writer = csv.DictWriter(f, DATA_HEADER) + writer.writeheader() + writer.writerows(stories) + + +def add_story(stories, inputs): + stories.append(inputs) + return stories + + +def update_story(stories, inputs): + for i, story in enumerate(stories): + if story[DATA_HEADER[0]] == inputs['id']: + stories[i] = inputs + return stories + + +def generate_id(stories): + ordered_stories = sorted(stories, key=lambda x: DATA_HEADER[0]) + return str(int(ordered_stories[-1][DATA_HEADER[0]]) + 1) + From 9b028a65627349f7b6d447c0cbde3510d86f88f3 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 10:28:26 +0200 Subject: [PATCH 002/182] Base server module --- server.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 server.py diff --git a/server.py b/server.py new file mode 100644 index 000000000..3e3e9cfe8 --- /dev/null +++ b/server.py @@ -0,0 +1,28 @@ + +from flask import Flask, render_template, request + +import data_handler + +app = Flask(__name__) + + +@app.route('/') +def hello_world(): + return 'Hello World!' + + +@app.route('/add-question', methods=["GET", "POST"]) +def add_question(): + if request.method == 'POST': + reqv = request.form.to_dict() + if reqv["question_id"] == '': + asd = data_handler.generate_question_dict(reqv) + elif reqv["question_id"] != '': + asd = data_handler.generate_answer_dict(reqv) + app.logger.info(asd) + return render_template('test.html', d=asd) + return render_template('add-question.html') + + +if __name__ == '__main__': + app.run(debug=True) From 78492a9e091ffb8c51afdc3e4f285de4127c1b4f Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 10:29:37 +0200 Subject: [PATCH 003/182] Added: dictionary generator functions for adding questions and answers --- data_handler.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/data_handler.py b/data_handler.py index fce2de444..3dd76faf0 100644 --- a/data_handler.py +++ b/data_handler.py @@ -1,5 +1,6 @@ import csv import os +import time DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data.csv' DATA_HEADER = ['id', 'title', 'user_story', 'acceptance_criteria', 'business_value', 'estimation', 'status'] @@ -36,3 +37,31 @@ def generate_id(stories): ordered_stories = sorted(stories, key=lambda x: DATA_HEADER[0]) return str(int(ordered_stories[-1][DATA_HEADER[0]]) + 1) + +def gen_id(): + return 0 + + +def generate_question_dict(data): + question_data = {} + + question_data.update(id=gen_id()) + question_data.update(submission_time=str(time.time())) + question_data.update(view_number=0) + question_data.update(vote_number=0) + question_data.update(title=data["title"]) + question_data.update(message=data["message"]) + question_data.update(image=data["image"]) + return question_data + + +def generate_answer_dict(data): + answer_data = {} + + answer_data.update(id=gen_id()) + answer_data.update(submission_time=str(time.time())) + answer_data.update(vote_number=0) + answer_data.update(question_id=data["question_id"]) + answer_data.update(message=data["message"]) + answer_data.update(image=data["image"]) + return answer_data From e60c20f84854b78d93d425c9734386fcb024b373 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 10:44:23 +0200 Subject: [PATCH 004/182] Added working data directory --- templates/add-question.html | 23 +++++++++++++++++++++++ templates/test.html | 12 ++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 templates/add-question.html create mode 100644 templates/test.html diff --git a/templates/add-question.html b/templates/add-question.html new file mode 100644 index 000000000..17eac3199 --- /dev/null +++ b/templates/add-question.html @@ -0,0 +1,23 @@ + + + + + Title + + +

Ask your question

+
+
+
+ + +
+
+ + +
+ +
+
+ + \ No newline at end of file diff --git a/templates/test.html b/templates/test.html new file mode 100644 index 000000000..3e67ca142 --- /dev/null +++ b/templates/test.html @@ -0,0 +1,12 @@ + + + + + Title + + +{% autoescape false %} +{{ d }} +{% endautoescape %} + + From f5f50f710227319e897524f28f765587b4615c0c Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 11:29:17 +0200 Subject: [PATCH 005/182] Added data files --- data/answer.csv | 3 +++ data/question.csv | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 data/answer.csv create mode 100644 data/question.csv diff --git a/data/answer.csv b/data/answer.csv new file mode 100644 index 000000000..3ddfa1ffb --- /dev/null +++ b/data/answer.csv @@ -0,0 +1,3 @@ +id,submission_time,vote_number,question_id,message,image +0,1493398154,4,0,"You need to use brackets: my_list = []", +1,1493088154,35,0,"Look it up in the Python docs", diff --git a/data/question.csv b/data/question.csv new file mode 100644 index 000000000..e65825dc3 --- /dev/null +++ b/data/question.csv @@ -0,0 +1,14 @@ +id,submission_time,view_number,vote_number,title,message,image +0,1493368154,29,7,"How to make lists in Python?","I am totally new to this, any hints?", +1,1493068124,15,9,"Wordpress loading multiple jQuery Versions","I developed a plugin that uses the jquery booklet plugin (http://builtbywill.com/booklet/#/) this plugin binds a function to $ so I cann call $('.myBook').booklet(); + +I could easy managing the loading order with wp_enqueue_script so first I load jquery then I load booklet so everything is fine. + +BUT in my theme i also using jquery via webpack so the loading order is now following: + +jquery +booklet +app.js (bundled file with webpack, including jquery)","images/image1.png" +2,1493015432,1364,57,"Drawing canvas with an image picked with Cordova Camera Plugin","I'm getting an image from device and drawing a canvas with filters using Pixi JS. It works all well using computer to get an image. But when I'm on IOS, it throws errors such as cross origin issue, or that I'm trying to use an unknown format. + +This is the code I'm using to draw the image (that works on web/desktop but not cordova built ios app)", From 4b4540a508b442872ae79987cd7106a69de67cca Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 11:30:16 +0200 Subject: [PATCH 006/182] Added csvreader for answers and questions --- connection.py | 20 ++++++++++++++++++++ data_handler.py | 38 ++++++++++++-------------------------- server.py | 6 ++++-- 3 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 connection.py diff --git a/connection.py b/connection.py new file mode 100644 index 000000000..0f32c8537 --- /dev/null +++ b/connection.py @@ -0,0 +1,20 @@ +# ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' +import csv, os + + +QUESTION_ANSWER_DATA_HEADER = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] +ANSWER_DATA_HEADER = ['id', 'submission_time', 'vote_number', 'question_id', 'message,image'] + + +def csv_to_dict(file_path): + with open(file_path, 'r', newline='') as f: + reader = csv.DictReader(f) + database = [dict(row) for row in reader] + return database + + +def write_all_user_story(file_path, data): + with open(file_path, 'w', newline='') as f: + writer = csv.DictWriter(f, ANSWER_DATA_HEADER) + writer.writeheader() + writer.writerows(data) diff --git a/data_handler.py b/data_handler.py index 3dd76faf0..50b04ab87 100644 --- a/data_handler.py +++ b/data_handler.py @@ -2,40 +2,26 @@ import os import time -DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data.csv' -DATA_HEADER = ['id', 'title', 'user_story', 'acceptance_criteria', 'business_value', 'estimation', 'status'] -STATUSES = ['planning', 'todo', 'in progress', 'review', 'done'] +# ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' +import connection +ANSWER_DATA_FILE_PATH = os.getcwd() + "/../data/answer.csv" +QUESTION_DATA_FILE_PATH = os.getcwd() + "/../data/question.csv" -def get_all_user_story(): - with open(DATA_FILE_PATH, 'r', newline='') as f: - reader = csv.DictReader(f) - database = [dict(row) for row in reader] - return database - - -def write_all_user_story(stories): - with open(DATA_FILE_PATH, 'w', newline='') as f: - writer = csv.DictWriter(f, DATA_HEADER) - writer.writeheader() - writer.writerows(stories) - -def add_story(stories, inputs): - stories.append(inputs) - return stories +def get_answer(): + database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) + return database -def update_story(stories, inputs): - for i, story in enumerate(stories): - if story[DATA_HEADER[0]] == inputs['id']: - stories[i] = inputs - return stories +def get_answer(): + database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) + return database def generate_id(stories): - ordered_stories = sorted(stories, key=lambda x: DATA_HEADER[0]) - return str(int(ordered_stories[-1][DATA_HEADER[0]]) + 1) + ordered_stories = sorted(stories, key=lambda x: ANSWER_DATA_HEADER[0]) + return str(int(ordered_stories[-1][ANSWER_DATA_HEADER[0]]) + 1) def gen_id(): diff --git a/server.py b/server.py index 3e3e9cfe8..5a907b6f7 100644 --- a/server.py +++ b/server.py @@ -20,9 +20,11 @@ def add_question(): elif reqv["question_id"] != '': asd = data_handler.generate_answer_dict(reqv) app.logger.info(asd) - return render_template('test.html', d=asd) - return render_template('add-question.html') + return render_template('add-question.html', question_id="") +@app.route("/test") +def test(): + return str(data_handler.get_answer()) if __name__ == '__main__': app.run(debug=True) From a3b5e2d03c35c4dce3475fa2929b7413d358c055 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 12:43:34 +0200 Subject: [PATCH 007/182] Added csv writer and append feature --- connection.py | 10 ++++++++-- server.py | 9 ++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/connection.py b/connection.py index 0f32c8537..15316b79a 100644 --- a/connection.py +++ b/connection.py @@ -13,8 +13,14 @@ def csv_to_dict(file_path): return database -def write_all_user_story(file_path, data): +def dict_to_csv(file_path, data, is_answers=True): with open(file_path, 'w', newline='') as f: - writer = csv.DictWriter(f, ANSWER_DATA_HEADER) + writer = csv.DictWriter(f, ANSWER_DATA_HEADER if is_answers else QUESTION_ANSWER_DATA_HEADER) writer.writeheader() writer.writerows(data) + + +def append_to_csv(file_path, data): + with open(file_path, 'a', newline='') as f: + writer = csv.writer(f) + writer.writerows([data.values()]) diff --git a/server.py b/server.py index 5a907b6f7..f74d66d6c 100644 --- a/server.py +++ b/server.py @@ -15,16 +15,19 @@ def hello_world(): def add_question(): if request.method == 'POST': reqv = request.form.to_dict() + asd = data_handler.get_answers() if reqv["question_id"] == '': - asd = data_handler.generate_question_dict(reqv) + asd.append(data_handler.generate_question_dict(reqv)) elif reqv["question_id"] != '': - asd = data_handler.generate_answer_dict(reqv) + asd.append(data_handler.generate_answer_dict(reqv)) app.logger.info(asd) + data_handler.add_entry(data_handler.generate_question_dict(reqv)) return render_template('add-question.html', question_id="") + @app.route("/test") def test(): - return str(data_handler.get_answer()) + return str(data_handler.get_answers()) if __name__ == '__main__': app.run(debug=True) From 6bea474654709ea946441bac27dec5b92486147f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 12:49:37 +0200 Subject: [PATCH 008/182] Create Home page and List html --- data_handler.py | 67 ---------------------------------------- templates/home_page.html | 11 +++++++ templates/list.html | 25 +++++++++++++++ 3 files changed, 36 insertions(+), 67 deletions(-) delete mode 100644 data_handler.py create mode 100644 templates/home_page.html create mode 100644 templates/list.html diff --git a/data_handler.py b/data_handler.py deleted file mode 100644 index 3dd76faf0..000000000 --- a/data_handler.py +++ /dev/null @@ -1,67 +0,0 @@ -import csv -import os -import time - -DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data.csv' -DATA_HEADER = ['id', 'title', 'user_story', 'acceptance_criteria', 'business_value', 'estimation', 'status'] -STATUSES = ['planning', 'todo', 'in progress', 'review', 'done'] - - -def get_all_user_story(): - with open(DATA_FILE_PATH, 'r', newline='') as f: - reader = csv.DictReader(f) - database = [dict(row) for row in reader] - return database - - -def write_all_user_story(stories): - with open(DATA_FILE_PATH, 'w', newline='') as f: - writer = csv.DictWriter(f, DATA_HEADER) - writer.writeheader() - writer.writerows(stories) - - -def add_story(stories, inputs): - stories.append(inputs) - return stories - - -def update_story(stories, inputs): - for i, story in enumerate(stories): - if story[DATA_HEADER[0]] == inputs['id']: - stories[i] = inputs - return stories - - -def generate_id(stories): - ordered_stories = sorted(stories, key=lambda x: DATA_HEADER[0]) - return str(int(ordered_stories[-1][DATA_HEADER[0]]) + 1) - - -def gen_id(): - return 0 - - -def generate_question_dict(data): - question_data = {} - - question_data.update(id=gen_id()) - question_data.update(submission_time=str(time.time())) - question_data.update(view_number=0) - question_data.update(vote_number=0) - question_data.update(title=data["title"]) - question_data.update(message=data["message"]) - question_data.update(image=data["image"]) - return question_data - - -def generate_answer_dict(data): - answer_data = {} - - answer_data.update(id=gen_id()) - answer_data.update(submission_time=str(time.time())) - answer_data.update(vote_number=0) - answer_data.update(question_id=data["question_id"]) - answer_data.update(message=data["message"]) - answer_data.update(image=data["image"]) - return answer_data diff --git a/templates/home_page.html b/templates/home_page.html new file mode 100644 index 000000000..56efc19bc --- /dev/null +++ b/templates/home_page.html @@ -0,0 +1,11 @@ + + + + + Welcome! | Ask Mate + + +

Ask Mate

+ Sorted by latest questions + + \ No newline at end of file diff --git a/templates/list.html b/templates/list.html new file mode 100644 index 000000000..329c12bdf --- /dev/null +++ b/templates/list.html @@ -0,0 +1,25 @@ + + + + + List of questions | Ask Mate + + +

Questions sorted by latest questions

+ + + {% for header in fieldnames %} + + {% endfor %} + + {% for questions in sorted_questions %} + + {% for data in questions.values() %} + + {% endfor %} + {% endfor %} + +
{{ header|capitalize|replace('_', ' ') }}
{{ data }}
+ + + \ No newline at end of file From e660fcfb753b8ef351bdd34ad3760121f22c35d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 12:54:04 +0200 Subject: [PATCH 009/182] Create list functionality --- data_handler.py | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ server.py | 12 +++++-- 2 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 data_handler.py diff --git a/data_handler.py b/data_handler.py new file mode 100644 index 000000000..9f2b515b4 --- /dev/null +++ b/data_handler.py @@ -0,0 +1,87 @@ +import csv +import os +import time + +DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data.csv' +DATA_HEADER = ['id', 'title', 'user_story', 'acceptance_criteria', 'business_value', 'estimation', 'status'] +STATUSES = ['planning', 'todo', 'in progress', 'review', 'done'] + + +def get_all_user_story(): + with open(DATA_FILE_PATH, 'r', newline='') as f: + reader = csv.DictReader(f) + database = [dict(row) for row in reader] + return database + + +def write_all_user_story(stories): + with open(DATA_FILE_PATH, 'w', newline='') as f: + writer = csv.DictWriter(f, DATA_HEADER) + writer.writeheader() + writer.writerows(stories) + + +def add_story(stories, inputs): + stories.append(inputs) + return stories + + +def update_story(stories, inputs): + for i, story in enumerate(stories): + if story[DATA_HEADER[0]] == inputs['id']: + stories[i] = inputs + return stories + + +def generate_id(stories): + ordered_stories = sorted(stories, key=lambda x: DATA_HEADER[0]) + return str(int(ordered_stories[-1][DATA_HEADER[0]]) + 1) + + +def gen_id(): + return 0 + + +def generate_question_dict(data): + question_data = {} + + question_data.update(id=gen_id()) + question_data.update(submission_time=str(time.time())) + question_data.update(view_number=0) + question_data.update(vote_number=0) + question_data.update(title=data["title"]) + question_data.update(message=data["message"]) + question_data.update(image=data["image"]) + return question_data + + +def generate_answer_dict(data): + answer_data = {} + + answer_data.update(id=gen_id()) + answer_data.update(submission_time=str(time.time())) + answer_data.update(vote_number=0) + answer_data.update(question_id=data["question_id"]) + answer_data.update(message=data["message"]) + answer_data.update(image=data["image"]) + return answer_data + + +def sorting_data(data, attribute, order_flag): + ''' + :param attribute: list of dictionaries + :param attribute: By which the data is sorted- + :param order_flag: The order is ascending (False) or descending (True). + :return: The sorted data. + ''' + try: + sorted_data = sorted(data, key=lambda x: int(x[attribute]) if x[attribute].isdigit() else x[attribute], reverse=order_flag) + except AttributeError: + sorted_data = sorted(data, key=lambda x: x[attribute], reverse=order_flag) + return sorted_data + + +def temporary_get_data(): + data = [{'id': 4, 'submission_time': 13, 'vote_number': '42', 'message': 'ersad'}, {'id': 2, 'submission_time': 123, 'vote_number': '25', 'message': 'csad'}, + {'id': 3, 'submission_time': 123, 'vote_number': '32', 'message': 'asad'}, {'id': 1, 'submission_time': 73, 'vote_number': '32', 'message': 'abad'}] + return data \ No newline at end of file diff --git a/server.py b/server.py index 3e3e9cfe8..e1c24dd2b 100644 --- a/server.py +++ b/server.py @@ -5,10 +5,16 @@ app = Flask(__name__) - @app.route('/') -def hello_world(): - return 'Hello World!' +def route_home_page(): + return render_template('home_page.html') + +@app.route('/list') +def list_questions(): + fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] + questions = data_handler.temporary_get_data() + sorted_questions = data_handler.sorting_data(questions, 'id', True) + return render_template('list.html', fieldnames=fieldnames, sorted_questions=sorted_questions) @app.route('/add-question', methods=["GET", "POST"]) From afaba313a6418cf3d022087dfde4c782ff21d388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 13:03:44 +0200 Subject: [PATCH 010/182] Create list functionality --- data_handler.py | 12 ++++-------- server.py | 2 +- templates/list.html | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/data_handler.py b/data_handler.py index a1bcf2705..bebf3fa77 100644 --- a/data_handler.py +++ b/data_handler.py @@ -5,16 +5,16 @@ # ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' import connection -ANSWER_DATA_FILE_PATH = os.getcwd() + "/../data/answer.csv" -QUESTION_DATA_FILE_PATH = os.getcwd() + "/../data/question.csv" +ANSWER_DATA_FILE_PATH = os.getcwd() + "/data/answer.csv" +QUESTION_DATA_FILE_PATH = os.getcwd() + "/data/question.csv" def get_answer(): - database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) + database = connection.csv_to_dict(ANSWER_DATA_FILE_PATH) return database -def get_answer(): +def get_question(): database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) return database @@ -67,7 +67,3 @@ def sorting_data(data, attribute, order_flag): return sorted_data -def temporary_get_data(): - data = [{'id': 4, 'submission_time': 13, 'vote_number': '42', 'message': 'ersad'}, {'id': 2, 'submission_time': 123, 'vote_number': '25', 'message': 'csad'}, - {'id': 3, 'submission_time': 123, 'vote_number': '32', 'message': 'asad'}, {'id': 1, 'submission_time': 73, 'vote_number': '32', 'message': 'abad'}] - return data \ No newline at end of file diff --git a/server.py b/server.py index 50bd3af5c..fa5cdfb34 100644 --- a/server.py +++ b/server.py @@ -12,7 +12,7 @@ def route_home_page(): @app.route('/list') def list_questions(): fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] - questions = data_handler.temporary_get_data() + questions = data_handler.get_question() sorted_questions = data_handler.sorting_data(questions, 'id', True) return render_template('list.html', fieldnames=fieldnames, sorted_questions=sorted_questions) diff --git a/templates/list.html b/templates/list.html index 329c12bdf..2b0209056 100644 --- a/templates/list.html +++ b/templates/list.html @@ -6,7 +6,7 @@

Questions sorted by latest questions

- +
{% for header in fieldnames %} From 2af60994dd98eb43f4b16a98235ef0eaaee2264a Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 13:05:47 +0200 Subject: [PATCH 011/182] Fixed dictinary value type --- data_handler.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/data_handler.py b/data_handler.py index 50b04ab87..01f3aa276 100644 --- a/data_handler.py +++ b/data_handler.py @@ -9,16 +9,20 @@ QUESTION_DATA_FILE_PATH = os.getcwd() + "/../data/question.csv" -def get_answer(): +def get_answers(): database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) return database -def get_answer(): +def get_questions(): database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) return database +def add_entry(entry): + connection.append_to_csv(ANSWER_DATA_FILE_PATH, entry) + + def generate_id(stories): ordered_stories = sorted(stories, key=lambda x: ANSWER_DATA_HEADER[0]) return str(int(ordered_stories[-1][ANSWER_DATA_HEADER[0]]) + 1) @@ -31,10 +35,10 @@ def gen_id(): def generate_question_dict(data): question_data = {} - question_data.update(id=gen_id()) + question_data.update(id=str(gen_id())) question_data.update(submission_time=str(time.time())) - question_data.update(view_number=0) - question_data.update(vote_number=0) + question_data.update(view_number=str(0)) + question_data.update(vote_number=str(0)) question_data.update(title=data["title"]) question_data.update(message=data["message"]) question_data.update(image=data["image"]) @@ -46,7 +50,7 @@ def generate_answer_dict(data): answer_data.update(id=gen_id()) answer_data.update(submission_time=str(time.time())) - answer_data.update(vote_number=0) + answer_data.update(vote_number=str(0)) answer_data.update(question_id=data["question_id"]) answer_data.update(message=data["message"]) answer_data.update(image=data["image"]) From 400b02fe78d555cf02ba6a2aad2a1507d1c7072e Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 13:06:55 +0200 Subject: [PATCH 012/182] Html layout changes --- templates/add-question.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/add-question.html b/templates/add-question.html index 17eac3199..db15f9f36 100644 --- a/templates/add-question.html +++ b/templates/add-question.html @@ -5,16 +5,16 @@ Title -

Ask your question

-
-
+
+

Ask your question

+
- +

- +
From fa1b0b18d6a3ec0d927a62c732dd2a9e6bd7351a Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 13:25:31 +0200 Subject: [PATCH 013/182] Fixed merging issues --- data_handler.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/data_handler.py b/data_handler.py index dba9c65a7..93c54c548 100644 --- a/data_handler.py +++ b/data_handler.py @@ -18,6 +18,9 @@ def get_questions(): database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) return database +def add_entry(entry): + connection.append_to_csv(QUESTION_DATA_FILE_PATH, entry) + def generate_id(stories): ordered_stories = sorted(stories, key=lambda x: ANSWER_DATA_HEADER[0]) @@ -31,10 +34,10 @@ def gen_id(): def generate_question_dict(data): question_data = {} - question_data.update(id=gen_id()) + question_data.update(id=str(gen_id())) question_data.update(submission_time=str(time.time())) - question_data.update(view_number=0) - question_data.update(vote_number=0) + question_data.update(view_number=str(0)) + question_data.update(vote_number=str(0)) question_data.update(title=data["title"]) question_data.update(message=data["message"]) question_data.update(image=data["image"]) @@ -44,9 +47,9 @@ def generate_question_dict(data): def generate_answer_dict(data): answer_data = {} - answer_data.update(id=gen_id()) + answer_data.update(id=str(gen_id())) answer_data.update(submission_time=str(time.time())) - answer_data.update(vote_number=0) + answer_data.update(vote_number=str(0)) answer_data.update(question_id=data["question_id"]) answer_data.update(message=data["message"]) answer_data.update(image=data["image"]) From 3afcbc92530b4346b858719b777f62a24c4afa00 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 10 Sep 2019 13:26:18 +0200 Subject: [PATCH 014/182] Add display_question.html template --- templates/display_question.html | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 templates/display_question.html diff --git a/templates/display_question.html b/templates/display_question.html new file mode 100644 index 000000000..fbb81192d --- /dev/null +++ b/templates/display_question.html @@ -0,0 +1,44 @@ + + + + + Question + + +

Question ID: {{ question.id }}

+ +

Question data

+
{{ header|capitalize|replace('_', ' ') }}
+ + + {% for header in question.keys() %} + + + + + + + {% for header in question.keys() %} + + + +
{{ header }}
{{ question.get(header) }}
+ +

Answers to the question

+ + + + {% for header in answer.keys() %} + + + + + {% for answer in answers %} + + {% for header in answer.keys() %} + + + +
{{ header }}
{{ question.get(header) }}
+ + \ No newline at end of file From c238014483db7be6e64e4f234c000b16b6726069 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 10 Sep 2019 13:27:13 +0200 Subject: [PATCH 015/182] Add get_question and get_answer to data_handler module --- data_handler.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data_handler.py b/data_handler.py index 3dd76faf0..c69b27785 100644 --- a/data_handler.py +++ b/data_handler.py @@ -65,3 +65,17 @@ def generate_answer_dict(data): answer_data.update(message=data["message"]) answer_data.update(image=data["image"]) return answer_data + + +def get_question(question_id, question_database): + for question_data in question_database: + if question_data['id'] == question_id: + return question_data + + +def get_answers(question_id, answer_database): + answers_of_question = [] + for answer_data in answer_database: + if answer_data['question_id'] == question_id: + answers_of_question.append(answer_data) + return answers_of_question From 5fc985a8a9a95da110a2958c1b408cee3fffede8 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 10 Sep 2019 13:28:23 +0200 Subject: [PATCH 016/182] Add /question/ route to server module --- server.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server.py b/server.py index 3e3e9cfe8..57d3bdfdd 100644 --- a/server.py +++ b/server.py @@ -24,5 +24,14 @@ def add_question(): return render_template('add-question.html') +@app.route('/question/') +def question_display(question_id): + question_database = {} + answer_database = {} + question = data_handler.get_question(question_id, question_database) + related_answers = data_handler.get_answers(question_id, answer_database) + return render_template('display_question.html', question=question, answers=related_answers) + + if __name__ == '__main__': app.run(debug=True) From ae0330c3f3797064d996cc16a74ba6254411769c Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 13:46:05 +0200 Subject: [PATCH 017/182] Refactor question and answer stroring code --- data_handler.py | 4 ++-- server.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/data_handler.py b/data_handler.py index 93c54c548..a9ecf4916 100644 --- a/data_handler.py +++ b/data_handler.py @@ -18,8 +18,8 @@ def get_questions(): database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) return database -def add_entry(entry): - connection.append_to_csv(QUESTION_DATA_FILE_PATH, entry) +def add_entry(entry, is_answer=False): + connection.append_to_csv(QUESTION_DATA_FILE_PATH if is_answer else ANSWER_DATA_FILE_PATH, entry) def generate_id(stories): diff --git a/server.py b/server.py index 1041db5cd..d9be20500 100644 --- a/server.py +++ b/server.py @@ -21,13 +21,20 @@ def list_questions(): def add_question(): if request.method == 'POST': reqv = request.form.to_dict() - asd = data_handler.get_answers() + answers = data_handler.get_answers() + questions = data_handler.get_questions() + if reqv["question_id"] == '': - asd.append(data_handler.generate_question_dict(reqv)) + answer = data_handler.generate_question_dict(reqv) + answers.append(answer) + data_handler.add_entry(answer,True) + elif reqv["question_id"] != '': - asd.append(data_handler.generate_answer_dict(reqv)) - app.logger.info(asd) - data_handler.add_entry(data_handler.generate_question_dict(reqv)) + question = data_handler.generate_answer_dict(reqv) + questions.append(question) + data_handler.add_entry(question) + + app.logger.info(answers) return render_template('add-question.html', question_id="") From 29d72346828631c67d601432578df99ba76e7674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 14:10:14 +0200 Subject: [PATCH 018/182] Add link to /list referring to question/guestion id --- server.py | 6 ++++++ templates/list.html | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index fa5cdfb34..97b0d93a2 100644 --- a/server.py +++ b/server.py @@ -3,6 +3,8 @@ import data_handler +from datetime import datetime + app = Flask(__name__) @app.route('/') @@ -35,5 +37,9 @@ def add_question(): def test(): return str(data_handler.get_answers()) +@app.route("/question/") +def question_display(question_id): + return question_id + if __name__ == '__main__': app.run(debug=True) diff --git a/templates/list.html b/templates/list.html index 2b0209056..89e92654d 100644 --- a/templates/list.html +++ b/templates/list.html @@ -14,8 +14,16 @@

Questions sorted by latest questions

{% for questions in sorted_questions %} - {% for data in questions.values() %} - {{ data }} + {% for header, data in questions.items() %} + {% if header == 'submission_time' %} + {{ data }} + {% elif header == 'title' %} + + {{ data }} + + {% else %} + {{ data }} + {% endif %} {% endfor %} {% endfor %} From 8da7bf2cabd49709797637f261622d9131a26a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 14:11:58 +0200 Subject: [PATCH 019/182] Correct function definition of sorting data --- data_handler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data_handler.py b/data_handler.py index bebf3fa77..c98e3a7d6 100644 --- a/data_handler.py +++ b/data_handler.py @@ -55,7 +55,7 @@ def generate_answer_dict(data): def sorting_data(data, attribute, order_flag): ''' - :param attribute: list of dictionaries + :param data: list of dictionaries :param attribute: By which the data is sorted- :param order_flag: The order is ascending (False) or descending (True). :return: The sorted data. @@ -66,4 +66,3 @@ def sorting_data(data, attribute, order_flag): sorted_data = sorted(data, key=lambda x: x[attribute], reverse=order_flag) return sorted_data - From 0957cfb7ccc602a80f2715c77b68dbd0395633e0 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 10 Sep 2019 14:22:38 +0200 Subject: [PATCH 020/182] Finish question display functionality --- data_handler.py | 2 +- server.py | 7 ++++--- templates/display_question.html | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/data_handler.py b/data_handler.py index 14e24a4c3..3d976cad9 100644 --- a/data_handler.py +++ b/data_handler.py @@ -62,7 +62,7 @@ def get_question(question_id, question_database): return question_data -def get_answers(question_id, answer_database): +def get_question_related_answers(question_id, answer_database): answers_of_question = [] for answer_data in answer_database: if answer_data['question_id'] == question_id: diff --git a/server.py b/server.py index 06a0756cf..fa389f623 100644 --- a/server.py +++ b/server.py @@ -35,12 +35,13 @@ def add_question(): def test(): return str(data_handler.get_answers()) + @app.route('/question/') def question_display(question_id): - question_database = {} - answer_database = {} + question_database = data_handler.get_questions() + answer_database = data_handler.get_answers() question = data_handler.get_question(question_id, question_database) - related_answers = data_handler.get_answers(question_id, answer_database) + related_answers = data_handler.get_question_related_answers(question_id, answer_database) return render_template('display_question.html', question=question, answers=related_answers) diff --git a/templates/display_question.html b/templates/display_question.html index fbb81192d..f3de02cc3 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -13,6 +13,7 @@

Question data

{% for header in question.keys() %} {{ header }} + {% endfor %} @@ -20,25 +21,33 @@

Question data

{% for header in question.keys() %} {{ question.get(header) }} + {% endfor %}

Answers to the question

+ {% if answers | length != 0 %} - {% for header in answer.keys() %} + {% for header in answers[0].keys() %} + {% endfor %} - {% for answer in answers %} + {% for answer in answers %} - {% for header in answer.keys() %} + {% for header in answers[0].keys() %} + {% endfor %} + {% endfor %}
{{ header }}
{{ question.get(header) }}
+ {% else %} + {{ "There is no answer to this question" }} + {% endif %} \ No newline at end of file From db4c22e5606a9e47e239dd86aa2cadad1db24423 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 10 Sep 2019 14:31:52 +0200 Subject: [PATCH 021/182] Added id generator --- data_handler.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/data_handler.py b/data_handler.py index a9ecf4916..613dc7cba 100644 --- a/data_handler.py +++ b/data_handler.py @@ -3,6 +3,9 @@ import time # ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' +import uuid +from operator import itemgetter + import connection ANSWER_DATA_FILE_PATH = os.getcwd() + "/data/answer.csv" @@ -18,23 +21,27 @@ def get_questions(): database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) return database + def add_entry(entry, is_answer=False): connection.append_to_csv(QUESTION_DATA_FILE_PATH if is_answer else ANSWER_DATA_FILE_PATH, entry) -def generate_id(stories): - ordered_stories = sorted(stories, key=lambda x: ANSWER_DATA_HEADER[0]) - return str(int(ordered_stories[-1][ANSWER_DATA_HEADER[0]]) + 1) +def gen_question_id(): + answers = get_questions() + items = [x['id'] for x in answers] + return int(max(items)) + 1 -def gen_id(): - return 0 +def gen_answer_id(): + answers = get_questions() + items = [x['id'] for x in answers] + return int(max(items)) + 1 def generate_question_dict(data): question_data = {} - question_data.update(id=str(gen_id())) + question_data.update(id=str(gen_question_id())) question_data.update(submission_time=str(time.time())) question_data.update(view_number=str(0)) question_data.update(vote_number=str(0)) @@ -47,7 +54,7 @@ def generate_question_dict(data): def generate_answer_dict(data): answer_data = {} - answer_data.update(id=str(gen_id())) + answer_data.update(id=str(gen_answer_id())) answer_data.update(submission_time=str(time.time())) answer_data.update(vote_number=str(0)) answer_data.update(question_id=data["question_id"]) From 5a78ed6588c23f67bc04a7cc007224f25af535cd Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 10 Sep 2019 14:49:04 +0200 Subject: [PATCH 022/182] Fix question display funcionality --- data_handler.py | 1 + templates/display_question.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data_handler.py b/data_handler.py index 92d5490bc..f4a50067b 100644 --- a/data_handler.py +++ b/data_handler.py @@ -18,6 +18,7 @@ def get_questions(): database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) return database + def add_entry(entry, is_answer=False): connection.append_to_csv(QUESTION_DATA_FILE_PATH if is_answer else ANSWER_DATA_FILE_PATH, entry) diff --git a/templates/display_question.html b/templates/display_question.html index f3de02cc3..6a3c8476a 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -39,8 +39,8 @@

Answers to the question

{% for answer in answers %} - {% for header in answers[0].keys() %} - {{ question.get(header) }} + {% for header in answer.keys() %} + {{ answer.get(header) }} {% endfor %} {% endfor %} From 412d7465da54ba33c8b89d935c394c5d6e04d12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 19:00:25 +0200 Subject: [PATCH 023/182] Add general sorting - not complete --- server.py | 17 +++++++++++------ templates/home_page.html | 11 ----------- templates/list.html | 21 +++++++++++++++++++-- 3 files changed, 30 insertions(+), 19 deletions(-) delete mode 100644 templates/home_page.html diff --git a/server.py b/server.py index 157f4a05a..31cdc5818 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,5 @@ -from flask import Flask, render_template, request +from flask import Flask, render_template, request, redirect, url_for import data_handler @@ -8,17 +8,22 @@ app = Flask(__name__) @app.route('/') -def route_home_page(): - return render_template('home_page.html') - -@app.route('/list') +@app.route('/list', methods=['GET', 'POST']) +# @app.route('/?order_by=vote_number&order_direction=desc', methods=['GET', 'POST']) def list_questions(): fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] questions = data_handler.get_questions() - sorted_questions = data_handler.sorting_data(questions, 'id', True) + sorted_questions = data_handler.sorting_data(questions, 'submission_time', True) + if request.method == 'POST': + order_by = request.form.get('order_by') + order_direction = False if request.form.get('order_direction') == 'asc' else True + # app.logger.info(order_by) + # app.logger.info(order_direction) + sorted_questions = data_handler.sorting_data(questions, order_by, order_direction) return render_template('list.html', fieldnames=fieldnames, sorted_questions=sorted_questions) + @app.route('/add-question', methods=["GET", "POST"]) def add_question(): if request.method == 'POST': diff --git a/templates/home_page.html b/templates/home_page.html deleted file mode 100644 index 56efc19bc..000000000 --- a/templates/home_page.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Welcome! | Ask Mate - - -

Ask Mate

- Sorted by latest questions - - \ No newline at end of file diff --git a/templates/list.html b/templates/list.html index 89e92654d..5804ba9e9 100644 --- a/templates/list.html +++ b/templates/list.html @@ -2,10 +2,27 @@ - List of questions | Ask Mate + Welcome! | Ask Mate -

Questions sorted by latest questions

+

Ask Mate

+

+ +

+ + + +
+ +

{% for header in fieldnames %} From bd87a2bf0a0c96e1492e242e5fc46319c9c21c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 20:01:08 +0200 Subject: [PATCH 024/182] Create delete_question function --- server.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 31cdc5818..1a89f9ce1 100644 --- a/server.py +++ b/server.py @@ -7,9 +7,9 @@ app = Flask(__name__) +@app.route('/?order_by=vote_number&order_direction=desc', methods=['GET', 'POST']) @app.route('/') @app.route('/list', methods=['GET', 'POST']) -# @app.route('/?order_by=vote_number&order_direction=desc', methods=['GET', 'POST']) def list_questions(): fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] questions = data_handler.get_questions() @@ -58,6 +58,18 @@ def question_display(question_id): related_answers = data_handler.get_question_related_answers(question_id, answer_database) return render_template('display_question.html', question=question, answers=related_answers) +@app.route('/question//delete') +def delete_question(question_id): + question_database = data_handler.get_questions() + answer_database = data_handler.get_answers() + for question in question_database: + if question['id'] == question_id: + question_database.remove(question) + for answer in answer_database: + if answer['question_id'] == question_id: + answer_database.remove(answer) + data_handler.modify_question_database(question_database, True) + return redirect(url_for('list_questions')) if __name__ == '__main__': app.run(debug=True) From 7cc511940db001c29900fc66b29626f4d644696f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 20:02:56 +0200 Subject: [PATCH 025/182] Add delete question link --- templates/display_question.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/display_question.html b/templates/display_question.html index f3de02cc3..d99ebaaaa 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -49,5 +49,8 @@

Answers to the question

{% else %} {{ "There is no answer to this question" }} {% endif %} +

+ Delete this question +

\ No newline at end of file From 49b2610b6bcdda3ef2dbfcb9ca5dea461142e539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 20:03:37 +0200 Subject: [PATCH 026/182] Create modify_question_database function --- data_handler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data_handler.py b/data_handler.py index 2fd22617a..cc3c5d7e0 100644 --- a/data_handler.py +++ b/data_handler.py @@ -25,6 +25,9 @@ def get_questions(): def add_entry(entry, is_answer=False): connection.append_to_csv(QUESTION_DATA_FILE_PATH if is_answer else ANSWER_DATA_FILE_PATH, entry) +def modify_question_database(entry, is_question=True): + connection.dict_to_csv(QUESTION_DATA_FILE_PATH if is_question else ANSWER_DATA_FILE_PATH, entry, False) + def gen_question_id(): answers = get_questions() From ff439d0b4f75d0a9a86ca9733e13654b0c54436c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 10 Sep 2019 20:04:37 +0200 Subject: [PATCH 027/182] Add general sorting --- templates/list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/list.html b/templates/list.html index 5804ba9e9..64065225f 100644 --- a/templates/list.html +++ b/templates/list.html @@ -8,7 +8,7 @@

Ask Mate

-
+

@@ -27,6 +27,7 @@

Question data

Answers to the question

+ {% if answers | length != 0 %} @@ -49,5 +50,13 @@

Answers to the question

{% else %} {{ "There is no answer to this question" }} {% endif %} +

+ + + +
+ + + \ No newline at end of file From 138449c151859213c5582d759faef96aceda02cd Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 10 Sep 2019 20:40:42 +0200 Subject: [PATCH 029/182] Replace new question button to delete button as new question button will be on /list --- templates/display_question.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index eb89f105b..7a721edfd 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -51,12 +51,12 @@

Answers to the question

{{ "There is no answer to this question" }} {% endif %}

-
- - -
+
+
+ + \ No newline at end of file From b76a48a69a78b1aa5053826784bc299e3af2f981 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 09:32:40 +0200 Subject: [PATCH 030/182] Fixed issue causing questions saved as answers --- data_handler.py | 2 +- server.py | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/data_handler.py b/data_handler.py index 2fd22617a..5e865a1fe 100644 --- a/data_handler.py +++ b/data_handler.py @@ -23,7 +23,7 @@ def get_questions(): def add_entry(entry, is_answer=False): - connection.append_to_csv(QUESTION_DATA_FILE_PATH if is_answer else ANSWER_DATA_FILE_PATH, entry) + connection.append_to_csv(QUESTION_DATA_FILE_PATH if not is_answer else ANSWER_DATA_FILE_PATH, entry) def gen_question_id(): diff --git a/server.py b/server.py index 157f4a05a..54ed1b7bc 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,5 @@ -from flask import Flask, render_template, request +from flask import Flask, render_template, request, redirect, url_for import data_handler @@ -23,26 +23,26 @@ def list_questions(): def add_question(): if request.method == 'POST': reqv = request.form.to_dict() - answers = data_handler.get_answers() questions = data_handler.get_questions() - if reqv["question_id"] == '': - answer = data_handler.generate_question_dict(reqv) - answers.append(answer) - data_handler.add_entry(answer,True) + question = data_handler.generate_question_dict(reqv) + questions.append(question) + data_handler.add_entry(question) + return redirect(url_for("list_questions")) - elif reqv["question_id"] != '': - question = data_handler.generate_answer_dict(reqv) - questions.append(question) - data_handler.add_entry(question) + return render_template("add-question.html", question_id="") - app.logger.info(answers) - return render_template('add-question.html', question_id="") +@app.route("/question//new-answer", methods=["GET", "POST"]) +def add_answer(question_id): + reqv = request.form.to_dict() + answers = data_handler.get_answers() -@app.route("/test") -def test(): - return str(data_handler.get_answers()) + if request.method == 'POST': + answer = data_handler.generate_answer_dict(reqv) + answers.append(answer) + data_handler.add_entry(answer, True) + return render_template("/question//", question_id=question_id ) @app.route('/question/') From eb3e93cec8ac180661276e2bfbfb2f094e2adeb9 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 10:31:16 +0200 Subject: [PATCH 031/182] fixed error causing question id for asnwers to be 0 --- templates/add-answer.html | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 templates/add-answer.html diff --git a/templates/add-answer.html b/templates/add-answer.html new file mode 100644 index 000000000..3461b0198 --- /dev/null +++ b/templates/add-answer.html @@ -0,0 +1,23 @@ + + + + + Title + + +
+

Ask your question

+
+
+ + +
+
+ + +
+ + +
+ + \ No newline at end of file From b4fdbe17456c2351ae6fc31c2686082cb00d3270 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 10:47:59 +0200 Subject: [PATCH 032/182] Fixed: answer id generation --- data_handler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data_handler.py b/data_handler.py index 5e865a1fe..4711c2b65 100644 --- a/data_handler.py +++ b/data_handler.py @@ -23,7 +23,10 @@ def get_questions(): def add_entry(entry, is_answer=False): - connection.append_to_csv(QUESTION_DATA_FILE_PATH if not is_answer else ANSWER_DATA_FILE_PATH, entry) + if not is_answer: + connection.append_to_csv(QUESTION_DATA_FILE_PATH, entry) + else: + connection.append_to_csv(ANSWER_DATA_FILE_PATH, entry) def gen_question_id(): @@ -33,7 +36,7 @@ def gen_question_id(): def gen_answer_id(): - answers = get_questions() + answers = get_answers() items = [x['id'] for x in answers] return int(max(items)) + 1 From c2d59f1cd8fc9ed031bf052d94cff1b9f3ba7150 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 10:49:44 +0200 Subject: [PATCH 033/182] refactor in server module --- server.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server.py b/server.py index 54ed1b7bc..4b18cda07 100644 --- a/server.py +++ b/server.py @@ -30,19 +30,22 @@ def add_question(): data_handler.add_entry(question) return redirect(url_for("list_questions")) - return render_template("add-question.html", question_id="") + return render_template("add-question.html", qid="") @app.route("/question//new-answer", methods=["GET", "POST"]) def add_answer(question_id): - reqv = request.form.to_dict() - answers = data_handler.get_answers() - if request.method == 'POST': + reqv = request.form.to_dict() + app.logger.info(request.form.to_dict()) answer = data_handler.generate_answer_dict(reqv) + answers = data_handler.get_answers() + answers.append(answer) data_handler.add_entry(answer, True) - return render_template("/question//", question_id=question_id ) + return redirect("/question/" + question_id) + + return render_template("add-answer.html", qid=question_id) @app.route('/question/') @@ -56,3 +59,4 @@ def question_display(question_id): if __name__ == '__main__': app.run(debug=True) + From ebe1d6a4a1ae06b4ceb2bbdea70574eb429447d1 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 10:50:47 +0200 Subject: [PATCH 034/182] Added links to post answers and questions --- templates/add-answer.html | 3 +-- templates/add-question.html | 2 +- templates/display_question.html | 8 ++++++-- templates/list.html | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/templates/add-answer.html b/templates/add-answer.html index 3461b0198..be24b4880 100644 --- a/templates/add-answer.html +++ b/templates/add-answer.html @@ -8,8 +8,7 @@

Ask your question


-
- +

diff --git a/templates/add-question.html b/templates/add-question.html index db15f9f36..48c6a907b 100644 --- a/templates/add-question.html +++ b/templates/add-question.html @@ -10,7 +10,7 @@

Ask your question


- +

diff --git a/templates/display_question.html b/templates/display_question.html index 7a721edfd..ff80fcce0 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -50,7 +50,10 @@

Answers to the question

{% else %} {{ "There is no answer to this question" }} {% endif %} -

+ + + +
@@ -59,4 +62,5 @@

Answers to the question

- \ No newline at end of file + + diff --git a/templates/list.html b/templates/list.html index 89e92654d..e1c1956d9 100644 --- a/templates/list.html +++ b/templates/list.html @@ -28,6 +28,8 @@

Questions sorted by latest questions

{% endfor %}
- +
+ +
\ No newline at end of file From 8c081468fafe8cb105cb4fc5e47efc35c83f756c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 11:16:49 +0200 Subject: [PATCH 035/182] Fix URL problem in sort functionality --- server.py | 23 ++++++++++++++--------- templates/list.html | 14 +++++++++++--- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/server.py b/server.py index 1a89f9ce1..d5e5c4ed2 100644 --- a/server.py +++ b/server.py @@ -7,20 +7,25 @@ app = Flask(__name__) -@app.route('/?order_by=vote_number&order_direction=desc', methods=['GET', 'POST']) + @app.route('/') -@app.route('/list', methods=['GET', 'POST']) +@app.route('/list') +@app.route('/?order_by=&order_direction=', methods=['GET', 'POST']) def list_questions(): fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] questions = data_handler.get_questions() - sorted_questions = data_handler.sorting_data(questions, 'submission_time', True) - if request.method == 'POST': - order_by = request.form.get('order_by') - order_direction = False if request.form.get('order_direction') == 'asc' else True - # app.logger.info(order_by) - # app.logger.info(order_direction) + try: + funcionality = 'sort_by_any_attribute' + order_by = request.args.get('order_by') + order_direction = False if request.args.get('order_direction') == 'asc' else True sorted_questions = data_handler.sorting_data(questions, order_by, order_direction) - return render_template('list.html', fieldnames=fieldnames, sorted_questions=sorted_questions) + order_direction = 'asc' if order_direction == False else 'desc' + except: + funcionality = 'sort_by_submission_time' + order_by = 'submission_time' + order_direction = 'desc' + sorted_questions = data_handler.sorting_data(questions, 'submission_time', True) + return render_template('list.html', fieldnames=fieldnames, sorted_questions=sorted_questions, funcionality=funcionality, order_by=order_by, order_direction=order_direction) diff --git a/templates/list.html b/templates/list.html index 64065225f..d3170ed08 100644 --- a/templates/list.html +++ b/templates/list.html @@ -8,15 +8,23 @@

Ask Mate

-

+ From 3dca5459a3cf45743046f4066849428248b1ee48 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 11:35:11 +0200 Subject: [PATCH 036/182] Added feature: vote up question --- server.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server.py b/server.py index 4b18cda07..d7691e0bd 100644 --- a/server.py +++ b/server.py @@ -1,6 +1,7 @@ from flask import Flask, render_template, request, redirect, url_for +import connection import data_handler from datetime import datetime @@ -56,6 +57,17 @@ def question_display(question_id): related_answers = data_handler.get_question_related_answers(question_id, answer_database) return render_template('display_question.html', question=question, answers=related_answers) +@app.route("/question//vote-up") +def vote_up(question_id): + questions = data_handler.get_questions() + question = data_handler.get_question(question_id, questions) + questions.remove(question) + question["vote_number"] = str(int(question["vote_number"]) + 1) + questions.append(question) + data_handler.save_questions(questions) + + return redirect("/list") + if __name__ == '__main__': app.run(debug=True) From 993561296375f85ed9e7855c4fb7fb5deddb621e Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 11:35:58 +0200 Subject: [PATCH 037/182] Added save questions functinality to data handler --- data_handler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data_handler.py b/data_handler.py index 4711c2b65..2b4ec23b8 100644 --- a/data_handler.py +++ b/data_handler.py @@ -22,6 +22,11 @@ def get_questions(): return database +def save_questions(data): + database = connection.dict_to_csv(QUESTION_DATA_FILE_PATH, data) + return database + + def add_entry(entry, is_answer=False): if not is_answer: connection.append_to_csv(QUESTION_DATA_FILE_PATH, entry) From 6c67802a0b5158cf2730876925bd827081d8348b Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 11:35:58 +0200 Subject: [PATCH 038/182] Add header capitalize to display_question template --- templates/display_question.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index 7a721edfd..b5f46e6a6 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -12,7 +12,7 @@

Question data

{% for header in question.keys() %} - {{ header }} + {{ header|capitalize|replace('_', ' ') }} {% endfor %} @@ -20,7 +20,7 @@

Question data

{% for header in question.keys() %} - {{ question.get(header) }} + {{ question.get(header)}} {% endfor %} @@ -33,7 +33,7 @@

Answers to the question

{% for header in answers[0].keys() %} - {{ header }} + {{ header|capitalize|replace('_', ' ') }} {% endfor %} From 64d494b46ba6b1a8f00e743ef44fbcde946d22c5 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 11:36:35 +0200 Subject: [PATCH 039/182] fixed list of dict saving in connection module --- connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connection.py b/connection.py index 15316b79a..339a354f1 100644 --- a/connection.py +++ b/connection.py @@ -3,7 +3,7 @@ QUESTION_ANSWER_DATA_HEADER = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] -ANSWER_DATA_HEADER = ['id', 'submission_time', 'vote_number', 'question_id', 'message,image'] +ANSWER_DATA_HEADER = ['id', 'submission_time', 'vote_number', 'question_id', 'message', 'image'] def csv_to_dict(file_path): @@ -13,7 +13,7 @@ def csv_to_dict(file_path): return database -def dict_to_csv(file_path, data, is_answers=True): +def dict_to_csv(file_path, data, is_answers=False): with open(file_path, 'w', newline='') as f: writer = csv.DictWriter(f, ANSWER_DATA_HEADER if is_answers else QUESTION_ANSWER_DATA_HEADER) writer.writeheader() From 64d59ffd4b3e2e8d099d55b038e3a2d5b38c0e49 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 11:38:18 +0200 Subject: [PATCH 040/182] Add edit-question.html template --- templates/edit-question.html | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 templates/edit-question.html diff --git a/templates/edit-question.html b/templates/edit-question.html new file mode 100644 index 000000000..a97217e2d --- /dev/null +++ b/templates/edit-question.html @@ -0,0 +1,27 @@ + + + + + Edit question + + +
+

Edit question

+
+ + +
+ + +
+
+ +
+ + +
+ + +
+ + \ No newline at end of file From fd681f1061a8eaf5c307a0f94bfa29bf85386b16 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 11:39:25 +0200 Subject: [PATCH 041/182] Add update question function to data handler module --- data_handler.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data_handler.py b/data_handler.py index 2fd22617a..44287e242 100644 --- a/data_handler.py +++ b/data_handler.py @@ -90,3 +90,14 @@ def sorting_data(data, attribute, order_flag): sorted_data = sorted(data, key=lambda x: x[attribute], reverse=order_flag) return sorted_data + +def update_questions(question_id, updated_data): + all_questions = get_questions() + question = get_question(question_id, all_questions) + question_index = all_questions.index(question) + + for key, value in updated_data.items(): + question[key] = value + all_questions[question_index] = question + connection.dict_to_csv(QUESTION_DATA_FILE_PATH, all_questions, True) + return question From 3c887bd06e6d932ddd380b50c8c1eca460ca4170 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 11:40:14 +0200 Subject: [PATCH 042/182] Add edit_question route to server --- server.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 157f4a05a..9e9dfdc2b 100644 --- a/server.py +++ b/server.py @@ -3,7 +3,7 @@ import data_handler -from datetime import datetime +from datetime import datetime, time app = Flask(__name__) @@ -54,5 +54,20 @@ def question_display(question_id): return render_template('display_question.html', question=question, answers=related_answers) +@app.route('//edit', methods=['GET', 'POST']) +def edit_question(question_id): + + if request.method == 'POST': + edited_question_data = request.form.to_dict() + edited_question_data['submission_time'] = time() + question = data_handler.update_questions(question_id, edited_question_data) + return render_template('display_question.html', question=question) + + all_questions = data_handler.get_questions() + question = data_handler.get_question(question_id, all_questions) + + return render_template('edit-question.html', question=question) + + if __name__ == '__main__': app.run(debug=True) From 0c881115fccfac03a17d333eb4d098bc9bfefe8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 11:46:22 +0200 Subject: [PATCH 043/182] Remove add-question button from index page --- templates/list.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/templates/list.html b/templates/list.html index 503aa1803..925078211 100644 --- a/templates/list.html +++ b/templates/list.html @@ -11,7 +11,7 @@

Ask Mate

-
+ {% if funcionality == 'index_page' %} +
+ +
+ {% endif %} \ No newline at end of file From 2af0ee5172c5bd7ebfc09653f5f5072fb37b5d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 11:47:27 +0200 Subject: [PATCH 044/182] Correct delete question URL in form tag --- templates/display_question.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/display_question.html b/templates/display_question.html index ff80fcce0..6356a51b7 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -58,7 +58,7 @@

Answers to the question


-
+
From caa6779a1a0e4e5bebec132dc3304a7c2f012371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 11:48:02 +0200 Subject: [PATCH 045/182] Small name modifikation --- server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 471a178dd..9fb86dccb 100644 --- a/server.py +++ b/server.py @@ -15,13 +15,13 @@ def list_questions(): fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] questions = data_handler.get_questions() try: - funcionality = 'sort_by_any_attribute' + funcionality = 'sort_by_any_attribute_page' order_by = request.args.get('order_by') order_direction = False if request.args.get('order_direction') == 'asc' else True sorted_questions = data_handler.sorting_data(questions, order_by, order_direction) order_direction = 'asc' if order_direction == False else 'desc' except: - funcionality = 'sort_by_submission_time' + funcionality = 'index_page' order_by = 'submission_time' order_direction = 'desc' sorted_questions = data_handler.sorting_data(questions, 'submission_time', True) From 4dcbb0723612d77e081bd39a090565ff1c5ae985 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 12:06:57 +0200 Subject: [PATCH 046/182] Fix variable name (id) in edit-question temlate --- templates/edit-question.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/edit-question.html b/templates/edit-question.html index a97217e2d..a970a1fac 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -12,7 +12,7 @@

Edit question


- +

From 5dfe41b54ce660429410d090acf7e29d2c6d67aa Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 12:08:23 +0200 Subject: [PATCH 047/182] Correct update_question function in data handler module --- data_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_handler.py b/data_handler.py index c758a3377..b6646450e 100644 --- a/data_handler.py +++ b/data_handler.py @@ -110,5 +110,5 @@ def update_questions(question_id, updated_data): for key, value in updated_data.items(): question[key] = value all_questions[question_index] = question - connection.dict_to_csv(QUESTION_DATA_FILE_PATH, all_questions, True) + connection.dict_to_csv(QUESTION_DATA_FILE_PATH, all_questions) return question From c095d9867cf16791e39bcc23356d8f6cdd2527d4 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 12:10:39 +0200 Subject: [PATCH 048/182] Correct edit_question route in server --- server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 8eb02fb58..e8d6031d8 100644 --- a/server.py +++ b/server.py @@ -98,7 +98,8 @@ def edit_question(question_id): edited_question_data = request.form.to_dict() edited_question_data['submission_time'] = time() question = data_handler.update_questions(question_id, edited_question_data) - return render_template('display_question.html', question=question) + related_answers = data_handler.get_question_related_answers(question_id, data_handler.get_answers()) + return render_template('display_question.html', question=question, answers=related_answers) all_questions = data_handler.get_questions() question = data_handler.get_question(question_id, all_questions) From 2f0206186084bbcd40d9ee40104b517988e6505b Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 12:21:23 +0200 Subject: [PATCH 049/182] Fixed crash at id generation when the list has 0 elements --- data_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data_handler.py b/data_handler.py index c758a3377..5eb061a79 100644 --- a/data_handler.py +++ b/data_handler.py @@ -45,6 +45,8 @@ def gen_question_id(): def gen_answer_id(): answers = get_answers() + if len(answers) == 0: + return 0 items = [x['id'] for x in answers] return int(max(items)) + 1 From eac068d934955a2d4d7372fdea06f41008b151a0 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 12:23:36 +0200 Subject: [PATCH 050/182] Fix edit_question func in server --- server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index e8d6031d8..3fc90b130 100644 --- a/server.py +++ b/server.py @@ -4,7 +4,8 @@ import connection import data_handler -from datetime import datetime, time +from datetime import datetime +import time app = Flask(__name__) @@ -96,7 +97,7 @@ def edit_question(question_id): if request.method == 'POST': edited_question_data = request.form.to_dict() - edited_question_data['submission_time'] = time() + edited_question_data['submission_time'] = str(int(time.time())) question = data_handler.update_questions(question_id, edited_question_data) related_answers = data_handler.get_question_related_answers(question_id, data_handler.get_answers()) return render_template('display_question.html', question=question, answers=related_answers) From 00233948077e3e8538da6abfd9e2440a99a7b640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 13:11:19 +0200 Subject: [PATCH 051/182] Remove functionality variable --- server.py | 8 +++++--- templates/list.html | 14 ++------------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/server.py b/server.py index 8eb02fb58..72c7b923f 100644 --- a/server.py +++ b/server.py @@ -16,17 +16,19 @@ def list_questions(): fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] questions = data_handler.get_questions() try: - funcionality = 'sort_by_any_attribute_page' order_by = request.args.get('order_by') order_direction = False if request.args.get('order_direction') == 'asc' else True sorted_questions = data_handler.sorting_data(questions, order_by, order_direction) order_direction = 'asc' if order_direction == False else 'desc' except: - funcionality = 'index_page' order_by = 'submission_time' order_direction = 'desc' sorted_questions = data_handler.sorting_data(questions, 'submission_time', True) - return render_template('list.html', fieldnames=fieldnames, sorted_questions=sorted_questions, funcionality=funcionality, order_by=order_by, order_direction=order_direction) + return render_template('list.html', + fieldnames=fieldnames, + sorted_questions=sorted_questions, + order_by=order_by, + order_direction=order_direction) diff --git a/templates/list.html b/templates/list.html index 925078211..d07db9143 100644 --- a/templates/list.html +++ b/templates/list.html @@ -11,20 +11,12 @@

Ask Mate

@@ -53,10 +45,8 @@

Ask Mate

{% endfor %} - {% if funcionality == 'index_page' %}
- {% endif %} \ No newline at end of file From 8330bf974935cd3f66045a14e84e61d3cd14fd7e Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 13:40:24 +0200 Subject: [PATCH 052/182] Add delete record to data_handler module - currentyl used to delete answer only --- data_handler.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/data_handler.py b/data_handler.py index 784be5737..1f40b4bcd 100644 --- a/data_handler.py +++ b/data_handler.py @@ -114,3 +114,14 @@ def update_questions(question_id, updated_data): all_questions[question_index] = question connection.dict_to_csv(QUESTION_DATA_FILE_PATH, all_questions) return question + + +def delete_record(id, answer=False): + if answer: + answers = get_answers() + for i, answer in enumerate(answers): + if answer['id'] == id: + question_id = answer['question_id'] + del answers[i] + connection.dict_to_csv(ANSWER_DATA_FILE_PATH,answers, is_answers=True) + return question_id From ac3825a143164e41ba04aeee7eb21a8c2608a5a2 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 13:41:38 +0200 Subject: [PATCH 053/182] Add delete_answer func to server --- server.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server.py b/server.py index 3fc90b130..34c88bb0c 100644 --- a/server.py +++ b/server.py @@ -108,6 +108,12 @@ def edit_question(question_id): return render_template('edit-question.html', question=question) +@app.route('/answer//delete') +def delete_answer(answer_id): + question_id = data_handler.delete_record(answer_id, True) + return redirect('/question/' + question_id) + + if __name__ == '__main__': app.run(debug=True) From 603cf9b83c71a17c783c6efa86c30964d0ceac7b Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 13:42:52 +0200 Subject: [PATCH 054/182] Add delete_answer button to display_question template --- templates/display_question.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/templates/display_question.html b/templates/display_question.html index 13522f52f..ffabb148a 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -43,6 +43,11 @@

Answers to the question

{% for header in answer.keys() %} {{ answer.get(header) }} {% endfor %} + +
+ +
+ {% endfor %} From 09d7ffc1b7fc68aafa703106859b18dd6eed9c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 13:51:03 +0200 Subject: [PATCH 055/182] Create and implement readable date function --- data_handler.py | 7 +++++++ server.py | 7 ++++--- templates/display_question.html | 12 ++++++++++-- templates/list.html | 2 +- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/data_handler.py b/data_handler.py index 784be5737..b4230ca04 100644 --- a/data_handler.py +++ b/data_handler.py @@ -1,6 +1,7 @@ import csv import os import time +from datetime import datetime # ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' import uuid @@ -114,3 +115,9 @@ def update_questions(question_id, updated_data): all_questions[question_index] = question connection.dict_to_csv(QUESTION_DATA_FILE_PATH, all_questions) return question + + +def convert_to_readable_date(timestamp): + readable_time = datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S') + return readable_time + diff --git a/server.py b/server.py index 53bec9d73..49412d966 100644 --- a/server.py +++ b/server.py @@ -29,7 +29,8 @@ def list_questions(): fieldnames=fieldnames, sorted_questions=sorted_questions, order_by=order_by, - order_direction=order_direction) + order_direction=order_direction, + convert_to_readable_date=data_handler.convert_to_readable_date) @@ -68,7 +69,7 @@ def question_display(question_id): answer_database = data_handler.get_answers() question = data_handler.get_question(question_id, question_database) related_answers = data_handler.get_question_related_answers(question_id, answer_database) - return render_template('display_question.html', question=question, answers=related_answers) + return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=data_handler.convert_to_readable_date) @app.route("/question//vote-up") def vote_up(question_id): @@ -102,7 +103,7 @@ def edit_question(question_id): edited_question_data['submission_time'] = str(int(time.time())) question = data_handler.update_questions(question_id, edited_question_data) related_answers = data_handler.get_question_related_answers(question_id, data_handler.get_answers()) - return render_template('display_question.html', question=question, answers=related_answers) + return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=data_handler.convert_to_readable_date) all_questions = data_handler.get_questions() question = data_handler.get_question(question_id, all_questions) diff --git a/templates/display_question.html b/templates/display_question.html index 13522f52f..685a68889 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -20,7 +20,11 @@

Question data

{% for header in question.keys() %} - {{ question.get(header)}} + {% if header == 'submission_time' %} + {{ convert_to_readable_date(question.get(header)) }} + {% else %} + {{ question.get(header) }} + {% endif %} {% endfor %} @@ -41,7 +45,11 @@

Answers to the question

{% for answer in answers %} {% for header in answer.keys() %} - {{ answer.get(header) }} + {% if header == 'submission_time' %} + {{ convert_to_readable_date(answer.get(header)) }} + {% else %} + {{ answer.get(header) }} + {% endif %} {% endfor %} {% endfor %} diff --git a/templates/list.html b/templates/list.html index d07db9143..274a295fe 100644 --- a/templates/list.html +++ b/templates/list.html @@ -33,7 +33,7 @@

Ask Mate

{% for header, data in questions.items() %} {% if header == 'submission_time' %} - {{ data }} + {{ convert_to_readable_date(data) }} {% elif header == 'title' %} {{ data }} From ca7b3b635d534b74557cdba2148f703f8babf910 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 13:59:23 +0200 Subject: [PATCH 056/182] added vote feature --- data_handler.py | 11 +++++++++++ server.py | 29 +++++++++++++++++++++-------- util.py | 20 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 util.py diff --git a/data_handler.py b/data_handler.py index 5eb061a79..9c9283518 100644 --- a/data_handler.py +++ b/data_handler.py @@ -27,6 +27,11 @@ def save_questions(data): return database +def save_answers(data): + database = connection.dict_to_csv(ANSWER_DATA_FILE_PATH, data, True) + return database + + def add_entry(entry, is_answer=False): if not is_answer: connection.append_to_csv(QUESTION_DATA_FILE_PATH, entry) @@ -82,6 +87,12 @@ def get_question(question_id, question_database): return question_data +def get_answer(answer_id, answer_database): + for answer_data in answer_database: + if answer_data['id'] == answer_id: + return answer_data + + def get_question_related_answers(question_id, answer_database): answers_of_question = [] for answer_data in answer_database: diff --git a/server.py b/server.py index 8eb02fb58..6ca910a02 100644 --- a/server.py +++ b/server.py @@ -6,8 +6,10 @@ from datetime import datetime, time -app = Flask(__name__) +import util +app = Flask(__name__) +app.debug = True @app.route('/') @app.route('/list') @@ -68,14 +70,25 @@ def question_display(question_id): return render_template('display_question.html', question=question, answers=related_answers) @app.route("/question//vote-up") -def vote_up(question_id): - questions = data_handler.get_questions() - question = data_handler.get_question(question_id, questions) - questions.remove(question) - question["vote_number"] = str(int(question["vote_number"]) + 1) - questions.append(question) - data_handler.save_questions(questions) +def vote_up_question(question_id): + util.vote_question(question_id, 1) + + return redirect("/question/" + question_id) + +@app.route("/question//vote-down") +def vote_down_question(question_id): + util.vote_question(question_id, -1) + + return redirect("/question/" + question_id) + + +@app.route("/vote-answer", methods=["POST"]) +def vote_answer(): + if request.method == 'POST': + req = request.form.to_dict() + util.vote_answer(req["id"], req["vote"]) + app.logger.info("asdsa") return redirect("/list") @app.route('/question//delete') diff --git a/util.py b/util.py new file mode 100644 index 000000000..cf26f1551 --- /dev/null +++ b/util.py @@ -0,0 +1,20 @@ +import data_handler + + +def vote_question(_id, vote): + questions = data_handler.get_questions() + question = data_handler.get_question(_id, questions) + questions.remove(question) + question["vote_number"] = str(int(question["vote_number"]) + vote) + questions.append(question) + data_handler.save_questions(questions) + + +def vote_answer(_id, vote): + delta = 1 if vote == "up" else -1 + answers = data_handler.get_answers() + answer = data_handler.get_answer(_id, answers) + answers.remove(answer) + answer["vote_number"] = str(int(answer["vote_number"]) + delta) + answers.append(answer) + data_handler.save_answers(answers) From d12bb5848734425c07b1a7cc361048bd6812ec1e Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 14:09:30 +0200 Subject: [PATCH 057/182] Add up and down vote buttons to display_question template --- templates/display_question.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/templates/display_question.html b/templates/display_question.html index ffabb148a..5be8bcd41 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -45,7 +45,17 @@

Answers to the question

{% endfor %}
- + +
+ + +
+ +
+ + +
+
From de3de5651cc738cbc24033416b58392fd7941027 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 14:41:02 +0200 Subject: [PATCH 058/182] Added upload file feature --- server.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server.py b/server.py index f2d574b72..77f0275fa 100644 --- a/server.py +++ b/server.py @@ -1,3 +1,4 @@ +import os from flask import Flask, render_template, request, redirect, url_for @@ -130,6 +131,14 @@ def delete_answer(answer_id): return redirect('/question/' + question_id) +@app.route("/upload", methods=["POST"]) +def upload_image(): + app.logger.info(request.files) + image = request.files["image"] + image.save(os.path.join(os.getcwd() + "/images/", image.filename)) + + return redirect("/") + if __name__ == '__main__': app.run(debug=True) From 50bdaa36b75cdb75b2a9bd3196cd6dd4eadc129e Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 15:35:49 +0200 Subject: [PATCH 059/182] Fix answer voting buttons --- templates/display_question.html | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index ad567e8b6..7ad84ce05 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -29,6 +29,10 @@

Question data

+
+
+ +

Answers to the question

@@ -43,6 +47,7 @@

Answers to the question

{% for answer in answers %} + {% set count=loop.index %} {% for header in answer.keys() %} {% if header == 'submission_time' %} @@ -52,13 +57,19 @@

Answers to the question

{% endif %} {% endfor %} -
- + + + + +
-
- + + + + +
@@ -73,9 +84,6 @@

Answers to the question

{% else %} {{ "There is no answer to this question" }} {% endif %} -
- -

From e3eb79dae64ad11143a4b392b3da1bc62ec6118d Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 15:37:02 +0200 Subject: [PATCH 060/182] Change answer vote function to redirect to particular question site --- server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index f2d574b72..b2d4358df 100644 --- a/server.py +++ b/server.py @@ -92,8 +92,8 @@ def vote_answer(): if request.method == 'POST': req = request.form.to_dict() util.vote_answer(req["id"], req["vote"]) - app.logger.info("asdsa") - return redirect("/list") + question_id = req['question_id'] + return redirect("/question/" + question_id) @app.route('/question//delete') def delete_question(question_id): From 49decd2d4da3d59c9a3233f9420a056c89727d1c Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 11 Sep 2019 16:10:01 +0200 Subject: [PATCH 061/182] Add question voting buttons to display_question template --- templates/display_question.html | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index 7ad84ce05..335524275 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -29,6 +29,24 @@

Question data

+
+ + + + + +
+ + + + + +
+ + +
+
+
@@ -59,7 +77,7 @@

Answers to the question

- +
@@ -67,7 +85,7 @@

Answers to the question

- +
From 55854bc44d90b97818d3c69e9e2c4270f8634115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 18:11:32 +0200 Subject: [PATCH 062/182] Finish delete question functionality --- server.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/server.py b/server.py index f2d574b72..52e512509 100644 --- a/server.py +++ b/server.py @@ -8,6 +8,7 @@ import time import util +import copy app = Flask(__name__) app.debug = True @@ -99,13 +100,17 @@ def vote_answer(): def delete_question(question_id): question_database = data_handler.get_questions() answer_database = data_handler.get_answers() + + copied_answer_database = copy.deepcopy(answer_database) + for answer in copied_answer_database: + if answer['question_id'] == question_id: + answer_database.remove(answer) for question in question_database: if question['id'] == question_id: question_database.remove(question) - for answer in answer_database: - if answer['question_id'] == question_id: - answer_database.remove(answer) - data_handler.modify_question_database(question_database, True) + + data_handler.save_questions(question_database) + data_handler.save_answers(answer_database) return redirect(url_for('list_questions')) @app.route('//edit', methods=['GET', 'POST']) From 85d04bdfe81e5ab81469f488c0c75d39b6035f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 18:12:26 +0200 Subject: [PATCH 063/182] Remove redundant data saving function --- data_handler.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/data_handler.py b/data_handler.py index b438c1e5b..570134b54 100644 --- a/data_handler.py +++ b/data_handler.py @@ -39,9 +39,6 @@ def add_entry(entry, is_answer=False): else: connection.append_to_csv(ANSWER_DATA_FILE_PATH, entry) -def modify_question_database(entry, is_question=True): - connection.dict_to_csv(QUESTION_DATA_FILE_PATH if is_question else ANSWER_DATA_FILE_PATH, entry, False) - def gen_question_id(): answers = get_questions() From 5aa9db9582dcf838f8508f57b8d9584b43c67192 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 18:20:57 +0200 Subject: [PATCH 064/182] added upload/display image feature --- server.py | 17 +++++++---------- templates/add-answer.html | 6 ++++-- templates/add-question.html | 3 ++- templates/display_question.html | 8 ++++++++ templates/list.html | 6 ++++++ util.py | 11 +++++++++++ 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/server.py b/server.py index 77f0275fa..2551f5399 100644 --- a/server.py +++ b/server.py @@ -1,14 +1,11 @@ import os +import time from flask import Flask, render_template, request, redirect, url_for -import connection import data_handler - -from datetime import datetime -import time - import util +from util import handle_upload app = Flask(__name__) app.debug = True @@ -40,10 +37,10 @@ def list_questions(): @app.route('/add-question', methods=["GET", "POST"]) def add_question(): if request.method == 'POST': - reqv = request.form.to_dict() + req = request.form.to_dict() questions = data_handler.get_questions() - - question = data_handler.generate_question_dict(reqv) + handle_upload(req) + question = data_handler.generate_question_dict(req) questions.append(question) data_handler.add_entry(question) return redirect(url_for("list_questions")) @@ -55,7 +52,7 @@ def add_question(): def add_answer(question_id): if request.method == 'POST': reqv = request.form.to_dict() - app.logger.info(request.form.to_dict()) + handle_upload(reqv) answer = data_handler.generate_answer_dict(reqv) answers = data_handler.get_answers() @@ -71,6 +68,7 @@ def question_display(question_id): question_database = data_handler.get_questions() answer_database = data_handler.get_answers() question = data_handler.get_question(question_id, question_database) + related_answers = data_handler.get_question_related_answers(question_id, answer_database) return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=data_handler.convert_to_readable_date) @@ -133,7 +131,6 @@ def delete_answer(answer_id): @app.route("/upload", methods=["POST"]) def upload_image(): - app.logger.info(request.files) image = request.files["image"] image.save(os.path.join(os.getcwd() + "/images/", image.filename)) diff --git a/templates/add-answer.html b/templates/add-answer.html index be24b4880..4c88ca91f 100644 --- a/templates/add-answer.html +++ b/templates/add-answer.html @@ -8,14 +8,16 @@

Ask your question


-
+ +


- +
+
diff --git a/templates/add-question.html b/templates/add-question.html index 48c6a907b..e8830417e 100644 --- a/templates/add-question.html +++ b/templates/add-question.html @@ -8,7 +8,7 @@

Ask your question


-
+
@@ -16,6 +16,7 @@

Ask your question


+
diff --git a/templates/display_question.html b/templates/display_question.html index 5181cda7e..e6fbf1fae 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -47,6 +47,13 @@

Answers to the question

{% for header in answer.keys() %} {% if header == 'submission_time' %} {{ convert_to_readable_date(answer.get(header)) }} + {% elif header == 'image' %} + + {% if answer.get(header) != "" %} + + {% endif %} + + {% else %} {{ answer.get(header) }} {% endif %} @@ -74,6 +81,7 @@

Answers to the question

+ diff --git a/templates/list.html b/templates/list.html index 274a295fe..87f400d08 100644 --- a/templates/list.html +++ b/templates/list.html @@ -38,6 +38,12 @@

Ask Mate

{{ data }} + {% elif header == 'image' %} + + {% if data != "" %} + + {% endif %} + {% else %} {{ data }} {% endif %} diff --git a/util.py b/util.py index cf26f1551..27d059552 100644 --- a/util.py +++ b/util.py @@ -1,3 +1,7 @@ +import os + +from flask import request + import data_handler @@ -18,3 +22,10 @@ def vote_answer(_id, vote): answer["vote_number"] = str(int(answer["vote_number"]) + delta) answers.append(answer) data_handler.save_answers(answers) + + +def handle_upload(req): + image = request.files["image"] + if image.filename != "": + req["image"] = "images/" + image.filename + image.save(os.path.join(os.getcwd() + "/static/images/", image.filename)) \ No newline at end of file From d1e0fe3d410625bf91b75410f8fe276a9fb32018 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 18:23:46 +0200 Subject: [PATCH 065/182] Added folder containing user uploaded images --- static/images/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 static/images/.gitignore diff --git a/static/images/.gitignore b/static/images/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/static/images/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 9307755beeedbb373694f9d205ba837c2c07d8cc Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 11 Sep 2019 18:42:30 +0200 Subject: [PATCH 066/182] Refactor: Moved functions to util module --- data_handler.py | 64 ------------------------------------------------- server.py | 16 ++++++------- util.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 73 deletions(-) diff --git a/data_handler.py b/data_handler.py index b438c1e5b..553fafe31 100644 --- a/data_handler.py +++ b/data_handler.py @@ -1,11 +1,6 @@ -import csv import os -import time -from datetime import datetime # ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' -import uuid -from operator import itemgetter import connection @@ -43,45 +38,6 @@ def modify_question_database(entry, is_question=True): connection.dict_to_csv(QUESTION_DATA_FILE_PATH if is_question else ANSWER_DATA_FILE_PATH, entry, False) -def gen_question_id(): - answers = get_questions() - items = [x['id'] for x in answers] - return int(max(items)) + 1 - - -def gen_answer_id(): - answers = get_answers() - if len(answers) == 0: - return 0 - items = [x['id'] for x in answers] - return int(max(items)) + 1 - - -def generate_question_dict(data): - question_data = {} - - question_data.update(id=str(gen_question_id())) - question_data.update(submission_time=str(int(time.time()))) - question_data.update(view_number=str(0)) - question_data.update(vote_number=str(0)) - question_data.update(title=data["title"]) - question_data.update(message=data["message"]) - question_data.update(image=data["image"]) - return question_data - - -def generate_answer_dict(data): - answer_data = {} - - answer_data.update(id=str(gen_answer_id())) - answer_data.update(submission_time=str(int(time.time()))) - answer_data.update(vote_number=str(0)) - answer_data.update(question_id=data["question_id"]) - answer_data.update(message=data["message"]) - answer_data.update(image=data["image"]) - return answer_data - - def get_question(question_id, question_database): for question_data in question_database: if question_data['id'] == question_id: @@ -102,20 +58,6 @@ def get_question_related_answers(question_id, answer_database): return answers_of_question -def sorting_data(data, attribute, order_flag): - ''' - :param data: list of dictionaries - :param attribute: By which the data is sorted- - :param order_flag: The order is ascending (False) or descending (True). - :return: The sorted data. - ''' - try: - sorted_data = sorted(data, key=lambda x: int(x[attribute]) if x[attribute].isdigit() else x[attribute], reverse=order_flag) - except AttributeError: - sorted_data = sorted(data, key=lambda x: x[attribute], reverse=order_flag) - return sorted_data - - def update_questions(question_id, updated_data): all_questions = get_questions() question = get_question(question_id, all_questions) @@ -128,12 +70,6 @@ def update_questions(question_id, updated_data): return question -def convert_to_readable_date(timestamp): - readable_time = datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S') - return readable_time - - - def delete_record(id, answer=False): if answer: answers = get_answers() diff --git a/server.py b/server.py index 106a357d2..437097186 100644 --- a/server.py +++ b/server.py @@ -19,18 +19,18 @@ def list_questions(): try: order_by = request.args.get('order_by') order_direction = False if request.args.get('order_direction') == 'asc' else True - sorted_questions = data_handler.sorting_data(questions, order_by, order_direction) + sorted_questions = util.sorting_data(questions, order_by, order_direction) order_direction = 'asc' if order_direction == False else 'desc' except: order_by = 'submission_time' order_direction = 'desc' - sorted_questions = data_handler.sorting_data(questions, 'submission_time', True) + sorted_questions = util.sorting_data(questions, 'submission_time', True) return render_template('list.html', fieldnames=fieldnames, sorted_questions=sorted_questions, order_by=order_by, order_direction=order_direction, - convert_to_readable_date=data_handler.convert_to_readable_date) + convert_to_readable_date=util.convert_to_readable_date) @@ -40,12 +40,12 @@ def add_question(): req = request.form.to_dict() questions = data_handler.get_questions() handle_upload(req) - question = data_handler.generate_question_dict(req) + question = util.generate_question_dict(req) questions.append(question) data_handler.add_entry(question) return redirect(url_for("list_questions")) - return render_template("add-question.html", qid="") + return render_template("edit-question.html", qid="") @app.route("/question//new-answer", methods=["GET", "POST"]) @@ -53,7 +53,7 @@ def add_answer(question_id): if request.method == 'POST': reqv = request.form.to_dict() handle_upload(reqv) - answer = data_handler.generate_answer_dict(reqv) + answer = util.generate_answer_dict(reqv) answers = data_handler.get_answers() answers.append(answer) @@ -70,7 +70,7 @@ def question_display(question_id): question = data_handler.get_question(question_id, question_database) related_answers = data_handler.get_question_related_answers(question_id, answer_database) - return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=data_handler.convert_to_readable_date) + return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=util.convert_to_readable_date) @app.route("/question//vote-up") def vote_up_question(question_id): @@ -115,7 +115,7 @@ def edit_question(question_id): edited_question_data['submission_time'] = str(int(time.time())) question = data_handler.update_questions(question_id, edited_question_data) related_answers = data_handler.get_question_related_answers(question_id, data_handler.get_answers()) - return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=data_handler.convert_to_readable_date) + return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=util.convert_to_readable_date) all_questions = data_handler.get_questions() question = data_handler.get_question(question_id, all_questions) diff --git a/util.py b/util.py index 27d059552..3cec3578a 100644 --- a/util.py +++ b/util.py @@ -1,8 +1,11 @@ import os +import time +from datetime import datetime from flask import request import data_handler +from data_handler import get_questions, get_answers def vote_question(_id, vote): @@ -28,4 +31,62 @@ def handle_upload(req): image = request.files["image"] if image.filename != "": req["image"] = "images/" + image.filename - image.save(os.path.join(os.getcwd() + "/static/images/", image.filename)) \ No newline at end of file + image.save(os.path.join(os.getcwd() + "/static/images/", image.filename)) + + +def sorting_data(data, attribute, order_flag): + ''' + :param data: list of dictionaries + :param attribute: By which the data is sorted- + :param order_flag: The order is ascending (False) or descending (True). + :return: The sorted data. + ''' + try: + sorted_data = sorted(data, key=lambda x: int(x[attribute]) if x[attribute].isdigit() else x[attribute], reverse=order_flag) + except AttributeError: + sorted_data = sorted(data, key=lambda x: x[attribute], reverse=order_flag) + return sorted_data + + +def convert_to_readable_date(timestamp): + readable_time = datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S') + return readable_time + + +def gen_question_id(): + answers = get_questions() + items = [x['id'] for x in answers] + return int(max(items)) + 1 + + +def gen_answer_id(): + answers = get_answers() + if len(answers) == 0: + return 0 + items = [x['id'] for x in answers] + return int(max(items)) + 1 + + +def generate_question_dict(data): + question_data = {} + + question_data.update(id=str(gen_question_id())) + question_data.update(submission_time=str(int(time.time()))) + question_data.update(view_number=str(0)) + question_data.update(vote_number=str(0)) + question_data.update(title=data["title"]) + question_data.update(message=data["message"]) + question_data.update(image=data["image"]) + return question_data + + +def generate_answer_dict(data): + answer_data = {} + + answer_data.update(id=str(gen_answer_id())) + answer_data.update(submission_time=str(int(time.time()))) + answer_data.update(vote_number=str(0)) + answer_data.update(question_id=data["question_id"]) + answer_data.update(message=data["message"]) + answer_data.update(image=data["image"]) + return answer_data \ No newline at end of file From 299eb112ca19365c4b923b0e8dcd72001c7fea9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 11 Sep 2019 19:12:22 +0200 Subject: [PATCH 067/182] Create %asking if really want to delete% functionality --- server.py | 37 ++++++++++++++++----------- templates/asking_if_delete_entry.html | 16 ++++++++++++ 2 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 templates/asking_if_delete_entry.html diff --git a/server.py b/server.py index 52e512509..984996b36 100644 --- a/server.py +++ b/server.py @@ -96,22 +96,29 @@ def vote_answer(): app.logger.info("asdsa") return redirect("/list") -@app.route('/question//delete') +@app.route('/question//delete', methods=['GET', 'POST']) def delete_question(question_id): - question_database = data_handler.get_questions() - answer_database = data_handler.get_answers() - - copied_answer_database = copy.deepcopy(answer_database) - for answer in copied_answer_database: - if answer['question_id'] == question_id: - answer_database.remove(answer) - for question in question_database: - if question['id'] == question_id: - question_database.remove(question) - - data_handler.save_questions(question_database) - data_handler.save_answers(answer_database) - return redirect(url_for('list_questions')) + if request.method == 'POST': + if request.form.get('delete') == 'Yes': + question_database = data_handler.get_questions() + answer_database = data_handler.get_answers() + + copied_answer_database = copy.deepcopy(answer_database) + for answer in copied_answer_database: + if answer['question_id'] == question_id: + answer_database.remove(answer) + for question in question_database: + if question['id'] == question_id: + question_database.remove(question) + + data_handler.save_questions(question_database) + data_handler.save_answers(answer_database) + return redirect(url_for('list_questions')) + + else: + return redirect(url_for('question_display', question_id=question_id)) + + return render_template('asking_if_delete_entry.html', question_id=question_id) @app.route('//edit', methods=['GET', 'POST']) def edit_question(question_id): diff --git a/templates/asking_if_delete_entry.html b/templates/asking_if_delete_entry.html new file mode 100644 index 000000000..3734a0dac --- /dev/null +++ b/templates/asking_if_delete_entry.html @@ -0,0 +1,16 @@ + + + + + Delete question + + +
+

Are you sure you want to delete this question?

+
+ + +
+
+ + \ No newline at end of file From 7d68832cc7e3153cd7011813100fe26c5ca1bb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 12 Sep 2019 10:37:20 +0200 Subject: [PATCH 068/182] Add search for keywords functionality --- server.py | 15 ++++++++++ templates/list.html | 9 +++++- .../search_for_keywords_in_questions.html | 28 +++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 templates/search_for_keywords_in_questions.html diff --git a/server.py b/server.py index 984996b36..61d8f9400 100644 --- a/server.py +++ b/server.py @@ -142,6 +142,21 @@ def delete_answer(answer_id): return redirect('/question/' + question_id) +@app.route('/search-for-questions', methods=['GET', 'POST']) +def search_for_questions(): + fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] + question_database = data_handler.get_questions() + keywords = str(request.args.get('keywords')).replace(',', '').split(' ') + questions_containing_keywords = [] + for question in question_database: + if any(keyword in question['title'] for keyword in keywords) or any(keyword in question['message'] for keyword in keywords): + questions_containing_keywords.append(question) + + return render_template('search_for_keywords_in_questions.html', + keywords=keywords, fieldnames=fieldnames, questions=questions_containing_keywords, + convert_to_readable_date=data_handler.convert_to_readable_date) + + if __name__ == '__main__': app.run(debug=True) diff --git a/templates/list.html b/templates/list.html index 274a295fe..ed35787a7 100644 --- a/templates/list.html +++ b/templates/list.html @@ -45,7 +45,14 @@

Ask Mate

{% endfor %} -
+

+ +
+ + +

+

+
diff --git a/templates/search_for_keywords_in_questions.html b/templates/search_for_keywords_in_questions.html new file mode 100644 index 000000000..da2d65903 --- /dev/null +++ b/templates/search_for_keywords_in_questions.html @@ -0,0 +1,28 @@ + + + + + Title + + +

Your keywords were found in the following questions:

+ + + {% for header in fieldnames %} + + {% endfor %} + + {% for question in questions %} + + {% for header, data in question.items() %} + {% if header == 'submission_time' %} + + {% else %} + + {% endif %} + {% endfor %} + {% endfor %} + +
{{ header|capitalize|replace('_', ' ') }}
{{ convert_to_readable_date(data) }}{{ data }}
+ + \ No newline at end of file From 4e6d82ce470813fde043cb5f0203c731b3b10643 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 12 Sep 2019 10:47:29 +0200 Subject: [PATCH 069/182] Merge add/eedit question pages intoa single html --- templates/edit-question.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/templates/edit-question.html b/templates/edit-question.html index a970a1fac..14d89f5ce 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -8,17 +8,18 @@

Edit question


-
+
- - + +


- - + +
+

From 8f766ec16a2b31f01f5a96d5f8765ac81fd65d59 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 12 Sep 2019 10:50:23 +0200 Subject: [PATCH 070/182] Revert merging of htmls --- templates/edit-question.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/templates/edit-question.html b/templates/edit-question.html index 14d89f5ce..a970a1fac 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -8,18 +8,17 @@

Edit question


-
+
- - + +


- -
-
+ +
From a8a35b1e3617bb1f3f80ac0fec48b482094bfda4 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 12 Sep 2019 11:10:08 +0200 Subject: [PATCH 071/182] Fix: issue question upload when not uploading image sometimes causing error --- templates/edit-question.html | 11 ++++++----- util.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/templates/edit-question.html b/templates/edit-question.html index a970a1fac..d249311b0 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -8,17 +8,18 @@

Edit question


-
+
- - + +


- - + +
+

diff --git a/util.py b/util.py index 3cec3578a..51d423c44 100644 --- a/util.py +++ b/util.py @@ -32,7 +32,8 @@ def handle_upload(req): if image.filename != "": req["image"] = "images/" + image.filename image.save(os.path.join(os.getcwd() + "/static/images/", image.filename)) - + else: + req["image"] = "" def sorting_data(data, attribute, order_flag): ''' From 9838fa68792fede3dba638972498d7aaac66afaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 12 Sep 2019 11:18:23 +0200 Subject: [PATCH 072/182] Add search-for-keywords functionality --- server.py | 2 +- templates/list.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 6474ac815..ac96faaac 100644 --- a/server.py +++ b/server.py @@ -153,7 +153,7 @@ def search_for_questions(): return render_template('search_for_keywords_in_questions.html', keywords=keywords, fieldnames=fieldnames, questions=questions_containing_keywords, - convert_to_readable_date=data_handler.convert_to_readable_date) + convert_to_readable_date=util.convert_to_readable_date) @app.route("/upload", methods=["POST"]) diff --git a/templates/list.html b/templates/list.html index c58522e87..1bc0a48fe 100644 --- a/templates/list.html +++ b/templates/list.html @@ -58,7 +58,7 @@

Ask Mate

-
+
From 8ba2eeb2abcb9a06a402bcc28c9700d946b559cd Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 12 Sep 2019 12:42:46 +0200 Subject: [PATCH 073/182] Page title changes according to context (add/edit) --- templates/edit-question.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/edit-question.html b/templates/edit-question.html index d249311b0..e2c5bb347 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -2,11 +2,11 @@ - Edit question + {{"Edit question" if question else "Ask your Question" }}
-

Edit question

+

{{"Edit question" if question else "Ask your Question" }}


From 5b1a300f1fc22146d3af9d2fa64fd979e8b3e352 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 12 Sep 2019 13:05:16 +0200 Subject: [PATCH 074/182] Sorting answers by date on display question page --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index ac96faaac..556173612 100644 --- a/server.py +++ b/server.py @@ -71,7 +71,7 @@ def question_display(question_id): question = data_handler.get_question(question_id, question_database) related_answers = data_handler.get_question_related_answers(question_id, answer_database) - return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=util.convert_to_readable_date) + return render_template('display_question.html', question=question, answers=util.sorting_data(related_answers, 'submission_time', True), convert_to_readable_date=util.convert_to_readable_date) @app.route("/question//vote-up") def vote_up_question(question_id): From 3831ef03b1d216e60cca9e8d73581f43d40ddcc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 12 Sep 2019 13:20:23 +0200 Subject: [PATCH 075/182] Modify title order on main page --- server.py | 3 +-- templates/list.html | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server.py b/server.py index ac96faaac..b067b79aa 100644 --- a/server.py +++ b/server.py @@ -15,7 +15,7 @@ @app.route('/list') @app.route('/?order_by=&order_direction=', methods=['GET', 'POST']) def list_questions(): - fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] + # fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] questions = data_handler.get_questions() try: order_by = request.args.get('order_by') @@ -27,7 +27,6 @@ def list_questions(): order_direction = 'desc' sorted_questions = util.sorting_data(questions, 'submission_time', True) return render_template('list.html', - fieldnames=fieldnames, sorted_questions=sorted_questions, order_by=order_by, order_direction=order_direction, diff --git a/templates/list.html b/templates/list.html index 1bc0a48fe..7827162e7 100644 --- a/templates/list.html +++ b/templates/list.html @@ -6,6 +6,7 @@

Ask Mate

+ {% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %}

@@ -31,21 +32,21 @@

Ask Mate

{% for questions in sorted_questions %} - {% for header, data in questions.items() %} - {% if header == 'submission_time' %} - {{ convert_to_readable_date(data) }} - {% elif header == 'title' %} + {% for cell in fieldnames %} + {% if cell == 'submission_time' %} + {{ convert_to_readable_date(questions[cell]) }} + {% elif cell == 'title' %} - {{ data }} + {{ questions[cell] }} - {% elif header == 'image' %} + {% elif cell == 'image' %} - {% if data != "" %} - + {% if questions[cell] != "" %} + {% endif %} {% else %} - {{ data }} + {{ questions[cell] }} {% endif %} {% endfor %} {% endfor %} From ca964491d7576098b671bb9bdc6d53c26a97d2fb Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 12 Sep 2019 13:41:06 +0200 Subject: [PATCH 076/182] Refactor --- server.py | 34 ++++++---------------------------- util.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/server.py b/server.py index 556173612..2b0e23a95 100644 --- a/server.py +++ b/server.py @@ -5,8 +5,7 @@ import data_handler import util -import copy -from util import handle_upload +from util import handle_delete_question, handle_add_answer, handle_add_question app = Flask(__name__) app.debug = True @@ -34,16 +33,11 @@ def list_questions(): convert_to_readable_date=util.convert_to_readable_date) - @app.route('/add-question', methods=["GET", "POST"]) def add_question(): if request.method == 'POST': req = request.form.to_dict() - questions = data_handler.get_questions() - handle_upload(req) - question = util.generate_question_dict(req) - questions.append(question) - data_handler.add_entry(question) + handle_add_question(req) return redirect(url_for("list_questions")) return render_template("edit-question.html", qid="") @@ -52,13 +46,8 @@ def add_question(): @app.route("/question//new-answer", methods=["GET", "POST"]) def add_answer(question_id): if request.method == 'POST': - reqv = request.form.to_dict() - handle_upload(reqv) - answer = util.generate_answer_dict(reqv) - answers = data_handler.get_answers() - - answers.append(answer) - data_handler.add_entry(answer, True) + req = request.form.to_dict() + handle_add_answer(req) return redirect("/question/" + question_id) return render_template("add-answer.html", qid=question_id) @@ -99,19 +88,7 @@ def vote_answer(): def delete_question(question_id): if request.method == 'POST': if request.form.get('delete') == 'Yes': - question_database = data_handler.get_questions() - answer_database = data_handler.get_answers() - - copied_answer_database = copy.deepcopy(answer_database) - for answer in copied_answer_database: - if answer['question_id'] == question_id: - answer_database.remove(answer) - for question in question_database: - if question['id'] == question_id: - question_database.remove(question) - - data_handler.save_questions(question_database) - data_handler.save_answers(answer_database) + handle_delete_question(question_id) return redirect(url_for('list_questions')) else: @@ -119,6 +96,7 @@ def delete_question(question_id): return render_template('asking_if_delete_entry.html', question_id=question_id) + @app.route('//edit', methods=['GET', 'POST']) def edit_question(question_id): diff --git a/util.py b/util.py index 51d423c44..93e0036d5 100644 --- a/util.py +++ b/util.py @@ -1,3 +1,4 @@ +import copy import os import time from datetime import datetime @@ -90,4 +91,34 @@ def generate_answer_dict(data): answer_data.update(question_id=data["question_id"]) answer_data.update(message=data["message"]) answer_data.update(image=data["image"]) - return answer_data \ No newline at end of file + return answer_data + + +def handle_delete_question(question_id): + question_database = data_handler.get_questions() + answer_database = data_handler.get_answers() + copied_answer_database = copy.deepcopy(answer_database) + for answer in copied_answer_database: + if answer['question_id'] == question_id: + answer_database.remove(answer) + for question in question_database: + if question['id'] == question_id: + question_database.remove(question) + data_handler.save_questions(question_database) + data_handler.save_answers(answer_database) + + +def handle_add_answer(reqv): + handle_upload(reqv) + answer = generate_answer_dict(reqv) + answers = data_handler.get_answers() + answers.append(answer) + data_handler.add_entry(answer, True) + + +def handle_add_question(req): + questions = data_handler.get_questions() + handle_upload(req) + question = generate_question_dict(req) + questions.append(question) + data_handler.add_entry(question) \ No newline at end of file From e3a319ac2bb4429dc7c113dee06e7b5738f44235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 12 Sep 2019 13:46:13 +0200 Subject: [PATCH 077/182] Change main page style --- templates/list.html | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/templates/list.html b/templates/list.html index 7827162e7..b1fdee773 100644 --- a/templates/list.html +++ b/templates/list.html @@ -5,7 +5,28 @@ Welcome! | Ask Mate -

Ask Mate

+
+

Ask Mate

+
+ +
+ +
+ +
+ + +

+

+ + + +
+ +
+

+
+ {% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %}

@@ -22,8 +43,8 @@

Ask Mate

-

+ {% for header in fieldnames %} @@ -52,15 +73,5 @@

Ask Mate

{% endfor %}
-

-

-
- - -
-

-
- -
\ No newline at end of file From c0cb5502af6b93e6dfc188d5a06f619c8174e6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 12 Sep 2019 13:48:20 +0200 Subject: [PATCH 078/182] Change main page style --- templates/list.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/templates/list.html b/templates/list.html index b1fdee773..0878c5433 100644 --- a/templates/list.html +++ b/templates/list.html @@ -16,11 +16,10 @@

Ask Mate


+

- - - +
From 4023123b3fb625d565cc83396ae96934021db2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 12 Sep 2019 14:13:31 +0200 Subject: [PATCH 079/182] Refactor list_question function --- server.py | 16 ++++++---------- templates/list.html | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/server.py b/server.py index bd774ce60..120e08653 100644 --- a/server.py +++ b/server.py @@ -14,17 +14,13 @@ @app.route('/list') @app.route('/?order_by=&order_direction=', methods=['GET', 'POST']) def list_questions(): - # fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] questions = data_handler.get_questions() - try: - order_by = request.args.get('order_by') - order_direction = False if request.args.get('order_direction') == 'asc' else True - sorted_questions = util.sorting_data(questions, order_by, order_direction) - order_direction = 'asc' if order_direction == False else 'desc' - except: - order_by = 'submission_time' - order_direction = 'desc' - sorted_questions = util.sorting_data(questions, 'submission_time', True) + + order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') + order_direction = False if request.args.get('order_direction') == 'asc' else True + sorted_questions = util.sorting_data(questions, order_by, order_direction) + order_direction = 'asc' if order_direction == False else 'desc' + return render_template('list.html', sorted_questions=sorted_questions, order_by=order_by, diff --git a/templates/list.html b/templates/list.html index 0878c5433..abf676441 100644 --- a/templates/list.html +++ b/templates/list.html @@ -47,7 +47,7 @@

Ask Mate

{% for header in fieldnames %} - + {% endfor %} {% for questions in sorted_questions %} From 54550adc34d7b573cd8d5f4d278b6a843e5829c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 12 Sep 2019 14:29:42 +0200 Subject: [PATCH 080/182] Change style --- templates/add-answer.html | 3 +-- templates/search_for_keywords_in_questions.html | 13 +++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/add-answer.html b/templates/add-answer.html index 4c88ca91f..34a9a5bda 100644 --- a/templates/add-answer.html +++ b/templates/add-answer.html @@ -6,9 +6,8 @@
-

Ask your question

+

Answer question


-

diff --git a/templates/search_for_keywords_in_questions.html b/templates/search_for_keywords_in_questions.html index da2d65903..7ffbf527d 100644 --- a/templates/search_for_keywords_in_questions.html +++ b/templates/search_for_keywords_in_questions.html @@ -7,18 +7,19 @@

Your keywords were found in the following questions:

{{ header|capitalize|replace('_', ' ') }}{{ header|capitalize|replace('_', ' ') }}
+ {% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %} - {% for header in fieldnames %} - + {% for cell in fieldnames %} + {% endfor %} {% for question in questions %} - {% for header, data in question.items() %} - {% if header == 'submission_time' %} - + {% for cell in fieldnames %} + {% if cell == 'submission_time' %} + {% else %} - + {% endif %} {% endfor %} {% endfor %} From a62f47c53841bc05ea2b8d3e7eea4350ae015a51 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 12 Sep 2019 20:36:01 +0200 Subject: [PATCH 081/182] Fixed issue not showing image on question details --- templates/display_question.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/display_question.html b/templates/display_question.html index aae8842c0..9c50ebcd9 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -22,6 +22,10 @@

Question data

{% for header in question.keys() %} {% if header == 'submission_time' %}
+ {% elif header == "image"%}} + {% else %} {% endif %} @@ -71,7 +75,7 @@

Answers to the question

{% if header == 'submission_time' %} {% elif header == 'image' %} - {% elif cell == 'image' %} diff --git a/util.py b/util.py index b956ed877..f461fecc9 100644 --- a/util.py +++ b/util.py @@ -63,6 +63,7 @@ def sorting_data(data, attribute, order_flag): def convert_to_readable_date(timestamp): + # depricated - Marked for removal ''' Converts unix timestamp into 2019-09-12 12:54:49 date-time format. :param timestamp: string @@ -79,6 +80,7 @@ def gen_question_id(): def gen_answer_id(): + # depricated - Marked for removal answers = get_answers() if len(answers) == 0: return 0 @@ -135,8 +137,8 @@ def handle_add_answer(reqv): def handle_add_question(req): - questions = data_handler.get_questions() + # questions = data_handler.get_questions() handle_upload(req) question = generate_question_dict(req) - questions.append(question) + # questions.append(question) data_handler.add_entry(question) From ff8ca5b9749e640d7b5e33e815409bcd1669f5af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Mon, 23 Sep 2019 18:26:34 +0200 Subject: [PATCH 100/182] Put search_for_questions to SQL; Search in answer added --- server.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 43bbf80d5..00ea52b86 100644 --- a/server.py +++ b/server.py @@ -5,6 +5,7 @@ import util from util import handle_delete_question, handle_add_answer, handle_add_question, sorting_data, convert_to_readable_date + app = Flask(__name__) app.debug = True @@ -145,16 +146,39 @@ def delete_answer(answer_id): @app.route('/search-for-questions', methods=['GET', 'POST']) def search_for_questions(): - question_database = data_handler.get_questions() + + # questions_containing_keywords = [] + # for keyword in keywords: + # print(type(keyword)) + # keyword = '\'%' + str(keyword) + '%\'' + # q = """""" + # query = """SELECT to_tsvector(title) from question @@ to_tsquery({keyword})""".format(keyword=str(keyword)) + # result = data_handler.execute_query(query) + # print(result) + # questions_containing_keywords.append(result) + + q_query = """SELECT * FROM question + """ + question_database = data_handler.execute_query(q_query) + a_query = """SELECT * FROM answer + """ + answer_database = data_handler.execute_query(a_query) keywords = str(request.args.get('keywords')).replace(',', '').split(' ') + + answer_related_question_id = [] + for answer in answer_database: + if any(keyword in answer['message'] for keyword in keywords): + answer_related_question_id.append(answer['question_id']) questions_containing_keywords = [] for question in question_database: if any(keyword in question['title'] for keyword in keywords) or any(keyword in question['message'] for keyword in keywords): questions_containing_keywords.append(question) + elif question['id'] in answer_related_question_id: + questions_containing_keywords.append(question) return render_template('search_for_keywords_in_questions.html', keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords, - convert_to_readable_date=util.convert_to_readable_date) + convert_to_readable_date=str) @app.route("/upload", methods=["POST"]) From 3f8a9430912eddf6a61e58117a82970cc644f22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Mon, 23 Sep 2019 19:15:04 +0200 Subject: [PATCH 101/182] Refactor search_for_questions function --- server.py | 28 +++++++--------------------- util.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/server.py b/server.py index 00ea52b86..ffb66b99c 100644 --- a/server.py +++ b/server.py @@ -147,34 +147,20 @@ def delete_answer(answer_id): @app.route('/search-for-questions', methods=['GET', 'POST']) def search_for_questions(): - # questions_containing_keywords = [] - # for keyword in keywords: - # print(type(keyword)) - # keyword = '\'%' + str(keyword) + '%\'' - # q = """""" - # query = """SELECT to_tsvector(title) from question @@ to_tsquery({keyword})""".format(keyword=str(keyword)) - # result = data_handler.execute_query(query) - # print(result) - # questions_containing_keywords.append(result) - q_query = """SELECT * FROM question """ question_database = data_handler.execute_query(q_query) a_query = """SELECT * FROM answer """ answer_database = data_handler.execute_query(a_query) - keywords = str(request.args.get('keywords')).replace(',', '').split(' ') - answer_related_question_id = [] - for answer in answer_database: - if any(keyword in answer['message'] for keyword in keywords): - answer_related_question_id.append(answer['question_id']) - questions_containing_keywords = [] - for question in question_database: - if any(keyword in question['title'] for keyword in keywords) or any(keyword in question['message'] for keyword in keywords): - questions_containing_keywords.append(question) - elif question['id'] in answer_related_question_id: - questions_containing_keywords.append(question) + keywords = str(request.args.get('keywords')).replace(',', '').split(' ') + answer_related_question_id = util.get_answer_related_question_ids(keywords, answer_database, 'message') + questions_containing_keywords = util.search_keywords_in_attribute(keywords, + answer_related_question_id, + question_database, + 'title', + 'message') return render_template('search_for_keywords_in_questions.html', keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords, diff --git a/util.py b/util.py index 8ba5d5f03..fa36ba06a 100644 --- a/util.py +++ b/util.py @@ -131,3 +131,40 @@ def handle_add_question(req): question = generate_question_dict(req) questions.append(question) data_handler.add_entry(question) + + +def get_answer_related_question_ids(keywords, answer_database, attribute): + """ + Search keywords in database using attribute as a key. If it founds a keyword + in the attribute, then its related question id is stored. + :param keywords: list + :param answer_database: list of dictionaries + :param attribute: string + :return: list of item related question ids + """ + answer_related_question_ids = [] + for answer in answer_database: + if any(keyword in answer[attribute] for keyword in keywords): + answer_related_question_ids.append(answer['question_id']) + return answer_related_question_ids + + +def search_keywords_in_attribute(keywords, id_s, database, attribute_1, attribute_2=None): + """ + Search keywords in table using attribute_1 and/or attribute_2 as key(s). + Search id in id_s in order to find and append other database related items. + :param keywords: list + :param id_s: list + :param database: list of dictionaries + :param attribute_1: string + :param attribute_2: string + :return: list of items containing keywords + """ + items_containing_keywords = [] + for item in database: + if any(keyword in item[attribute_1] for keyword in keywords) or \ + any(keyword in item[attribute_2] for keyword in keywords): + items_containing_keywords.append(item) + elif item['id'] in id_s: + items_containing_keywords.append(item) + return items_containing_keywords From 73884815bb73af0de5247efba45bcb4551cbc186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Mon, 23 Sep 2019 23:58:10 +0200 Subject: [PATCH 102/182] Remove sorting_data function from util.py --- server.py | 2 +- util.py | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/server.py b/server.py index ffb66b99c..fbf5ad02a 100644 --- a/server.py +++ b/server.py @@ -3,7 +3,7 @@ from flask import Flask, render_template, request, redirect, url_for import data_handler import util -from util import handle_delete_question, handle_add_answer, handle_add_question, sorting_data, convert_to_readable_date +from util import handle_delete_question, handle_add_answer, handle_add_question, convert_to_readable_date app = Flask(__name__) diff --git a/util.py b/util.py index fa36ba06a..669bb88cf 100644 --- a/util.py +++ b/util.py @@ -38,19 +38,6 @@ def handle_upload(req): else: req["image"] = "" -def sorting_data(data, attribute, order_flag): - ''' - Sorts data by attribute in order order_flag. - :param data: list of dictionaries - :param attribute: By which the data is sorted- This is the key of dictionaries. - :param order_flag: Boolean. The order is ascending (False) or descending (True). - :return: The sorted data. List of dictionaries. - ''' - try: - sorted_data = sorted(data, key=lambda x: int(x[attribute]) if x[attribute].isdigit() else x[attribute], reverse=order_flag) - except AttributeError: - sorted_data = sorted(data, key=lambda x: x[attribute], reverse=order_flag) - return sorted_data def convert_to_readable_date(timestamp): From c3668bcfc67f69f78a7ea1a3027b17a8c1e6d62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 24 Sep 2019 00:09:48 +0200 Subject: [PATCH 103/182] Remove convert_to_date function --- server.py | 12 +++++------- templates/display_question.html | 4 ++-- templates/list.html | 2 +- templates/search_for_keywords_in_questions.html | 2 +- util.py | 11 ----------- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/server.py b/server.py index fbf5ad02a..8ca4702e2 100644 --- a/server.py +++ b/server.py @@ -3,7 +3,7 @@ from flask import Flask, render_template, request, redirect, url_for import data_handler import util -from util import handle_delete_question, handle_add_answer, handle_add_question, convert_to_readable_date +from util import handle_delete_question, handle_add_answer, handle_add_question app = Flask(__name__) @@ -30,8 +30,7 @@ def list_questions(): return render_template('list.html', sorted_questions=questions, order_by=order_by, - order_direction=order_direction, - convert_to_readable_date=str) + order_direction=order_direction) @app.route('/add-question', methods=["GET", "POST"]) @@ -65,7 +64,7 @@ def question_display(question_id): question = data_handler.execute_query(question_query) related_answers = data_handler.execute_query(answers_query) - return render_template('display_question.html', question=question.pop(), answers=related_answers, convert_to_readable_date=str) + return render_template('display_question.html', question=question.pop(), answers=related_answers) @app.route("/question//vote-up") def vote_up_question(question_id): @@ -124,7 +123,7 @@ def edit_question(question_id): edited_question_data['submission_time'] = str(int(time.time())) question = data_handler.update_questions(question_id, edited_question_data) related_answers = data_handler.get_question_related_answers(question_id, data_handler.get_answers()) - return render_template('display_question.html', question=question, answers=related_answers, convert_to_readable_date=util.convert_to_readable_date) + return render_template('display_question.html', question=question, answers=related_answers) all_questions = data_handler.get_questions() question = data_handler.get_question(question_id, all_questions) @@ -163,8 +162,7 @@ def search_for_questions(): 'message') return render_template('search_for_keywords_in_questions.html', - keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords, - convert_to_readable_date=str) + keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords) @app.route("/upload", methods=["POST"]) diff --git a/templates/display_question.html b/templates/display_question.html index 10f05fcbf..38b181543 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -35,7 +35,7 @@

{{ question.title }}

{% for key, width, align in keys %} {% if key == 'submission_time' %} -
+ {% elif key == 'image' %} {% for key, width, align in answer_keys %} {% if key == 'submission_time' %} - + {% elif key == 'image' %} {% for cell in fieldnames %} {% if cell == 'submission_time' %} - + {% elif cell == 'title' %} {% for cell in fieldnames %} {% if cell == 'submission_time' %} - + {% else %} {% endif %} diff --git a/util.py b/util.py index 669bb88cf..732ce732c 100644 --- a/util.py +++ b/util.py @@ -39,17 +39,6 @@ def handle_upload(req): req["image"] = "" - -def convert_to_readable_date(timestamp): - ''' - Converts unix timestamp into 2019-09-12 12:54:49 date-time format. - :param timestamp: string - :return: string - ''' - readable_time = datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S') - return readable_time - - def gen_question_id(): answers = get_questions() items = [x['id'] for x in answers] From 9ecd92ae99b59ee8f081187500e87ade5a0d66e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 24 Sep 2019 10:37:00 +0200 Subject: [PATCH 104/182] Fix bug in delete_question func on server.py --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index 8ca4702e2..d1f41bd26 100644 --- a/server.py +++ b/server.py @@ -93,7 +93,7 @@ def delete_question(question_id): if request.method == 'POST': if request.form.get('delete') == 'Yes': - q = """DELETE FROM comment WHERE id = {question_id} OR answer_id = (SELECT id FROM answer WHERE id = {question_id}) + q = """DELETE FROM comment WHERE question_id = {question_id} OR answer_id = (SELECT id FROM answer WHERE id = {question_id}) """.format(question_id=question_id) data_handler.execute_query(q) q = """DELETE FROM answer WHERE question_id = {question_id} From cc6670a6c1fef67a54351c7c3c8f217d26cbe999 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 11:23:48 +0200 Subject: [PATCH 105/182] Change delete_record func in data_handler to use database --- data_handler.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/data_handler.py b/data_handler.py index 2a0343e2f..9a25b177d 100644 --- a/data_handler.py +++ b/data_handler.py @@ -69,13 +69,19 @@ def update_questions(question_id, updated_data): def delete_record(id, answer=False, delete=False): if answer: - answers = get_answers() - for i, answer in enumerate(answers): - question_id = answer['question_id'] - if answer['id'] == id and delete: - del answers[i] - save_answers(answers) - return question_id + question_id_query = f"""SELECT question_id FROM answer + WHERE id={id};""" + delete_answer_query = f"""DELETE FROM answer + WHERE id={id};""" + delete_comment_query = f"""DELETE FROM comment + WHERE answer_id={id};""" + question_id = execute_query(question_id_query)[0]['question_id'] + + if delete: + execute_query(delete_comment_query) + execute_query(delete_answer_query) + + return question_id @connection.connection_handler def execute_query(cursor, query): From 66129c43c75f4cbee8359295f728c428aa938ff0 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 11:25:55 +0200 Subject: [PATCH 106/182] Modify delete_answer in server module as database return question_id as integer and should be casted into str for redirection --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index 3ab33e2d8..799c4da4c 100644 --- a/server.py +++ b/server.py @@ -124,7 +124,7 @@ def delete_answer(answer_id): if request.form.get('delete') == 'Yes': delete = True question_id = data_handler.delete_record(answer_id, True, delete=delete) - return redirect('/question/' + question_id) + return redirect('/question/' + str(question_id)) else: return render_template('asking_if_delete_answer.html', answer_id=answer_id) From 160bfe19d587114a46740f904d8a45c7fe7307b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 24 Sep 2019 11:35:37 +0200 Subject: [PATCH 107/182] Search keywords in questions put into SQL (search_for_q func) --- server.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/server.py b/server.py index d1f41bd26..f4efaf54b 100644 --- a/server.py +++ b/server.py @@ -146,20 +146,34 @@ def delete_answer(answer_id): @app.route('/search-for-questions', methods=['GET', 'POST']) def search_for_questions(): - q_query = """SELECT * FROM question + question_query = """SELECT * FROM question """ - question_database = data_handler.execute_query(q_query) - a_query = """SELECT * FROM answer + question_database = data_handler.execute_query(question_query) + answer_query = """SELECT * FROM answer """ - answer_database = data_handler.execute_query(a_query) - + answer_database = data_handler.execute_query(answer_query) + print(answer_database) keywords = str(request.args.get('keywords')).replace(',', '').split(' ') - answer_related_question_id = util.get_answer_related_question_ids(keywords, answer_database, 'message') + + def check_keywords_in_answers(keywords): + string = f'\'%{keywords[0]}%\'' + for keyword in keywords[1:]: + string += f' OR message LIKE \'%{keyword}%\'' + return string + + + answer_related_question_id_query = """SELECT id FROM answer WHERE message LIKE {string} + """.format(string=check_keywords_in_answers(keywords)) + + answer_related_question_id = data_handler.execute_query(answer_related_question_id_query) + print(answer_related_question_id) + #answer_related_question_id = util.get_answer_related_question_ids(keywords, answer_database, 'message') questions_containing_keywords = util.search_keywords_in_attribute(keywords, answer_related_question_id, question_database, 'title', 'message') + print(questions_containing_keywords) return render_template('search_for_keywords_in_questions.html', keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords) From db6f21ee0e259d1bdc89adcb2ff39de7f17ff4c8 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 24 Sep 2019 12:45:27 +0200 Subject: [PATCH 108/182] Added backend functions for comments --- data_handler.py | 18 ++++++++++-------- server.py | 18 ++++++++++++++++-- templates/add-comment.html | 21 +++++++++++++++++++++ util.py | 12 ++++++++++-- 4 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 templates/add-comment.html diff --git a/data_handler.py b/data_handler.py index f61f9a454..0f87ca7d4 100644 --- a/data_handler.py +++ b/data_handler.py @@ -1,8 +1,10 @@ import os +from datetime import datetime from psycopg2 import sql import connection +from util import string_builder ANSWER_DATA_FILE_PATH = os.getcwd() + "/data/answer.csv" QUESTION_DATA_FILE_PATH = os.getcwd() + "/data/question.csv" @@ -83,6 +85,7 @@ def delete_record(id, answer=False, delete=False): save_answers(answers) return question_id + @connection.connection_handler def execute_query(cursor, query): # print(query.startswith("INSERT")) @@ -97,11 +100,10 @@ def execute_query(cursor, query): return result -def string_builder(lst, is_key=True): - result = "" - for element in lst: - if is_key: - result += "" + element + ", " - else: - result += "\'" + element + "\', " - return result[:-2] +def handle_add_comment(req): + req.update(submission_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + query ="""INSERT INTO comment ({columns}) + VALUES ({value_list})""".format(columns=string_builder(req.keys(), True), + value_list=string_builder(req.values(), False) + ) + execute_query(query) diff --git a/server.py b/server.py index 8ca4702e2..6ebc59f7e 100644 --- a/server.py +++ b/server.py @@ -3,8 +3,8 @@ from flask import Flask, render_template, request, redirect, url_for import data_handler import util -from util import handle_delete_question, handle_add_answer, handle_add_question - +from util import handle_add_answer, handle_add_question +from data_handler import handle_add_comment app = Flask(__name__) app.debug = True @@ -172,6 +172,20 @@ def upload_image(): return redirect("/") + +@app.route("/question//new-comment", methods=["GET", "POST"]) +@app.route("/answer//new-comment", methods=["GET", "POST"]) +def comment_question(id): + comment_type = "question" + if "answer" in str(request.url_rule): + comment_type = "answer" + + if request.method == 'POST': + req = request.form.to_dict() + handle_add_comment(req) + return render_template("add-comment.html", qid=id, type=comment_type) + + if __name__ == '__main__': app.run(debug=True) diff --git a/templates/add-comment.html b/templates/add-comment.html new file mode 100644 index 000000000..b66e57ef3 --- /dev/null +++ b/templates/add-comment.html @@ -0,0 +1,21 @@ + + + + + Title + + +
+

Comment

+
+
+ +
+
+ +
+ + +
+ + \ No newline at end of file diff --git a/util.py b/util.py index 7c43f8899..6fcf6af28 100644 --- a/util.py +++ b/util.py @@ -1,10 +1,8 @@ import copy import os -import time from datetime import datetime from flask import request import data_handler -from data_handler import get_questions, get_answers QUESTION_DATA_HEADER = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] @@ -154,3 +152,13 @@ def search_keywords_in_attribute(keywords, id_s, database, attribute_1, attribut elif item['id'] in id_s: items_containing_keywords.append(item) return items_containing_keywords + + +def string_builder(lst, is_key=True): + result = "" + for element in lst: + if is_key: + result += "" + element + ", " + else: + result += "\'" + element + "\', " + return result[:-2] \ No newline at end of file From 93d83688f006791bc3234f49e8a91d3889e51235 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 24 Sep 2019 13:11:29 +0200 Subject: [PATCH 109/182] Added comment display feature to html --- data_handler.py | 9 +++++++++ server.py | 5 +++-- templates/display_question.html | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/data_handler.py b/data_handler.py index 5000ce5c2..b6dd9cb1d 100644 --- a/data_handler.py +++ b/data_handler.py @@ -113,3 +113,12 @@ def handle_add_comment(req): value_list=string_builder(req.values(), False) ) execute_query(query) + + +def get_comments(comment_tpe, _id): + comment_tpe += "_id" + query = """SELECT message, submission_time, edited_count FROM comment + WHERE {col} = {id} + """.format(col=comment_tpe, id=_id) + print(query) + return execute_query(query) \ No newline at end of file diff --git a/server.py b/server.py index 8b9cd21da..6633cea52 100644 --- a/server.py +++ b/server.py @@ -63,8 +63,9 @@ def question_display(question_id): question = data_handler.execute_query(question_query) related_answers = data_handler.execute_query(answers_query) - - return render_template('display_question.html', question=question.pop(), answers=related_answers) + question_comments = data_handler.get_comments("question", question_id) + print(question_comments) + return render_template('display_question.html', question=question.pop(), question_comments=question_comments, answers=related_answers) @app.route("/question//vote-up") def vote_up_question(question_id): diff --git a/templates/display_question.html b/templates/display_question.html index 38b181543..e3fc60f8d 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -67,6 +67,32 @@

{{ question.title }}

+
+ +
{{ header|capitalize|replace('_', ' ') }}{{ cell|capitalize|replace('_', ' ') }}
{{ convert_to_readable_date(data) }}{{ convert_to_readable_date(question[cell]) }}{{ data }}{{ question[cell] }}{{ convert_to_readable_date(question.get(header)) }} + + {{ question.get(header) }}{{ convert_to_readable_date(answer.get(header)) }} + {% if answer.get(header) != "" %} {% endif %} From f3c37314adccbade2b22a88ad496d8ea536c773c Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 12 Sep 2019 21:23:23 +0200 Subject: [PATCH 082/182] Add some design to display question --- templates/display_question.html | 121 ++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index aae8842c0..10f05fcbf 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -1,29 +1,49 @@ +{% set headers = ['Vote', 'Question details', 'Image', 'Submission', 'View'] %} +{% set keys = [('vote_number', '10%', 'left'), ('message', '55%', 'left'), ('image', '20%', 'center'), ('submission_time', '10%', 'center'), ('view_number', '5%', 'center')] %} Question -

{{ question.title }}

- -

Question data

- +

{{ question.title }}

+
+
- {% for header in question.keys() %} - + {% for header in headers %} + {% if header == "Vote" %} + + {% else %} + + {% endif %} {% endfor %} - {% for header in question.keys() %} - {% if header == 'submission_time' %} - + + {% for key, width, align in keys %} + {% if key == 'submission_time' %} + + {% elif key == 'image' %} + {% else %} - + {% endif %} {% endfor %} @@ -33,63 +53,54 @@

Question data

{{ header|capitalize|replace('_', ' ') }}{{ header }}{{ header }}
{{ convert_to_readable_date(question.get(header)) }} + + + +
+ + +
+
{{ convert_to_readable_date(question.get(key)) }} + {% if question.get(key) != "" %} + Picture + {% endif %} + {{ question.get(header) }}{{ question.get(key) }}
+
-
- - + + +
+
+
+
-
- - + +
+ -
-
- -
-

Answers to the question

+ {% set answer_headers = ['Vote', 'Answer', 'Image', 'Submission'] %} + {% set answer_keys = [('vote_number', '10%', 'left'), ('message', '55%', 'left'), ('image', '20%', 'center'), ('submission_time', '10%', 'center')] %} + +

Answers to the question

{% if answers | length != 0 %} - +
+
- {% for header in answers[0].keys() %} - + {% for header in answer_headers %} + {% if header =="Vote" %} + + {% else %} + + {% endif %} {% endfor %} {% for answer in answers %} - {% set count=loop.index %} - {% for header in answer.keys() %} - {% if header == 'submission_time' %} - - {% elif header == 'image' %} - - - {% else %} - - {% endif %} - {% endfor %} - - + {% for key, width, align in answer_keys %} + {% if key == 'submission_time' %} + + {% elif key == 'image' %} + + {% else %} + + {% endif %} + {% endfor %} {% endfor %}
{{ header|capitalize|replace('_', ' ') }}{{ header}}{{ header}}
{{ convert_to_readable_date(answer.get(header)) }} - {% if answer.get(header) != "" %} - - {% endif %} - {{ answer.get(header) }} +
-
@@ -97,26 +108,34 @@

Answers to the question

{{ convert_to_readable_date(answer.get(key)) }} + {% if answer.get(key) != "" %} + Picture + {% endif %} + {{ answer.get(key) }}
- +
+ {% else %} - {{ "There is no answer to this question" }} +

{{ "There is no answer to this question" }}

{% endif %}
-
- -
-
-
- -
+ From ee5d4b86842c115afbdfb53e2c73b3a0dd30bccb Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 12 Sep 2019 22:13:32 +0200 Subject: [PATCH 083/182] Refactor update question and delete record functions --- data_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_handler.py b/data_handler.py index f29caa6fb..b52e89117 100644 --- a/data_handler.py +++ b/data_handler.py @@ -63,7 +63,7 @@ def update_questions(question_id, updated_data): for key, value in updated_data.items(): question[key] = value all_questions[question_index] = question - connection.dict_to_csv(QUESTION_DATA_FILE_PATH, all_questions) + save_questions(all_questions) return question @@ -74,5 +74,5 @@ def delete_record(id, answer=False): if answer['id'] == id: question_id = answer['question_id'] del answers[i] - connection.dict_to_csv(ANSWER_DATA_FILE_PATH,answers, is_answers=True) + save_answers(answers) return question_id From 42cd9c58affd5e8925acb040ff554ae6f38de067 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 12 Sep 2019 23:35:42 +0200 Subject: [PATCH 084/182] Add 'sure?' question to delete answer feature --- data_handler.py | 8 ++++---- server.py | 12 +++++++++--- templates/asking_if_delete_answer.html | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 templates/asking_if_delete_answer.html diff --git a/data_handler.py b/data_handler.py index b52e89117..16a50acb5 100644 --- a/data_handler.py +++ b/data_handler.py @@ -67,12 +67,12 @@ def update_questions(question_id, updated_data): return question -def delete_record(id, answer=False): +def delete_record(id, answer=False, delete=False): if answer: answers = get_answers() for i, answer in enumerate(answers): - if answer['id'] == id: - question_id = answer['question_id'] + question_id = answer['question_id'] + if answer['id'] == id and delete: del answers[i] save_answers(answers) - return question_id + return question_id diff --git a/server.py b/server.py index 120e08653..62d1e6bee 100644 --- a/server.py +++ b/server.py @@ -108,10 +108,16 @@ def edit_question(question_id): return render_template('edit-question.html', question=question) -@app.route('/answer//delete') +@app.route('/answer//delete', methods=['GET', 'POST']) def delete_answer(answer_id): - question_id = data_handler.delete_record(answer_id, True) - return redirect('/question/' + question_id) + if request.method == 'POST': + delete = False + if request.form.get('delete') == 'Yes': + delete = True + question_id = data_handler.delete_record(answer_id, True, delete=delete) + return redirect('/question/' + question_id) + else: + return render_template('asking_if_delete_answer.html', answer_id=answer_id) @app.route('/search-for-questions', methods=['GET', 'POST']) diff --git a/templates/asking_if_delete_answer.html b/templates/asking_if_delete_answer.html new file mode 100644 index 000000000..35c1023cb --- /dev/null +++ b/templates/asking_if_delete_answer.html @@ -0,0 +1,16 @@ + + + + + Delete answer + + +
+

Are you sure you want to delete this answer?

+
+ + +
+
+ + \ No newline at end of file From d65bd97a9ab5629805a4e7f463248b53d71a196a Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Fri, 13 Sep 2019 08:44:05 +0200 Subject: [PATCH 085/182] Refactor in server module --- server.py | 13 ++----------- util.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/server.py b/server.py index 62d1e6bee..b8f283e98 100644 --- a/server.py +++ b/server.py @@ -5,7 +5,7 @@ import data_handler import util -from util import handle_delete_question, handle_add_answer, handle_add_question +from util import handle_delete_question, handle_add_answer, handle_add_question, handle_list_question app = Flask(__name__) app.debug = True @@ -16,16 +16,7 @@ def list_questions(): questions = data_handler.get_questions() - order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') - order_direction = False if request.args.get('order_direction') == 'asc' else True - sorted_questions = util.sorting_data(questions, order_by, order_direction) - order_direction = 'asc' if order_direction == False else 'desc' - - return render_template('list.html', - sorted_questions=sorted_questions, - order_by=order_by, - order_direction=order_direction, - convert_to_readable_date=util.convert_to_readable_date) + return handle_list_question(questions) @app.route('/add-question', methods=["GET", "POST"]) diff --git a/util.py b/util.py index 93e0036d5..8c67fd677 100644 --- a/util.py +++ b/util.py @@ -3,7 +3,7 @@ import time from datetime import datetime -from flask import request +from flask import request, render_template import data_handler from data_handler import get_questions, get_answers @@ -121,4 +121,16 @@ def handle_add_question(req): handle_upload(req) question = generate_question_dict(req) questions.append(question) - data_handler.add_entry(question) \ No newline at end of file + data_handler.add_entry(question) + + +def handle_list_question(questions): + order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') + order_direction = False if request.args.get('order_direction') == 'asc' else True + sorted_questions = sorting_data(questions, order_by, order_direction) + order_direction = 'asc' if order_direction == False else 'desc' + return render_template('list.html', + sorted_questions=sorted_questions, + order_by=order_by, + order_direction=order_direction, + convert_to_readable_date=convert_to_readable_date) \ No newline at end of file From 58a8091425fd4f5da2f09de834cdeb4f05434ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Fri, 13 Sep 2019 13:57:49 +0200 Subject: [PATCH 086/182] Write docstrings for handle_list_question and convert_to_readable_date functions. --- util.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/util.py b/util.py index 8c67fd677..0aaad1fb5 100644 --- a/util.py +++ b/util.py @@ -38,10 +38,11 @@ def handle_upload(req): def sorting_data(data, attribute, order_flag): ''' + Sorts data by attribute in order order_flag. :param data: list of dictionaries - :param attribute: By which the data is sorted- - :param order_flag: The order is ascending (False) or descending (True). - :return: The sorted data. + :param attribute: By which the data is sorted- This is the key of dictionaries. + :param order_flag: Boolean. The order is ascending (False) or descending (True). + :return: The sorted data. List of dictionaries. ''' try: sorted_data = sorted(data, key=lambda x: int(x[attribute]) if x[attribute].isdigit() else x[attribute], reverse=order_flag) @@ -51,6 +52,11 @@ def sorting_data(data, attribute, order_flag): def convert_to_readable_date(timestamp): + ''' + Converts unix timestamp into 2019-09-12 12:54:49 date-time format. + :param timestamp: string + :return: string + ''' readable_time = datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S') return readable_time @@ -125,6 +131,12 @@ def handle_add_question(req): def handle_list_question(questions): + ''' + Assign values to the parameters of the sorted_questions function. It happens by getting them from the user. + Sort the questions according to sorted questions's parameters. + :param questions:list of dictionaries + :return: sorted list of dictionaries + ''' order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') order_direction = False if request.args.get('order_direction') == 'asc' else True sorted_questions = sorting_data(questions, order_by, order_direction) From 0b57167787e8b191e9725dcccd1260fcf6bee896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Fri, 13 Sep 2019 14:40:03 +0200 Subject: [PATCH 087/182] Move handle_list_question (util) into list_question (server) --- server.py | 19 ++++++++++++++++--- util.py | 18 ------------------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/server.py b/server.py index b8f283e98..43fcd3475 100644 --- a/server.py +++ b/server.py @@ -5,7 +5,7 @@ import data_handler import util -from util import handle_delete_question, handle_add_answer, handle_add_question, handle_list_question +from util import handle_delete_question, handle_add_answer, handle_add_question, sorting_data, convert_to_readable_date app = Flask(__name__) app.debug = True @@ -14,9 +14,22 @@ @app.route('/list') @app.route('/?order_by=&order_direction=', methods=['GET', 'POST']) def list_questions(): + ''' + Assign values to the parameters of the sorted_questions function. It happens by getting them from the user. + Sort the questions according to sorted questions's parameters. + :param questions:list of dictionaries + :return: sorted list of dictionaries + ''' questions = data_handler.get_questions() - - return handle_list_question(questions) + order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') + order_direction = False if request.args.get('order_direction') == 'asc' else True + sorted_questions = sorting_data(questions, order_by, order_direction) + order_direction = 'asc' if order_direction == False else 'desc' + return render_template('list.html', + sorted_questions=sorted_questions, + order_by=order_by, + order_direction=order_direction, + convert_to_readable_date=convert_to_readable_date) @app.route('/add-question', methods=["GET", "POST"]) diff --git a/util.py b/util.py index 0aaad1fb5..53d8fbdce 100644 --- a/util.py +++ b/util.py @@ -128,21 +128,3 @@ def handle_add_question(req): question = generate_question_dict(req) questions.append(question) data_handler.add_entry(question) - - -def handle_list_question(questions): - ''' - Assign values to the parameters of the sorted_questions function. It happens by getting them from the user. - Sort the questions according to sorted questions's parameters. - :param questions:list of dictionaries - :return: sorted list of dictionaries - ''' - order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') - order_direction = False if request.args.get('order_direction') == 'asc' else True - sorted_questions = sorting_data(questions, order_by, order_direction) - order_direction = 'asc' if order_direction == False else 'desc' - return render_template('list.html', - sorted_questions=sorted_questions, - order_by=order_by, - order_direction=order_direction, - convert_to_readable_date=convert_to_readable_date) \ No newline at end of file From 55c6af3cf9503f7684ca0e00617496435362497c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Fri, 13 Sep 2019 15:00:57 +0200 Subject: [PATCH 088/182] Move headers form connection to data_handler, delete redundant comments --- connection.py | 10 ++-------- data_handler.py | 3 --- server.py | 8 +++----- util.py | 8 +++++--- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/connection.py b/connection.py index 339a354f1..e03bb7ec8 100644 --- a/connection.py +++ b/connection.py @@ -1,10 +1,4 @@ -# ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' -import csv, os - - -QUESTION_ANSWER_DATA_HEADER = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] -ANSWER_DATA_HEADER = ['id', 'submission_time', 'vote_number', 'question_id', 'message', 'image'] - +import csv def csv_to_dict(file_path): with open(file_path, 'r', newline='') as f: @@ -15,7 +9,7 @@ def csv_to_dict(file_path): def dict_to_csv(file_path, data, is_answers=False): with open(file_path, 'w', newline='') as f: - writer = csv.DictWriter(f, ANSWER_DATA_HEADER if is_answers else QUESTION_ANSWER_DATA_HEADER) + writer = csv.DictWriter(f, ANSWER_DATA_HEADER if is_answers else QUESTION_DATA_HEADER) writer.writeheader() writer.writerows(data) diff --git a/data_handler.py b/data_handler.py index 16a50acb5..254d5dfca 100644 --- a/data_handler.py +++ b/data_handler.py @@ -1,7 +1,4 @@ import os - -# ANSWER_DATA_FILE_PATH = os.getenv('DATA_FILE_PATH') if 'DATA_FILE_PATH' in os.environ else 'data/answer.csv' - import connection ANSWER_DATA_FILE_PATH = os.getcwd() + "/data/answer.csv" diff --git a/server.py b/server.py index 43fcd3475..3980b4571 100644 --- a/server.py +++ b/server.py @@ -1,8 +1,6 @@ import os import time - from flask import Flask, render_template, request, redirect, url_for - import data_handler import util from util import handle_delete_question, handle_add_answer, handle_add_question, sorting_data, convert_to_readable_date @@ -17,8 +15,9 @@ def list_questions(): ''' Assign values to the parameters of the sorted_questions function. It happens by getting them from the user. Sort the questions according to sorted questions's parameters. + Convert order_direction from boolean to string. It is needed for user interface (html). :param questions:list of dictionaries - :return: sorted list of dictionaries + :return: ''' questions = data_handler.get_questions() order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') @@ -126,7 +125,6 @@ def delete_answer(answer_id): @app.route('/search-for-questions', methods=['GET', 'POST']) def search_for_questions(): - fieldnames = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] question_database = data_handler.get_questions() keywords = str(request.args.get('keywords')).replace(',', '').split(' ') questions_containing_keywords = [] @@ -135,7 +133,7 @@ def search_for_questions(): questions_containing_keywords.append(question) return render_template('search_for_keywords_in_questions.html', - keywords=keywords, fieldnames=fieldnames, questions=questions_containing_keywords, + keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords, convert_to_readable_date=util.convert_to_readable_date) diff --git a/util.py b/util.py index 53d8fbdce..bd747965f 100644 --- a/util.py +++ b/util.py @@ -2,13 +2,15 @@ import os import time from datetime import datetime - -from flask import request, render_template - +from flask import request import data_handler from data_handler import get_questions, get_answers +QUESTION_DATA_HEADER = ['id', 'submission_time', 'view_number', 'vote_number', 'title', 'message', 'image'] +ANSWER_DATA_HEADER = ['id', 'submission_time', 'vote_number', 'question_id', 'message', 'image'] + + def vote_question(_id, vote): questions = data_handler.get_questions() question = data_handler.get_question(_id, questions) From 98771906ee1500944d41d6458a4077f048ea8913 Mon Sep 17 00:00:00 2001 From: lterray Date: Mon, 28 Aug 2017 16:35:10 +0200 Subject: [PATCH 089/182] add sql sample data --- sample_data/askmatepart2-sample-data.sql | 127 +++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 sample_data/askmatepart2-sample-data.sql diff --git a/sample_data/askmatepart2-sample-data.sql b/sample_data/askmatepart2-sample-data.sql new file mode 100644 index 000000000..369355a65 --- /dev/null +++ b/sample_data/askmatepart2-sample-data.sql @@ -0,0 +1,127 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.5.6 +-- Dumped by pg_dump version 9.5.6 + +ALTER TABLE IF EXISTS ONLY public.question DROP CONSTRAINT IF EXISTS pk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.answer DROP CONSTRAINT IF EXISTS pk_answer_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.answer DROP CONSTRAINT IF EXISTS fk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.comment DROP CONSTRAINT IF EXISTS pk_comment_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.comment DROP CONSTRAINT IF EXISTS fk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.comment DROP CONSTRAINT IF EXISTS fk_answer_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.question_tag DROP CONSTRAINT IF EXISTS pk_question_tag_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.question_tag DROP CONSTRAINT IF EXISTS fk_question_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.tag DROP CONSTRAINT IF EXISTS pk_tag_id CASCADE; +ALTER TABLE IF EXISTS ONLY public.question_tag DROP CONSTRAINT IF EXISTS fk_tag_id CASCADE; + +DROP TABLE IF EXISTS public.question; +DROP SEQUENCE IF EXISTS public.question_id_seq; +CREATE TABLE question ( + id serial NOT NULL, + submission_time timestamp without time zone, + view_number integer, + vote_number integer, + title text, + message text, + image text +); + +DROP TABLE IF EXISTS public.answer; +DROP SEQUENCE IF EXISTS public.answer_id_seq; +CREATE TABLE answer ( + id serial NOT NULL, + submission_time timestamp without time zone, + vote_number integer, + question_id integer, + message text, + image text +); + +DROP TABLE IF EXISTS public.comment; +DROP SEQUENCE IF EXISTS public.comment_id_seq; +CREATE TABLE comment ( + id serial NOT NULL, + question_id integer, + answer_id integer, + message text, + submission_time timestamp without time zone, + edited_count integer +); + + +DROP TABLE IF EXISTS public.question_tag; +CREATE TABLE question_tag ( + question_id integer NOT NULL, + tag_id integer NOT NULL +); + +DROP TABLE IF EXISTS public.tag; +DROP SEQUENCE IF EXISTS public.tag_id_seq; +CREATE TABLE tag ( + id serial NOT NULL, + name text +); + + +ALTER TABLE ONLY answer + ADD CONSTRAINT pk_answer_id PRIMARY KEY (id); + +ALTER TABLE ONLY comment + ADD CONSTRAINT pk_comment_id PRIMARY KEY (id); + +ALTER TABLE ONLY question + ADD CONSTRAINT pk_question_id PRIMARY KEY (id); + +ALTER TABLE ONLY question_tag + ADD CONSTRAINT pk_question_tag_id PRIMARY KEY (question_id, tag_id); + +ALTER TABLE ONLY tag + ADD CONSTRAINT pk_tag_id PRIMARY KEY (id); + +ALTER TABLE ONLY comment + ADD CONSTRAINT fk_answer_id FOREIGN KEY (answer_id) REFERENCES answer(id); + +ALTER TABLE ONLY answer + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES question(id); + +ALTER TABLE ONLY question_tag + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES question(id); + +ALTER TABLE ONLY comment + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES question(id); + +ALTER TABLE ONLY question_tag + ADD CONSTRAINT fk_tag_id FOREIGN KEY (tag_id) REFERENCES tag(id); + +INSERT INTO question VALUES (0, '2017-04-28 08:29:00', 29, 7, 'How to make lists in Python?', 'I am totally new to this, any hints?', NULL); +INSERT INTO question VALUES (1, '2017-04-29 09:19:00', 15, 9, 'Wordpress loading multiple jQuery Versions', 'I developed a plugin that uses the jquery booklet plugin (http://builtbywill.com/booklet/#/) this plugin binds a function to $ so I cann call $(".myBook").booklet(); + +I could easy managing the loading order with wp_enqueue_script so first I load jquery then I load booklet so everything is fine. + +BUT in my theme i also using jquery via webpack so the loading order is now following: + +jquery +booklet +app.js (bundled file with webpack, including jquery)', 'images/image1.png'); +INSERT INTO question VALUES (2, '2017-05-01 10:41:00', 1364, 57, 'Drawing canvas with an image picked with Cordova Camera Plugin', 'I''m getting an image from device and drawing a canvas with filters using Pixi JS. It works all well using computer to get an image. But when I''m on IOS, it throws errors such as cross origin issue, or that I''m trying to use an unknown format. +', NULL); +SELECT pg_catalog.setval('question_id_seq', 2, true); + +INSERT INTO answer VALUES (1, '2017-04-28 16:49:00', 4, 1, 'You need to use brackets: my_list = []', NULL); +INSERT INTO answer VALUES (2, '2017-04-25 14:42:00', 35, 1, 'Look it up in the Python docs', 'images/image2.jpg'); +SELECT pg_catalog.setval('answer_id_seq', 2, true); + +INSERT INTO comment VALUES (1, 0, NULL, 'Please clarify the question as it is too vague!', '2017-05-01 05:49:00'); +INSERT INTO comment VALUES (2, NULL, 1, 'I think you could use my_list = list() as well.', '2017-05-02 16:55:00'); +SELECT pg_catalog.setval('comment_id_seq', 2, true); + +INSERT INTO tag VALUES (1, 'python'); +INSERT INTO tag VALUES (2, 'sql'); +INSERT INTO tag VALUES (3, 'css'); +SELECT pg_catalog.setval('tag_id_seq', 3, true); + +INSERT INTO question_tag VALUES (0, 1); +INSERT INTO question_tag VALUES (1, 3); +INSERT INTO question_tag VALUES (2, 3); From ee32710429c06d4ab47be5484a741b67e50dc0ae Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Mon, 23 Sep 2019 13:04:04 +0200 Subject: [PATCH 090/182] Add connection and data manager module - connection to database and test it --- connection.py | 51 ++++++++++++++++++ data_manager.py | 10 ++++ .../Screenshot from 2019-09-12 21-17-50.png | Bin 0 -> 84238 bytes static/images/image1.png | Bin 0 -> 2214 bytes static/images/image2.jpeg | Bin 0 -> 3727 bytes 5 files changed, 61 insertions(+) create mode 100644 connection.py create mode 100644 data_manager.py create mode 100644 static/images/Screenshot from 2019-09-12 21-17-50.png create mode 100644 static/images/image1.png create mode 100644 static/images/image2.jpeg diff --git a/connection.py b/connection.py new file mode 100644 index 000000000..c9bda939a --- /dev/null +++ b/connection.py @@ -0,0 +1,51 @@ +# Creates a decorator to handle the database connection/cursor opening/closing. +# Creates the cursor with RealDictCursor, thus it returns real dictionaries, where the column names are the keys. +import os +import psycopg2 +import psycopg2.extras + + +def get_connection_string(): + # setup connection string + # to do this, please define these environment variables first + user_name = os.environ.get('PSQL_USER_NAME') + password = os.environ.get('PSQL_PASSWORD') + host = os.environ.get('PSQL_HOST') + database_name = os.environ.get('PSQL_DB_NAME') + + env_variables_defined = user_name and password and host and database_name + + if env_variables_defined: + # this string describes all info for psycopg2 to connect to the database + return 'postgresql://{user_name}:{password}@{host}/{database_name}'.format( + user_name=user_name, + password=password, + host=host, + database_name=database_name + ) + else: + raise KeyError('Some necessary environment variable(s) are not defined') + + +def open_database(): + try: + connection_string = get_connection_string() + connection = psycopg2.connect(connection_string) + connection.autocommit = True + except psycopg2.DatabaseError as exception: + print('Database connection problem') + raise exception + return connection + + +def connection_handler(function): + def wrapper(*args, **kwargs): + connection = open_database() + # we set the cursor_factory parameter to return with a RealDictCursor cursor (cursor which provide dictionaries) + dict_cur = connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + ret_value = function(dict_cur, *args, **kwargs) + dict_cur.close() + connection.close() + return ret_value + + return wrapper diff --git a/data_manager.py b/data_manager.py new file mode 100644 index 000000000..30aa600bf --- /dev/null +++ b/data_manager.py @@ -0,0 +1,10 @@ +import connection + + +@connection.connection_handler +def test_connection(cursor): + print('ok') + + +if __name__ == '__main__': + test_connection() diff --git a/static/images/Screenshot from 2019-09-12 21-17-50.png b/static/images/Screenshot from 2019-09-12 21-17-50.png new file mode 100644 index 0000000000000000000000000000000000000000..62125ec28588213aec9ff1f2412a4a062bac4e8c GIT binary patch literal 84238 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 z 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 Qj}r?6J=43Tcbs1Q7mom@F#rGn literal 0 HcmV?d00001 diff --git a/static/images/image1.png b/static/images/image1.png new file mode 100644 index 0000000000000000000000000000000000000000..b8b2b3116f96f99ccc7101b5f18dba52e5e5134b GIT binary patch literal 2214 zcmV;X2wC@uP)rJ9f(8yg#Oad9*>G&?&x5fKr*yu3z6 zMpIK$PEJnk?d=E%2v}HHUS3}H_4P6`G8q{e&CShhY;4!p*Ao*Hetv%E=jVikgr1(B zWo2cIjEsSSfu6yR(EtDk>`6pHRCt{2o#}e&AP|NVwQAjM<67%d?YaL;JTYb&VF(a` z=@a;V#suayB*4r-qUfRHHugiE2}+|UqICZt2J@pGg5G9j@3t;2j7!| zgK);kZcPmMh#34-)%?$HO&s`$5_|<}wmN+IYv3@uaAmhD4tz`r4ubQO;+Um(90~aQ zG{%1b&23s7i_aGuHV}X>??VOjKJGxpZ{S#Bp-bn$Cw9Sic~%vA7YCr?;)KVavFMm? z@ovG7Ww-(b2dQSK;#k=V47Yd&d~6f^4U7uku@k_|@Vht$n!nK&?+kqPoH>YsgWrec zb?~Z52fM71IPfnk;74|469#_3eqS*c{upZUPQaJ^Iu{Haq|vU+gDiL)Z;W_m?1S&n zv|tPzxb(N>K^{DP1rL#{HN}CSo)5eV{>~&C)PaMat7jPlPxbrih}JInlJVfxf*;YJ z1>kXfJHBL^MetpBEixJfhf4&iqeAdFp1<*hD2oA~7y}ozaWOcE%x|irHQ;yi8n<{3 zd|?E9$;2g8z`?KA>S#T9I?`#_;%$KMu1vF00*9dl03!oWTYMvzg?VsE8_%>sY=xnO zr}BP;#S~h1^`|hV!3Xla2F6$P!31Nwg!ySHFLa8Ifn9;`I?V6~vrzO*f^)jukC*O3 zSYoceUW@0znJ!2J{KBNVW5$U)pbIz+9g`kzfvUEf?wb9SUx$m%(L5U|lvM7)nsMVN2N?MOy32;I*6O zjFsQB;AxpDU9hTO{c9h*rv85#g9_k>{87E8u7lGDX9|#li`JWeYPm#VC_!T&vb&0U z6J79yHaNb+Kv#3R2tx^a?xr+c)Sa|BDE6&cAYVAgMDy*7m+3tWY9OC z8(eB@H7<+9U7CIH zK`fGR=FJO?!>01HOYOvSBTDe}wht9=JGe&OWqhdHX^<8?R=n*nl%Nztu3Auy18?Sf+FhkyP;uW5Lkaa0x>ZkFjcORx5O`D<87(vt4CzFn1g-|DKT{)c zZVnvD_uzHIJjdX&o3QYUJfNbLDq#|w%kaVU#m`v5yGoSYBlR$9C&!otzZb~leIwdF z`zWBF5q-oJZnl0plyUI1la=pKzV3mi9gg;~;$#u$W(jZ>z|(su-@X~#S`Z`HB85qd zYL@b71w7TTenbhuL0ssHlvH1iTJ!*sEpS<&p#q0~My#u^E2ZB&$xrH;Hf&Ikyd3hcprRXMToE)WI;e#1{Y3YDsYhQ$tMIv0`S?(OLeS+3lauO zaQKL$BQNCQrkPGq4!~3KQVj8o&8?+(2Y#QlSB010pe~%M`u)e`8ay@vWD`6BF^>Wq`Wf*{@*l?r za@3(Xa5K5AV&yHq00&WK!43i0Z)R2{Ksg7`TYLcyW^#g@pV;`yt4svosg|RnCk9ms z#@%D%Ywa>|;D(oGNCf?ic6SJ1#ev&dekBs{Gw$uy#EKXk^zF(yKW4;%TV7Pl2s~Jp zcL-ob3l9B^mUjqXMGFr6jG74nFN+(pI^8M(xMK4I+|Z`wnqvF9_@FbguZtJD zK4mF*q)X&yN1iQlq&9*=Ssv}X{Jgd)PndZ};1FIf(W)?pT&p;kb z55DhL7UnjRWUHh?hSwhb0R`{?9>4>50B@55cmNOJ0X%@WNdY{72k-zMz}ut%9>4>5 z01x18l44}i)P{YIH0Ver{T$#!Q+Y{$>A#V0v~EypABJ-yg|GCQQ_V*&ohyI`@Bkjb o19$)r-~l{<2k-zMzzu=_2Ttmm?tKr$+W-In07*qoM6N<$g2>k%;{X5v literal 0 HcmV?d00001 diff --git a/static/images/image2.jpeg b/static/images/image2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..1d8969788e925100fdda14bf165722255b72cfb5 GIT binary patch literal 3727 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 Date: Mon, 23 Sep 2019 13:09:47 +0200 Subject: [PATCH 091/182] Add sqlconnection --- connection.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ data_handler.py | 18 +++++++++++++++++ server.py | 8 ++++++-- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/connection.py b/connection.py index e03bb7ec8..abf636761 100644 --- a/connection.py +++ b/connection.py @@ -1,4 +1,8 @@ import csv +import os +import psycopg2 +import psycopg2.extras + def csv_to_dict(file_path): with open(file_path, 'r', newline='') as f: @@ -18,3 +22,53 @@ def append_to_csv(file_path, data): with open(file_path, 'a', newline='') as f: writer = csv.writer(f) writer.writerows([data.values()]) + + +# Creates a decorator to handle the database connection/cursor opening/closing. +# Creates the cursor with RealDictCursor, thus it returns real dictionaries, where the column names are the keys. + + +def get_connection_string(): + # setup connection string + # to do this, please define these environment variables first + user_name = os.environ.get('PSQL_USER_NAME') + password = os.environ.get('PSQL_PASSWORD') + host = os.environ.get('PSQL_HOST') + database_name = os.environ.get('PSQL_DB_NAME') + + env_variables_defined = user_name and password and host and database_name + + if env_variables_defined: + # this string describes all info for psycopg2 to connect to the database + return 'postgresql://{user_name}:{password}@{host}/{database_name}'.format( + user_name=user_name, + password=password, + host=host, + database_name=database_name + ) + else: + raise KeyError('Some necessary environment variable(s) are not defined') + + +def open_database(): + try: + connection_string = get_connection_string() + connection = psycopg2.connect(connection_string) + connection.autocommit = True + except psycopg2.DatabaseError as exception: + print('Database connection problem') + raise exception + return connection + + +def connection_handler(function): + def wrapper(*args, **kwargs): + connection = open_database() + # we set the cursor_factory parameter to return with a RealDictCursor cursor (cursor which provide dictionaries) + dict_cur = connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + ret_value = function(dict_cur, *args, **kwargs) + dict_cur.close() + connection.close() + return ret_value + + return wrapper diff --git a/data_handler.py b/data_handler.py index 254d5dfca..1f074c648 100644 --- a/data_handler.py +++ b/data_handler.py @@ -1,4 +1,7 @@ import os + +from psycopg2 import sql + import connection ANSWER_DATA_FILE_PATH = os.getcwd() + "/data/answer.csv" @@ -73,3 +76,18 @@ def delete_record(id, answer=False, delete=False): del answers[i] save_answers(answers) return question_id + +@connection.connection_handler +def execute_query(cursor, query): + # print(query.startswith("INSERT")) + if query.startswith("SELECT"): + cursor.execute( + sql.SQL(query) + ) + print(query) + result = cursor.fetchall() + + else: + result = cursor.execute(query) + return result + diff --git a/server.py b/server.py index 3980b4571..f86c14618 100644 --- a/server.py +++ b/server.py @@ -19,7 +19,11 @@ def list_questions(): :param questions:list of dictionaries :return: ''' - questions = data_handler.get_questions() + q = """SELECT * FROM question + """ + # questions = data_handler.get_questions() + questions = data_handler.execute_query(q) + print(questions) order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') order_direction = False if request.args.get('order_direction') == 'asc' else True sorted_questions = sorting_data(questions, order_by, order_direction) @@ -28,7 +32,7 @@ def list_questions(): sorted_questions=sorted_questions, order_by=order_by, order_direction=order_direction, - convert_to_readable_date=convert_to_readable_date) + convert_to_readable_date=str) @app.route('/add-question', methods=["GET", "POST"]) From 3fe82058299ec9e74180bbeb3ea9feade7aa0ed5 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Mon, 23 Sep 2019 13:39:59 +0200 Subject: [PATCH 092/182] Implemented: list now questions uses sql --- data_handler.py | 1 - server.py | 16 +++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/data_handler.py b/data_handler.py index 1f074c648..2a0343e2f 100644 --- a/data_handler.py +++ b/data_handler.py @@ -84,7 +84,6 @@ def execute_query(cursor, query): cursor.execute( sql.SQL(query) ) - print(query) result = cursor.fetchall() else: diff --git a/server.py b/server.py index f86c14618..12caede9c 100644 --- a/server.py +++ b/server.py @@ -19,17 +19,15 @@ def list_questions(): :param questions:list of dictionaries :return: ''' - q = """SELECT * FROM question - """ - # questions = data_handler.get_questions() - questions = data_handler.execute_query(q) - print(questions) - order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') order_direction = False if request.args.get('order_direction') == 'asc' else True - sorted_questions = sorting_data(questions, order_by, order_direction) - order_direction = 'asc' if order_direction == False else 'desc' + order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') + order_direction = 'ASC' if order_direction == False else 'DESC' + + q = """SELECT * FROM question ORDER BY {order_by} {order_direction} + """.format(order_by=order_by, order_direction=order_direction) + questions = data_handler.execute_query(q) return render_template('list.html', - sorted_questions=sorted_questions, + sorted_questions=questions, order_by=order_by, order_direction=order_direction, convert_to_readable_date=str) From eeeb8d8cfe397faf476537e2ebf85c00eb673a73 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Mon, 23 Sep 2019 13:41:09 +0200 Subject: [PATCH 093/182] Delete data_manager.py inserted earlier locally --- data_manager.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 data_manager.py diff --git a/data_manager.py b/data_manager.py deleted file mode 100644 index 30aa600bf..000000000 --- a/data_manager.py +++ /dev/null @@ -1,10 +0,0 @@ -import connection - - -@connection.connection_handler -def test_connection(cursor): - print('ok') - - -if __name__ == '__main__': - test_connection() From d45984a53c3db25c151845a44c84735c0b85c196 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Mon, 23 Sep 2019 14:49:57 +0200 Subject: [PATCH 094/182] Changed: Added sql support question/answer to sql --- data_handler.py | 21 ++++++++++++++++++--- util.py | 9 +++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/data_handler.py b/data_handler.py index 2a0343e2f..f61f9a454 100644 --- a/data_handler.py +++ b/data_handler.py @@ -29,11 +29,17 @@ def save_answers(data): def add_entry(entry, is_answer=False): + table = "answer" if not is_answer: - connection.append_to_csv(QUESTION_DATA_FILE_PATH, entry) - else: - connection.append_to_csv(ANSWER_DATA_FILE_PATH, entry) + table = "question" + query = """INSERT INTO {table} + ({columns}) VALUES ({values}); + """.format(columns=string_builder(entry.keys()), + values=string_builder(entry.values(), False), + table=table) + print(query) + execute_query(query) def get_question(question_id, question_database): for question_data in question_database: @@ -90,3 +96,12 @@ def execute_query(cursor, query): result = cursor.execute(query) return result + +def string_builder(lst, is_key=True): + result = "" + for element in lst: + if is_key: + result += "" + element + ", " + else: + result += "\'" + element + "\', " + return result[:-2] diff --git a/util.py b/util.py index bd747965f..8ba5d5f03 100644 --- a/util.py +++ b/util.py @@ -80,8 +80,8 @@ def gen_answer_id(): def generate_question_dict(data): question_data = {} - question_data.update(id=str(gen_question_id())) - question_data.update(submission_time=str(int(time.time()))) + # question_data.update(id="") + question_data.update(submission_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) question_data.update(view_number=str(0)) question_data.update(vote_number=str(0)) question_data.update(title=data["title"]) @@ -93,8 +93,9 @@ def generate_question_dict(data): def generate_answer_dict(data): answer_data = {} - answer_data.update(id=str(gen_answer_id())) - answer_data.update(submission_time=str(int(time.time()))) + # answer_data.update(id=str(gen_answer_id())) + # answer_data.update(submission_time=str(int(time.time()))) + answer_data.update(submission_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) answer_data.update(vote_number=str(0)) answer_data.update(question_id=data["question_id"]) answer_data.update(message=data["message"]) From c19fca15617a8adbc677b9e1aa6719888d686a6e Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Mon, 23 Sep 2019 14:51:07 +0200 Subject: [PATCH 095/182] Rewrite question_display to use database in server module --- server.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server.py b/server.py index 12caede9c..3ab33e2d8 100644 --- a/server.py +++ b/server.py @@ -55,12 +55,16 @@ def add_answer(question_id): @app.route('/question/') def question_display(question_id): - question_database = data_handler.get_questions() - answer_database = data_handler.get_answers() - question = data_handler.get_question(question_id, question_database) + question_query = f"""SELECT * FROM question + WHERE id={question_id};""" + + answers_query = f"""SELECT * FROM answer + WHERE question_id={question_id};""" + + question = data_handler.execute_query(question_query) + related_answers = data_handler.execute_query(answers_query) - related_answers = data_handler.get_question_related_answers(question_id, answer_database) - return render_template('display_question.html', question=question, answers=util.sorting_data(related_answers, 'submission_time', True), convert_to_readable_date=util.convert_to_readable_date) + return render_template('display_question.html', question=question.pop(), answers=related_answers, convert_to_readable_date=str) @app.route("/question//vote-up") def vote_up_question(question_id): From 83feb59e1a3e3f213bed092f5769b1e5db192c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Mon, 23 Sep 2019 14:57:42 +0200 Subject: [PATCH 096/182] delete_question to SQL - refactor needed --- server.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 12caede9c..cef6a9d7f 100644 --- a/server.py +++ b/server.py @@ -88,7 +88,21 @@ def vote_answer(): def delete_question(question_id): if request.method == 'POST': if request.form.get('delete') == 'Yes': - handle_delete_question(question_id) + + q = """DELETE FROM comment WHERE answer_id = (SELECT id FROM answer WHERE id = {question_id}) + """.format(question_id=question_id) + data_handler.execute_query(q) + q = """DELETE FROM answer WHERE question_id = {question_id} + """.format(question_id=question_id) + data_handler.execute_query(q) + q = """DELETE FROM question_tag WHERE question_id = {question_id} + """.format(question_id=question_id) + data_handler.execute_query(q) + q = """DELETE FROM question WHERE id = {question_id} + """.format(question_id=question_id) + data_handler.execute_query(q) + + # handle_delete_question(question_id) return redirect(url_for('list_questions')) else: From 7eabca6151ed9f2be67dc9d8a7871effb8f9b363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Mon, 23 Sep 2019 15:06:21 +0200 Subject: [PATCH 097/182] delete_question convert to SQL - refactor needed --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index fb6970529..43bbf80d5 100644 --- a/server.py +++ b/server.py @@ -93,7 +93,7 @@ def delete_question(question_id): if request.method == 'POST': if request.form.get('delete') == 'Yes': - q = """DELETE FROM comment WHERE answer_id = (SELECT id FROM answer WHERE id = {question_id}) + q = """DELETE FROM comment WHERE id = {question_id} OR answer_id = (SELECT id FROM answer WHERE id = {question_id}) """.format(question_id=question_id) data_handler.execute_query(q) q = """DELETE FROM answer WHERE question_id = {question_id} From 4acabf66c48c2cff006ce53a247701693fa3b799 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Mon, 23 Sep 2019 17:39:55 +0200 Subject: [PATCH 098/182] Reworked vote system to use sql instead of csv --- util.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/util.py b/util.py index 8ba5d5f03..b956ed877 100644 --- a/util.py +++ b/util.py @@ -12,22 +12,31 @@ def vote_question(_id, vote): - questions = data_handler.get_questions() - question = data_handler.get_question(_id, questions) - questions.remove(question) - question["vote_number"] = str(int(question["vote_number"]) + vote) - questions.append(question) - data_handler.save_questions(questions) + # questions = data_handler.get_questions() + # question = data_handler.get_question(_id, questions) + # questions.remove(question) + # question["vote_number"] = str(int(question["vote_number"]) + vote) + # questions.append(question) + # data_handler.save_questions(questions) + # data_handler.save_questions(questions) + query ="""UPDATE question SET vote_number = question.vote_number +{vote} + WHERE id = {id} + """.format(vote=vote,id=_id) + data_handler.execute_query(query) def vote_answer(_id, vote): delta = 1 if vote == "up" else -1 - answers = data_handler.get_answers() - answer = data_handler.get_answer(_id, answers) - answers.remove(answer) - answer["vote_number"] = str(int(answer["vote_number"]) + delta) - answers.append(answer) - data_handler.save_answers(answers) + # answers = data_handler.get_answers() + # answer = data_handler.get_answer(_id, answers) + # answers.remove(answer) + # answer["vote_number"] = str(int(answer["vote_number"]) + delta) + # answers.append(answer) + # data_handler.save_answers(answers) + query ="""UPDATE answer SET vote_number = vote_number +{vote} + WHERE id = {id} + """.format(vote=delta,id=_id) + data_handler.execute_query(query) def handle_upload(req): From 8f217375d87193ed089c7a8438890b66f3fee4a1 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Mon, 23 Sep 2019 18:08:15 +0200 Subject: [PATCH 099/182] Fixed: htmldoest display missing image when no image is uploaded --- templates/list.html | 2 +- util.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/list.html b/templates/list.html index abf676441..ff0dec97a 100644 --- a/templates/list.html +++ b/templates/list.html @@ -61,7 +61,7 @@

Ask Mate

- {% if questions[cell] != "" %} + {% if questions[cell] %} {% endif %} {{ convert_to_readable_date(question.get(key)) }}{{ question.get(key) }} {% if question.get(key) != "" %} @@ -110,7 +110,7 @@

Answers to the question

{{ convert_to_readable_date(answer.get(key)) }}{{ answer.get(key) }} {% if answer.get(key) != "" %} diff --git a/templates/list.html b/templates/list.html index abf676441..52a89a436 100644 --- a/templates/list.html +++ b/templates/list.html @@ -54,7 +54,7 @@

Ask Mate

{{ convert_to_readable_date(questions[cell]) }}{{ questions[cell] }} {{ questions[cell] }} diff --git a/templates/search_for_keywords_in_questions.html b/templates/search_for_keywords_in_questions.html index 7ffbf527d..7292d87e5 100644 --- a/templates/search_for_keywords_in_questions.html +++ b/templates/search_for_keywords_in_questions.html @@ -17,7 +17,7 @@

Your keywords were found in the following questions:

{{ convert_to_readable_date(question[cell]) }}{{ question[cell] }}{{ question[cell] }} +
+ +
+
+ + + + {% for title in question_comments[0].keys() %} + + {% endfor %} + + + {% for comment in question_comments %} + + {% for value in comment.values() %} + + {% endfor %} + + {% endfor %}
+ {{ title }} +
+ {{ value }} +
From d8d4eb50fe0230c2e89aa15a2cb897fbe7d44f94 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 24 Sep 2019 13:22:27 +0200 Subject: [PATCH 110/182] Fixed: User is now retuned to question display after posting a comment --- server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server.py b/server.py index 6633cea52..b524b08fb 100644 --- a/server.py +++ b/server.py @@ -184,6 +184,7 @@ def comment_question(id): if request.method == 'POST': req = request.form.to_dict() handle_add_comment(req) + return redirect(url_for("question_display", question_id=id)) return render_template("add-comment.html", qid=id, type=comment_type) From e2ac18b18a31436f7e728c7eda1485b3bf3b2542 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Tue, 24 Sep 2019 14:21:38 +0200 Subject: [PATCH 111/182] Added display comment feature for answers --- server.py | 7 +++++-- templates/display_question.html | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/server.py b/server.py index b524b08fb..6a3a077dc 100644 --- a/server.py +++ b/server.py @@ -64,8 +64,11 @@ def question_display(question_id): question = data_handler.execute_query(question_query) related_answers = data_handler.execute_query(answers_query) question_comments = data_handler.get_comments("question", question_id) - print(question_comments) - return render_template('display_question.html', question=question.pop(), question_comments=question_comments, answers=related_answers) + return render_template('display_question.html', + question=question.pop(), + question_comments=question_comments, + answers=related_answers, + get_comments=data_handler.get_comments) @app.route("/question//vote-up") def vote_up_question(question_id): diff --git a/templates/display_question.html b/templates/display_question.html index e3fc60f8d..98f029f39 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -74,7 +74,7 @@

{{ question.title }}

- +
{% for title in question_comments[0].keys() %} @@ -105,7 +105,7 @@

Answers to the question

{% if answers | length != 0 %}
-
+
{% for header in answer_headers %} @@ -146,13 +146,23 @@

Answers to the question

{% else %} {% endif %} + + {% endfor %} - + + {% set comments = get_comments("answer",answer["id"]) %} + {% for comment in comments %} + + + + {% endfor %} {% endfor %}
{{ answer.get(key) }}
+ {{ comment["message"] }} +
From a80a090373e61bc92b0de0e946586454141d91c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 24 Sep 2019 15:05:36 +0200 Subject: [PATCH 112/182] Put search_for_question into SQL with several bugs --- server.py | 70 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/server.py b/server.py index f4efaf54b..3af82e48c 100644 --- a/server.py +++ b/server.py @@ -146,34 +146,66 @@ def delete_answer(answer_id): @app.route('/search-for-questions', methods=['GET', 'POST']) def search_for_questions(): - question_query = """SELECT * FROM question - """ - question_database = data_handler.execute_query(question_query) - answer_query = """SELECT * FROM answer - """ - answer_database = data_handler.execute_query(answer_query) - print(answer_database) keywords = str(request.args.get('keywords')).replace(',', '').split(' ') - def check_keywords_in_answers(keywords): + def check_keywords_in_items(keywords): string = f'\'%{keywords[0]}%\'' for keyword in keywords[1:]: string += f' OR message LIKE \'%{keyword}%\'' return string - answer_related_question_id_query = """SELECT id FROM answer WHERE message LIKE {string} - """.format(string=check_keywords_in_answers(keywords)) + # Get the question ids from the answer database where keywords were found + answer_related_question_id_query = """SELECT question_id FROM answer WHERE message LIKE {string} + """.format(string=check_keywords_in_items(keywords)) - answer_related_question_id = data_handler.execute_query(answer_related_question_id_query) - print(answer_related_question_id) - #answer_related_question_id = util.get_answer_related_question_ids(keywords, answer_database, 'message') - questions_containing_keywords = util.search_keywords_in_attribute(keywords, - answer_related_question_id, - question_database, - 'title', - 'message') - print(questions_containing_keywords) + + def change_list_of_dict_to_tuple(): + # Changes list to tuple. Tuple is needed for SQL VALUE(,,,) format + answer_related_question_ids = data_handler.execute_query(answer_related_question_id_query) + answer_id_sql_format = [] + for question_id in answer_related_question_ids: + answer_id_sql_format.append(question_id.get('question_id')) + answer_related_question_id_sql_format = tuple(answer_id_sql_format) + return answer_related_question_id_sql_format + + answer_related_question_id_sql_format = change_list_of_dict_to_tuple() + + def check_question_id_in_answer_ids(answer_related_question_id_sql_format): + if answer_related_question_id_sql_format != (): + string_2 = f' OR (question.id IN {answer_related_question_id_sql_format})' + else: + string_2 = '' + return string_2 + + print(check_question_id_in_answer_ids(answer_related_question_id_sql_format)) + + + print(answer_related_question_id_sql_format) + + questions_containing_keywords_query = """SELECT DISTINCT question.* FROM question + JOIN answer ON question.id = answer.question_id + WHERE (question.title LIKE {string_1}) + OR (question.message LIKE {string_1}) {string_2} + """.format(string_1=check_keywords_in_items(keywords), + string_2=check_question_id_in_answer_ids(answer_related_question_id_sql_format)) + print(questions_containing_keywords_query) + questions_containing_keywords = data_handler.execute_query(questions_containing_keywords_query) + + + # question_query = """SELECT * FROM question + # """ + # question_database = data_handler.execute_query(question_query) + # answer_query = """SELECT * FROM answer + # """ + # answer_database = data_handler.execute_query(answer_query) + # print(answer_database) + # questions_containing_keywords = util.search_keywords_in_attribute(keywords, + # answer_related_question_id, + # question_database, + # 'title', + # 'message') + # print(questions_containing_keywords) return render_template('search_for_keywords_in_questions.html', keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords) From cb0e8a5a28cd001c0d75b49063db48bee10a655b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 24 Sep 2019 16:18:31 +0200 Subject: [PATCH 113/182] Fix bugs in search_for_questions func --- server.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/server.py b/server.py index 3af82e48c..a787a53ee 100644 --- a/server.py +++ b/server.py @@ -148,29 +148,29 @@ def search_for_questions(): keywords = str(request.args.get('keywords')).replace(',', '').split(' ') - def check_keywords_in_items(keywords): + def create_check_keywords_in_database_string(keywords, database): string = f'\'%{keywords[0]}%\'' for keyword in keywords[1:]: - string += f' OR message LIKE \'%{keyword}%\'' + string += f' OR {database}.message LIKE \'%{keyword}%\'' return string + # Get the question ids from the answer database where keywords were found answer_related_question_id_query = """SELECT question_id FROM answer WHERE message LIKE {string} - """.format(string=check_keywords_in_items(keywords)) - + """.format(string=create_check_keywords_in_database_string(keywords, 'answer')) + print(data_handler.execute_query(answer_related_question_id_query)) def change_list_of_dict_to_tuple(): # Changes list to tuple. Tuple is needed for SQL VALUE(,,,) format answer_related_question_ids = data_handler.execute_query(answer_related_question_id_query) answer_id_sql_format = [] - for question_id in answer_related_question_ids: + for question_id in answer_related_question_ids[:-1]: answer_id_sql_format.append(question_id.get('question_id')) answer_related_question_id_sql_format = tuple(answer_id_sql_format) return answer_related_question_id_sql_format answer_related_question_id_sql_format = change_list_of_dict_to_tuple() - def check_question_id_in_answer_ids(answer_related_question_id_sql_format): if answer_related_question_id_sql_format != (): string_2 = f' OR (question.id IN {answer_related_question_id_sql_format})' @@ -178,16 +178,12 @@ def check_question_id_in_answer_ids(answer_related_question_id_sql_format): string_2 = '' return string_2 - print(check_question_id_in_answer_ids(answer_related_question_id_sql_format)) - - - print(answer_related_question_id_sql_format) questions_containing_keywords_query = """SELECT DISTINCT question.* FROM question JOIN answer ON question.id = answer.question_id WHERE (question.title LIKE {string_1}) OR (question.message LIKE {string_1}) {string_2} - """.format(string_1=check_keywords_in_items(keywords), + """.format(string_1=create_check_keywords_in_database_string(keywords, 'answer'), string_2=check_question_id_in_answer_ids(answer_related_question_id_sql_format)) print(questions_containing_keywords_query) questions_containing_keywords = data_handler.execute_query(questions_containing_keywords_query) From ef45b9fccd2f8534318c96989af02d49ad451442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Tue, 24 Sep 2019 16:51:10 +0200 Subject: [PATCH 114/182] Simplify search_for_questions; there is a bug to correct --- server.py | 59 ++++++------------------------------------------------- util.py | 40 +++++-------------------------------- 2 files changed, 11 insertions(+), 88 deletions(-) diff --git a/server.py b/server.py index a787a53ee..57bf74896 100644 --- a/server.py +++ b/server.py @@ -3,7 +3,7 @@ from flask import Flask, render_template, request, redirect, url_for import data_handler import util -from util import handle_delete_question, handle_add_answer, handle_add_question +from util import handle_delete_question, handle_add_answer, handle_add_question, create_check_keywords_in_database_string app = Flask(__name__) @@ -145,64 +145,17 @@ def delete_answer(answer_id): @app.route('/search-for-questions', methods=['GET', 'POST']) def search_for_questions(): - keywords = str(request.args.get('keywords')).replace(',', '').split(' ') - - def create_check_keywords_in_database_string(keywords, database): - string = f'\'%{keywords[0]}%\'' - for keyword in keywords[1:]: - string += f' OR {database}.message LIKE \'%{keyword}%\'' - return string - - - - # Get the question ids from the answer database where keywords were found - answer_related_question_id_query = """SELECT question_id FROM answer WHERE message LIKE {string} - """.format(string=create_check_keywords_in_database_string(keywords, 'answer')) - print(data_handler.execute_query(answer_related_question_id_query)) - - def change_list_of_dict_to_tuple(): - # Changes list to tuple. Tuple is needed for SQL VALUE(,,,) format - answer_related_question_ids = data_handler.execute_query(answer_related_question_id_query) - answer_id_sql_format = [] - for question_id in answer_related_question_ids[:-1]: - answer_id_sql_format.append(question_id.get('question_id')) - answer_related_question_id_sql_format = tuple(answer_id_sql_format) - return answer_related_question_id_sql_format - - answer_related_question_id_sql_format = change_list_of_dict_to_tuple() - def check_question_id_in_answer_ids(answer_related_question_id_sql_format): - if answer_related_question_id_sql_format != (): - string_2 = f' OR (question.id IN {answer_related_question_id_sql_format})' - else: - string_2 = '' - return string_2 - - questions_containing_keywords_query = """SELECT DISTINCT question.* FROM question JOIN answer ON question.id = answer.question_id WHERE (question.title LIKE {string_1}) - OR (question.message LIKE {string_1}) {string_2} - """.format(string_1=create_check_keywords_in_database_string(keywords, 'answer'), - string_2=check_question_id_in_answer_ids(answer_related_question_id_sql_format)) - print(questions_containing_keywords_query) + OR (question.message LIKE {string_2}) + OR (answer.message LIKE {string_3}) + """.format(string_1=create_check_keywords_in_database_string(keywords, 'question', 'title'), + string_2=create_check_keywords_in_database_string(keywords, 'question', 'message'), + string_3=create_check_keywords_in_database_string(keywords, 'answer', 'message')) questions_containing_keywords = data_handler.execute_query(questions_containing_keywords_query) - - # question_query = """SELECT * FROM question - # """ - # question_database = data_handler.execute_query(question_query) - # answer_query = """SELECT * FROM answer - # """ - # answer_database = data_handler.execute_query(answer_query) - # print(answer_database) - # questions_containing_keywords = util.search_keywords_in_attribute(keywords, - # answer_related_question_id, - # question_database, - # 'title', - # 'message') - # print(questions_containing_keywords) - return render_template('search_for_keywords_in_questions.html', keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords) diff --git a/util.py b/util.py index 7c43f8899..d2a09f0df 100644 --- a/util.py +++ b/util.py @@ -119,38 +119,8 @@ def handle_add_question(req): data_handler.add_entry(question) -def get_answer_related_question_ids(keywords, answer_database, attribute): - """ - Search keywords in database using attribute as a key. If it founds a keyword - in the attribute, then its related question id is stored. - :param keywords: list - :param answer_database: list of dictionaries - :param attribute: string - :return: list of item related question ids - """ - answer_related_question_ids = [] - for answer in answer_database: - if any(keyword in answer[attribute] for keyword in keywords): - answer_related_question_ids.append(answer['question_id']) - return answer_related_question_ids - - -def search_keywords_in_attribute(keywords, id_s, database, attribute_1, attribute_2=None): - """ - Search keywords in table using attribute_1 and/or attribute_2 as key(s). - Search id in id_s in order to find and append other database related items. - :param keywords: list - :param id_s: list - :param database: list of dictionaries - :param attribute_1: string - :param attribute_2: string - :return: list of items containing keywords - """ - items_containing_keywords = [] - for item in database: - if any(keyword in item[attribute_1] for keyword in keywords) or \ - any(keyword in item[attribute_2] for keyword in keywords): - items_containing_keywords.append(item) - elif item['id'] in id_s: - items_containing_keywords.append(item) - return items_containing_keywords +def create_check_keywords_in_database_string(keywords, database, attribute): + string = f'\'%{keywords[0]}%\'' + for keyword in keywords[1:]: + string += f' OR {database}.{attribute} LIKE \'%{keyword}%\'' + return string From a97e7b53410b282fe9afe1acf8d4905b3a6e9219 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:19:30 +0200 Subject: [PATCH 115/182] Add handle_edit_question to util module --- util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util.py b/util.py index 7c43f8899..ea7083ad1 100644 --- a/util.py +++ b/util.py @@ -119,6 +119,11 @@ def handle_add_question(req): data_handler.add_entry(question) +def handle_edit_question(req): + handle_upload(req) + data_handler.update_record(req, is_answer=False) + + def get_answer_related_question_ids(keywords, answer_database, attribute): """ Search keywords in database using attribute as a key. If it founds a keyword From 20e599288f853331c0b4d27f94e79448ce76077e Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:23:10 +0200 Subject: [PATCH 116/182] Rewrite get_question in data_handler to use database --- data_handler.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/data_handler.py b/data_handler.py index 7cdbbba12..ec37f5fa8 100644 --- a/data_handler.py +++ b/data_handler.py @@ -41,10 +41,12 @@ def add_entry(entry, is_answer=False): print(query) execute_query(query) -def get_question(question_id, question_database): - for question_data in question_database: - if question_data['id'] == question_id: - return question_data + +def get_question(question_id): + question_query = f"""SELECT * FROM question + WHERE id={int(question_id)};""" + question_data = execute_query(question_query) + return question_data def get_answer(answer_id, answer_database): From b04a218c5bd864b8d37f9dd5e810756d22b28e92 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:25:17 +0200 Subject: [PATCH 117/182] Rewrite get_question_related_answers in data_handler to use database --- data_handler.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/data_handler.py b/data_handler.py index ec37f5fa8..60109c7eb 100644 --- a/data_handler.py +++ b/data_handler.py @@ -55,11 +55,10 @@ def get_answer(answer_id, answer_database): return answer_data -def get_question_related_answers(question_id, answer_database): - answers_of_question = [] - for answer_data in answer_database: - if answer_data['question_id'] == question_id: - answers_of_question.append(answer_data) +def get_question_related_answers(question_id): + answers_query = f"""SELECT * FROM answer + WHERE question_id={int(question_id)};""" + answers_of_question = execute_query(answers_query) return answers_of_question From 0735772f3ac5bb851e966c9e1e4a2faf105a26d9 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:27:18 +0200 Subject: [PATCH 118/182] Add escape_single_quote to data handler --- data_handler.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data_handler.py b/data_handler.py index 60109c7eb..9af042231 100644 --- a/data_handler.py +++ b/data_handler.py @@ -112,3 +112,10 @@ def string_builder(lst, is_key=True): else: result += "\'" + element + "\', " return result[:-2] + + +def escape_single_quotes(dictionary): + for key, value in dictionary.items(): + if type(value) == str and "'" in value: + dictionary[key] = value.replace("'", "''") + return dictionary From dc5344128d22c1587b22af3cfaec10e4a1b82198 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:30:01 +0200 Subject: [PATCH 119/182] Refactor display_question to use get question and get question related answers rewritten --- server.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index 2dd570006..0c7ae9cdf 100644 --- a/server.py +++ b/server.py @@ -55,17 +55,12 @@ def add_answer(question_id): @app.route('/question/') def question_display(question_id): - question_query = f"""SELECT * FROM question - WHERE id={question_id};""" - - answers_query = f"""SELECT * FROM answer - WHERE question_id={question_id};""" - - question = data_handler.execute_query(question_query) - related_answers = data_handler.execute_query(answers_query) + question = data_handler.get_question(question_id) + related_answers = data_handler.get_question_related_answers(question_id) return render_template('display_question.html', question=question.pop(), answers=related_answers) + @app.route("/question//vote-up") def vote_up_question(question_id): util.vote_question(question_id, 1) From 8d13d0b709623f73913ae3ebe4bd4372f0f5f5e3 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:32:46 +0200 Subject: [PATCH 120/182] Rewrite and rename update_question to update entry which can be used for edit answer also --- data_handler.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/data_handler.py b/data_handler.py index 9af042231..464fe2990 100644 --- a/data_handler.py +++ b/data_handler.py @@ -62,16 +62,28 @@ def get_question_related_answers(question_id): return answers_of_question -def update_questions(question_id, updated_data): - all_questions = get_questions() - question = get_question(question_id, all_questions) - question_index = all_questions.index(question) - - for key, value in updated_data.items(): - question[key] = value - all_questions[question_index] = question - save_questions(all_questions) - return question +def update_record(record, is_answer=False): + table = "answer" + record = escape_single_quotes(record) + id_ = record['id'] + if not is_answer: + table = "question" + query = f"""UPDATE {table} + SET submission_time={"'" + record['submission_time'] + "'"}, + title={"'" + record['title'] + "'"}, + message={"'" + record['message'] + "'"}, + image={"'" + record['image'] + "'"} + WHERE id={id_}; + """ + else: + query = f"""UPDATE {table} + SET submission_time={"'" + record['submission_time'] + "'"}, + message={"'" + record['title'] + "'"}, + image={"'" + record['image'] + "'"} + WHERE id={id_}; + """ + + execute_query(query) def delete_record(id, answer=False, delete=False): From 7a59bc73ee8f06528204c9fd0e85fce59f1c04d6 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:34:53 +0200 Subject: [PATCH 121/182] Rewrite edit question route now it saves the image as well --- server.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/server.py b/server.py index 0c7ae9cdf..68447cc96 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,5 @@ import os -import time +from datetime import datetime from flask import Flask, render_template, request, redirect, url_for import data_handler import util @@ -115,13 +115,15 @@ def edit_question(question_id): if request.method == 'POST': edited_question_data = request.form.to_dict() - edited_question_data['submission_time'] = str(int(time.time())) - question = data_handler.update_questions(question_id, edited_question_data) - related_answers = data_handler.get_question_related_answers(question_id, data_handler.get_answers()) + edited_question_data['id'] = int(edited_question_data['id']) + edited_question_data['submission_time'] = str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + util.handle_edit_question(edited_question_data) + question = data_handler.get_question(question_id)[0] + related_answers = data_handler.get_question_related_answers(question_id) + return render_template('display_question.html', question=question, answers=related_answers) - all_questions = data_handler.get_questions() - question = data_handler.get_question(question_id, all_questions) + question = data_handler.get_question(question_id)[0] return render_template('edit-question.html', question=question) From 6c6a7f74ab27cfb19356cbb3da09009f925fc515 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 19:36:33 +0200 Subject: [PATCH 122/182] Cast question_id into string to handle form action properly --- templates/edit-question.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/edit-question.html b/templates/edit-question.html index e2c5bb347..300b6264d 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -8,7 +8,7 @@

{{"Edit question" if question else "Ask your Question" }}


-
+
From ca2e96085f3b5e9cc6439428184bbdca0e1d5d6f Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 20:50:02 +0200 Subject: [PATCH 123/182] Correct comment display issues written in email 20190924 20:30 --- server.py | 5 ++++- templates/display_question.html | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index e6cb79129..fc11bb8c4 100644 --- a/server.py +++ b/server.py @@ -128,7 +128,10 @@ def edit_question(question_id): question = data_handler.get_question(question_id)[0] related_answers = data_handler.get_question_related_answers(question_id) - return render_template('display_question.html', question=question, answers=related_answers) + return render_template('display_question.html', + question=question, + answers=related_answers, + get_comments=data_handler.get_comments) question = data_handler.get_question(question_id)[0] diff --git a/templates/display_question.html b/templates/display_question.html index 98f029f39..2eb222537 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -76,12 +76,13 @@

{{ question.title }}

- + {% if question_comments | length > 0 %} {% for title in question_comments[0].keys() %} {% endfor %} + {% endif %} {% for comment in question_comments %} From 9d7fe461de541e27f5232eae1fbccd3238af8203 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 22:40:28 +0200 Subject: [PATCH 124/182] Add default ordering by submission time to answer display --- data_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data_handler.py b/data_handler.py index 7407b6b82..1ed6f7e11 100644 --- a/data_handler.py +++ b/data_handler.py @@ -59,7 +59,8 @@ def get_answer(answer_id, answer_database): def get_question_related_answers(question_id): answers_query = f"""SELECT * FROM answer - WHERE question_id={int(question_id)};""" + WHERE question_id={int(question_id)} + ORDER BY submission_time DESC;""" answers_of_question = execute_query(answers_query) return answers_of_question From 6ad0fe650e4bb92fdf3c9c72be0471c7ee8058cb Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Tue, 24 Sep 2019 22:56:05 +0200 Subject: [PATCH 125/182] Insert escape_single_quote into add new entry function --- data_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_handler.py b/data_handler.py index 1ed6f7e11..3f13990ee 100644 --- a/data_handler.py +++ b/data_handler.py @@ -35,6 +35,7 @@ def add_entry(entry, is_answer=False): if not is_answer: table = "question" + entry = escape_single_quotes(entry) query = """INSERT INTO {table} ({columns}) VALUES ({values}); """.format(columns=string_builder(entry.keys()), From 8071f9ce4e31443a19549d15e209ccf8ca5bb012 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 09:41:20 +0200 Subject: [PATCH 126/182] Fixed: comment return url Usernow should be redirected back to question display after commenting on an answer --- data_handler.py | 3 +-- server.py | 7 +++++-- templates/add-comment.html | 2 +- templates/display_question.html | 1 + util.py | 12 +++++++++++- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/data_handler.py b/data_handler.py index b6dd9cb1d..79dd0cd47 100644 --- a/data_handler.py +++ b/data_handler.py @@ -40,7 +40,6 @@ def add_entry(entry, is_answer=False): """.format(columns=string_builder(entry.keys()), values=string_builder(entry.values(), False), table=table) - print(query) execute_query(query) def get_question(question_id, question_database): @@ -120,5 +119,5 @@ def get_comments(comment_tpe, _id): query = """SELECT message, submission_time, edited_count FROM comment WHERE {col} = {id} """.format(col=comment_tpe, id=_id) - print(query) + #qid aid return execute_query(query) \ No newline at end of file diff --git a/server.py b/server.py index 6a3a077dc..10fe69b24 100644 --- a/server.py +++ b/server.py @@ -181,13 +181,16 @@ def upload_image(): @app.route("/answer//new-comment", methods=["GET", "POST"]) def comment_question(id): comment_type = "question" + question_id = id if "answer" in str(request.url_rule): comment_type = "answer" - + question_id = util.get_related_question_id(id) + print(question_id) if request.method == 'POST': req = request.form.to_dict() handle_add_comment(req) - return redirect(url_for("question_display", question_id=id)) + # return redirect(url_for("question_display", question_id=question_id)) + return redirect("/question/" + str(question_id)) return render_template("add-comment.html", qid=id, type=comment_type) diff --git a/templates/add-comment.html b/templates/add-comment.html index b66e57ef3..e7953a3d6 100644 --- a/templates/add-comment.html +++ b/templates/add-comment.html @@ -8,7 +8,7 @@

Comment


- +

diff --git a/templates/display_question.html b/templates/display_question.html index 98f029f39..725226970 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -158,6 +158,7 @@

Answers to the question

{% set comments = get_comments("answer",answer["id"]) %} {% for comment in comments %}
+ diff --git a/util.py b/util.py index 6fcf6af28..9b1ebe680 100644 --- a/util.py +++ b/util.py @@ -161,4 +161,14 @@ def string_builder(lst, is_key=True): result += "" + element + ", " else: result += "\'" + element + "\', " - return result[:-2] \ No newline at end of file + return result[:-2] + + +def get_related_question_id(id): + query = """SELECT answer.question_id FROM answer JOIN comment ON comment.answer_id = answer.id + WHERE answer.id = {id} + """.format(id=id) + print(query) + result = data_handler.execute_query(query) + print(result) + return result.pop()["question_id"] From 9d7b0395105687c5f0bb9d7c77c6b6b9ddebf760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 25 Sep 2019 10:15:35 +0200 Subject: [PATCH 127/182] Fix bug in search_for_question --- server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.py b/server.py index 0015dc412..471c42e23 100644 --- a/server.py +++ b/server.py @@ -153,10 +153,10 @@ def delete_answer(answer_id): def search_for_questions(): keywords = str(request.args.get('keywords')).replace(',', '').split(' ') questions_containing_keywords_query = """SELECT DISTINCT question.* FROM question - JOIN answer ON question.id = answer.question_id - WHERE (question.title LIKE {string_1}) - OR (question.message LIKE {string_2}) - OR (answer.message LIKE {string_3}) + LEFT JOIN answer ON question.id = answer.question_id + WHERE (question.title ILIKE {string_1}) + OR (question.message ILIKE {string_2}) + OR (answer.message ILIKE {string_3}) """.format(string_1=create_check_keywords_in_database_string(keywords, 'question', 'title'), string_2=create_check_keywords_in_database_string(keywords, 'question', 'message'), string_3=create_check_keywords_in_database_string(keywords, 'answer', 'message')) From 99c7a2ef9852f79db71314c7278853e6d2158ab9 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 11:12:21 +0200 Subject: [PATCH 128/182] Added Button for comments in ui --- data_handler.py | 4 ++-- templates/display_question.html | 23 +++++++++++++++++++++-- util.py | 5 +++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/data_handler.py b/data_handler.py index 32abe739a..c89ae5d98 100644 --- a/data_handler.py +++ b/data_handler.py @@ -35,7 +35,7 @@ def add_entry(entry, is_answer=False): if not is_answer: table = "question" - entry = escape_single_quotes(entry) + # entry = escape_single_quotes(entry) query = """INSERT INTO {table} ({columns}) VALUES ({values}); """.format(columns=string_builder(entry.keys()), @@ -138,7 +138,7 @@ def escape_single_quotes(dictionary): def get_comments(comment_tpe, _id): comment_tpe += "_id" - query = """SELECT message, submission_time, edited_count FROM comment + query = """SELECT message, submission_time, edited_count, comment.id FROM comment WHERE {col} = {id} """.format(col=comment_tpe, id=_id) #qid aid diff --git a/templates/display_question.html b/templates/display_question.html index 1e30b846b..a938ec791 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -78,20 +78,34 @@

{{ question.title }}

{% if question_comments | length > 0 %} {% for title in question_comments[0].keys() %} + {% if title != "id" %} + {% endif %} {% endfor %} {% endif %} {% for comment in question_comments %} - {% for value in comment.values() %} + {% for key in comment.keys() %} + {% if key != ("id") %} + {% endif %} {% endfor %} + + {% endfor %} @@ -155,6 +169,11 @@

Answers to the question

+ {% set comments = get_comments("answer",answer["id"]) %} {% for comment in comments %} diff --git a/util.py b/util.py index 253ff3fa7..803bae1ca 100644 --- a/util.py +++ b/util.py @@ -169,10 +169,11 @@ def search_keywords_in_attribute(keywords, id_s, database, attribute_1, attribut def string_builder(lst, is_key=True): result = "" for element in lst: + escaped_element = element.replace("'", "''") if is_key: - result += "" + element + ", " + result += "" + escaped_element + ", " else: - result += "\'" + element + "\', " + result += "\'" + escaped_element + "\', " return result[:-2] From f0ff7b34b4e10717186740b1abc5d83c4facd320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 25 Sep 2019 11:42:17 +0200 Subject: [PATCH 129/182] Create adding new_tag_id for tag_question --- server.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/server.py b/server.py index 4f9859477..e507c823a 100644 --- a/server.py +++ b/server.py @@ -192,6 +192,21 @@ def comment_question(id): return render_template("add-comment.html", qid=id, type=comment_type) +@app.route("/question//new-tag", methods=["GET", "POST"]) +def tag_question(id): + MAX_ID = 0 + new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 + + # Add new tag id and related question id to question_tag database + ids_for_question_tag_database = """INSERT INTO question_tag (question_id, tag_id) VALUES ({question_id}, {tag_id}) + """.format(question_id=id, tag_id=new_tag_id) + data_handler.execute_query(ids_for_question_tag_database) + + + + return new_tag_id + + if __name__ == '__main__': app.run(debug=True) From 2f769f1c5c56868e4e28cff9c87a0a07803f4045 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 13:28:50 +0200 Subject: [PATCH 130/182] Added: edit question comment feature --- data_handler.py | 12 ++++++++++-- server.py | 21 +++++++++++++++++++-- templates/add-comment.html | 8 ++++++-- templates/display_question.html | 3 +++ util.py | 2 +- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/data_handler.py b/data_handler.py index c89ae5d98..05688a950 100644 --- a/data_handler.py +++ b/data_handler.py @@ -138,8 +138,16 @@ def escape_single_quotes(dictionary): def get_comments(comment_tpe, _id): comment_tpe += "_id" - query = """SELECT message, submission_time, edited_count, comment.id FROM comment + query = """SELECT message, submission_time, edited_count, comment.question_id, comment.answer_id, comment.id FROM comment WHERE {col} = {id} """.format(col=comment_tpe, id=_id) #qid aid - return execute_query(query) \ No newline at end of file + return execute_query(query) + + +def handle_edit_comment(id, msg): + query = """UPDATE comment + SET message = {msg} + WHERE id = {id} + """.format(id=id,msg=("'" + msg["message"].replace("'", "''")) + "'") + execute_query(query) \ No newline at end of file diff --git a/server.py b/server.py index 4f9859477..61959d523 100644 --- a/server.py +++ b/server.py @@ -3,10 +3,10 @@ from flask import Flask, render_template, request, redirect, url_for import data_handler import util -from util import handle_delete_question, handle_add_answer, handle_add_question, create_check_keywords_in_database_string +from util import create_check_keywords_in_database_string from util import handle_add_answer, handle_add_question -from data_handler import handle_add_comment +from data_handler import handle_add_comment, handle_edit_comment app = Flask(__name__) app.debug = True @@ -192,6 +192,23 @@ def comment_question(id): return render_template("add-comment.html", qid=id, type=comment_type) +@app.route("/comments//edit", methods=["GET", "POST"]) +def edit_comment(id): + comment_type = "question" + ref_question_id = request.args.get('qid') + if "answer" in str(request.url_rule): + comment_type = "answer" + question_id = util.get_related_question_id(id) + print(question_id) + if request.method == 'POST': + req = request.form.to_dict() + question_id = req["qid"] + del req["qid"] + handle_edit_comment(id,req) + return redirect("/question/" + str(question_id)) + + return render_template("add-comment.html", qid=id, type=comment_type, message="asd", question_id = ref_question_id) + if __name__ == '__main__': app.run(debug=True) diff --git a/templates/add-comment.html b/templates/add-comment.html index e7953a3d6..a0a3f8c26 100644 --- a/templates/add-comment.html +++ b/templates/add-comment.html @@ -8,13 +8,17 @@

Comment


-
+

- +
+ {% if question_id %} + + {% endif %} +{# {{ "" if message else ""}}#}
diff --git a/templates/display_question.html b/templates/display_question.html index a938ec791..2cad26ce3 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -103,6 +103,7 @@

{{ question.title }}

@@ -166,11 +167,13 @@

Answers to the question

{% endfor %} diff --git a/util.py b/util.py index 803bae1ca..6c4b54588 100644 --- a/util.py +++ b/util.py @@ -178,7 +178,7 @@ def string_builder(lst, is_key=True): def get_related_question_id(id): - query = """SELECT answer.question_id FROM answer JOIN comment ON comment.answer_id = answer.id + query = """SELECT answer.question_id, comment.question_id FROM answer JOIN comment ON comment.answer_id = answer.id WHERE answer.id = {id} """.format(id=id) print(query) From 2e9ef4861a5e510bcb66ba489867114a044a45ed Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 13:38:21 +0200 Subject: [PATCH 131/182] Added: edit comment for answers --- server.py | 1 - templates/display_question.html | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index 61959d523..536fd0396 100644 --- a/server.py +++ b/server.py @@ -199,7 +199,6 @@ def edit_comment(id): if "answer" in str(request.url_rule): comment_type = "answer" question_id = util.get_related_question_id(id) - print(question_id) if request.method == 'POST': req = request.form.to_dict() question_id = req["qid"] diff --git a/templates/display_question.html b/templates/display_question.html index 2cad26ce3..0691a6d49 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -78,7 +78,7 @@

{{ question.title }}

{% if question_comments | length > 0 %} {% for title in question_comments[0].keys() %} - {% if title != "id" %} + {% if "id" not in title %} @@ -90,7 +90,7 @@

{{ question.title }}

{% for comment in question_comments %} {% for key in comment.keys() %} - {% if key != ("id") %} + {% if "id" not in key %} @@ -185,6 +185,19 @@

Answers to the question

+ + {% endfor %} {% endfor %} From 06f0095fb1cd0ea08390f020fc15a15ee31222d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 25 Sep 2019 13:56:38 +0200 Subject: [PATCH 132/182] Add \if new tag\ to tag db - tag_question func --- server.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/server.py b/server.py index e507c823a..22d1d3435 100644 --- a/server.py +++ b/server.py @@ -194,17 +194,31 @@ def comment_question(id): @app.route("/question//new-tag", methods=["GET", "POST"]) def tag_question(id): - MAX_ID = 0 - new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 + existing_tags = data_handler.execute_query("""SELECT name FROM tag""") + print(existing_tags) + if request.method == 'POST': + MAX_ID = 0 + new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 + + + # if existing tag => dont insert in the tag database, only in the question_tag database (done later) + # if completely new tag => insert into both databases + + # Add data to tag database + existing_tags_list = [tag['name'] for tag in [tags_name for tags_name in existing_tags]] + if request.form.get('add_new_tag') not in existing_tags_list: + new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' + data_handler.execute_query("""INSERT INTO tag (id, name) VALUES ({new_tag_id}, {new_tag_name})""" + .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) - # Add new tag id and related question id to question_tag database - ids_for_question_tag_database = """INSERT INTO question_tag (question_id, tag_id) VALUES ({question_id}, {tag_id}) - """.format(question_id=id, tag_id=new_tag_id) - data_handler.execute_query(ids_for_question_tag_database) + # Add new tag id and related question id to question_tag database + ids_for_question_tag_database = """INSERT INTO question_tag (question_id, tag_id) VALUES ({question_id}, {tag_id}) + """.format(question_id=id, tag_id=new_tag_id) + data_handler.execute_query(ids_for_question_tag_database) - return new_tag_id + return render_template("tag-question.html", qid=id, existing_tags=existing_tags) if __name__ == '__main__': From 7dcb15ad57fc363c8c57afeadc5faa82172df684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 25 Sep 2019 13:57:28 +0200 Subject: [PATCH 133/182] Add basic format for tag_question --- templates/display_question.html | 7 +++++++ templates/tag-question.html | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 templates/tag-question.html diff --git a/templates/display_question.html b/templates/display_question.html index 1e30b846b..c17812ceb 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -73,6 +73,13 @@

{{ question.title }}

+
+ +
{{ title }}
{{ comment["message"] }}
{{ title }}
- {{ value }} + {{ comment[key] }} + + + + +
+ +
+
+
+ +
+
+
+
+
{{ title }}
{{ comment[key] }} {{ comment["message"] }} +
+ + +
+
+
+ + +
+ +
+
+ +
+
diff --git a/templates/tag-question.html b/templates/tag-question.html new file mode 100644 index 000000000..594d4e616 --- /dev/null +++ b/templates/tag-question.html @@ -0,0 +1,37 @@ + + + + + Title + + +
+

Add tag

+
+
+ +
+
+ +
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ + \ No newline at end of file From 94b44f607164f320bc940ab6da98bee514950e62 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 13:59:05 +0200 Subject: [PATCH 134/182] Fixed: issue casuing unability to post comments --- data_handler.py | 5 +++-- server.py | 2 +- templates/display_question.html | 6 +++++- util.py | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/data_handler.py b/data_handler.py index 05688a950..8e7ccdba4 100644 --- a/data_handler.py +++ b/data_handler.py @@ -139,7 +139,7 @@ def escape_single_quotes(dictionary): def get_comments(comment_tpe, _id): comment_tpe += "_id" query = """SELECT message, submission_time, edited_count, comment.question_id, comment.answer_id, comment.id FROM comment - WHERE {col} = {id} + WHERE {col} = {id} ORDER BY submission_time DESC """.format(col=comment_tpe, id=_id) #qid aid return execute_query(query) @@ -147,7 +147,8 @@ def get_comments(comment_tpe, _id): def handle_edit_comment(id, msg): query = """UPDATE comment - SET message = {msg} + SET message = {msg}, + edited_count = COALESCE (edited_count, 0) +1 WHERE id = {id} """.format(id=id,msg=("'" + msg["message"].replace("'", "''")) + "'") execute_query(query) \ No newline at end of file diff --git a/server.py b/server.py index 536fd0396..bbcd04fba 100644 --- a/server.py +++ b/server.py @@ -198,7 +198,7 @@ def edit_comment(id): ref_question_id = request.args.get('qid') if "answer" in str(request.url_rule): comment_type = "answer" - question_id = util.get_related_question_id(id) +# question_id = util.get_related_question_id(id) if request.method == 'POST': req = request.form.to_dict() question_id = req["qid"] diff --git a/templates/display_question.html b/templates/display_question.html index 0691a6d49..4820bdd78 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -92,7 +92,11 @@

{{ question.title }}

{% for key in comment.keys() %} {% if "id" not in key %}
{% endif %} {% endfor %} diff --git a/util.py b/util.py index 6c4b54588..803bae1ca 100644 --- a/util.py +++ b/util.py @@ -178,7 +178,7 @@ def string_builder(lst, is_key=True): def get_related_question_id(id): - query = """SELECT answer.question_id, comment.question_id FROM answer JOIN comment ON comment.answer_id = answer.id + query = """SELECT answer.question_id FROM answer JOIN comment ON comment.answer_id = answer.id WHERE answer.id = {id} """.format(id=id) print(query) From cf4c9641ce9c2ad27e254790028f8a7392f88d8f Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 14:03:39 +0200 Subject: [PATCH 135/182] Rewrite get answer to use adatabase - needed for edit answer feature --- data_handler.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/data_handler.py b/data_handler.py index 32abe739a..254e67561 100644 --- a/data_handler.py +++ b/data_handler.py @@ -51,10 +51,11 @@ def get_question(question_id): return question_data -def get_answer(answer_id, answer_database): - for answer_data in answer_database: - if answer_data['id'] == answer_id: - return answer_data +def get_answer(answer_id): + answer_query = f"""SELECT * FROM answer + WHERE id={int(answer_id)}""" + answer_data = execute_query(answer_query) + return answer_data def get_question_related_answers(question_id): From 10720128aeff5e20ef15f039e7b8794b726a3b3b Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 14:05:03 +0200 Subject: [PATCH 136/182] Fix typo in update record function --- data_handler.py | 2 +- server.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data_handler.py b/data_handler.py index 254e67561..2237167d3 100644 --- a/data_handler.py +++ b/data_handler.py @@ -82,7 +82,7 @@ def update_record(record, is_answer=False): else: query = f"""UPDATE {table} SET submission_time={"'" + record['submission_time'] + "'"}, - message={"'" + record['title'] + "'"}, + message={"'" + record['message'] + "'"}, image={"'" + record['image'] + "'"} WHERE id={id_}; """ diff --git a/server.py b/server.py index 4f9859477..ce152c9ed 100644 --- a/server.py +++ b/server.py @@ -90,6 +90,7 @@ def vote_answer(): question_id = req['question_id'] return redirect("/question/" + question_id) + @app.route('/question//delete', methods=['GET', 'POST']) def delete_question(question_id): if request.method == 'POST': From 822ad61369dcdbdfc6545301904fd94548e0b648 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 14:06:16 +0200 Subject: [PATCH 137/182] Change render template to redirect in edit question POST request --- server.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index ce152c9ed..ee0d2fec3 100644 --- a/server.py +++ b/server.py @@ -125,14 +125,9 @@ def edit_question(question_id): edited_question_data = request.form.to_dict() edited_question_data['id'] = int(edited_question_data['id']) edited_question_data['submission_time'] = str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - util.handle_edit_question(edited_question_data) - question = data_handler.get_question(question_id)[0] - related_answers = data_handler.get_question_related_answers(question_id) - - return render_template('display_question.html', - question=question, - answers=related_answers, - get_comments=data_handler.get_comments) + util.handle_edit_entry(edited_question_data) + + return redirect("/question/" + str(question_id)) question = data_handler.get_question(question_id)[0] From ccca04fc8ee210592f8bbc9c0d95ad3e039cc11a Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 14:07:31 +0200 Subject: [PATCH 138/182] Add edit answer function to server --- server.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server.py b/server.py index ee0d2fec3..c724e03ac 100644 --- a/server.py +++ b/server.py @@ -188,6 +188,24 @@ def comment_question(id): return render_template("add-comment.html", qid=id, type=comment_type) +@app.route('/answer/', methods=["GET", "POST"]) +def edit_answer(answer_id): + + if request.method == 'POST': + edited_answer_data = request.form.to_dict() + edited_answer_data['id'] = int(edited_answer_data['id']) + edited_answer_data['submission_time'] = str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + util.handle_edit_entry(edited_answer_data, is_answer=True) + question_id = edited_answer_data['question_id'] + + return redirect("/question/" + str(question_id)) + + answer = data_handler.get_answer(answer_id)[0] + question_id = answer['question_id'] + + return render_template('add-answer.html', qid=question_id, answer=answer, answer_id=answer_id) + + if __name__ == '__main__': app.run(debug=True) From 642c9b2084e3a1860e8623a508cb3dc5357145db Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 14:09:23 +0200 Subject: [PATCH 139/182] Change handle edit question and rename to handle edit entry, so now can handle edit answer also --- util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util.py b/util.py index 253ff3fa7..507537fe2 100644 --- a/util.py +++ b/util.py @@ -117,9 +117,9 @@ def handle_add_question(req): data_handler.add_entry(question) -def handle_edit_question(req): +def handle_edit_entry(req, is_answer=False): handle_upload(req) - data_handler.update_record(req, is_answer=False) + data_handler.update_record(req, is_answer) def get_answer_related_question_ids(keywords, answer_database, attribute): From 3f571810bfb22c25cdbfd6cfdcb2734e9319bbfc Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 14:10:15 +0200 Subject: [PATCH 140/182] Change add-answer.html template to handle editing answers as well --- templates/add-answer.html | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/templates/add-answer.html b/templates/add-answer.html index 34a9a5bda..a14db6ab7 100644 --- a/templates/add-answer.html +++ b/templates/add-answer.html @@ -2,18 +2,21 @@ - Title + {{"Edit answer" if answer else "Answer question" }}
-

Answer question

+

{{"Edit answer" if answer else "Answer question" }}


-
+ + {% if answer %} + + {% endif %}

- - + +

From fe67cfecc2a9e0d60f8866c3d5a74490fad8a390 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 14:12:50 +0200 Subject: [PATCH 141/182] Added: featrure to delete comments --- server.py | 12 ++++++++++++ templates/display_question.html | 1 + 2 files changed, 13 insertions(+) diff --git a/server.py b/server.py index bbcd04fba..59fe43b57 100644 --- a/server.py +++ b/server.py @@ -208,6 +208,18 @@ def edit_comment(id): return render_template("add-comment.html", qid=id, type=comment_type, message="asd", question_id = ref_question_id) + +@app.route("/comments//delete", methods=["GET"]) +def delete_comment(comment_id): + question_id = request.args.get("qid") + query = """DELETE FROM comment WHERE id = {comment_id} + """.format(comment_id=comment_id) + data_handler.execute_query(query) + return redirect("/question/" + str(question_id)) + + return query + + if __name__ == '__main__': app.run(debug=True) diff --git a/templates/display_question.html b/templates/display_question.html index 4820bdd78..c234eeb94 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -102,6 +102,7 @@

{{ question.title }}

{% endfor %}
From cdc762966e8c5ebe30173b8803cfbfe6cd6f9cd0 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 14:50:05 +0200 Subject: [PATCH 142/182] Fixed: The origanalmessage is visible on edit screen --- server.py | 3 ++- templates/display_question.html | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 19c287da6..1de371883 100644 --- a/server.py +++ b/server.py @@ -210,6 +210,7 @@ def edit_answer(answer_id): def edit_comment(id): comment_type = "question" ref_question_id = request.args.get('qid') + message =request.args.get("message") if "answer" in str(request.url_rule): comment_type = "answer" # question_id = util.get_related_question_id(id) @@ -220,7 +221,7 @@ def edit_comment(id): handle_edit_comment(id,req) return redirect("/question/" + str(question_id)) - return render_template("add-comment.html", qid=id, type=comment_type, message="asd", question_id = ref_question_id) + return render_template("add-comment.html", qid=id, type=comment_type, message=message, question_id = ref_question_id) @app.route("/comments//delete", methods=["GET"]) diff --git a/templates/display_question.html b/templates/display_question.html index c234eeb94..ca174d823 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -109,6 +109,7 @@

{{ question.title }}

@@ -199,6 +200,7 @@

Answers to the question

{% endif %} {% endfor %} + - + {% endfor %} @@ -171,6 +172,11 @@

Answers to the question

{% endfor %} + {% endfor %} From 8b485ef9987077f7f02227f0772bba34ff17f780 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 18:14:18 +0200 Subject: [PATCH 145/182] Added navlinks and header design --- static/css/main.css | 35 +++++++++++++++++++++++++++++++++ templates/banner.html | 10 ++++++++++ templates/display_question.html | 7 ++++--- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 static/css/main.css create mode 100644 templates/banner.html diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 000000000..91e39a9d9 --- /dev/null +++ b/static/css/main.css @@ -0,0 +1,35 @@ +*{ + margin: 0; + padding: 0; +} + +.header{ + border-bottom-left-radius: 25px; + border-bottom-right-radius: 25px; + width: 100%; + min-height: 10%; + text-align: center; + height: 10%; + background-color: beige; + top: 0px; + border-bottom: 3px solid darkgray; +} + +.nav-link{ + display: table-cell; + vertical-align: bottom; + alignment: center; + padding-bottom: 5px; +} + +#title{ + width: 100px; + margin-left: auto; + margin-right: auto; + margin-block-end: 0.27em; +} + +.spacer{ + display: flex; + justify-content: space-evenly; +} \ No newline at end of file diff --git a/templates/banner.html b/templates/banner.html new file mode 100644 index 000000000..1503407c9 --- /dev/null +++ b/templates/banner.html @@ -0,0 +1,10 @@ + +
+

Ask Mate

+
+
+ Home + Hot + Search +
+
\ No newline at end of file diff --git a/templates/display_question.html b/templates/display_question.html index ca174d823..3bb7c2eb1 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -7,8 +7,9 @@ Question + {% include "banner.html" %}

{{ question.title }}

-
+
- {{ comment[key] }} + {% if key != "edited_count" %} + {{ comment[key] }} + {% else %} + {{ comment[key] if comment[key] else "0"}} + {% endif %} +
+
+
From 085f7daaafe39dcca7d494e21ff1a01c065ee32c Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 14:52:37 +0200 Subject: [PATCH 143/182] Add the feature which shows on the '/' route only the last five questions --- server.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/server.py b/server.py index 19c287da6..44427cea6 100644 --- a/server.py +++ b/server.py @@ -22,17 +22,28 @@ def list_questions(): :param questions:list of dictionaries :return: ''' - order_direction = False if request.args.get('order_direction') == 'asc' else True - order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') - order_direction = 'ASC' if order_direction == False else 'DESC' - - q = """SELECT * FROM question ORDER BY {order_by} {order_direction} - """.format(order_by=order_by, order_direction=order_direction) - questions = data_handler.execute_query(q) - return render_template('list.html', - sorted_questions=questions, - order_by=order_by, - order_direction=order_direction) + if str(request.url_rule) == '/': + q = """SELECT * FROM question ORDER BY submission_time DESC + LIMIT 5 + """ + questions = data_handler.execute_query(q) + return render_template('list.html', + sorted_questions=questions, + order_by='submission_time', + order_direction="DESC") + + else: + order_direction = False if request.args.get('order_direction') == 'asc' else True + order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') + order_direction = 'ASC' if order_direction == False else 'DESC' + + q = """SELECT * FROM question ORDER BY {order_by} {order_direction} + """.format(order_by=order_by, order_direction=order_direction) + questions = data_handler.execute_query(q) + return render_template('list.html', + sorted_questions=questions, + order_by=order_by, + order_direction=order_direction) @app.route('/add-question', methods=["GET", "POST"]) From 0fbfc6d120d0490da6ecd797340624436e21f189 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 15:50:15 +0200 Subject: [PATCH 144/182] Swap delete and edit buttons on question display template --- templates/display_question.html | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index ca174d823..8bb82e948 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -100,19 +100,20 @@

{{ question.title }}

+
+ + + +
+
-
- - - -
-
+
+ +
+
@@ -192,18 +198,17 @@

Answers to the question

{{ comment["message"] }}
- + - + + -
+ - - +
-
@@ -117,7 +118,6 @@

{{ question.title }}

{% endfor %}
-
{% set answer_headers = ['Vote', 'Answer', 'Image', 'Submission'] %} @@ -126,7 +126,7 @@

{{ question.title }}

Answers to the question

{% if answers | length != 0 %} -
+
@@ -217,6 +217,7 @@

Answers to the question


+ From 63570ae74c1b6b4040e9a2df7b3fe670d8cd78dd Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 19:12:01 +0200 Subject: [PATCH 146/182] Added: style for question information section --- static/css/main.css | 32 +++++++++++++++++++++++++++++--- templates/display_question.html | 9 ++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 91e39a9d9..196a6d10e 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -4,8 +4,8 @@ } .header{ - border-bottom-left-radius: 25px; - border-bottom-right-radius: 25px; + border-bottom-left-radius: 50px; + border-bottom-right-radius: 50px; width: 100%; min-height: 10%; text-align: center; @@ -32,4 +32,30 @@ .spacer{ display: flex; justify-content: space-evenly; -} \ No newline at end of file +} + +.question_display{ + align-content: center; + padding-bottom: 50px; + padding-left: 10%; + position: absolute; + height: auto; +} + +.question_detail{ + width: 90%; + border-radius: 25px; + background-color: beige; + border: 3px solid darkgray; +} + +.control_buttons{ + /*border: 1px solid black;*/ + width: 100%; + align-content: center; + text-align: center; +} + +/*td{*/ +/* border: 1px solid black;*/ +/*}*/ \ No newline at end of file diff --git a/templates/display_question.html b/templates/display_question.html index 3bb7c2eb1..c7361d62c 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -9,8 +9,9 @@ {% include "banner.html" %}

{{ question.title }}

-
-
+
+
+
{% for header in headers %} @@ -50,8 +51,9 @@

{{ question.title }}

+
- +
@@ -75,6 +77,7 @@

{{ question.title }}

+
{% if question_comments | length > 0 %} From af44f9386470c2d3a0ad7a43cff8cf90784ed520 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 19:35:57 +0200 Subject: [PATCH 147/182] Added header title style --- static/css/main.css | 12 ++++++++++++ templates/display_question.html | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index 196a6d10e..720572820 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -56,6 +56,18 @@ text-align: center; } +.question_title{ + margin-top: 25px; + text-align: center; + width: 79%; + text-align: center; + background-color: aquamarine; + margin-left: 11.2%; + border-top: 1px solid; + border-top-left-radius: 75px; + border-top-right-radius: 75px; +} + /*td{*/ /* border: 1px solid black;*/ /*}*/ \ No newline at end of file diff --git a/templates/display_question.html b/templates/display_question.html index c7361d62c..2212e1d18 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -8,7 +8,7 @@ {% include "banner.html" %} -

{{ question.title }}

+

{{ question.title }}

From 2c3c66330b65349a0bba27c347e3474fc54503b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 25 Sep 2019 19:52:04 +0200 Subject: [PATCH 148/182] Create tag_question function - refactor needed --- server.py | 46 +++++++++++++++++++++++-------------- templates/tag-question.html | 15 ++++++------ 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/server.py b/server.py index 22d1d3435..e275f62ca 100644 --- a/server.py +++ b/server.py @@ -7,6 +7,7 @@ from util import handle_add_answer, handle_add_question from data_handler import handle_add_comment +import ast app = Flask(__name__) app.debug = True @@ -194,29 +195,40 @@ def comment_question(id): @app.route("/question//new-tag", methods=["GET", "POST"]) def tag_question(id): - existing_tags = data_handler.execute_query("""SELECT name FROM tag""") + existing_tags = data_handler.execute_query("""SELECT id, name FROM tag""") print(existing_tags) - if request.method == 'POST': - MAX_ID = 0 - new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 - - # if existing tag => dont insert in the tag database, only in the question_tag database (done later) - # if completely new tag => insert into both databases + if request.method == 'POST': - # Add data to tag database - existing_tags_list = [tag['name'] for tag in [tags_name for tags_name in existing_tags]] - if request.form.get('add_new_tag') not in existing_tags_list: - new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' - data_handler.execute_query("""INSERT INTO tag (id, name) VALUES ({new_tag_id}, {new_tag_name})""" + # When chosen from existing tags + if request.form.get('selected_tag_name'): + selected_tag = ast.literal_eval(request.form.to_dict('selected_tag_name')['selected_tag_name']) # data of selected tag + selected_tag_id = selected_tag['id'] + print(selected_tag_id) + # Add new tag id and related question id to question_tag database + q = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag + WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) + print(q) + if q == []: + data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) + VALUES({q_id}, {t_id})""".format(q_id=id, t_id=selected_tag_id)) + + # When the user entered + new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 + print(request.form.get('add_new_tag')) + new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' + print(new_tag_name) + q = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag + WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag + WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) + if q == []: + data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) - # Add new tag id and related question id to question_tag database - ids_for_question_tag_database = """INSERT INTO question_tag (question_id, tag_id) VALUES ({question_id}, {tag_id}) - """.format(question_id=id, tag_id=new_tag_id) - data_handler.execute_query(ids_for_question_tag_database) - + data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) + VALUES({q_id}, {t_id})""".format(q_id=id, t_id=new_tag_id)) + return redirect("/question/" + id) return render_template("tag-question.html", qid=id, existing_tags=existing_tags) diff --git a/templates/tag-question.html b/templates/tag-question.html index 594d4e616..1030400d4 100644 --- a/templates/tag-question.html +++ b/templates/tag-question.html @@ -9,25 +9,26 @@

Add tag


-


- {% for tag in existing_tags %} - + {% endfor %} -
+ + +

-

+
+
-
+
-


From 7f16f40443fc771f118e5893b0994831058b2343 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Wed, 25 Sep 2019 20:40:34 +0200 Subject: [PATCH 149/182] Added button styles --- static/css/main.css | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 720572820..21eaab19d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -3,6 +3,10 @@ padding: 0; } +body{ + /*background-color: #484848;*/ +} + .header{ border-bottom-left-radius: 50px; border-bottom-right-radius: 50px; @@ -10,9 +14,10 @@ min-height: 10%; text-align: center; height: 10%; - background-color: beige; + background-color: #e8e8bcfa; top: 0px; border-bottom: 3px solid darkgray; + } .nav-link{ @@ -40,13 +45,17 @@ padding-left: 10%; position: absolute; height: auto; + + } .question_detail{ width: 90%; border-radius: 25px; - background-color: beige; + background-color: #e8e8bcfa; border: 3px solid darkgray; + border-bottom-left-radius: 13px; + border-bottom-right-radius: 13px; } .control_buttons{ @@ -61,13 +70,36 @@ text-align: center; width: 79%; text-align: center; - background-color: aquamarine; + background-color: #ecc0578c; margin-left: 11.2%; border-top: 1px solid; border-top-left-radius: 75px; border-top-right-radius: 75px; } +.control_buttons input { + border-radius: 50px; + padding: 10px; + background: transparent; + border: 2px solid; + color: #e26901; + /*text-shadow: 2px 0px #000000;*/ + text-shadow: 1px 0px rgba(63, 107, 169, 0.5); + font-weight: bold; +} + +.control_buttons input:hover { + background: orange; + border-color: orange; + color: black; +} + +.control_buttons { + margin-bottom: 10px; +} + + + /*td{*/ /* border: 1px solid black;*/ -/*}*/ \ No newline at end of file +/*}*/ From 1dfc1c4049bc101e5d812f26c7ecc5dd27bcf804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 25 Sep 2019 20:50:12 +0200 Subject: [PATCH 150/182] Display tags on question/q_id page --- templates/display_question.html | 8 ++++++-- util.py | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index c17812ceb..8bee0796f 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -72,14 +72,18 @@

{{ question.title }}

- -
+ + + {% for related_tag in question_related_tags %} + {{ related_tag['name'] + ',' }} + {% endfor %} +
diff --git a/util.py b/util.py index 253ff3fa7..d78d977d4 100644 --- a/util.py +++ b/util.py @@ -184,3 +184,8 @@ def get_related_question_id(id): result = data_handler.execute_query(query) print(result) return result.pop()["question_id"] + +def get_question_related_tags(question_id): + question_related_tags = data_handler.execute_query("""SELECT tag.name FROM question_tag LEFT JOIN tag + ON question_tag.tag_id = tag.id WHERE question_tag.question_id = {id}""".format(id=question_id)) + return question_related_tags \ No newline at end of file From 1266a652d947f06623ab7449e6e23992176f2bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Wed, 25 Sep 2019 20:50:58 +0200 Subject: [PATCH 151/182] Finish tag question - refactor still needed --- server.py | 61 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/server.py b/server.py index e275f62ca..4904dd7aa 100644 --- a/server.py +++ b/server.py @@ -5,7 +5,7 @@ import util from util import handle_delete_question, handle_add_answer, handle_add_question, create_check_keywords_in_database_string -from util import handle_add_answer, handle_add_question +from util import handle_add_answer, handle_add_question, get_question_related_tags from data_handler import handle_add_comment import ast @@ -61,12 +61,14 @@ def question_display(question_id): question = data_handler.get_question(question_id) related_answers = data_handler.get_question_related_answers(question_id) question_comments = data_handler.get_comments("question", question_id) + question_related_tags = get_question_related_tags(question_id) return render_template('display_question.html', question=question.pop(), question_comments=question_comments, answers=related_answers, - get_comments=data_handler.get_comments) + get_comments=data_handler.get_comments, + question_related_tags=question_related_tags) @app.route("/question//vote-up") @@ -200,33 +202,48 @@ def tag_question(id): if request.method == 'POST': - # When chosen from existing tags + # If the user chooses a tag from the existing tags if request.form.get('selected_tag_name'): selected_tag = ast.literal_eval(request.form.to_dict('selected_tag_name')['selected_tag_name']) # data of selected tag selected_tag_id = selected_tag['id'] - print(selected_tag_id) + # Add new tag id and related question id to question_tag database - q = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag - WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) - print(q) - if q == []: + form = 'select existing tag' + get_quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag + WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) # Check in question_tag database whether there is a tag to the current question and get the ids + if get_quest_tag_id_combination == []: data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) VALUES({q_id}, {t_id})""".format(q_id=id, t_id=selected_tag_id)) - # When the user entered - new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 - print(request.form.get('add_new_tag')) - new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' - print(new_tag_name) - q = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag - WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag - WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) - if q == []: - data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" - .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) - - data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) - VALUES({q_id}, {t_id})""".format(q_id=id, t_id=new_tag_id)) + # When the user enter a tag + elif request.form.get('add_new_tag'): + new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 + new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' # ' is needed for the SQL query + + get_quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag + WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag + WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) + if get_quest_tag_id_combination == []: + data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" + .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) + + data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) + VALUES({q_id}, {t_id})""".format(q_id=id, t_id=new_tag_id)) + +## REFACTOR +# def get_quest_tag_id_combination(form, selected_tag_id): +# if form == 'select existing tag': +# selected_tag_id = +# +# else: +# quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag +# WHERE question_id = {q_id} AND tag_id = +# if get_quest_tag_id_combination == []: +# data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" +# .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) +# +# return quest_tag_id_combination + return redirect("/question/" + id) From 004558a0406fcc3f53ea4fef42492a076ed3e511 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 21:39:54 +0200 Subject: [PATCH 152/182] Change list.html for / route: swap sorting buttons to a button point to /list route --- server.py | 7 +++++-- templates/list.html | 40 ++++++++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/server.py b/server.py index c9fab830a..7ab6b134c 100644 --- a/server.py +++ b/server.py @@ -11,6 +11,7 @@ app = Flask(__name__) app.debug = True + @app.route('/') @app.route('/list') @app.route('/?order_by=&order_direction=', methods=['GET', 'POST']) @@ -30,7 +31,8 @@ def list_questions(): return render_template('list.html', sorted_questions=questions, order_by='submission_time', - order_direction="DESC") + order_direction="DESC", + is_main=True) else: order_direction = False if request.args.get('order_direction') == 'asc' else True @@ -43,7 +45,8 @@ def list_questions(): return render_template('list.html', sorted_questions=questions, order_by=order_by, - order_direction=order_direction) + order_direction=order_direction, + is_main=False) @app.route('/add-question', methods=["GET", "POST"]) diff --git a/templates/list.html b/templates/list.html index 989e7eb79..bcbf8a5a1 100644 --- a/templates/list.html +++ b/templates/list.html @@ -25,24 +25,32 @@

Ask Mate

- {% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %} -

- -
- - - + + {% if is_main %} +

The most recent 5 questions

+ + -

+ {% else %} + +

+ +
+ + + + +

+ {% endif %}
From ebbec7d8b9edb46ea85e8682317b09d43f85e22a Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Wed, 25 Sep 2019 21:51:30 +0200 Subject: [PATCH 153/182] Change the route of edit answer from /answer/ to /answer//edit as it is in requirements --- server.py | 2 +- templates/display_question.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 7ab6b134c..a16e96cd2 100644 --- a/server.py +++ b/server.py @@ -202,7 +202,7 @@ def comment_question(id): return render_template("add-comment.html", qid=id, type=comment_type) -@app.route('/answer/', methods=["GET", "POST"]) +@app.route('/answer//edit', methods=["GET", "POST"]) def edit_answer(answer_id): if request.method == 'POST': diff --git a/templates/display_question.html b/templates/display_question.html index 8bb82e948..4687a9f9c 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -173,7 +173,7 @@

Answers to the question

{% endfor %}
From 9b2294a31057ec3db1fb6225ab430f0c3b5451af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 26 Sep 2019 10:25:30 +0200 Subject: [PATCH 154/182] Add Requirement txt --- requirements.txt | 1 + server.py | 15 ++++++++------- templates/tag-question.html | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..cfb6bf5c4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ast \ No newline at end of file diff --git a/server.py b/server.py index 3f49b0f99..ef1c29cc3 100644 --- a/server.py +++ b/server.py @@ -208,19 +208,19 @@ def comment_question(id): @app.route("/question//new-tag", methods=["GET", "POST"]) def tag_question(id): existing_tags = data_handler.execute_query("""SELECT id, name FROM tag""") - print(existing_tags) - if request.method == 'POST': - # If the user chooses a tag from the existing tags - if request.form.get('selected_tag_name'): - selected_tag = ast.literal_eval(request.form.to_dict('selected_tag_name')['selected_tag_name']) # data of selected tag + if request.form.get('selected_tag'): + selected_tag = ast.literal_eval(request.form.to_dict('selected_tag')['selected_tag']) # data of selected tag selected_tag_id = selected_tag['id'] - # Add new tag id and related question id to question_tag database + print(request.form.to_dict('selected_tag')) form = 'select existing tag' + # Check in question_tag database whether there is a tag to the current question and get the ids... get_quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag - WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) # Check in question_tag database whether there is a tag to the current question and get the ids + WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) + + # ... if there is not then add new tag id and related question id to question_tag database if get_quest_tag_id_combination == []: data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) VALUES({q_id}, {t_id})""".format(q_id=id, t_id=selected_tag_id)) @@ -230,6 +230,7 @@ def tag_question(id): new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' # ' is needed for the SQL query + ### almost same as above get_quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) diff --git a/templates/tag-question.html b/templates/tag-question.html index 1030400d4..70bf4af46 100644 --- a/templates/tag-question.html +++ b/templates/tag-question.html @@ -13,7 +13,7 @@

Add tag



- {% for tag in existing_tags %} {% endfor %} From 6938c70f218a2b315bd91b72b2c744f786d6ae61 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 26 Sep 2019 10:27:14 +0200 Subject: [PATCH 155/182] Refactor util.py: delete not used functions, imports, and change according to PEP8 --- util.py | 100 ++++---------------------------------------------------- 1 file changed, 6 insertions(+), 94 deletions(-) diff --git a/util.py b/util.py index b5c181b01..9cddfbbb9 100644 --- a/util.py +++ b/util.py @@ -1,4 +1,3 @@ -import copy import os from datetime import datetime from flask import request @@ -10,14 +9,7 @@ def vote_question(_id, vote): - # questions = data_handler.get_questions() - # question = data_handler.get_question(_id, questions) - # questions.remove(question) - # question["vote_number"] = str(int(question["vote_number"]) + vote) - # questions.append(question) - # data_handler.save_questions(questions) - # data_handler.save_questions(questions) - query ="""UPDATE question SET vote_number = question.vote_number +{vote} + query = """UPDATE question SET vote_number = question.vote_number +{vote} WHERE id = {id} """.format(vote=vote,id=_id) data_handler.execute_query(query) @@ -25,13 +17,7 @@ def vote_question(_id, vote): def vote_answer(_id, vote): delta = 1 if vote == "up" else -1 - # answers = data_handler.get_answers() - # answer = data_handler.get_answer(_id, answers) - # answers.remove(answer) - # answer["vote_number"] = str(int(answer["vote_number"]) + delta) - # answers.append(answer) - # data_handler.save_answers(answers) - query ="""UPDATE answer SET vote_number = vote_number +{vote} + query = """UPDATE answer SET vote_number = vote_number +{vote} WHERE id = {id} """.format(vote=delta,id=_id) data_handler.execute_query(query) @@ -46,25 +32,8 @@ def handle_upload(req): req["image"] = "" -def gen_question_id(): - answers = get_questions() - items = [x['id'] for x in answers] - return int(max(items)) + 1 - - -def gen_answer_id(): - # depricated - Marked for removal - answers = get_answers() - if len(answers) == 0: - return 0 - items = [x['id'] for x in answers] - return int(max(items)) + 1 - - def generate_question_dict(data): question_data = {} - - # question_data.update(id="") question_data.update(submission_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) question_data.update(view_number=str(0)) question_data.update(vote_number=str(0)) @@ -76,9 +45,6 @@ def generate_question_dict(data): def generate_answer_dict(data): answer_data = {} - - # answer_data.update(id=str(gen_answer_id())) - # answer_data.update(submission_time=str(int(time.time()))) answer_data.update(submission_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) answer_data.update(vote_number=str(0)) answer_data.update(question_id=data["question_id"]) @@ -87,20 +53,6 @@ def generate_answer_dict(data): return answer_data -def handle_delete_question(question_id): - question_database = data_handler.get_questions() - answer_database = data_handler.get_answers() - copied_answer_database = copy.deepcopy(answer_database) - for answer in copied_answer_database: - if answer['question_id'] == question_id: - answer_database.remove(answer) - for question in question_database: - if question['id'] == question_id: - question_database.remove(question) - data_handler.save_questions(question_database) - data_handler.save_answers(answer_database) - - def handle_add_answer(reqv): handle_upload(reqv) answer = generate_answer_dict(reqv) @@ -110,10 +62,8 @@ def handle_add_answer(reqv): def handle_add_question(req): - # questions = data_handler.get_questions() handle_upload(req) question = generate_question_dict(req) - # questions.append(question) data_handler.add_entry(question) @@ -122,22 +72,6 @@ def handle_edit_entry(req, is_answer=False): data_handler.update_record(req, is_answer) -def get_answer_related_question_ids(keywords, answer_database, attribute): - """ - Search keywords in database using attribute as a key. If it founds a keyword - in the attribute, then its related question id is stored. - :param keywords: list - :param answer_database: list of dictionaries - :param attribute: string - :return: list of item related question ids - """ - answer_related_question_ids = [] - for answer in answer_database: - if any(keyword in answer[attribute] for keyword in keywords): - answer_related_question_ids.append(answer['question_id']) - return answer_related_question_ids - - def create_check_keywords_in_database_string(keywords, database, attribute): string = f'\'%{keywords[0]}%\'' for keyword in keywords[1:]: @@ -145,27 +79,6 @@ def create_check_keywords_in_database_string(keywords, database, attribute): return string -def search_keywords_in_attribute(keywords, id_s, database, attribute_1, attribute_2=None): - """ - Search keywords in table using attribute_1 and/or attribute_2 as key(s). - Search id in id_s in order to find and append other database related items. - :param keywords: list - :param id_s: list - :param database: list of dictionaries - :param attribute_1: string - :param attribute_2: string - :return: list of items containing keywords - """ - items_containing_keywords = [] - for item in database: - if any(keyword in item[attribute_1] for keyword in keywords) or \ - any(keyword in item[attribute_2] for keyword in keywords): - items_containing_keywords.append(item) - elif item['id'] in id_s: - items_containing_keywords.append(item) - return items_containing_keywords - - def string_builder(lst, is_key=True): result = "" for element in lst: @@ -179,14 +92,13 @@ def string_builder(lst, is_key=True): def get_related_question_id(id): query = """SELECT answer.question_id FROM answer JOIN comment ON comment.answer_id = answer.id - WHERE answer.id = {id} - """.format(id=id) - print(query) + WHERE answer.id = {id} + """.format(id=id) result = data_handler.execute_query(query) - print(result) return result.pop()["question_id"] + def get_question_related_tags(question_id): question_related_tags = data_handler.execute_query("""SELECT tag.name FROM question_tag LEFT JOIN tag ON question_tag.tag_id = tag.id WHERE question_tag.question_id = {id}""".format(id=question_id)) - return question_related_tags \ No newline at end of file + return question_related_tags From 90de10d039cd6769b5fda00ec0c40bec29020fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 26 Sep 2019 11:00:23 +0200 Subject: [PATCH 156/182] Update requirements.txt --- requirements.txt | 87 +++++++++++++++++++++++++++++++++++++++++++++++- server.py | 2 +- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cfb6bf5c4..96529486f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,86 @@ -ast \ No newline at end of file +apturl==0.5.2 +asn1crypto==0.24.0 +autopep8==1.4.4 +backcall==0.1.0 +Brlapi==0.6.6 +certifi==2018.1.18 +chardet==3.0.4 +Click==7.0 +command-not-found==0.3 +cryptography==2.1.4 +cupshelpers==1.0 +decorator==4.4.0 +defer==1.0.6 +digital-stopwatch==0.1.0 +distro-info===0.18ubuntu0.18.04.1 +entrypoints==0.3 +flake8==3.7.8 +Flask==1.1.1 +getch==1.0 +httplib2==0.9.2 +idna==2.6 +ipython==7.6.0 +ipython-genutils==0.2.0 +itsdangerous==1.1.0 +jedi==0.14.0 +Jinja2==2.10.1 +keyring==10.6.0 +keyrings.alt==3.0 +language-selector==0.1 +launchpadlib==1.10.6 +lazr.restfulclient==0.13.5 +lazr.uri==1.0.3 +louis==3.5.0 +macaroonbakery==1.1.3 +Mako==1.0.7 +MarkupSafe==1.1.1 +mccabe==0.6.1 +netifaces==0.10.4 +oauth==1.0.1 +olefile==0.45.1 +parso==0.5.0 +pexpect==4.7.0 +pickleshare==0.7.5 +Pillow==5.1.0 +prompt-toolkit==2.0.9 +protobuf==3.0.0 +psycopg2==2.8.3 +ptyprocess==0.6.0 +pycairo==1.16.2 +pycodestyle==2.5.0 +pycrypto==2.6.1 +pycups==1.9.73 +pyflakes==2.1.1 +pygame==1.9.6 +Pygments==2.4.2 +pygobject==3.26.1 +pymacaroons==0.13.0 +PyNaCl==1.1.2 +pyRFC3339==1.0 +python-apt==1.6.4 +python-dateutil==2.6.1 +python-debian==0.1.32 +pytz==2018.3 +pyxdg==0.25 +PyYAML==3.12 +reportlab==3.4.0 +requests==2.18.4 +requests-unixsocket==0.1.5 +SecretStorage==2.3.1 +simplejson==3.13.2 +six==1.12.0 +system-service==0.3 +systemd-python==234 +thonny==2.1.16 +traitlets==4.3.2 +ubuntu-drivers-common==0.0.0 +ufw==0.36 +unattended-upgrades==0.1 +urllib3==1.22 +usb-creator==0.3.3 +virtualenv==16.7.4 +wadllib==1.3.2 +wcwidth==0.1.7 +Werkzeug==0.15.5 +xkit==0.0.0 +zope.interface==4.3.2 diff --git a/server.py b/server.py index ef1c29cc3..b3f9bd1e7 100644 --- a/server.py +++ b/server.py @@ -214,7 +214,7 @@ def tag_question(id): selected_tag = ast.literal_eval(request.form.to_dict('selected_tag')['selected_tag']) # data of selected tag selected_tag_id = selected_tag['id'] - print(request.form.to_dict('selected_tag')) + print(type(request.form.to_dict('selected_tag'))) form = 'select existing tag' # Check in question_tag database whether there is a tag to the current question and get the ids... get_quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag From 8f1fd885b9582f87a34895fc3fd7937ae24381e8 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 26 Sep 2019 12:57:59 +0200 Subject: [PATCH 157/182] Refactor server.py: optimizing imports, PEP8 --- server.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/server.py b/server.py index b3f9bd1e7..a10afcddd 100644 --- a/server.py +++ b/server.py @@ -1,13 +1,10 @@ import os from datetime import datetime from flask import Flask, render_template, request, redirect, url_for +import ast + import data_handler import util -from util import create_check_keywords_in_database_string - -import ast -from util import handle_add_answer, handle_add_question, get_question_related_tags -from data_handler import handle_add_comment, handle_edit_comment app = Flask(__name__) app.debug = True @@ -54,7 +51,7 @@ def list_questions(): def add_question(): if request.method == 'POST': req = request.form.to_dict() - handle_add_question(req) + util.handle_add_question(req) return redirect(url_for("list_questions")) return render_template("edit-question.html", qid="") @@ -64,7 +61,7 @@ def add_question(): def add_answer(question_id): if request.method == 'POST': req = request.form.to_dict() - handle_add_answer(req) + util.handle_add_answer(req) return redirect("/question/" + question_id) return render_template("add-answer.html", qid=question_id) @@ -75,7 +72,7 @@ def question_display(question_id): question = data_handler.get_question(question_id) related_answers = data_handler.get_question_related_answers(question_id) question_comments = data_handler.get_comments("question", question_id) - question_related_tags = get_question_related_tags(question_id) + question_related_tags = util.get_question_related_tags(question_id) return render_template('display_question.html', question=question.pop(), @@ -171,9 +168,9 @@ def search_for_questions(): WHERE (question.title ILIKE {string_1}) OR (question.message ILIKE {string_2}) OR (answer.message ILIKE {string_3}) - """.format(string_1=create_check_keywords_in_database_string(keywords, 'question', 'title'), - string_2=create_check_keywords_in_database_string(keywords, 'question', 'message'), - string_3=create_check_keywords_in_database_string(keywords, 'answer', 'message')) + """.format(string_1=util.create_check_keywords_in_database_string(keywords, 'question', 'title'), + string_2=util.create_check_keywords_in_database_string(keywords, 'question', 'message'), + string_3=util.create_check_keywords_in_database_string(keywords, 'answer', 'message')) questions_containing_keywords = data_handler.execute_query(questions_containing_keywords_query) return render_template('search_for_keywords_in_questions.html', @@ -199,7 +196,7 @@ def comment_question(id): print(question_id) if request.method == 'POST': req = request.form.to_dict() - handle_add_comment(req) + data_handler.handle_add_comment(req) # return redirect(url_for("question_display", question_id=question_id)) return redirect("/question/" + str(question_id)) return render_template("add-comment.html", qid=id, type=comment_type) @@ -291,7 +288,7 @@ def edit_comment(id): req = request.form.to_dict() question_id = req["qid"] del req["qid"] - handle_edit_comment(id,req) + data_handler.handle_edit_comment(id,req) return redirect("/question/" + str(question_id)) return render_template("add-comment.html", qid=id, type=comment_type, message=message, question_id = ref_question_id) @@ -305,8 +302,6 @@ def delete_comment(comment_id): data_handler.execute_query(query) return redirect("/question/" + str(question_id)) - return query - if __name__ == '__main__': app.run(debug=True) From e698df34eb55bcc0ec831c946735e70d84d865e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 26 Sep 2019 12:59:09 +0200 Subject: [PATCH 158/182] Fix bug in tag_question --- server.py | 37 ++++++++++++++++++------------------- templates/tag-question.html | 4 ++-- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/server.py b/server.py index b3f9bd1e7..83c9e1c3d 100644 --- a/server.py +++ b/server.py @@ -207,21 +207,23 @@ def comment_question(id): @app.route("/question//new-tag", methods=["GET", "POST"]) def tag_question(id): - existing_tags = data_handler.execute_query("""SELECT id, name FROM tag""") + existing_tags = [name['name'] for name in [tag for tag in data_handler.execute_query("""SELECT id, name FROM tag""")]] if request.method == 'POST': # If the user chooses a tag from the existing tags - if request.form.get('selected_tag'): - selected_tag = ast.literal_eval(request.form.to_dict('selected_tag')['selected_tag']) # data of selected tag - selected_tag_id = selected_tag['id'] + if request.form.get('selected_tag_name'): + selected_tag_name = '\'' + request.form.to_dict('selected_tag_name')['selected_tag_name'] + '\'' + selected_tag_id = data_handler.execute_query("""SELECT id FROM tag + LEFT JOIN question_tag ON tag.id = question_tag.tag_id WHERE tag.name = {selected_tag}""" + .format(selected_tag=selected_tag_name))[0]['id'] + - print(type(request.form.to_dict('selected_tag'))) form = 'select existing tag' # Check in question_tag database whether there is a tag to the current question and get the ids... - get_quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag + quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) # ... if there is not then add new tag id and related question id to question_tag database - if get_quest_tag_id_combination == []: + if quest_tag_id_combination == []: data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) VALUES({q_id}, {t_id})""".format(q_id=id, t_id=selected_tag_id)) @@ -231,29 +233,26 @@ def tag_question(id): new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' # ' is needed for the SQL query ### almost same as above - get_quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag + quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) - if get_quest_tag_id_combination == []: + if quest_tag_id_combination == []: data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) + ### data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) VALUES({q_id}, {t_id})""".format(q_id=id, t_id=new_tag_id)) -## REFACTOR -# def get_quest_tag_id_combination(form, selected_tag_id): -# if form == 'select existing tag': -# selected_tag_id = -# -# else: +## Refactor in progress +# def add_data_to_tag_database(): # quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag -# WHERE question_id = {q_id} AND tag_id = -# if get_quest_tag_id_combination == []: +# WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag +# WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) +# if quest_tag_id_combination == []: # data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" # .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) -# -# return quest_tag_id_combination + return redirect("/question/" + id) diff --git a/templates/tag-question.html b/templates/tag-question.html index 70bf4af46..026384228 100644 --- a/templates/tag-question.html +++ b/templates/tag-question.html @@ -13,9 +13,9 @@

Add tag



- {% for tag in existing_tags %} - + {% endfor %} From d7c5ca4a309583fc5b269bf55ac0609d2e5a0c15 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 26 Sep 2019 13:05:18 +0200 Subject: [PATCH 159/182] Refactor handle add answer in util: delete not used codes --- util.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/util.py b/util.py index 9cddfbbb9..50621abe6 100644 --- a/util.py +++ b/util.py @@ -56,8 +56,6 @@ def generate_answer_dict(data): def handle_add_answer(reqv): handle_upload(reqv) answer = generate_answer_dict(reqv) - answers = data_handler.get_answers() - answers.append(answer) data_handler.add_entry(answer, True) From 9410dbc62c90675c1665a1df5be4c23e5dd83417 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 13:05:56 +0200 Subject: [PATCH 160/182] Css improvements --- static/css/main.css | 70 +++++++++++++++++++++++++++++++++ templates/display_question.html | 64 ++++++++++++++++++++++++------ 2 files changed, 122 insertions(+), 12 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 21eaab19d..3fcf50078 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -7,6 +7,12 @@ body{ /*background-color: #484848;*/ } +table.center { + margin-left:auto; + margin-right:auto; + width: 95%; +} + .header{ border-bottom-left-radius: 50px; border-bottom-right-radius: 50px; @@ -34,6 +40,8 @@ body{ margin-block-end: 0.27em; } + + .spacer{ display: flex; justify-content: space-evenly; @@ -56,6 +64,35 @@ body{ border: 3px solid darkgray; border-bottom-left-radius: 13px; border-bottom-right-radius: 13px; + min-height: auto; +} + +.answer_display{ + position: relative; + /*padding-left: 15%;*/ + width: 95%; + border-radius: 25px; + background-color: #e8e8bcff; + border: 3px solid darkgray; + border-bottom-left-radius: 13px; + border-bottom-right-radius: 13px; + margin: auto; +} + + +.answer{ + position: relative; + /*padding-left: 15%;*/ + width: 95%; + border-radius: 25px; + background-color: #e8e8bcff; + border: 3px solid darkgray; + border-bottom-left-radius: 13px; + border-bottom-right-radius: 13px; + margin: auto; + align-content: center; + /*margin-top: 15px;*/ + margin-bottom: 15px; } .control_buttons{ @@ -77,6 +114,30 @@ body{ border-top-right-radius: 75px; } +.answer_title_single{ + margin-top: 25px; + text-align: right; + width: 25%; + text-align: center; + background-color: #ecc0578c; + margin-right: 10%; + border-top: 1px solid; + border-top-left-radius: 75px; + border-top-right-radius: 75px; + padding: 0%; + margin-left: 69%; + margin-top: 66px; +} + + +.answer_title{ + width: 90%; + margin: auto; + /*position: relative;*/ + /*padding: auto;*/ +} + + .control_buttons input { border-radius: 50px; padding: 10px; @@ -98,7 +159,16 @@ body{ margin-bottom: 10px; } +.comment{ + position: relative; + width: 90%; + background-color: #fdff4f; + margin: auto; + margin-top: -15px; + border-radius: 15px 15px 15px 15px; + /*margin-bottom: 66px;*/ +} /*td{*/ /* border: 1px solid black;*/ diff --git a/templates/display_question.html b/templates/display_question.html index 2212e1d18..879e3fdb3 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -77,7 +77,7 @@

{{ question.title }}

-
+
-
+{#
#} {% if question_comments | length > 0 %} @@ -126,11 +126,20 @@

{{ question.title }}

{% set answer_headers = ['Vote', 'Answer', 'Image', 'Submission'] %} {% set answer_keys = [('vote_number', '10%', 'left'), ('message', '55%', 'left'), ('image', '20%', 'center'), ('submission_time', '10%', 'center')] %} -

Answers to the question

+

Answers to the question

+
{% if answers | length != 0 %} -
+ + + + + + + + + {% for header in answer_headers %} @@ -142,8 +151,22 @@

Answers to the question

{% endfor %} - - {% for answer in answers %} +
+{# #} + + {% for answer in answers %} +

 

+
+ + + + + + + + + + +
@@ -187,14 +210,30 @@

Answers to the question

+
{% set comments = get_comments("answer",answer["id"]) %} - {% for comment in comments %} + {% if comments %} +
+ + + + + + + + + {% for comment in comments %} - - + {% endfor %} - {% endfor %} - -
+{# #} + {{ comment["message"] }} +{# #} + {{ comment["submission_time"] }} +
@@ -210,10 +249,11 @@

Answers to the question

-
+ + +
+ {% endif %} + {% endfor %} {% else %}

{{ "There is no answer to this question" }}

{% endif %} From 411a2fb072765caec49f5550d09e78d445178947 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 26 Sep 2019 13:16:55 +0200 Subject: [PATCH 161/182] Refactor data_handler: delete not used functions, rows commented out, print statments + format according to PEP8 --- data_handler.py | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/data_handler.py b/data_handler.py index eba6858a8..93246294c 100644 --- a/data_handler.py +++ b/data_handler.py @@ -1,41 +1,15 @@ -import os from datetime import datetime - from psycopg2 import sql import connection from util import string_builder -ANSWER_DATA_FILE_PATH = os.getcwd() + "/data/answer.csv" -QUESTION_DATA_FILE_PATH = os.getcwd() + "/data/question.csv" - - -def get_answers(): - database = connection.csv_to_dict(ANSWER_DATA_FILE_PATH) - return database - - -def get_questions(): - database = connection.csv_to_dict(QUESTION_DATA_FILE_PATH) - return database - - -def save_questions(data): - database = connection.dict_to_csv(QUESTION_DATA_FILE_PATH, data) - return database - - -def save_answers(data): - database = connection.dict_to_csv(ANSWER_DATA_FILE_PATH, data, True) - return database - def add_entry(entry, is_answer=False): table = "answer" if not is_answer: table = "question" - # entry = escape_single_quotes(entry) query = """INSERT INTO {table} ({columns}) VALUES ({values}); """.format(columns=string_builder(entry.keys()), @@ -109,7 +83,6 @@ def delete_record(id, answer=False, delete=False): @connection.connection_handler def execute_query(cursor, query): - # print(query.startswith("INSERT")) if query.startswith("SELECT"): cursor.execute( sql.SQL(query) @@ -123,7 +96,7 @@ def execute_query(cursor, query): def handle_add_comment(req): req.update(submission_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - query ="""INSERT INTO comment ({columns}) + query = """INSERT INTO comment ({columns}) VALUES ({value_list})""".format(columns=string_builder(req.keys(), True), value_list=string_builder(req.values(), False) ) @@ -142,7 +115,6 @@ def get_comments(comment_tpe, _id): query = """SELECT message, submission_time, edited_count, comment.question_id, comment.answer_id, comment.id FROM comment WHERE {col} = {id} ORDER BY submission_time DESC """.format(col=comment_tpe, id=_id) - #qid aid return execute_query(query) @@ -151,5 +123,5 @@ def handle_edit_comment(id, msg): SET message = {msg}, edited_count = COALESCE (edited_count, 0) +1 WHERE id = {id} - """.format(id=id,msg=("'" + msg["message"].replace("'", "''")) + "'") - execute_query(query) \ No newline at end of file + """.format(id=id, msg=("'" + msg["message"].replace("'", "''")) + "'") + execute_query(query) From 2d5918d5b4c1e6c49c3dcd758ddcbea2f81d2f20 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 26 Sep 2019 13:18:21 +0200 Subject: [PATCH 162/182] Refactor server: delete unused ast import --- server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/server.py b/server.py index caf2c05a7..c08a7479f 100644 --- a/server.py +++ b/server.py @@ -1,7 +1,6 @@ import os from datetime import datetime from flask import Flask, render_template, request, redirect, url_for -import ast import data_handler import util From 4a50066fd5a4041f11e739ab6b7a89be83056c2b Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Thu, 26 Sep 2019 13:25:12 +0200 Subject: [PATCH 163/182] Refactor: delete unused html templates --- templates/add-question.html | 24 ------------------------ templates/test.html | 12 ------------ 2 files changed, 36 deletions(-) delete mode 100644 templates/add-question.html delete mode 100644 templates/test.html diff --git a/templates/add-question.html b/templates/add-question.html deleted file mode 100644 index e8830417e..000000000 --- a/templates/add-question.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Title - - -
-

Ask your question

-
- - - -
-
- - -
-
- - -
- - \ No newline at end of file diff --git a/templates/test.html b/templates/test.html deleted file mode 100644 index 3e67ca142..000000000 --- a/templates/test.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Title - - -{% autoescape false %} -{{ d }} -{% endautoescape %} - - From 90dbc6c02d7598e28828e225945c08762fb49526 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 13:29:09 +0200 Subject: [PATCH 164/182] Display question styling --- server.py | 10 +++++++--- static/css/main.css | 25 ++++++++++++++++++++++++- templates/add-comment.html | 2 -- templates/display_question.html | 19 +++++++++++++++---- 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/server.py b/server.py index 1de371883..800836e73 100644 --- a/server.py +++ b/server.py @@ -176,16 +176,20 @@ def upload_image(): def comment_question(id): comment_type = "question" question_id = id + ref_question_id = request.args.get("qid") if "answer" in str(request.url_rule): comment_type = "answer" - question_id = util.get_related_question_id(id) + # question_id = util.get_related_question_id(id) print(question_id) if request.method == 'POST': req = request.form.to_dict() + ref_question_id = req["qid"] + del req["qid"] handle_add_comment(req) + # question_id = req["qid"] # return redirect(url_for("question_display", question_id=question_id)) - return redirect("/question/" + str(question_id)) - return render_template("add-comment.html", qid=id, type=comment_type) + return redirect("/question/" + str(ref_question_id)) + return render_template("add-comment.html", qid=id, type=comment_type, question_id=ref_question_id) @app.route('/answer/', methods=["GET", "POST"]) diff --git a/static/css/main.css b/static/css/main.css index 3fcf50078..b2b1ee61c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -13,6 +13,22 @@ table.center { width: 95%; } + + +.comment_buttons input { + padding: 5px; + background: transparent; + border: 2px solid; + color: #e26901; + +} + +.comment_buttons input:hover { + background: orange; + border-color: orange; + color: black; +} + .header{ border-bottom-left-radius: 50px; border-bottom-right-radius: 50px; @@ -131,10 +147,11 @@ table.center { .answer_title{ - width: 90%; + width: 93%; margin: auto; /*position: relative;*/ /*padding: auto;*/ + margin-top: 25px; } @@ -170,6 +187,12 @@ table.center { } +.pt15{ + margin-top: 15px; +} + + + /*td{*/ /* border: 1px solid black;*/ /*}*/ diff --git a/templates/add-comment.html b/templates/add-comment.html index a0a3f8c26..f0a40ca24 100644 --- a/templates/add-comment.html +++ b/templates/add-comment.html @@ -14,9 +14,7 @@

Comment



- {% if question_id %} - {% endif %} {# {{ "" if message else ""}}#} diff --git a/templates/display_question.html b/templates/display_question.html index 879e3fdb3..af042e1c4 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -78,7 +78,14 @@

{{ question.title }}

{#
#} - +
+
+ + + + + + {% if question_comments | length > 0 %} {% for title in question_comments[0].keys() %} @@ -91,6 +98,8 @@

{{ question.title }}

{% endif %} + + {% for comment in question_comments %} {% for key in comment.keys() %} @@ -120,7 +129,9 @@

{{ question.title }}

{% endfor %} -
+ + + {% set answer_headers = ['Vote', 'Answer', 'Image', 'Submission'] %} @@ -156,7 +167,7 @@

Answers to the question

{% for answer in answers %}

 

-
+
@@ -214,7 +225,7 @@

 

{% set comments = get_comments("answer",answer["id"]) %} {% if comments %} -
+
From 62ed486815a9d08df82ec8e652f87646d1459845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 26 Sep 2019 13:55:43 +0200 Subject: [PATCH 165/182] Move SQL queries to data_handler except one --- data_handler.py | 69 +++++++++++++++++++++++++++++++++++++++++-- server.py | 78 ++++--------------------------------------------- 2 files changed, 73 insertions(+), 74 deletions(-) diff --git a/data_handler.py b/data_handler.py index eba6858a8..ed54c9e39 100644 --- a/data_handler.py +++ b/data_handler.py @@ -4,7 +4,8 @@ from psycopg2 import sql import connection -from util import string_builder +from util import string_builder, create_check_keywords_in_database_string +from flask import request ANSWER_DATA_FILE_PATH = os.getcwd() + "/data/answer.csv" QUESTION_DATA_FILE_PATH = os.getcwd() + "/data/question.csv" @@ -152,4 +153,68 @@ def handle_edit_comment(id, msg): edited_count = COALESCE (edited_count, 0) +1 WHERE id = {id} """.format(id=id,msg=("'" + msg["message"].replace("'", "''")) + "'") - execute_query(query) \ No newline at end of file + execute_query(query) + + +def create_questions_containing_keywords_query(keywords): + questions_containing_keywords_query = """SELECT DISTINCT question.* FROM question + LEFT JOIN answer ON question.id = answer.question_id + WHERE (question.title ILIKE {string_1}) + OR (question.message ILIKE {string_2}) + OR (answer.message ILIKE {string_3}) + """.format(string_1=create_check_keywords_in_database_string(keywords, 'question', 'title'), + string_2=create_check_keywords_in_database_string(keywords, 'question', 'message'), + string_3=create_check_keywords_in_database_string(keywords, 'answer', 'message')) + return questions_containing_keywords_query + + +def delete_question(question_id): + q = """DELETE FROM comment WHERE question_id = {question_id} OR answer_id = (SELECT id FROM answer WHERE id = {question_id}) + """.format(question_id=question_id) + execute_query(q) + q = """DELETE FROM answer WHERE question_id = {question_id} + """.format(question_id=question_id) + execute_query(q) + q = """DELETE FROM question_tag WHERE question_id = {question_id} + """.format(question_id=question_id) + execute_query(q) + q = """DELETE FROM question WHERE id = {question_id} + """.format(question_id=question_id) + execute_query(q) + + +def get_existing_tags(): + existing_tags = [name['name'] for name in + [tag for tag in execute_query("""SELECT id, name FROM tag""")]] + return existing_tags + + +### REFACTOR IN PROGRESS ### +def tag_question_when_user_choose_from_existing_tags(id): + selected_tag_name = '\'' + request.form.to_dict('selected_tag_name')['selected_tag_name'] + '\'' + selected_tag_id = execute_query("""SELECT id FROM tag + LEFT JOIN question_tag ON tag.id = question_tag.tag_id WHERE tag.name = {selected_tag}""" + .format(selected_tag=selected_tag_name))[0]['id'] + # Check in question_tag database whether there is a tag to the current question and get the ids... + quest_tag_id_combination = execute_query("""SELECT question_id, tag_id FROM question_tag + WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) + + # ... if there is not then add new tag id and related question id to question_tag database + if quest_tag_id_combination == []: + execute_query("""INSERT INTO question_tag (question_id, tag_id) + VALUES({q_id}, {t_id})""".format(q_id=id, t_id=selected_tag_id)) + +### REFACTOR IN PROGRESS ### +def tag_question_when_user_enter_new_tag(id): + new_tag_id = execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 + new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' # ' is needed for the SQL query + + quest_tag_id_combination = execute_query("""SELECT question_id, tag_id FROM question_tag + WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag + WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) + if quest_tag_id_combination == []: + execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" + .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) + + execute_query("""INSERT INTO question_tag (question_id, tag_id) + VALUES({q_id}, {t_id})""".format(q_id=id, t_id=new_tag_id)) diff --git a/server.py b/server.py index 83c9e1c3d..ece95b537 100644 --- a/server.py +++ b/server.py @@ -3,9 +3,8 @@ from flask import Flask, render_template, request, redirect, url_for import data_handler import util -from util import create_check_keywords_in_database_string -import ast + from util import handle_add_answer, handle_add_question, get_question_related_tags from data_handler import handle_add_comment, handle_edit_comment @@ -112,26 +111,10 @@ def vote_answer(): def delete_question(question_id): if request.method == 'POST': if request.form.get('delete') == 'Yes': - - q = """DELETE FROM comment WHERE question_id = {question_id} OR answer_id = (SELECT id FROM answer WHERE id = {question_id}) - """.format(question_id=question_id) - data_handler.execute_query(q) - q = """DELETE FROM answer WHERE question_id = {question_id} - """.format(question_id=question_id) - data_handler.execute_query(q) - q = """DELETE FROM question_tag WHERE question_id = {question_id} - """.format(question_id=question_id) - data_handler.execute_query(q) - q = """DELETE FROM question WHERE id = {question_id} - """.format(question_id=question_id) - data_handler.execute_query(q) - - # handle_delete_question(question_id) + data_handler.delete_question(question_id) return redirect(url_for('list_questions')) - else: return redirect(url_for('question_display', question_id=question_id)) - return render_template('asking_if_delete_entry.html', question_id=question_id) @@ -166,16 +149,8 @@ def delete_answer(answer_id): @app.route('/search-for-questions', methods=['GET', 'POST']) def search_for_questions(): keywords = str(request.args.get('keywords')).replace(',', '').split(' ') - questions_containing_keywords_query = """SELECT DISTINCT question.* FROM question - LEFT JOIN answer ON question.id = answer.question_id - WHERE (question.title ILIKE {string_1}) - OR (question.message ILIKE {string_2}) - OR (answer.message ILIKE {string_3}) - """.format(string_1=create_check_keywords_in_database_string(keywords, 'question', 'title'), - string_2=create_check_keywords_in_database_string(keywords, 'question', 'message'), - string_3=create_check_keywords_in_database_string(keywords, 'answer', 'message')) + questions_containing_keywords_query = data_handler.create_questions_containing_keywords_query(keywords) questions_containing_keywords = data_handler.execute_query(questions_containing_keywords_query) - return render_template('search_for_keywords_in_questions.html', keywords=keywords, fieldnames=util.QUESTION_DATA_HEADER, questions=questions_containing_keywords) @@ -207,53 +182,12 @@ def comment_question(id): @app.route("/question//new-tag", methods=["GET", "POST"]) def tag_question(id): - existing_tags = [name['name'] for name in [tag for tag in data_handler.execute_query("""SELECT id, name FROM tag""")]] + existing_tags = data_handler.get_existing_tags() if request.method == 'POST': - # If the user chooses a tag from the existing tags if request.form.get('selected_tag_name'): - selected_tag_name = '\'' + request.form.to_dict('selected_tag_name')['selected_tag_name'] + '\'' - selected_tag_id = data_handler.execute_query("""SELECT id FROM tag - LEFT JOIN question_tag ON tag.id = question_tag.tag_id WHERE tag.name = {selected_tag}""" - .format(selected_tag=selected_tag_name))[0]['id'] - - - form = 'select existing tag' - # Check in question_tag database whether there is a tag to the current question and get the ids... - quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag - WHERE question_id = {q_id} AND tag_id = {t_id}""".format(q_id=id, t_id=selected_tag_id)) - - # ... if there is not then add new tag id and related question id to question_tag database - if quest_tag_id_combination == []: - data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) - VALUES({q_id}, {t_id})""".format(q_id=id, t_id=selected_tag_id)) - - # When the user enter a tag + data_handler.tag_question_when_user_choose_from_existing_tags(id) elif request.form.get('add_new_tag'): - new_tag_id = data_handler.execute_query("""SELECT MAX(id) FROM tag""")[0]['max'] + 1 - new_tag_name = '\'' + request.form.get('add_new_tag') + '\'' # ' is needed for the SQL query - - ### almost same as above - quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag - WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag - WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) - if quest_tag_id_combination == []: - data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" - .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) - ### - - data_handler.execute_query("""INSERT INTO question_tag (question_id, tag_id) - VALUES({q_id}, {t_id})""".format(q_id=id, t_id=new_tag_id)) - -## Refactor in progress -# def add_data_to_tag_database(): -# quest_tag_id_combination = data_handler.execute_query("""SELECT question_id, tag_id FROM question_tag -# WHERE question_id = {q_id} AND tag_id = (SELECT id FROM tag -# WHERE name = {t_name})""".format(q_id=id, t_name=new_tag_name)) -# if quest_tag_id_combination == []: -# data_handler.execute_query("""INSERT INTO tag (id, name) VALUES({new_tag_id}, {new_tag_name})""" -# .format(new_tag_id=new_tag_id, new_tag_name=new_tag_name)) - - + data_handler.tag_question_when_user_enter_new_tag(id) return redirect("/question/" + id) From 691cae26745d2f3ed8b092d66920308f004cf14a Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 13:56:32 +0200 Subject: [PATCH 166/182] Added ccs support for tags --- server.py | 2 +- static/css/main.css | 5 +++++ templates/display_question.html | 16 +++++++++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/server.py b/server.py index 0a54d0706..a2d68a5a6 100644 --- a/server.py +++ b/server.py @@ -197,7 +197,7 @@ def comment_question(id): req = request.form.to_dict() ref_question_id = req["qid"] del req["qid"] - handle_add_comment(req) + data_handler.handle_add_comment(req) # question_id = req["qid"] # return redirect(url_for("question_display", question_id=question_id)) return redirect("/question/" + str(ref_question_id)) diff --git a/static/css/main.css b/static/css/main.css index b2b1ee61c..8fed486c1 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -191,7 +191,12 @@ table.center { margin-top: 15px; } +.tags{ + text-align: center; + font-style: italic; + color: deepskyblue; +} /*td{*/ /* border: 1px solid black;*/ diff --git a/templates/display_question.html b/templates/display_question.html index b00c7a1d1..96f9a9e04 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -54,6 +54,15 @@

{{ question.title }}


+ + + + + - - - {% for related_tag in question_related_tags %} - {{ related_tag['name'] + ',' }} - {% endfor %} -
+ {% for related_tag in question_related_tags %} + {{ related_tag['name'] + ',' }} + {% endfor %} +
@@ -81,12 +90,6 @@

{{ question.title }}

{#
#}
@@ -262,7 +265,6 @@

 

{{ comment["submission_time"] }} -
From 5d2de056b6a10c63330ded8a442013c98a8a736f Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 14:08:53 +0200 Subject: [PATCH 167/182] Moved answer titles to it's correct location --- templates/display_question.html | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index 96f9a9e04..806b6d3a9 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -156,15 +156,18 @@

Answers to the question

{% if answers | length != 0 %} - +{# #} + + {% for answer in answers %} +

 

+
+
- - + + - - @@ -178,12 +181,7 @@

Answers to the question

-{# #} - - {% for answer in answers %} -

 

-
- +
From cd5b481e88666d2e5b66e5520a2c2b8b2e31b1b3 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 14:20:09 +0200 Subject: [PATCH 168/182] Added banner to allpages --- static/css/main.css | 1 + templates/add-answer.html | 1 + templates/add-comment.html | 1 + templates/edit-question.html | 1 + templates/list.html | 1 + templates/search_for_keywords_in_questions.html | 1 + templates/tag-question.html | 1 + 7 files changed, 7 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index 8fed486c1..513e4277b 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -109,6 +109,7 @@ table.center { align-content: center; /*margin-top: 15px;*/ margin-bottom: 15px; + min-height: 150px; } .control_buttons{ diff --git a/templates/add-answer.html b/templates/add-answer.html index a14db6ab7..f33dbb2d8 100644 --- a/templates/add-answer.html +++ b/templates/add-answer.html @@ -5,6 +5,7 @@ {{"Edit answer" if answer else "Answer question" }} +{% include "banner.html" %}

{{"Edit answer" if answer else "Answer question" }}


diff --git a/templates/add-comment.html b/templates/add-comment.html index f0a40ca24..4e2cd7204 100644 --- a/templates/add-comment.html +++ b/templates/add-comment.html @@ -5,6 +5,7 @@ Title +{% include "banner.html" %}

Comment


diff --git a/templates/edit-question.html b/templates/edit-question.html index 300b6264d..bdfe60efd 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -5,6 +5,7 @@ {{"Edit question" if question else "Ask your Question" }} + {% include "banner.html" %}

{{"Edit question" if question else "Ask your Question" }}


diff --git a/templates/list.html b/templates/list.html index bcbf8a5a1..8d64dd371 100644 --- a/templates/list.html +++ b/templates/list.html @@ -5,6 +5,7 @@ Welcome! | Ask Mate + {% include "banner.html" %}

Ask Mate

diff --git a/templates/search_for_keywords_in_questions.html b/templates/search_for_keywords_in_questions.html index 7292d87e5..98c00d6c8 100644 --- a/templates/search_for_keywords_in_questions.html +++ b/templates/search_for_keywords_in_questions.html @@ -5,6 +5,7 @@ Title + {% include "banner.html" %}

Your keywords were found in the following questions:

{% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %} diff --git a/templates/tag-question.html b/templates/tag-question.html index 026384228..74b5f611a 100644 --- a/templates/tag-question.html +++ b/templates/tag-question.html @@ -5,6 +5,7 @@ Title + {% include "banner.html" %}

Add tag


From c60278fcc8050323169578a16f4fe827c4f3e8f7 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 14:32:29 +0200 Subject: [PATCH 169/182] Fine-tune question display css --- static/css/main.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index 513e4277b..ac02aa68d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -74,13 +74,14 @@ table.center { } .question_detail{ - width: 90%; + width: 95%; border-radius: 25px; background-color: #e8e8bcfa; border: 3px solid darkgray; border-bottom-left-radius: 13px; border-bottom-right-radius: 13px; min-height: auto; + padding-bottom: 25px; } .answer_display{ From b4cea3c6f2bd8b5a24d93b026970f7a22fd4b68e Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 14:44:09 +0200 Subject: [PATCH 170/182] Fixed inabilty to post question comments --- templates/display_question.html | 1 + templates/search.html | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 templates/search.html diff --git a/templates/display_question.html b/templates/display_question.html index 806b6d3a9..9bc23e491 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -81,6 +81,7 @@

{{ question.title }}

diff --git a/templates/search.html b/templates/search.html new file mode 100644 index 000000000..f7e588482 --- /dev/null +++ b/templates/search.html @@ -0,0 +1,10 @@ + + + + + $Title$ + + +$END$ + + \ No newline at end of file From 350f4eaf7a948957f42d436478585cf1f095de19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 26 Sep 2019 15:07:11 +0200 Subject: [PATCH 171/182] Move SQL funct to data_handler except list_questions --- data_handler.py | 34 ++++++++++++++++++++++++++++++++++ server.py | 41 +++++++++++++++-------------------------- util.py | 27 --------------------------- 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/data_handler.py b/data_handler.py index 9be8a6d5c..3a2f20b8e 100644 --- a/data_handler.py +++ b/data_handler.py @@ -191,3 +191,37 @@ def tag_question_when_user_enter_new_tag(id): execute_query("""INSERT INTO question_tag (question_id, tag_id) VALUES({q_id}, {t_id})""".format(q_id=id, t_id=new_tag_id)) + +def vote_question(_id, vote): + query = """UPDATE question SET vote_number = question.vote_number +{vote} + WHERE id = {id} + """.format(vote=vote,id=_id) + execute_query(query) + + +def vote_answer(_id, vote): + delta = 1 if vote == "up" else -1 + query = """UPDATE answer SET vote_number = vote_number +{vote} + WHERE id = {id} + """.format(vote=delta,id=_id) + execute_query(query) + + +def get_related_question_id(id): + query = """SELECT answer.question_id FROM answer JOIN comment ON comment.answer_id = answer.id + WHERE answer.id = {id} + """.format(id=id) + result = execute_query(query) + return result.pop()["question_id"] + + +def get_question_related_tags(question_id): + question_related_tags = execute_query("""SELECT tag.name FROM question_tag LEFT JOIN tag + ON question_tag.tag_id = tag.id WHERE question_tag.question_id = {id}""".format(id=question_id)) + return question_related_tags + + +def delete_comment(comment_id): + query = """DELETE FROM comment WHERE id = {comment_id} + """.format(comment_id=comment_id) + execute_query(query) \ No newline at end of file diff --git a/server.py b/server.py index f86439345..bf0cec3b2 100644 --- a/server.py +++ b/server.py @@ -20,30 +20,27 @@ def list_questions(): :param questions:list of dictionaries :return: ''' + order_direction = False if request.args.get('order_direction') == 'asc' else True + order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') + order_direction = 'ASC' if order_direction == False else 'DESC' if str(request.url_rule) == '/': + is_main = True q = """SELECT * FROM question ORDER BY submission_time DESC LIMIT 5 """ questions = data_handler.execute_query(q) - return render_template('list.html', - sorted_questions=questions, - order_by='submission_time', - order_direction="DESC", - is_main=True) else: - order_direction = False if request.args.get('order_direction') == 'asc' else True - order_by = 'submission_time' if request.args.get('order_by') == None else request.args.get('order_by') - order_direction = 'ASC' if order_direction == False else 'DESC' + is_main = False q = """SELECT * FROM question ORDER BY {order_by} {order_direction} """.format(order_by=order_by, order_direction=order_direction) questions = data_handler.execute_query(q) - return render_template('list.html', - sorted_questions=questions, - order_by=order_by, - order_direction=order_direction, - is_main=False) + return render_template('list.html', + sorted_questions=questions, + order_by=order_by, + order_direction=order_direction, + is_main=is_main) @app.route('/add-question', methods=["GET", "POST"]) @@ -71,7 +68,7 @@ def question_display(question_id): question = data_handler.get_question(question_id) related_answers = data_handler.get_question_related_answers(question_id) question_comments = data_handler.get_comments("question", question_id) - question_related_tags = util.get_question_related_tags(question_id) + question_related_tags = data_handler.get_question_related_tags(question_id) return render_template('display_question.html', question=question.pop(), @@ -83,14 +80,14 @@ def question_display(question_id): @app.route("/question//vote-up") def vote_up_question(question_id): - util.vote_question(question_id, 1) + data_handler.vote_question(question_id, 1) return redirect("/question/" + question_id) @app.route("/question//vote-down") def vote_down_question(question_id): - util.vote_question(question_id, -1) + data_handler.vote_question(question_id, -1) return redirect("/question/" + question_id) @@ -99,7 +96,7 @@ def vote_down_question(question_id): def vote_answer(): if request.method == 'POST': req = request.form.to_dict() - util.vote_answer(req["id"], req["vote"]) + data_handler.vote_answer(req["id"], req["vote"]) question_id = req['question_id'] return redirect("/question/" + question_id) @@ -164,19 +161,14 @@ def upload_image(): @app.route("/answer//new-comment", methods=["GET", "POST"]) def comment_question(id): comment_type = "question" - question_id = id ref_question_id = request.args.get("qid") if "answer" in str(request.url_rule): comment_type = "answer" - # question_id = util.get_related_question_id(id) - print(question_id) if request.method == 'POST': req = request.form.to_dict() ref_question_id = req["qid"] del req["qid"] data_handler.handle_add_comment(req) - # question_id = req["qid"] - # return redirect(url_for("question_display", question_id=question_id)) return redirect("/question/" + str(ref_question_id)) return render_template("add-comment.html", qid=id, type=comment_type, question_id=ref_question_id) @@ -220,7 +212,6 @@ def edit_comment(id): message =request.args.get("message") if "answer" in str(request.url_rule): comment_type = "answer" -# question_id = util.get_related_question_id(id) if request.method == 'POST': req = request.form.to_dict() question_id = req["qid"] @@ -234,9 +225,7 @@ def edit_comment(id): @app.route("/comments//delete", methods=["GET"]) def delete_comment(comment_id): question_id = request.args.get("qid") - query = """DELETE FROM comment WHERE id = {comment_id} - """.format(comment_id=comment_id) - data_handler.execute_query(query) + data_handler.delete_comment(comment_id) return redirect("/question/" + str(question_id)) diff --git a/util.py b/util.py index 50621abe6..b616a44fe 100644 --- a/util.py +++ b/util.py @@ -8,21 +8,6 @@ ANSWER_DATA_HEADER = ['id', 'submission_time', 'vote_number', 'question_id', 'message', 'image'] -def vote_question(_id, vote): - query = """UPDATE question SET vote_number = question.vote_number +{vote} - WHERE id = {id} - """.format(vote=vote,id=_id) - data_handler.execute_query(query) - - -def vote_answer(_id, vote): - delta = 1 if vote == "up" else -1 - query = """UPDATE answer SET vote_number = vote_number +{vote} - WHERE id = {id} - """.format(vote=delta,id=_id) - data_handler.execute_query(query) - - def handle_upload(req): image = request.files["image"] if image.filename != "": @@ -88,15 +73,3 @@ def string_builder(lst, is_key=True): return result[:-2] -def get_related_question_id(id): - query = """SELECT answer.question_id FROM answer JOIN comment ON comment.answer_id = answer.id - WHERE answer.id = {id} - """.format(id=id) - result = data_handler.execute_query(query) - return result.pop()["question_id"] - - -def get_question_related_tags(question_id): - question_related_tags = data_handler.execute_query("""SELECT tag.name FROM question_tag LEFT JOIN tag - ON question_tag.tag_id = tag.id WHERE question_tag.question_id = {id}""".format(id=question_id)) - return question_related_tags From 1fbd69a5b66a80a652b10e42c435780cd0af9a85 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 16:03:24 +0200 Subject: [PATCH 172/182] APlly css to main page --- static/css/main.css | 56 ++++++++++++++++++++++++++++++--- templates/display_question.html | 2 +- templates/list.html | 38 +++++++++------------- templates/search.html | 26 +++++++++------ 4 files changed, 83 insertions(+), 39 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index ac02aa68d..a09774343 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -29,6 +29,15 @@ table.center { color: black; } +.comment_buttons select { + border-radius: 25px; + padding: 5px; + background: transparent; + border: 2px solid; + color: #e26901; + /*background-color: #e26901;*/ +} + .header{ border-bottom-left-radius: 50px; border-bottom-right-radius: 50px; @@ -66,7 +75,7 @@ table.center { .question_display{ align-content: center; padding-bottom: 50px; - padding-left: 10%; + padding-left: 5%; position: absolute; height: auto; @@ -84,6 +93,26 @@ table.center { padding-bottom: 25px; } + +.search{ + width: 40%; + margin-top: 25px; + margin-left: auto; + margin-right: auto; + border-radius: 25px; + background-color: #e8e8bcfa; + border: 3px solid darkgray; + border-bottom-left-radius: 13px; + border-bottom-right-radius: 13px; + min-height: auto; + padding-bottom: 25px; + text-align-last: center; +} + +.search_buttons input{ + width: auto; +} + .answer_display{ position: relative; /*padding-left: 15%;*/ @@ -113,20 +142,25 @@ table.center { min-height: 150px; } +.rowHeight tr{ + height: 4rem; +} + .control_buttons{ /*border: 1px solid black;*/ - width: 100%; + /*width: 100%;*/ align-content: center; text-align: center; } + .question_title{ margin-top: 25px; text-align: center; - width: 79%; + width: 88.5%; text-align: center; background-color: #ecc0578c; - margin-left: 11.2%; + margin-left: 6%; border-top: 1px solid; border-top-left-radius: 75px; border-top-right-radius: 75px; @@ -166,6 +200,7 @@ table.center { /*text-shadow: 2px 0px #000000;*/ text-shadow: 1px 0px rgba(63, 107, 169, 0.5); font-weight: bold; + margin-top: 5px; } .control_buttons input:hover { @@ -200,6 +235,19 @@ table.center { } +.w100p{ + width: 100%; +} + +.padLeft2p{ + padding-left: 2%; +} + + +.padTop2p{ + padding-top: 2%; +} + /*td{*/ /* border: 1px solid black;*/ /*}*/ diff --git a/templates/display_question.html b/templates/display_question.html index 9bc23e491..55cfd9ac8 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -53,7 +53,7 @@

{{ question.title }}

+

- +
- + + {% set titles = ["Comment", "Date", "Edits", "" ] %} + + {% for title_id in comments[0].keys() %} + + {% endfor %} + {% for comment in comments %} {# + {% elif key == 'image' %} From 9530cbe60d2a08f1803dbb2b118ea6c7d5eb027b Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Fri, 27 Sep 2019 08:57:03 +0200 Subject: [PATCH 179/182] Fixed image not sshowing on search screen --- templates/search_for_keywords_in_questions.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/templates/search_for_keywords_in_questions.html b/templates/search_for_keywords_in_questions.html index 425d72778..0871d4547 100644 --- a/templates/search_for_keywords_in_questions.html +++ b/templates/search_for_keywords_in_questions.html @@ -29,6 +29,10 @@

Your keywords were found in the following questions:

{% for cell in fieldnames %} {% if cell == 'submission_time' %}
+ {% elif cell == 'image' and question.get(cell) %} + {% else %} {% endif %} From bc8138f99c16a61b4cee44bf0470adc2624fc068 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Fri, 27 Sep 2019 09:00:28 +0200 Subject: [PATCH 180/182] Refactor display question --- templates/display_question.html | 465 ++++++++++++++++---------------- 1 file changed, 234 insertions(+), 231 deletions(-) diff --git a/templates/display_question.html b/templates/display_question.html index aa328d15a..3e465ebf7 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -7,298 +7,301 @@ Question - {% include "banner.html" %} -

{{ question.title }}

-
-
-
{% for related_tag in question_related_tags %} diff --git a/templates/list.html b/templates/list.html index 8d64dd371..108df525d 100644 --- a/templates/list.html +++ b/templates/list.html @@ -6,26 +6,7 @@ {% include "banner.html" %} -
-

Ask Mate

-
- -
-
-
- -
- -
-
-

-

- -
- -
-

-
+ {% include "search.html" %} {% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %} {% if is_main %} @@ -34,10 +15,10 @@

The most recent 5 questions

{% else %} - +

- -

+ + +
+ + + + + + + + {% for header in fieldnames %} @@ -81,5 +70,6 @@

The most recent 5 questions

{% endfor %}
{{ header|capitalize|replace('_', ' ') }}
+
\ No newline at end of file diff --git a/templates/search.html b/templates/search.html index f7e588482..a0909de47 100644 --- a/templates/search.html +++ b/templates/search.html @@ -1,10 +1,16 @@ - - - - - $Title$ - - -$END$ - - \ No newline at end of file + From ddea11f7634143395275ee8de9f871b15db72d8b Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 16:38:56 +0200 Subject: [PATCH 173/182] Added css for edit question --- static/css/main.css | 38 +++++++++++++++++++++++++++++++++++- templates/edit-question.html | 5 +++-- templates/list.html | 4 ++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index a09774343..3219d0d4b 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -209,6 +209,20 @@ table.center { color: black; } +input::-webkit-file-upload-button { + border-radius: 50px; + /*padding: 10px;*/ + background: transparent; + border: 2px solid; + color: #e26901; + /*text-shadow: 2px 0px #000000;*/ + text-shadow: 1px 0px rgba(63, 107, 169, 0.5); + font-weight: bold; + /*margin-top: 5px;*/ +} + + + .control_buttons { margin-bottom: 10px; } @@ -221,7 +235,7 @@ table.center { margin-top: -15px; border-radius: 15px 15px 15px 15px; /*margin-bottom: 66px;*/ - + min-height: 0; } .pt15{ @@ -248,6 +262,28 @@ table.center { padding-top: 2%; } +.edit_page{ + text-align: center; + margin-top: 25px; +} + +#title{ + width: 25%; + text-align: center; +} + +.control_buttons textarea{ + border-radius: 40px; + padding: 10px; + background: transparent; + border: 2px solid; + border-color: #e26901; + /*text-shadow: 2px 0px #000000;*/ + text-shadow: 1px 0px rgba(63, 107, 169, 0.5); + font-weight: bold; + margin-top: 5px; +} + /*td{*/ /* border: 1px solid black;*/ /*}*/ diff --git a/templates/edit-question.html b/templates/edit-question.html index bdfe60efd..d39a4e05d 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -6,11 +6,12 @@ {% include "banner.html" %} -
+{#
#} +

{{"Edit question" if question else "Ask your Question" }}


- +
diff --git a/templates/list.html b/templates/list.html index 108df525d..047973b9b 100644 --- a/templates/list.html +++ b/templates/list.html @@ -8,14 +8,14 @@ {% include "banner.html" %} {% include "search.html" %} {% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %} - +
{% if is_main %}

The most recent 5 questions

{% else %} -
+

From b6b10f079b93852d22267c28eb4f9c93db2806d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rajnai=20M=C3=A1t=C3=A9?= Date: Thu, 26 Sep 2019 16:44:13 +0200 Subject: [PATCH 174/182] Move SQL to data_hand; refactor list_quest --- data_handler.py | 19 ++++++++++--------- server.py | 11 ++--------- util.py | 5 +++++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/data_handler.py b/data_handler.py index 3a2f20b8e..3392cb528 100644 --- a/data_handler.py +++ b/data_handler.py @@ -2,7 +2,7 @@ from psycopg2 import sql import connection -from util import string_builder, create_check_keywords_in_database_string +from util import string_builder, create_check_keywords_in_database_string, escape_single_quotes from flask import request @@ -104,13 +104,6 @@ def handle_add_comment(req): execute_query(query) -def escape_single_quotes(dictionary): - for key, value in dictionary.items(): - if type(value) == str and "'" in value: - dictionary[key] = value.replace("'", "''") - return dictionary - - def get_comments(comment_tpe, _id): comment_tpe += "_id" query = """SELECT message, submission_time, edited_count, comment.question_id, comment.answer_id, comment.id FROM comment @@ -224,4 +217,12 @@ def get_question_related_tags(question_id): def delete_comment(comment_id): query = """DELETE FROM comment WHERE id = {comment_id} """.format(comment_id=comment_id) - execute_query(query) \ No newline at end of file + execute_query(query) + + +def order_questions(order_by, order_direction, is_main): + limit = 'LIMIT 5' if is_main == True else '' + q = """SELECT * FROM question ORDER BY {order_by} {order_direction} {limit} + """.format(order_by=order_by, order_direction=order_direction, limit=limit) + questions = execute_query(q) + return questions \ No newline at end of file diff --git a/server.py b/server.py index bf0cec3b2..015967d33 100644 --- a/server.py +++ b/server.py @@ -25,17 +25,10 @@ def list_questions(): order_direction = 'ASC' if order_direction == False else 'DESC' if str(request.url_rule) == '/': is_main = True - q = """SELECT * FROM question ORDER BY submission_time DESC - LIMIT 5 - """ - questions = data_handler.execute_query(q) - + questions = data_handler.order_questions('submission_time', 'DESC', True) else: - is_main = False - q = """SELECT * FROM question ORDER BY {order_by} {order_direction} - """.format(order_by=order_by, order_direction=order_direction) - questions = data_handler.execute_query(q) + questions = data_handler.order_questions(order_by, order_direction, False) return render_template('list.html', sorted_questions=questions, order_by=order_by, diff --git a/util.py b/util.py index b616a44fe..1333dd314 100644 --- a/util.py +++ b/util.py @@ -73,3 +73,8 @@ def string_builder(lst, is_key=True): return result[:-2] +def escape_single_quotes(dictionary): + for key, value in dictionary.items(): + if type(value) == str and "'" in value: + dictionary[key] = value.replace("'", "''") + return dictionary \ No newline at end of file From 2eaa897d608cb43f5c98e909f0a15d99c4c74d5b Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 16:54:35 +0200 Subject: [PATCH 175/182] Added css for edit question --- templates/add-comment.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/add-comment.html b/templates/add-comment.html index 4e2cd7204..19cb58b74 100644 --- a/templates/add-comment.html +++ b/templates/add-comment.html @@ -6,7 +6,8 @@ {% include "banner.html" %} -
+{#
#} +

Comment


From 1dfdbdc263b223055068ec0262d986ce0fefb113 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 17:06:36 +0200 Subject: [PATCH 176/182] Fixed answer comment titles Now they are visible --- templates/display_question.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/templates/display_question.html b/templates/display_question.html index 55cfd9ac8..0eaef90fc 100644 --- a/templates/display_question.html +++ b/templates/display_question.html @@ -252,7 +252,13 @@

 

{{ titles[loop.index - 1] }}
#} @@ -263,6 +269,9 @@

 

{#
#} {{ comment["submission_time"] }} + {{ comment["edited_count"] }} + From 0633535aab8459eee9f867a75ec367f84eb91e6d Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Thu, 26 Sep 2019 17:07:35 +0200 Subject: [PATCH 177/182] Added css to search page --- templates/search_for_keywords_in_questions.html | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/templates/search_for_keywords_in_questions.html b/templates/search_for_keywords_in_questions.html index 98c00d6c8..425d72778 100644 --- a/templates/search_for_keywords_in_questions.html +++ b/templates/search_for_keywords_in_questions.html @@ -6,8 +6,18 @@ {% include "banner.html" %} + {% include "search.html" %}

Your keywords were found in the following questions:

- +
+
+ + + + + + + + {% set fieldnames = ['title', 'message', 'image', 'vote_number', 'view_number', 'submission_time'] %} {% for cell in fieldnames %} @@ -26,5 +36,6 @@

Your keywords were found in the following questions:

{% endfor %}
+ \ No newline at end of file From 326dc363376a8e117ed404477cfb9eec6b8a9f13 Mon Sep 17 00:00:00 2001 From: Gergo Kovacs Date: Fri, 27 Sep 2019 08:51:09 +0200 Subject: [PATCH 178/182] Change display image in display question --- .../Screenshot from 2019-09-12 21-17-50.png | Bin 84238 -> 0 bytes static/images/image2.jpeg | Bin 3727 -> 0 bytes templates/display_question.html | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 static/images/Screenshot from 2019-09-12 21-17-50.png delete mode 100644 static/images/image2.jpeg diff --git a/static/images/Screenshot from 2019-09-12 21-17-50.png b/static/images/Screenshot from 2019-09-12 21-17-50.png deleted file mode 100644 index 62125ec28588213aec9ff1f2412a4a062bac4e8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84238 zcmeFYXH-*P^foAh0)ikQy-635-n$@Gnu0XxU3w>kUX)&>NQZzRi1glj2|e@{dVtV7 z1PEo~%zA(Nf7g7Nna^|Ax{I63&ABJ%?DOospZ%Qh_p0(wpHMt`@ZiByMFko42M;jC zA3VUI#(Ipp)7_}4hWdlyDy{et3w8KlnTMf1le@|4xM?_Ax&cgFEFM@nINDpVyPCOJ zSU9+Tc62*LZEhf+y>`Sf8V&$Zis zzN#l)sHv&7**}<`{ynQwLZVkxvfOA|BI!ihZSed(3Grz8+kj^;%$XXHLpOKzin9+H z*O}=~yfV5JU92{F4;zvf+M7@Ae4=d28fJ4Ic_Fq3`(SE6^biMbhgnMM>Mlxnq;X`w zi;66Er`hcm4F}*jn;-O>OLLV73H~0|fZoJIgVDWF*R`+7E}Qk(_PViQ)t!5F%PUjK z_eS!`bo76Z@zMpn;XyISrhUNKfx&3+Mwo(a6fJ-Q{V~-r=j0X-);~PSd%)yUqokJU9||q_fDY&6a?Yn zrz0)jINNkO^HqAN4I;w+9t!EMqL_-};4Bw8(eL#16>cZ}1t$xx_uE@Mf8Q@7Gru79 z7IGjgy#FyGtXmy%AWXG~)cfawb?8cVj+LPy#N(B$j45k@cd4}6KYbFWug9+qp4qjJ z6_H`=Rc~_J-Xi?>+g#6ZzaCu$eR*g?pY$j|bcAsUM#nuC2_~A^28(_!CiyuKs( z+n}(EEIoKaSgnnrZryhtsKl11J&l)^rY86PSX-6DqKI_Pvj;S>p+P|p9a^`+ zc|U*FY+uR7m_HH!XEtJ#bKa*6L}XHle|{GHm~>4@Aj!f6D)2y2F&vj{6x7)n_}b|D z>GE^mw_b8jvg-4jUO_FJDM~564Qf7`>z<=4zga0me*w1ODYhciz98?@upRKUbZ6b( zE<{p8G=<8S7csr0IqFw_Sc*HVr1IMe_ybqaY8{tjU_x%&<6qa;&24OLS=rcN`@*hA zUM*+4fJr(~ZWlS-D=khlS(R79+f7;KP=g`1x|=9#HgA`c@P=-Hak$I6*T#2pZrW3M z!KL=&uFxu{v9**~_P-`cUt*<8vavB0MZq5-@#qTUAzJ6ElbM1wbgZ3+D&`rU{MK{} zTVa1;u|ZYVr6+#Ab?vRE=#nWrVaxH=)w zdl7G~3Ib8d9?Zg9+$N?MV*VxY+k2%i{xacG zfxJi}t%uUTHMob!0S3?}uGTx6d(Im_m|~=AflIaLPHOE2&IE90`}>zR?hJ_2_+M{Z z=``37Bh*^TWMMTHT!o_=f7j0X5z&)SZsmklU*rk`neHpOd|0;yZR6aXc6{+1i-`2l zq3T#2HcPtT%e6=fbV$wa^zml8 zBY%TGBcaKBu0LE5un0Wrh<=t10`*Q%x{%#My)Qr98<6tdbSIFXPVV0v47VMx|H8cd z;4kR0V3WUml>V7zQ&SwRTnq=ew-!3xTmDAS0*dW^u!GHdgCL+{#ojHl6P;y zvc$a!@$_Wq<49=iN2{#x-@SY1G!q)35{+05rKNUCtT0m(UhyQg@;sW5WF9TY&rIh)hi9(Q**oKt!bigPwa!IrXm<8@_6{S{81 z2uRy`tpb!YGke2q9ap`D1@x@Mdo=uk-`3j}2Y+7ZDSO{2brvl4Q5RU5PJjPky+rEjLR62s z_em^Ep0_-s3Et`)fcskm3IBkc09Zjx@~@M&G5?Gjz=WCr4!7W&!=C%W^-vj zj!}AGYb6{)Y$tY5d3RgCKe(}wP8|#7t2fgNK!)J;7V1pY$5P`t=}QZ6Chn(e)LRFy z&_91N_ERZYoP>b=_3L$r|24ydiG1-!`Cl-PCIq$=0$e=*M)AAirtnoB--o7z(b$d# z0txmx=?3n^vrSwI{chVTw*HoNLq9R*`K!6Z{34x-^;jRVvZ%wSvvL+(;aLng+skZd zeam&F)ppnG>jED;mi)g`S)2F=2HI{8T;zSdQ0Gcb@*B!!-u?O2cdG{ zg3Ayh+w?Y;1FQ0&mqn+tJR*{LPgJtP2dVFSuK6H(nITtIDCf9>#C8LL7#6$@%e}s^ zcY!kCiG0D`0UDys?6}Qn~+)~w**sjMaulw>m z1L*PFYNDJWaA0HXG@W@)S2v1=Hw}lgK7g&PIcRUs&SUz(eCu0|*zFMSM{RBJ-a-s% z)EoUO1mJs#xk0<>uB)W!iEERib3%DT+Di9n{n>2K4Ghw@G6GZGtj30si)SlEPUE^e z>W&OF;(EmlgkCF5mM4&TN;~S`&-a~(1RJs$%e#!^+c!BWWZvpeTaM8o=lBB!f zp+BaUm)V7u+CN7w=RErx3dAKj;dZWa#lUQEStaj%#?bJ|g-<+Rq^Zdil)HN@o;sR} ztpsSm1?-^9o*qI0M87R!IXX|Kgjz*#ConLJ+1V;?DG^k&#XT-pfqA|>;R2~k)3D=b zxCb`!HTLQD&^lhTUgbthD_+YXGLELb@m~808sF!!;ECdj%x--z#@S*+UFFff1wqd! zJ7ZutC8FFeewT<+&9!t)HSZ|IyP@|9yPNEHCG8677H z%L3ss73yl%$x+3C+YKlkPI>!Rb4;E>KComw*xR|Z(}7hneL~*e_EonXp3YXw(>z?+ z1l<&CnnnI&411{@=pcxii;F4Dt-2sf5T4_+! z?mITyn*{5Jic3R&wj1aLFG$+Gbk6VCH}dIORCLN{fVMwQ{5mAwYdgxEVk1+!&k~gonBg7Jk1B3P))yn8A5kH0Zns%?puMzwrdyjPAf8_J(BaT zjQeQ8_Z<|4J1gg2dd;N;(`V2BT8D6Dy@A84SFaQljs!wtiHV7aMW#1`PEOt}WEmJ_ zszhU!dmfV_saXgs@PwAu0+P-2+Z~SQl~hR(PhXkO)^fI2)H{xHH&_~X{)#Pqdo$f{Xt?giJQhcCzglB^s)!WW0E~|Po|MFcvYG`4LBZuS*yhs% z&n?jn%U=ImOv45^UTu~i4GT@W>~BX=BX-Ydghb(s?P6%Xr2>@@UdZ?1ZwcN6hy@(6 zqgVQ#A75p?C-}bUpb^qb3%P<->fTHTE2}g{xvKTiGwI7(ThpVnP`!mzGBvj>(6b1p zo99ME7umchrDUby+IxKs?|~tA+HS0l8RdF3-`iZ6?QwfgQA-9zLWW?qy7(?ev23Zc zP}z<6WKYWvJm`*T?QaGCt%Ab|3)WQUAt@p}jEosv0-Z z#U?;cZKkClH~;IpI(w)GVf-4UYe0T4as;3%vZ6CJHHAs#=ur~d-@me00(IR9Xq|SP8XBF?Ex;WoK1K;ADdsp6!TU^~4=m5dCPBVZv0R_Al^+RcBd1C#Ns_1_zngTLx)todtUMR9+SRW?gLlx;`nR zJh5Qrtrw-O4TF(p5vt-l2KGsD^gVS1ZyYi=~dfZXEj2=*y1AB=suxgWqN}AGwW?41C%LQe{_uLpN=VDhUt*BHa^(i zCO5aR;!_obaw+W|7#+T3EBNsve%+jtBe`g?_jKk_{is1l;^-$r3JP_~lgVrSiNheP zHRQ>=m#_dBIc!A4Y3piSsRTzHs!{KSj>1B_Uk6=)Uefr&E5Qvcz;Vz2%nd)*Zal-n zsGtr_OngpA)Lbv-@SylT1_%Nfng4{_^@6Tr!W`4WJ1W^6Q@HEbicV3=-t}nJ&=)34 ziNiRl5_131VjKYK_LqKMDOyZW^!_R zWfGETQ;)mAK~%$S@ePY}otrty-*907nvC1E_2OX~|3x*GqLH5hk^hqbjt342C3uQ;A6g*;PdZ07Vn5yRpuZ;oQ{a?0)}Pt zjr#w_h~zKKsyNo~#rM1I&zJZX5yYkJOqZ}Ft2M;KvwZXaH;RK-#ls7ajO!Z?i|S5* ziwxs5N8=VD$!neV9ZfdT#(x|DL7ad|6t(v_O(9&Ch;wzIsPrJ)MJBIA?0hm|$OkC* zKRZF_+6#oo0KCpe3bbPlGyLYa&uF>Ib8QLZ1S(Bs2@c!tY0}>bRQSw|$8gC1O~Pat znWNHne*41fi2wQJ1O(fRLopn0{&`MDCXXLARR0WMm_kARzxf>MVG(|SJn0Mjb}lEb zAGFjD(7U#_e*DvhNZXb#fW>Qh(V5GMg64S~Uf$@bUN^TgRhK!!N5iDxZ{rGkGKWo+ z(MCc6W&x|SWHF0@-@~cqo_qP>2!)`f7?V{4d$)z&ssBxQ){myp5EZ$?zsNJc5>UR) z1kvxS+Io4o#F(s*m0keR0q<8>|JqBaU{}}@NR>?I{W6`Y|}RDztN%*d=Xk%{s-;pO~2V{^Lg(Q&V~{jrVJDud{X{Qu$;K1Chq= z6fT?JMQ7AAi0sGKFohA?VBJDC}Dm)aN zIXCE|yhVJ1nnWs!rD*ZGpy%e!$yIV7{+gAUXy5obl=$BaFGfcKgdfn|-ThW%2~Am) zu*%C0+{>pd@THd54VL>J$)86fuL**3N7tsS3qHd_t!<|UeW77oXKFAV9UU4f4E*i7 zd=&_Kq`9plFpH-X^A^L&F6=?lQ=w-rE6o}2?u4czt$Pm|W-EVwxuS)zyoVl%P}Gaz zS>BY2K{4pj&_&gkw|9jY3wn4jh9>YDB3IS1^xCqRlv=YJ8`Fl<1vRv^ z7 zoxe4Uj*+)0oU8zzTB1VUt@#=@MMcGwd_{G2!qn7MpOb!;z1fQ3vop7MN_A@<4gDh{ zHi$Gcy8Ilgw=25(&2cw3H#&HWrZu8~ldI0J`-DDzXJcS7#Et9Z)YXM&bG;`+PY-3P zD1ZZna~X~i5h@)*@m8vWFFmyErh{QpQ9EI$lv2M1>~lnZ1}qtP+{(qm?ACAZhfHAHq^`rx z6`A)=TboBwRnyqOi?Q@zp;@5TFZ@#-o=IWICye;TF#bNMO|Gz zEHbjoIinY>Rq|RQ6~COSoVV$Jk@dcxatY*9vkj6(@lIeA^T#pI$`t(`wG28!as8N z{A#Y=nkY`;ivpvxyE{MXc*B>Xs-W;!Uv z3#U4z-?thMZ*ePI0$blj@@Ywe&$o`4H7QkJA8|r*PKFLAng$)zLJ}0@yeL{3XYOLf zCp850Paohdr+)13rG(r2R9R4=7tIf}MNK|~0_Ft=FNr50$}76i(!#p z=Enj*Lukmf8n_hyjmmfzM4hCOfteaqYesYuiv5v7<{_R|`->dPZ&ZxWxGHYG%|ScP z8nHeKkvTt`wKyFt z?O~_uLU@#d_U0=AX!h#MQK`I?lD#5lQHqX^8i7__Zl%<8C|g7s)VGZIaWbEfuGJJ! z0Scv2d^cwMQ^`xTKZx7gPLvHe^l`XZefpe)q<1ev73ZNE2m(eyfSjQNUfbOigc2R1 z=WzR|Zs8i|0S_yS%mRSIX+vMEC-8$3$_MFQwnRc!B7v2`6NiN zGe&KB{d*0ER|bC|POlrd&5eYYM7zfvS-KnfzP34~amWu>S)^H7qHAbv7Kd}K8Lgk2 z8W3xHQM@ssrj!E-k7_d(v)Y8(rAvm^O$$Rat3h!_9_LxHDV^ z`c)22XC-M1e*JoB33tOSn9Izf^*h6E4FDM#A|`?lyl>rSj*#Am@UB>i)jjOtbR(}W z4Zz9KZ=!}($1dOo7_`^Gz3O(5>FWhcp0;LveSc)SlwOCXx{sOl`XqEejc8u-Ucdo) zgHd{UN%-W#i^ys3tJvu{-njn-RUK%NS}3MeNz(G98m{AWUPk6H7g~KNRYv!|y9nYp z5Mx5r;9&mP^2L|s1XV3nlEsSy3U`4Db09T2i+;QAZG2!>hCyqp6$xj-<>lB}rCyzQ z+p5onfL1Ynf`HgO+D;0;-Q_yNCh_;mVXM^$B_^gKK^9fA;r2ZDp)8X9z6>$0Yvj}E zlhm8r1h5LdQDe=@3mATCQ)yIMKxlPVm$3W7d#_$mA-QkVILjNuW$%9d3cg%F zpz=SsirRZ(TydTW25wPvF)We+Z)Kz2u~{6n8Cu?!7d3d**WGs3mnM-jwn;*#2aCx=H90xaF2-J5y}23Xe>=X;DH;x$BHP8yaK~j>wGRM>`MxG#>HN0V7U)l0^tPM=vr{ye5v=TqUo*plaJ8N}Mf;OiD?Ah%)}n!vhLOu;>^bq+2a$%(jM_ zE908l4r^{KHi$t>#c4cHx_Mb-X7oa{Jr8o8j!p=3JWWLyGe`#Z7nF~?)x zy;zGZcP|7bHZvB7y%i$s#;$YhidjCy{89FOaHDg>g@BY)-qMm;S63H>Xq9f;>T56< ziYC)Gvz-2^L>km#Y!+Gy6S}*|@*Gd&hL%R5n>_(?M;6Hj?NS~wB z1pk*7AQCNw%g-kPN{fWUhB8G3nc+6;WPEjVu5*5t- ztkVr&YgpUo(gE9|wxj*Fjp|J`o5vuM7wBt7vn;&v(5u_gqTkyU=&LHSELnzm43&fp zH+*NzbQZAM=w97BGC8=oo8JEtDU=(F#(~dTh`c+Bh`Hw}D^WD*o5ju`H0s)cNURPg z)9ZWoeDdSgt(&z~CU|48o2*vLy%-(moNs)K=DF!?U?!LdK>Yf7IPZw->7#7)kBh z`ia$iN)4YfW<`hunuKH(63^n050qSIDU4Kn=l5_b{q4tM{D4&^!fI@cV3~}Ir{cD| zI~0Fx>J-g@e@$r>v+6O4c#$~?ddfHZV z3%ER4_?*-cBfGvnvg@)ZzWZ6%5q8MiHz;_hcdjsIxLAhao-@Q8wVax#$o z^P;Rpxqs+ti$OKH zPxExwCuJ`!b8WX@GmF z12TzPA1=^$Rxp>d@xxFN+SdOV4XQa*%u|1rPnmkLWtM2iYSDRFg~9{KX11O~13^HY z<*>;G)U^*4d~c>ZFmhJ_pOWJZA6U(Yg2RIIe10ub_!^gyKjj|UYD^003X59J*ci6j znYVsM`@$Gg(Fus4K6~~j{YjggoLoV1u_{E+T1v^rMF-r^L-uyi7{wKeLQoB9XTVbe zw)s`^%Gs*~+60&3nlWlM(crWUDs{)ZjZ*l_$?I*4{c_nC&l#yI$`?s2if%t9GLHUi z?+(X-^?Ss9vo8n6#qQWkz3!qXL~$86a`ON$JrE~_7ez>*bz1%6L2r!+DyQhLv>3z+ z?fR&tMTq-&uECWBm3ex$EcMO|a^X^e>7E_cDMq6bn5a^?F!u#Ds3dEtcEj|P^3k}k z(57l@mdIDokYpA}0>B2|YcVWnNN9jSeq5|$)|ZF6>cF**(IKmifdLz4yJ=TGM+a1v zo;;Tw{rkKnzxU^R2i#5KW4GVt!&ZLJz5_C5-uV*Ei7GhYY4>OiV7*5Uz{IMdFiWA> z%p@tHL$VNe|I90hT0x%EONJQgH-{g!ae6t!r)O-neYI?8fQq0acWYvmnjg%Td4#06 z>XhLl=j8}~L8qt}PzkSS?HPAi;$ifUPTA=>k!e&3Mfstq+DDq-<as-nqy7y-Aazn`B(Pfw5C?EB_r2gqZ0cL^H*HRluWjk*I_w+u^w_vXgtdv*2k zy&R=?@A?L#4XP^54$Oyx6RwVnS}w;<@m)<~nOmiNT!F4qguVq?a=m^_H}nQg@pBeT z7E^C-pKl-RLs{`H5@z;C5#r{hGL85`nHoxhM>q3RcA^%~g%zy`rTrS`wPP=kLgh~zizLI9l%=T}eUg@9=Bm|enO(D?A7TV#UKslUC33{aVOq!!T zT-C~!S=xuBUO>v8tkbf(p^sYJEXuOBgqBSt#U6Td3c6n(5k1JRtxamX-FIAY8D!5Z zC^!Wp4_Uam2?i@EL~->7D>^)OX7=57W_GLX%Nca^1m3p!MI+?|92uX7yczAK^my^y z=KSidR=uC^2v-bohc>18U~_yJF7+2FV7AZo4B_YZuylI!Zh_;3lxc1 z1U$TYKNa2eGBGi^%wz(lL;Wcm9Cv(C43nv*UJ*aBXRnR8S^z8*AJcexZw_-2k!l69 z82iy<$H`_pC9HRH$7kO6EQ$R-?c^jP_(0Flu%mLrNKlyiAm@qS5vAXa9R<<-DOd9O zBE%8HhdWeu1I**$cH;-eKBtz=sY`JrIeg-z?O&fV9~FpuxI=Z)TC%$Lo$(X7_%?-6 zS8X&usvcUl9KrchR6IUY%MtUXUTfS=wIA+p{Hx+2JM~2r6O55wI`&;5Ua)f{;x}`h z9{J)nq4rx%Sut|*?WQ|(=6D_2Hdi$G+A7#g?#FKZDxxbDPcK?61Ns{AfwzM~Ax1)hFy6Ff;H&=(XPj!O8)KYz zTv1us9K}x*f2vACK@RH1F*D7lXX|94e*W{$R41@XVCB92GZ47}-Y~j(in(QGcjX;m zg2jT0@%(XD#AECj^bpLlZQ=FPgEEpjs0zyUANn7m!Fr=%1$9dUvlV8#*HhXbKWeSH zG*A>M*6dG*`kCyAl4UBKsWIZNa>=}I_V@i_Jm>M?wGlOOTU+{NBUci%qc@}vtuZK# zl`#An*4J_dk~pU?VZ=Yyi!v_<-kwPaXBBq}y^6fGGx!jyLMBSsQTby6I&VM8-XIx{ zjg6A-q4G!+vE8<;hN8nBu(GnEBA$0jO04Yc7SLLIb4yFYCtrX4{yop|!ksE~GBHu0 z(0M#BT-pDwAq4=GW_zn9#Uk;>$Em_($K9tBwHZrSXv!00W#X1%7 zKioGi0ZI;n!$xx1hgfp%bEl0_J9U@m0M+$omPqa)0)3z}Nl)x;h-7ok7og)(mp!Le zg8%6TZr$G7B}Dc^+2-pq|I>9i@vJ2i+a`y}+`Nk2LUSDP>E_Y&3<#r9n^VUZu*-+((XLja;a{ zS<^JCvNPiw;$xesxJ*a&r7^gKQlMha3Kp%@ZDujnk{(&P0jjF@#OW#0DsOdI7Ftr= z0`~yaNYB%|A|_);H?U2`^|;>6`oth_(|_3jqQiy}%waLGH{1HYC?z=6WwzfcEY2}xl=$5hn@=?(_z zrP;I}JbT63AqwZk1(1Oqi7vi|^`v6=y15f2+fu#vCHRb!yx*WJZMv}eL%cz|@c9ltd%w5o#90pNjXy8t0I099`k?Uq)hGAnCf(l}y2-N|soYgo5LC zP2utxXZE=|rYHq0|0|m$pHuQFDblP&H!s}Gc54=^Aega`LuIQg9{lmrMkZj%I5qWM zos<=?wdW`0+NQcvb9c?h=+{3dxQ2^X8ONgCJ7kp(fwKuSQ{4B7=;tig`WqnOxKqiyZw-;la0}4a)NitP;a9jF!5hm%X~3eZ zKH7)8an%c}QKC$wUCv}&7c$c$pZY?}{Gt;^qov?9Dty=!u)%WQd@>ejXOIpFnwnCudUV#83Q$vUSV zA=oi^>1>5|vMwtSZ}wu!Lk7|_%6*R#-ajq2OKwIW>k`p*_!?E-}slZ+qvJI z9`d*N_v~-eK2+);F5+LlK#{WfgFU^`l){Jw*M?oeHWc3`akItXb+M;-e|H8(o(hkd?pB72GS9^Sr z1XrU9PHI1-7|qwn2!0O)f3(B;cAsgk?+`p*=Bp$33~aHg%ER4)x2izK_SiP9R;JFu zps=WjfiQfu&ENme+hC%oeL>Cu47Fz>#)K}y&5ROxIqiSBUd&wN_k64zU1Mk(0es4ePK2(6#cDUtl zsV`3F%4Uuy%jd)z6?JuY<3Lz=dDA$OlC`-`H?(zYZ)_bJx8<&Br|Ra+L}G%;b8D!n z!5sn~i%5Rz1P-OLpmmzbvlh@dB4*nSPC5s4e2ayF^KCOd-;uO53YzQPuEZ(MWw;Pt z3=EZ*k%@V7H8eEXv9g+6oot-#E`xGoM4pt*4E7N)C@3mAoL|v3#{6oztIG?$!_gS0 zd#FRABM{dr{;To8CW_RsoA*$O@ZO2@LDAb9b3;F%Sv%mD-${ZV5LuO)alYGnY17$^ zfS-SKz~NubVQJ1tlL`840v?B~(@p-*3>`2%Rl~Lvq z9v;39hr7C&(80j5vDPz1+d@L@SmN@TTtPW+A0MyEgUapZc#=5u@ZO*}8W=dUXI4WF zT-v2!I^wavvddqSN5g*T$=c_o*S29#=r-v5BKH^zQ1NuZ<8xn6w0!P z`drLep~yXy5Dps~ySt|+E-kIIJf{maOTD%B%2iWr>~i2OSA2_{rartZj3kvV+`xm4 z-TuZ^kD&ZuqA&@PqIRMmkgKF06j_bek>WJ>Ya8mVo(*oX;Ax-6V1CfSth*gf3N||BHOExwJcJ}D^FT1x3K&o0Wg1U>-2+N3e2Zi=j!} zd2*nZGDs-EB%lh(z8jKaS(i7=)e$kv$js=3+^^$TML^X<=eOr{LQR59-fL*Q_3;to z(B{+r)ab@4?tPI5I9$RAmO}9bgyAwq@`IhuB8TEYloaB-iA*pgru6N9F@=oG#p)Zr zqe(lDaTDCvQ0C`P;!;u+P|g(pW|DCLg+q&SwGuUxv+3!D%0ZI+! zoDpNYVy)9vm7qrSYMh+)>_)(eD(YHrOdysK`t#@Cut{e9nR~mh9cFvQ?%`AsP$7@u zvlrBnpsRwLiV_zcli_*{jVn>}A9YUmo3mE$w}OSi*1?isRp8_>Uv~ziIFPf#m3G)_+d;&&7Bl>%THu0)jYI zm7IS>J^!D7+W+5`{}&T9FBS0PULT_9JFa*Lq|(SAF8Gbwb7&p%`w>2mg$&;I+@p-M zU>4RVRGly4>gpxH?OYev2~K!w2VubeOgZH&J!=;GV;UOc>+UbUZJbeV`OP%J@n-bx zXG&?mxdT}_0dDq9kz>16TO2VlP(;hC)$YRQJ&o%OW61z#eGSMVmUtU|Q!%__BF%F- znr(#?yQ@bbeYdzd#Q5GZ5_kWrYdpSxp$GGQuTD2{S&AOe3 z5Ke>jW^^U$<)0vu+l+VPyfpl;f|akv5{8!F%T8@_rrcGrE&%G6>YrIZ^8_`OUOt*2 zwf|`Y_S(GPWzN4jf99al1lb(BQu4e*eyu30sEU|!W!ukHS?X$0&N3VjycbNBIM8`M zN7Ah9w??^n3v()sn{AN*FZUpgn)@O0Kr=(rQw*K^4mX;csdHtKU7?tRgtuPr9?I$8 zwiSAjIGu$}2*cxduX-G5*ZY&<8r8W(w_Q6aO`f}vf(@>2^5SiwRgPD-_w03dWi0%M z_wv}-c~)?zvx)-Ok?(EBJN`=rpFzu`TV1jDbze|BBcrQh>Qmm9pBv5aZj>FOnu{nI zv~rnQ2k~mOEuxnGxCtKoz7e&RgUaJt|JZT!>0QtdVx^Sh(T|p;bN0ES(VZG%@%=c>*hekcf!Pyti_J&vzN zwI9SE%6~%%ce?99ZJ#}1&sRllD~(H1*zKw)P&Ge^7IgjuYH^roL~?E92&Nv-r#A}% zuy+Suv1TG2{romdDH{VzPJt&$_mHLA5CQkQdze27kI+{<)tq1qD$jS1G@dIv8wV{N zDJ&aDpleX^bp%G@={E-23eL_0%?`JWi|}fv@$I6~@*PQ(Ud4dl*smw~yf_ z%B6e@_=@q|tG#^}z+j#38h>}6>kh$u&L<3C7nu=;H;yzI!W%iLX_lXDNF;Uk97jqW^vU26DfB`@LbzLUa%^+g2+M2k zW4-EA(;GjUxk;g3zX;Pyo9AO=+{$@Xeap1qBVp>aAoVxqlo+zVNNAABA-_AOTKi53 z_%K=vJtYQI89YX6p5bSp*kW|fW~02?eI*n7Q1^+!;&+jFEgM%qL&L(hmSd#U=;?o` z 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 Qj}r?6J=43Tcbs1Q7mom@F#rGn diff --git a/static/images/image2.jpeg b/static/images/image2.jpeg deleted file mode 100644 index 1d8969788e925100fdda14bf165722255b72cfb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3727 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  
{{ answer.get(key) }} - {% if answer.get(key) != "" %} + {% if answer.get(key) %} Picture {% endif %} {{ question[cell] }} + Picture + {{ question[cell] }}
- +{% include "banner.html" %} +

{{ question.title }}

+
+
+
+ {% for header in headers %} {% if header == "Vote" %} - {% else %} + {% else %} {% endif %} {% endfor %} - + - + - {% for key, width, align in keys %} {% if key == 'submission_time' %} - + {% elif key == 'image' %} - {% else %} {% endif %} - {% endfor %} - - -
{{ header }}{{ header }}
+ + -
+
{{ question.get(key) }}{{ question.get(key) }} + {% if question.get(key) != "" %} - Picture + Picture {% endif %} {{ question.get(key) }}
- -
- - - - - - - - - - - - - -
- {% for related_tag in question_related_tags %} - {{ related_tag['name'] + ',' }} {% endfor %} -
-
- -
-
-
- -
-
-
- -
-
-
- - -
-
-
- -
-
-{#
#} -
- - - - - - - - - {% if question_comments | length > 0 %} - {% for title in question_comments[0].keys() %} - {% if "id" not in title %} - - {% endif %} - {% endfor %} - {% endif %} - - - - - {% for comment in question_comments %} - - {% for key in comment.keys() %} - {% if "id" not in key %} - - {% endif %} - {% endfor %} - - - - - {% endfor %} - - -
- {{ title }} -
- {% if key != "edited_count" %} - {{ comment[key] }} - {% else %} - {{ comment[key] if comment[key] else "0"}} - {% endif %} - -
- - - -
-
-
- - -
-
-
- - - {% set answer_headers = ['Vote', 'Answer', 'Image', 'Submission'] %} - {% set answer_keys = [('vote_number', '10%', 'left'), ('message', '55%', 'left'), ('image', '20%', 'center'), ('submission_time', '10%', 'center')] %} - -

Answers to the question

- -
- {% if answers | length != 0 %} -{# #} + + + - {% for answer in answers %} -

 

-
- - - - - - - - - +
+
- {% for header in answer_headers %} - {% if header =="Vote" %} - - {% else %} - - {% endif %} - {% endfor %} + + + - -
{{ header}}{{ header}} + {% for related_tag in question_related_tags %} + {{ related_tag['name'] + ',' }} + {% endfor %} +
- - - - - - - - - - - + - {% for key, width, align in answer_keys %} - {% if key == 'submission_time' %} - - {% elif key == 'image' %} - - {% else %} - - {% endif %} - - - {% endfor %} + +
-
- - - - +
+ + -
- - - - +
+ + {{ answer.get(key) }} - {% if answer.get(key) %} - Picture - {% endif %} - {{ answer.get(key) }} -
- + +
-
- - + + +
-
- - + +
+ {#
#} +
+ + + + + + + + + {% if question_comments | length > 0 %} + {% for title in question_comments[0].keys() %} + {% if "id" not in title %} + + {% endif %} + {% endfor %} + {% endif %} -
+ {{ title }} +
-
- {% set comments = get_comments("answer",answer["id"]) %} - {% if comments %} -
- - - - - - - - - {% set titles = ["Comment", "Date", "Edits", "" ] %} - - {% for title_id in comments[0].keys() %} - - {% endfor %} - - {% for comment in comments %} + + + + {% for comment in question_comments %} -{# - - + {% for key in comment.keys() %} + {% if "id" not in key %} + + {% endif %} + {% endfor %} + - {% endfor %} + {% endfor %} + -
{{ titles[loop.index - 1] }}
#} - - {{ comment["message"] }} - -{# #} - {{ comment["submission_time"] }} - - {{ comment["edited_count"] }} - + {% if key != "edited_count" %} + {{ comment[key] }} + {% else %} + {{ comment[key] if comment[key] else "0" }} + {% endif %} +
- +
- +
-
+ +
+ + + {% set answer_headers = ['Vote', 'Answer', 'Image', 'Submission'] %} + {% set answer_keys = [('vote_number', '10%', 'left'), ('message', '55%', 'left'), ('image', '20%', 'center'), ('submission_time', '10%', 'center')] %} + +

Answers to the question

+ +
+ {% if answers | length != 0 %} + {# #} + + {% for answer in answers %} +

 

+
+ + + + + + + + + + + {% for header in answer_headers %} + {% if header =="Vote" %} + + {% else %} + + {% endif %} + {% endfor %} + + +
{{ header }}{{ header }}
+ + + + + + + + + + + + + {% for key, width, align in answer_keys %} + {% if key == 'submission_time' %} + + {% elif key == 'image' %} + + {% else %} + + {% endif %} + + + {% endfor %} + + + + +
+
+ + + + +
+
+ + + + +
+
{{ answer.get(key) }} + {% if answer.get(key) %} + Picture + {% endif %} + {{ answer.get(key) }} +
+ +
+
+
+ + +
+
+
+ + +
+
+
+ {% set comments = get_comments("answer",answer["id"]) %} + {% if comments %} +
+ + + + + + + + + {% set titles = ["Comment", "Date", "Edits", "" ] %} + + {% for title_id in comments[0].keys() %} + + {% endfor %} + + {% for comment in comments %} + + {# + + + + + + {% endfor %} + +
{{ titles[loop.index - 1] }}
#} + + {{ comment["message"] }} + + {# #} + {{ comment["submission_time"] }} + + {{ comment["edited_count"] }} + +
+ + + +
+
+
+ + +
+
+
+ {% endif %} + {% endfor %} + {% else %} +

{{ "There is no answer to this question" }}

{% endif %} - {% endfor %} - {% else %} -

{{ "There is no answer to this question" }}

- {% endif %} -
+
-
+
From ce494c33c275d1e3b5b7ccc551bfa9b59560ba07 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Fri, 27 Sep 2019 09:28:32 +0200 Subject: [PATCH 181/182] Fixed missing styles --- static/css/main.css | 16 ++++++++++++++++ templates/add-answer.html | 3 ++- templates/edit-question.html | 2 +- templates/tag-question.html | 4 +++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 3219d0d4b..5e6469d33 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -38,6 +38,8 @@ table.center { /*background-color: #e26901;*/ } + + .header{ border-bottom-left-radius: 50px; border-bottom-right-radius: 50px; @@ -142,6 +144,16 @@ table.center { min-height: 150px; } +.answer select { + border-radius: 25px; + padding: 5px; + background: transparent; + border: 2px solid; + color: #e26901; + /*background-color: #e26901;*/ +} +} + .rowHeight tr{ height: 4rem; } @@ -284,6 +296,10 @@ input::-webkit-file-upload-button { margin-top: 5px; } +.bold{ + font-weight: bold; +} + /*td{*/ /* border: 1px solid black;*/ /*}*/ diff --git a/templates/add-answer.html b/templates/add-answer.html index f33dbb2d8..165feafe7 100644 --- a/templates/add-answer.html +++ b/templates/add-answer.html @@ -6,7 +6,8 @@ {% include "banner.html" %} -
+{#
#} +

{{"Edit answer" if answer else "Answer question" }}


diff --git a/templates/edit-question.html b/templates/edit-question.html index d39a4e05d..766c9826a 100644 --- a/templates/edit-question.html +++ b/templates/edit-question.html @@ -7,7 +7,7 @@ {% include "banner.html" %} {#
#} -
+

{{"Edit question" if question else "Ask your Question" }}


diff --git a/templates/tag-question.html b/templates/tag-question.html index 74b5f611a..9e3bf32cf 100644 --- a/templates/tag-question.html +++ b/templates/tag-question.html @@ -6,7 +6,9 @@ {% include "banner.html" %} -
+
+{#
#} +

Add tag


From 14784a391f152854ec55ddcb2779a7c59cb60b20 Mon Sep 17 00:00:00 2001 From: Krisztian Pometko Date: Mon, 7 Oct 2019 16:02:59 +0200 Subject: [PATCH 182/182] Modified db for Sprint 3 sql file: sample_data/db.sql --- sample_data/db.sql | 551 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 sample_data/db.sql diff --git a/sample_data/db.sql b/sample_data/db.sql new file mode 100644 index 000000000..56c62d902 --- /dev/null +++ b/sample_data/db.sql @@ -0,0 +1,551 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 10.10 (Ubuntu 10.10-0ubuntu0.18.04.1) +-- Dumped by pg_dump version 10.10 (Ubuntu 10.10-0ubuntu0.18.04.1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: answer; Type: TABLE; Schema: public; Owner: tw2 +-- + +CREATE TABLE public.answer ( + id integer NOT NULL, + submission_time timestamp without time zone, + vote_number integer, + question_id integer, + message text, + image text, + user_id integer NOT NULL, + accepted boolean DEFAULT false +); + + +ALTER TABLE public.answer OWNER TO tw2; + +-- +-- Name: answer_id_seq; Type: SEQUENCE; Schema: public; Owner: tw2 +-- + +CREATE SEQUENCE public.answer_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.answer_id_seq OWNER TO tw2; + +-- +-- Name: answer_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tw2 +-- + +ALTER SEQUENCE public.answer_id_seq OWNED BY public.answer.id; + + +-- +-- Name: comment; Type: TABLE; Schema: public; Owner: tw2 +-- + +CREATE TABLE public.comment ( + id integer NOT NULL, + question_id integer, + answer_id integer, + message text, + submission_time timestamp without time zone, + edited_count integer, + user_id integer NOT NULL +); + + +ALTER TABLE public.comment OWNER TO tw2; + +-- +-- Name: comment_id_seq; Type: SEQUENCE; Schema: public; Owner: tw2 +-- + +CREATE SEQUENCE public.comment_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.comment_id_seq OWNER TO tw2; + +-- +-- Name: comment_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tw2 +-- + +ALTER SEQUENCE public.comment_id_seq OWNED BY public.comment.id; + + +-- +-- Name: question; Type: TABLE; Schema: public; Owner: tw2 +-- + +CREATE TABLE public.question ( + id integer NOT NULL, + submission_time timestamp without time zone, + view_number integer, + vote_number integer, + title text, + message text, + image text, + user_id integer +); + + +ALTER TABLE public.question OWNER TO tw2; + +-- +-- Name: question_id_seq; Type: SEQUENCE; Schema: public; Owner: tw2 +-- + +CREATE SEQUENCE public.question_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.question_id_seq OWNER TO tw2; + +-- +-- Name: question_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tw2 +-- + +ALTER SEQUENCE public.question_id_seq OWNED BY public.question.id; + + +-- +-- Name: question_tag; Type: TABLE; Schema: public; Owner: tw2 +-- + +CREATE TABLE public.question_tag ( + question_id integer NOT NULL, + tag_id integer NOT NULL +); + + +ALTER TABLE public.question_tag OWNER TO tw2; + +-- +-- Name: tag; Type: TABLE; Schema: public; Owner: tw2 +-- + +CREATE TABLE public.tag ( + id integer NOT NULL, + name text +); + + +ALTER TABLE public.tag OWNER TO tw2; + +-- +-- Name: tag_id_seq; Type: SEQUENCE; Schema: public; Owner: tw2 +-- + +CREATE SEQUENCE public.tag_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.tag_id_seq OWNER TO tw2; + +-- +-- Name: tag_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: tw2 +-- + +ALTER SEQUENCE public.tag_id_seq OWNED BY public.tag.id; + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.users ( + id integer NOT NULL, + name character varying(32) NOT NULL, + password character varying NOT NULL, + reg_date timestamp(6) without time zone DEFAULT now() NOT NULL, + reputation integer DEFAULT 0 +); + + +ALTER TABLE public.users OWNER TO postgres; + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.users_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE public.users_id_seq OWNER TO postgres; + +-- +-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres +-- + +ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id; + + +-- +-- Name: answer id; Type: DEFAULT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.answer ALTER COLUMN id SET DEFAULT nextval('public.answer_id_seq'::regclass); + + +-- +-- Name: comment id; Type: DEFAULT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.comment ALTER COLUMN id SET DEFAULT nextval('public.comment_id_seq'::regclass); + + +-- +-- Name: question id; Type: DEFAULT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.question ALTER COLUMN id SET DEFAULT nextval('public.question_id_seq'::regclass); + + +-- +-- Name: tag id; Type: DEFAULT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.tag ALTER COLUMN id SET DEFAULT nextval('public.tag_id_seq'::regclass); + + +-- +-- Name: users id; Type: DEFAULT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass); + + +-- +-- Data for Name: answer; Type: TABLE DATA; Schema: public; Owner: tw2 +-- + +COPY public.answer (id, submission_time, vote_number, question_id, message, image, user_id, accepted) FROM stdin; +2 2017-04-25 14:42:00 35 1 Look it up in the Python docs images/image2.jpg 0 f +3 2019-09-24 11:04:49 8 1 sajt 0 f +4 2019-09-24 11:05:30 13 1 sajt 0 f +5 2019-09-24 15:08:42 0 0 sanyi 0 f +6 2019-09-25 10:47:03 0 1 asd''asd 0 f +7 2019-09-25 10:47:29 0 1 ''a 0 f +8 2019-09-25 10:48:47 0 1 a''1 0 f +9 2019-09-25 10:51:21 0 3 a'1 0 f +10 2019-09-25 13:53:01 1 2 bump 0 f +1 2019-09-25 14:16:42 4 1 You need to use brackets: my_list = [] changed ----- 0 f +11 2019-09-26 11:02:47 0 5 sajt 0 f +12 2019-09-26 11:02:55 0 5 sajt2 0 f +13 2019-09-26 13:28:16 0 4 salata 0 f +14 2019-09-26 14:27:16 0 6 nanaaaaaaaan 0 f +\. + + +-- +-- Data for Name: comment; Type: TABLE DATA; Schema: public; Owner: tw2 +-- + +COPY public.comment (id, question_id, answer_id, message, submission_time, edited_count, user_id) FROM stdin; +73 1 \N asd 2019-09-25 14:48:11 \N 0 +2 \N 1 I think you could use my_list = list() as well. 2017-05-02 16:55:00 \N 0 +4 \N 2 asdsa 2019-09-24 11:29:23 \N 0 +5 \N 2 asdsa 2019-09-24 11:32:29 \N 0 +6 2 \N asdas 2019-09-24 11:37:48 \N 0 +7 2 \N asdsada 2019-09-24 11:38:15 \N 0 +68 1 \N sajotskeksz 3 2019-09-25 13:26:20 2 0 +11 \N 1 sadsad 2019-09-24 11:40:05 \N 0 +22 \N 1 bliblubla 2019-09-24 13:29:56 2 0 +16 2 \N asdas 2019-09-24 13:17:55 \N 0 +76 \N 1 komment edit 2019-09-26 12:51:10 1 0 +77 1 \N komment q edit 2019-09-26 12:51:20 1 0 +34 \N 4 asdsa 2019-09-24 14:50:51 \N 0 +79 \N 3 asdsa 2019-09-26 13:20:45 \N 0 +82 \N 7 aaaaaaaaaaaaaaaaqqqqqqqqqqqqq 2019-09-26 13:26:56 \N 0 +85 \N 13 nokedli + porkolt 2019-09-26 13:28:24 1 0 +45 \N 4 asdsasdsa 2019-09-24 14:59:37 \N 0 +46 \N 4 asdsasdsa 2019-09-24 15:00:06 \N 0 +31 \N 4 123 edited hope it owrks 2019-09-24 14:48:44 1 0 +49 \N 4 asdsadsad 2019-09-24 15:03:19 \N 0 +87 5 \N asdsad 2019-09-26 14:41:18 \N 0 +51 \N 2 asdsa 2019-09-24 15:04:44 \N 0 +88 5 \N asdsa 2019-09-26 14:43:27 \N 0 +89 \N 1 gdsvsdv 2019-09-27 13:45:42 \N 0 +57 \N 5 asdsadas 2019-09-24 15:10:54 \N 0 +58 \N 5 sajt 2019-09-25 09:39:09 \N 0 +59 \N 5 sajtsdfds 2019-09-25 09:39:48 \N 0 +63 1 \N as'asdsa 2019-09-25 10:46:37 \N 0 +65 3 \N a'1 2019-09-25 10:51:27 \N 0 +66 \N 5 new comment test #1 2019-09-25 11:11:42 \N 0 +64 1 \N asd 2019-09-25 10:48:37 \N 0 +61 0 \N asd 2019-09-25 10:34:48 \N 0 +12 1 \N 123asd123 2019-09-24 11:42:07 \N 0 +14 1 \N asd 2019-09-24 13:08:58 \N 0 +19 1 \N asdsad 2019-09-24 13:21:06 \N 0 +20 1 \N selele 2019-09-24 13:21:14 \N 0 +21 1 \N asdasdsadzxcxzc 2019-09-24 13:29:38 \N 0 +15 2 \N alma lama 2019-09-24 13:10:00 \N 0 +18 2 \N asd 2019-09-24 13:20:58 2 0 +17 2 \N sadsadas 2019-09-24 13:19:41 1 0 +3 2 \N lajos 2019-09-24 11:27:46 1 0 +69 \N 10 sajt 2019-09-25 13:56:41 1 0 +\. + + +-- +-- Data for Name: question; Type: TABLE DATA; Schema: public; Owner: tw2 +-- + +COPY public.question (id, submission_time, view_number, vote_number, title, message, image, user_id) FROM stdin; +1 2019-09-25 10:47:48 15 9 Wordpress loading multiple jQuery Versions I'asd developed a plugin that uses the jquery booklet plugin (http://builtbywill.com/booklet/#/) this plugin binds a function to $ so I cann call $(".myBook").booklet();\r\n\r\nI could easy managing the loading order with wp_enqueue_script so first I load jquery then I load booklet so everything is fine.\r\n\r\nBUT in my theme i also using jquery via webpack so the loading order is now following:\r\n\r\njquery\r\nbooklet\r\napp.js (bundled file with webpack, including jquery) 0 +3 2019-09-25 10:53:11 0 0 asd'12 qwe'%123 0 +0 2017-04-28 08:29:00 29 8 How to make lists in Python? I am totally new to this, any hints? \N 0 +2 2017-05-01 10:41:00 1364 62 Drawing canvas with an image picked with Cordova Camera Plugin I'm getting an image from device and drawing a canvas with filters using Pixi JS. It works all well using computer to get an image. But when I'm on IOS, it throws errors such as cross origin issue, or that I'm trying to use an unknown format.\n \N 0 +4 2019-09-25 17:23:31 0 0 sadsa asdsad 0 +5 2019-09-25 17:23:36 0 0 asdasd asqdas 0 +6 2019-09-26 17:21:03 0 0 1234 12345 images/Screenshot from 2019-07-04 14-41-52.png 0 +\. + + +-- +-- Data for Name: question_tag; Type: TABLE DATA; Schema: public; Owner: tw2 +-- + +COPY public.question_tag (question_id, tag_id) FROM stdin; +0 1 +1 3 +2 3 +\. + + +-- +-- Data for Name: tag; Type: TABLE DATA; Schema: public; Owner: tw2 +-- + +COPY public.tag (id, name) FROM stdin; +1 python +2 sql +3 css +\. + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.users (id, name, password, reg_date, reputation) FROM stdin; +0 Admin admin 2019-10-07 00:00:00 0 +1 user user 2019-10-07 00:00:00 0 +\. + + +-- +-- Name: answer_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tw2 +-- + +SELECT pg_catalog.setval('public.answer_id_seq', 14, true); + + +-- +-- Name: comment_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tw2 +-- + +SELECT pg_catalog.setval('public.comment_id_seq', 89, true); + + +-- +-- Name: question_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tw2 +-- + +SELECT pg_catalog.setval('public.question_id_seq', 6, true); + + +-- +-- Name: tag_id_seq; Type: SEQUENCE SET; Schema: public; Owner: tw2 +-- + +SELECT pg_catalog.setval('public.tag_id_seq', 3, true); + + +-- +-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.users_id_seq', 4, true); + + +-- +-- Name: answer pk_answer_id; Type: CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.answer + ADD CONSTRAINT pk_answer_id PRIMARY KEY (id); + + +-- +-- Name: comment pk_comment_id; Type: CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.comment + ADD CONSTRAINT pk_comment_id PRIMARY KEY (id); + + +-- +-- Name: question pk_question_id; Type: CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.question + ADD CONSTRAINT pk_question_id PRIMARY KEY (id); + + +-- +-- Name: question_tag pk_question_tag_id; Type: CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.question_tag + ADD CONSTRAINT pk_question_tag_id PRIMARY KEY (question_id, tag_id); + + +-- +-- Name: tag pk_tag_id; Type: CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.tag + ADD CONSTRAINT pk_tag_id PRIMARY KEY (id); + + +-- +-- Name: users users_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_name_key UNIQUE (name); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: comment fk_answer_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.comment + ADD CONSTRAINT fk_answer_id FOREIGN KEY (answer_id) REFERENCES public.answer(id); + + +-- +-- Name: answer fk_question_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.answer + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES public.question(id); + + +-- +-- Name: question_tag fk_question_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.question_tag + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES public.question(id); + + +-- +-- Name: comment fk_question_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.comment + ADD CONSTRAINT fk_question_id FOREIGN KEY (question_id) REFERENCES public.question(id); + + +-- +-- Name: question_tag fk_tag_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.question_tag + ADD CONSTRAINT fk_tag_id FOREIGN KEY (tag_id) REFERENCES public.tag(id); + + +-- +-- Name: answer fk_user_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.answer + ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- Name: comment fk_user_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.comment + ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- Name: question fk_user_id; Type: FK CONSTRAINT; Schema: public; Owner: tw2 +-- + +ALTER TABLE ONLY public.question + ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- PostgreSQL database dump complete +-- +