diff --git a/connection.py b/connection.py new file mode 100644 index 000000000..abf636761 --- /dev/null +++ b/connection.py @@ -0,0 +1,74 @@ +import csv +import os +import psycopg2 +import psycopg2.extras + + +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 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_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()]) + + +# 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/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)", diff --git a/data_handler.py b/data_handler.py new file mode 100644 index 000000000..3392cb528 --- /dev/null +++ b/data_handler.py @@ -0,0 +1,228 @@ +from datetime import datetime +from psycopg2 import sql + +import connection +from util import string_builder, create_check_keywords_in_database_string, escape_single_quotes +from flask import request + + +def add_entry(entry, is_answer=False): + table = "answer" + if not is_answer: + table = "question" + + query = """INSERT INTO {table} + ({columns}) VALUES ({values}); + """.format(columns=string_builder(entry.keys()), + values=string_builder(entry.values(), False), + table=table) + execute_query(query) + + +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_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): + answers_query = f"""SELECT * FROM answer + WHERE question_id={int(question_id)} + ORDER BY submission_time DESC;""" + answers_of_question = execute_query(answers_query) + return answers_of_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['message'] + "'"}, + image={"'" + record['image'] + "'"} + WHERE id={id_}; + """ + + execute_query(query) + + +def delete_record(id, answer=False, delete=False): + if answer: + 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): + if query.startswith("SELECT"): + cursor.execute( + sql.SQL(query) + ) + result = cursor.fetchall() + + else: + result = cursor.execute(query) + return result + + +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) + + +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} ORDER BY submission_time DESC + """.format(col=comment_tpe, id=_id) + return execute_query(query) + + +def handle_edit_comment(id, msg): + query = """UPDATE comment + SET message = {msg}, + edited_count = COALESCE (edited_count, 0) +1 + WHERE id = {id} + """.format(id=id, msg=("'" + msg["message"].replace("'", "''")) + "'") + 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)) + + +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) + + +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/requirements.txt b/requirements.txt new file mode 100644 index 000000000..96529486f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,86 @@ +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/sample_data/askmatepart2-sample-data.sql b/sample_data/askmatepart2-sample-data.sql index a51c461b8..369355a65 100644 --- a/sample_data/askmatepart2-sample-data.sql +++ b/sample_data/askmatepart2-sample-data.sql @@ -17,6 +17,7 @@ ALTER TABLE IF EXISTS ONLY public.tag DROP CONSTRAINT IF EXISTS pk_tag_id CASCAD 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, @@ -28,6 +29,7 @@ CREATE TABLE question ( ); 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, @@ -38,6 +40,7 @@ CREATE TABLE answer ( ); DROP TABLE IF EXISTS public.comment; +DROP SEQUENCE IF EXISTS public.comment_id_seq; CREATE TABLE comment ( id serial NOT NULL, question_id integer, @@ -55,6 +58,7 @@ CREATE TABLE question_tag ( ); DROP TABLE IF EXISTS public.tag; +DROP SEQUENCE IF EXISTS public.tag_id_seq; CREATE TABLE tag ( id serial NOT NULL, name text 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 +-- + diff --git a/server.py b/server.py new file mode 100644 index 000000000..015967d33 --- /dev/null +++ b/server.py @@ -0,0 +1,227 @@ +import os +from datetime import datetime +from flask import Flask, render_template, request, redirect, url_for +import data_handler +import util + + +app = Flask(__name__) +app.debug = True + + +@app.route('/') +@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. + Convert order_direction from boolean to string. It is needed for user interface (html). + :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 + questions = data_handler.order_questions('submission_time', 'DESC', True) + else: + is_main = False + questions = data_handler.order_questions(order_by, order_direction, 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"]) +def add_question(): + if request.method == 'POST': + req = request.form.to_dict() + util.handle_add_question(req) + return redirect(url_for("list_questions")) + + return render_template("edit-question.html", qid="") + + +@app.route("/question//new-answer", methods=["GET", "POST"]) +def add_answer(question_id): + if request.method == 'POST': + req = request.form.to_dict() + util.handle_add_answer(req) + return redirect("/question/" + question_id) + + return render_template("add-answer.html", qid=question_id) + + +@app.route('/question/') +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 = data_handler.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, + question_related_tags=question_related_tags) + + +@app.route("/question//vote-up") +def vote_up_question(question_id): + data_handler.vote_question(question_id, 1) + + return redirect("/question/" + question_id) + + +@app.route("/question//vote-down") +def vote_down_question(question_id): + data_handler.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() + data_handler.vote_answer(req["id"], req["vote"]) + 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': + if request.form.get('delete') == 'Yes': + 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) + + +@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['id'] = int(edited_question_data['id']) + edited_question_data['submission_time'] = str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')) + util.handle_edit_entry(edited_question_data) + + return redirect("/question/" + str(question_id)) + + question = data_handler.get_question(question_id)[0] + + return render_template('edit-question.html', question=question) + + +@app.route('/answer//delete', methods=['GET', 'POST']) +def delete_answer(answer_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/' + str(question_id)) + else: + return render_template('asking_if_delete_answer.html', answer_id=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 = 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) + + +@app.route("/upload", methods=["POST"]) +def upload_image(): + image = request.files["image"] + image.save(os.path.join(os.getcwd() + "/images/", image.filename)) + + 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" + ref_question_id = request.args.get("qid") + if "answer" in str(request.url_rule): + comment_type = "answer" + if request.method == 'POST': + req = request.form.to_dict() + ref_question_id = req["qid"] + del req["qid"] + data_handler.handle_add_comment(req) + 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("/question//new-tag", methods=["GET", "POST"]) +def tag_question(id): + existing_tags = data_handler.get_existing_tags() + if request.method == 'POST': + if request.form.get('selected_tag_name'): + data_handler.tag_question_when_user_choose_from_existing_tags(id) + elif request.form.get('add_new_tag'): + data_handler.tag_question_when_user_enter_new_tag(id) + + return redirect("/question/" + id) + + return render_template("tag-question.html", qid=id, existing_tags=existing_tags) + + +@app.route('/answer//edit', 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) + + +@app.route("/comments//edit", methods=["GET", "POST"]) +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" + if request.method == 'POST': + req = request.form.to_dict() + question_id = req["qid"] + del req["qid"] + 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) + + +@app.route("/comments//delete", methods=["GET"]) +def delete_comment(comment_id): + question_id = request.args.get("qid") + data_handler.delete_comment(comment_id) + return redirect("/question/" + str(question_id)) + + +if __name__ == '__main__': + app.run(debug=True) + diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 000000000..5e6469d33 --- /dev/null +++ b/static/css/main.css @@ -0,0 +1,305 @@ +*{ + margin: 0; + padding: 0; +} + +body{ + /*background-color: #484848;*/ +} + +table.center { + margin-left:auto; + margin-right:auto; + 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; +} + +.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; + width: 100%; + min-height: 10%; + text-align: center; + height: 10%; + background-color: #e8e8bcfa; + 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; +} + +.question_display{ + align-content: center; + padding-bottom: 50px; + padding-left: 5%; + position: absolute; + height: auto; + + +} + +.question_detail{ + 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; +} + + +.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%;*/ + 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; + min-height: 150px; +} + +.answer select { + border-radius: 25px; + padding: 5px; + background: transparent; + border: 2px solid; + color: #e26901; + /*background-color: #e26901;*/ +} +} + +.rowHeight tr{ + height: 4rem; +} + +.control_buttons{ + /*border: 1px solid black;*/ + /*width: 100%;*/ + align-content: center; + text-align: center; +} + + +.question_title{ + margin-top: 25px; + text-align: center; + width: 88.5%; + text-align: center; + background-color: #ecc0578c; + margin-left: 6%; + border-top: 1px solid; + border-top-left-radius: 75px; + 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: 93%; + margin: auto; + /*position: relative;*/ + /*padding: auto;*/ + margin-top: 25px; +} + + +.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; + margin-top: 5px; +} + +.control_buttons input:hover { + background: orange; + border-color: orange; + 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; +} + +.comment{ + position: relative; + width: 90%; + background-color: #fdff4f; + margin: auto; + margin-top: -15px; + border-radius: 15px 15px 15px 15px; + /*margin-bottom: 66px;*/ + min-height: 0; +} + +.pt15{ + margin-top: 15px; +} + +.tags{ + text-align: center; + font-style: italic; + color: deepskyblue; + +} + +.w100p{ + width: 100%; +} + +.padLeft2p{ + padding-left: 2%; +} + + +.padTop2p{ + 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; +} + +.bold{ + font-weight: bold; +} + +/*td{*/ +/* border: 1px solid black;*/ +/*}*/ 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 diff --git a/static/images/image1.png b/static/images/image1.png new file mode 100644 index 000000000..b8b2b3116 Binary files /dev/null and b/static/images/image1.png differ diff --git a/templates/add-answer.html b/templates/add-answer.html new file mode 100644 index 000000000..165feafe7 --- /dev/null +++ b/templates/add-answer.html @@ -0,0 +1,28 @@ + + + + + {{"Edit answer" if answer else "Answer question" }} + + +{% include "banner.html" %} +{#
#} +
+

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

+
+
+ + {% if answer %} + + {% endif %} +
+
+ + +
+
+ +
+
+ + \ No newline at end of file diff --git a/templates/add-comment.html b/templates/add-comment.html new file mode 100644 index 000000000..19cb58b74 --- /dev/null +++ b/templates/add-comment.html @@ -0,0 +1,25 @@ + + + + + Title + + +{% include "banner.html" %} +{#
#} +
+

Comment

+
+
+ +
+
+ +
+ + +{# {{ "" if message else ""}}#} +
+
+ + \ No newline at end of file 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 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 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 new file mode 100644 index 000000000..3e465ebf7 --- /dev/null +++ b/templates/display_question.html @@ -0,0 +1,307 @@ + +{% 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 + + +{% include "banner.html" %} +

{{ question.title }}

+
+
+ + + + {% for header in headers %} + {% if header == "Vote" %} + + {% else %} + + {% endif %} + {% endfor %} + + + + + + + {% for key, width, align in keys %} + {% if key == 'submission_time' %} + + {% elif key == 'image' %} + + {% else %} + + {% endif %} + {% endfor %} + + +
{{ header }}{{ header }}
+
+ + +
+
+ + +
+
{{ question.get(key) }} + {% if question.get(key) != "" %} + 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 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 %} +
+ + +
+ + + diff --git a/templates/edit-question.html b/templates/edit-question.html new file mode 100644 index 000000000..766c9826a --- /dev/null +++ b/templates/edit-question.html @@ -0,0 +1,30 @@ + + + + + {{"Edit question" if question else "Ask your Question" }} + + + {% include "banner.html" %} +{#
#} +
+

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

+
+
+ +
+ + +
+
+ +
+ +
+
+
+ + +
+ + \ No newline at end of file diff --git a/templates/list.html b/templates/list.html new file mode 100644 index 000000000..047973b9b --- /dev/null +++ b/templates/list.html @@ -0,0 +1,75 @@ + + + + + Welcome! | Ask Mate + + + {% 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 %} + +

+ +

+ + + +
+

+ {% endif %} + + + + + + + + + + + + {% for header in fieldnames %} + + {% endfor %} + + {% for questions in sorted_questions %} + + {% for cell in fieldnames %} + {% if cell == 'submission_time' %} + + {% elif cell == 'title' %} + + {% elif cell == 'image' %} + + {% else %} + + {% endif %} + {% endfor %} + {% endfor %} + +
{{ header|capitalize|replace('_', ' ') }}
{{ questions[cell] }} + {{ questions[cell] }} + + {% if questions[cell] %} + + {% endif %} + {{ questions[cell] }}
+
+ + \ No newline at end of file diff --git a/templates/search.html b/templates/search.html new file mode 100644 index 000000000..a0909de47 --- /dev/null +++ b/templates/search.html @@ -0,0 +1,16 @@ + diff --git a/templates/search_for_keywords_in_questions.html b/templates/search_for_keywords_in_questions.html new file mode 100644 index 000000000..0871d4547 --- /dev/null +++ b/templates/search_for_keywords_in_questions.html @@ -0,0 +1,45 @@ + + + + + Title + + + {% 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 %} + + {% endfor %} + + {% for question in questions %} + + {% for cell in fieldnames %} + {% if cell == 'submission_time' %} + + {% elif cell == 'image' and question.get(cell) %} + + {% else %} + + {% endif %} + {% endfor %} + {% endfor %} + +
{{ cell|capitalize|replace('_', ' ') }}
{{ question[cell] }} + Picture + {{ question[cell] }}
+
+ + \ No newline at end of file diff --git a/templates/tag-question.html b/templates/tag-question.html new file mode 100644 index 000000000..9e3bf32cf --- /dev/null +++ b/templates/tag-question.html @@ -0,0 +1,41 @@ + + + + + Title + + + {% include "banner.html" %} +
+{#
#} + +

Add tag

+
+
+
+
+ +
+ + +
+
+
+ +
+
+
+ +
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/util.py b/util.py new file mode 100644 index 000000000..1333dd314 --- /dev/null +++ b/util.py @@ -0,0 +1,80 @@ +import os +from datetime import datetime +from flask import request +import data_handler + + +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 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)) + else: + req["image"] = "" + + +def generate_question_dict(data): + question_data = {} + 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"]) + 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(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"]) + answer_data.update(image=data["image"]) + return answer_data + + +def handle_add_answer(reqv): + handle_upload(reqv) + answer = generate_answer_dict(reqv) + data_handler.add_entry(answer, True) + + +def handle_add_question(req): + handle_upload(req) + question = generate_question_dict(req) + data_handler.add_entry(question) + + +def handle_edit_entry(req, is_answer=False): + handle_upload(req) + data_handler.update_record(req, is_answer) + + +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 + + +def string_builder(lst, is_key=True): + result = "" + for element in lst: + escaped_element = element.replace("'", "''") + if is_key: + result += "" + escaped_element + ", " + else: + result += "\'" + escaped_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 \ No newline at end of file