From 775f324e40d4caa1ed44b075643a897cd3122934 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Wed, 26 Oct 2022 23:48:48 -0500 Subject: [PATCH 01/28] Updated write_reviews and database --- src/backend/database/database.db | Bin 32768 -> 32768 bytes src/backend/database/schema.sql | 2 +- src/backend/mainpage.py | 30 +++++++++++++++--------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/backend/database/database.db b/src/backend/database/database.db index cc50ec21094db653a791ab47fafd93b8147a0071..683721f2a0197bdbbe53f00083063c1c65de598e 100644 GIT binary patch delta 260 zcmZo@U}|V!njkHhz`(%30mY0!+GwJVv19^+o_`H5Zz2O5k2C|{UVeVwOT3vp(!55S z6$L7|Cx779=4ujQV;7f}W^AsXoXj(oT_M!ZGceS3@(-Rub6s{eaYt!JhRnQ_)QaN5 zoXnEc_{7qZd@w&gD77pzwY)gq5XxYOTF$UJlXoH0=EV5p{F5)r8}I^!8Tr34@PFgK z&VL;!yn|nq|0#nYP#6VhPIQzO;9z7H=L{`QEh+|SV-Nr;|HVJafn#%W{0}(I#s!mO L71+#O@Rc6`@=8J0 delta 435 zcmZo@U}|V!njkG0&cMLH0mV!}T5FV*I_aF_4jQ<;2FNlN>mN`0s$>6n;1a`lptkF=t|Qac$pY~Y;2TeWZ|FApg*xuk8#Cj!GvG@6DMqBWZcYA@Pi)!TD@s0 diff --git a/src/backend/database/schema.sql b/src/backend/database/schema.sql index 2145dbf2a..2628cc32c 100644 --- a/src/backend/database/schema.sql +++ b/src/backend/database/schema.sql @@ -28,7 +28,7 @@ CREATE TABLE AptPics ( CREATE TABLE Reviews ( rating_id INTEGER PRIMARY KEY AUTOINCREMENT, apt_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, + user_id INTEGER NOT NULL UNIQUE, date_of_rating DATE NOT NULL, comment TEXT, vote INTEGER CHECK(vote = 1 OR vote = 0 OR vote = -1) diff --git a/src/backend/mainpage.py b/src/backend/mainpage.py index 314f6a828..5ae54c637 100644 --- a/src/backend/mainpage.py +++ b/src/backend/mainpage.py @@ -159,27 +159,24 @@ def get_apartments_pictures(self, apt_id: int) -> List[str]: def write_apartment_review( self, apt_id: int, username: str, comment: str, vote: int - ) -> bool: + ) -> List[Review]: """Write a new review for apartment""" connection = sqlite3.connect("database/database.db") cursor = connection.cursor() user_id = cursor.execute( "SELECT user_id FROM Users WHERE username = ?", (username,) ).fetchone()[0] - current_review = cursor.execute( - "SELECT user_id FROM Reviews WHERE user_id = ?", (user_id,) - ).fetchone() - if current_review is None: - cursor.execute( - "INSERT INTO Reviews (apt_id, user_id, date_of_rating, comment, vote) \ - VALUES (?, ?, date(), ?, ?)", - (apt_id, user_id, comment, vote), - ) - connection.commit() - connection.close() - return True + cursor.execute( + "INSERT INTO Reviews (apt_id, user_id, date_of_rating, comment, vote) \ + VALUES (?, ?, date(), ?, ?)", + (apt_id, user_id, comment, vote), + ) + connection.commit() connection.close() - return False + new_reviews = self.get_apartments_reviews(apt_id) + new_review_ind = [i for i, x in enumerate(new_reviews) if x.username == username][0] + new_reviews.insert(0, new_reviews.pop(new_review_ind)) + return new_reviews def get_apartments_reviews(self, apt_id: int) -> List[Review]: """Returns a list of apartment reviews""" @@ -187,7 +184,10 @@ def get_apartments_reviews(self, apt_id: int) -> List[Review]: cursor = connection.cursor() ratings_query = cursor.execute( "SELECT Users.username, Reviews.date_of_rating, Reviews.comment, Reviews.vote \ - FROM Users, Reviews WHERE Users.user_id = Reviews.user_id AND Reviews.apt_id = ?", + FROM Users INNER JOIN Reviews \ + ON Users.user_id = Reviews.user_id \ + WHERE Reviews.apt_id = ? \ + ORDER BY Reviews.date_of_rating DESC", (apt_id,), ).fetchall() reviews = [] From 64ce3875ef22ecf3270be5dbc1c535c7f3c53f38 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Wed, 26 Oct 2022 23:55:39 -0500 Subject: [PATCH 02/28] Fixed db again --- src/backend/database/schema.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/database/schema.sql b/src/backend/database/schema.sql index 2628cc32c..ab8861de8 100644 --- a/src/backend/database/schema.sql +++ b/src/backend/database/schema.sql @@ -28,10 +28,11 @@ CREATE TABLE AptPics ( CREATE TABLE Reviews ( rating_id INTEGER PRIMARY KEY AUTOINCREMENT, apt_id INTEGER NOT NULL, - user_id INTEGER NOT NULL UNIQUE, + user_id INTEGER NOT NULL, date_of_rating DATE NOT NULL, comment TEXT, - vote INTEGER CHECK(vote = 1 OR vote = 0 OR vote = -1) + vote INTEGER CHECK(vote = 1 OR vote = 0 OR vote = -1), + UNIQUE(apt_id, user_id) ); -- TEST From b16fe16998ec710de78fe2c8fde072adb9f610e9 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Wed, 26 Oct 2022 23:59:44 -0500 Subject: [PATCH 03/28] Fixed write review --- src/backend/mainpage.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/mainpage.py b/src/backend/mainpage.py index 5ae54c637..33f20f3c3 100644 --- a/src/backend/mainpage.py +++ b/src/backend/mainpage.py @@ -168,7 +168,11 @@ def write_apartment_review( ).fetchone()[0] cursor.execute( "INSERT INTO Reviews (apt_id, user_id, date_of_rating, comment, vote) \ - VALUES (?, ?, date(), ?, ?)", + VALUES (?, ?, date(), ?, ?) \ + ON CONFLICT DO UPDATE SET \ + date_of_rating = excluded.date_of_rating, \ + comment = excluded.comment \ + vote = excluded.vote", (apt_id, user_id, comment, vote), ) connection.commit() From d035555c5e0fe49f3e2567cec57fbd41967d773d Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Thu, 27 Oct 2022 00:22:16 -0500 Subject: [PATCH 04/28] Fully implemented write_review --- src/backend/database/database.db | Bin 32768 -> 32768 bytes src/backend/mainpage.py | 15 +++++---- src/backend/tests/test_mainpage.py | 51 +++++++++-------------------- 3 files changed, 25 insertions(+), 41 deletions(-) diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 683721f2a0197bdbbe53f00083063c1c65de598e..5d66deaa87409e8fa6cdf0db5b139d777bae39bb 100644 GIT binary patch delta 365 zcmZo@U}|V!njj@)Wx~L~zyZaKKssomj*%2lRL{SLmp74tjfa(iZ!bST?<|Wi*hw-v9XIwOEb1;PgdobI{6?^zKjl+0uY4yc?O2MY9tnv#Al}HD3lhb z76BQX^?7eIaqxFQAkdgfen!uUjjod~${X<|Wi%y&%#?>Uk#x5=`&DdN&nV)AWyF#d+XJDx7MxfIL<%$ zqPzhw&|F6TZw&n3_^ List[Review]: ).fetchall() reviews = [] for entry in ratings_query: - vote = False - if entry[3] == 1: - vote = True - reviews.append(Review(entry[0], entry[1], entry[2], vote)) + if entry[0] is not None: + vote = False + if entry[3] == 1: + vote = True + reviews.append(Review(entry[0], entry[1], entry[2], vote)) cursor.close() return reviews diff --git a/src/backend/tests/test_mainpage.py b/src/backend/tests/test_mainpage.py index e6aab86c9..94c0aed12 100644 --- a/src/backend/tests/test_mainpage.py +++ b/src/backend/tests/test_mainpage.py @@ -1,5 +1,6 @@ """Test mainpage.py""" import sqlite3 +from datetime import date from mainpage import MainPage from apt import Apt from review import Review @@ -269,38 +270,36 @@ def test_write_apartment_review(self): sample_comment = "Bruh this really sucks" connection = sqlite3.connect("database/database.db") cursor = connection.cursor() - fig_binger_id = cursor.execute( - "SELECT user_id FROM Users WHERE (username = 'Fig_binger')" - ).fetchone()[0] sherman_id = cursor.execute( "SELECT apt_id FROM Apartments WHERE (apt_name = 'Sherman')" ).fetchone()[0] + today = date.today().strftime("%Y-%m-%d") connection.close() - can_write = self.main_page.write_apartment_review( + write_result = self.main_page.write_apartment_review( sherman_id, "Fig_binger", sample_comment, -1 ) - - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - find_review = cursor.execute( - "SELECT * FROM Reviews WHERE user_id = ?", (fig_binger_id,) - ).fetchall() - cursor.execute("DELETE FROM Reviews WHERE apt_id = ?", (sherman_id,)) - connection.close() + sample_apts_review = [] + sample_apts_review.append(Review("Fig_binger", today, sample_comment, False)) + sample_apts_review.append(Review("Big_finger", "2022-10-09", "Decent", True)) + sample_apts_review.append( + Review("Minh", "2022-10-08", "Bruh this sucks", False) + ) + sample_apts_review.append( + Review("Minh Phan", "2022-10-07", "Pretty good", True) + ) self.main_page_stage.clean_all() - assert can_write - assert find_review is not None + assert write_result == sample_apts_review def test_get_apartments_reviews(self): """Test get_apartments_reviews()""" sample_apts_review = [] + sample_apts_review.append(Review("Big_finger", "2022-10-09", "Decent", True)) sample_apts_review.append( - Review("Minh Phan", "2022-10-07", "Pretty good", True) + Review("Minh", "2022-10-08", "Bruh this sucks", False) ) sample_apts_review.append( - Review("Minh", "2022-10-08", "Bruh this sucks", False) + Review("Minh Phan", "2022-10-07", "Pretty good", True) ) - sample_apts_review.append(Review("Big_finger", "2022-10-09", "Decent", True)) self.main_page_stage.initialize_all() @@ -344,24 +343,6 @@ def test_get_apartments_pictures_invalid(self): self.main_page_stage.clean_all() assert sample_apts_picture == res - def test_write_apartment_review_invalid(self): - """Test write review while having existing review""" - self.main_page_stage.initialize_all() - - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - sample_comment = "Bruh this really sucks" - sherman_id = cursor.execute( - "SELECT apt_id FROM Apartments WHERE (apt_name = 'Sherman')" - ).fetchone()[0] - connection.close() - can_write = self.main_page.write_apartment_review( - sherman_id, "Big_finger", sample_comment, -1 - ) - self.main_page_stage.clean_all() - - assert not can_write - def test_get_apartments_reviews_empty(self): """Test get reviews of invalid apartments""" sample_apts_review = [] From f646cf0261dfef3085c5fa09d0d490f11ba5102d Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Thu, 27 Oct 2022 00:27:21 -0500 Subject: [PATCH 05/28] Fix database --- src/backend/database/database.db | Bin 32768 -> 32768 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 5d66deaa87409e8fa6cdf0db5b139d777bae39bb..a52ecf3ffd79b706c4571892bdb0a79203484c8f 100644 GIT binary patch delta 256 zcmZo@U}|V!njkH>i-CcG1Bw}ebksy0W5!(@6IS!H_wa+^W*dAmWg8%~qe+&$6Ruu5%XIwI|als@9PS*SUK)6}4;0iyZ;zUPT zpb&RDKMYy})vEKe@GoG{nb@e!ICEm-)JX}P+&B2)5a_%;{5&8 zPZ^l`k1_Cn;XlTIeY2p$4t^hgCT2w@$AZM7lHAn1l46GIJj~3}OhKt-nW^Q)47Y)% z@&hppa4|BAGldqX78Nt>=LHE00OiABaI>O-2S4M=iH%DqIdHPx;RiyXB^UV_RVFqn z0EM_C_+d~Js8*Dpg?~DO{=`N-#uXDAmrP3F Date: Thu, 27 Oct 2022 00:34:03 -0500 Subject: [PATCH 06/28] Fix database --- src/backend/database/database.db | Bin 32768 -> 28672 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/backend/database/database.db b/src/backend/database/database.db index a52ecf3ffd79b706c4571892bdb0a79203484c8f..2bf4bf0f83e4a83032bb061a20bc89740b27fe55 100644 GIT binary patch delta 172 zcmZo@U}|{4I6+E?ft7)QfgOlpfMueN5lB?ezlN7Lk%5hem4Tn1Z|}x}2JVRyM46kk zST}R3e@s5x=w6# zoP1H8$mm#*SX7dm znpaZn#>vPm&KO#pT2yS$3(_XQz`!2^gFq8~`5Bi?Y+NwOfrE|zJ_v3WRJg*=s5sG4 z7AVHUpALuCK-KE}Ec^=?bS5@xGtQjYICWA2P{j=d1ghA>&%^%$98@6NG$uCc3$g Date: Thu, 27 Oct 2022 00:36:14 -0500 Subject: [PATCH 07/28] Remove db --- src/backend/database/database.db | Bin 28672 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/backend/database/database.db diff --git a/src/backend/database/database.db b/src/backend/database/database.db deleted file mode 100644 index 2bf4bf0f83e4a83032bb061a20bc89740b27fe55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI&&raJg90za*XbTlxcgvxNnFCTYC;~QinI@5S(WodbWa$K_$ZHKmYm<_MKZjiy zd#ydh_As31Bu+=Vt>dx--Pfuc*N$udK6Vb4^6sGNgq$9G{(yz_8F@?;g=|ww2+4_D z5IIeAq7daYDQRKCHAmLkpGqZ0@{2iAE!zVw2DS9pO@qoxS}jtMd=@(HiS6`gqh%Ry z4U_JhjXm8wqPxZs)jL+Z(GuPFjFz<;cVNTt?%l1nMO&R_Gif{zxc}$Y9t*kc9oxy6 z^i4FUZ2zt|7;rbFmT@SDyYR%>CY@S+$EfdC~H6N|r$y`i)y&Mzk!*Ji} z2J({25X(wQoJ?R8L^k5S<9>|89Z3|m>(kkSy0oO+ROCp^4Rnf`H=)RH?R zo#fx1favxKTr_@6hAap!J-;`?dhvXmlnF^^76vrFKJuUtchF|SrfS?a&rE^CFR?P z5+LRY0SG_<0uX=z1Rwwb2tWV=5P-n_1m=}lbs>I Date: Thu, 27 Oct 2022 00:36:53 -0500 Subject: [PATCH 08/28] Readd db --- src/backend/database/database.db | Bin 0 -> 28672 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/backend/database/database.db diff --git a/src/backend/database/database.db b/src/backend/database/database.db new file mode 100644 index 0000000000000000000000000000000000000000..2bf4bf0f83e4a83032bb061a20bc89740b27fe55 GIT binary patch literal 28672 zcmeI&&raJg90za*XbTlxcgvxNnFCTYC;~QinI@5S(WodbWa$K_$ZHKmYm<_MKZjiy zd#ydh_As31Bu+=Vt>dx--Pfuc*N$udK6Vb4^6sGNgq$9G{(yz_8F@?;g=|ww2+4_D z5IIeAq7daYDQRKCHAmLkpGqZ0@{2iAE!zVw2DS9pO@qoxS}jtMd=@(HiS6`gqh%Ry z4U_JhjXm8wqPxZs)jL+Z(GuPFjFz<;cVNTt?%l1nMO&R_Gif{zxc}$Y9t*kc9oxy6 z^i4FUZ2zt|7;rbFmT@SDyYR%>CY@S+$EfdC~H6N|r$y`i)y&Mzk!*Ji} z2J({25X(wQoJ?R8L^k5S<9>|89Z3|m>(kkSy0oO+ROCp^4Rnf`H=)RH?R zo#fx1favxKTr_@6hAap!J-;`?dhvXmlnF^^76vrFKJuUtchF|SrfS?a&rE^CFR?P z5+LRY0SG_<0uX=z1Rwwb2tWV=5P-n_1m=}lbs>I Date: Thu, 27 Oct 2022 00:45:25 -0500 Subject: [PATCH 09/28] Fix ubuntu version in actions? --- .github/workflows/pytest-coverage.yml | 2 +- .github/workflows/python-app.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-coverage.yml b/.github/workflows/pytest-coverage.yml index 7a4de4fff..1d59c7aa2 100644 --- a/.github/workflows/pytest-coverage.yml +++ b/.github/workflows/pytest-coverage.yml @@ -2,7 +2,7 @@ name: Pytest Coverage Calculation on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: python-version: ["3.8", "3.9", "3.10"] diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index cbcbb35e5..633cf223e 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -15,7 +15,7 @@ permissions: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 From 7a526681f5f30d4bff7645bd82b9827bb571b855 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Thu, 27 Oct 2022 00:51:24 -0500 Subject: [PATCH 10/28] Manually update sqlite on github runner --- .github/workflows/pytest-coverage.yml | 12 +++++++++++- .github/workflows/python-app.yml | 13 ++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-coverage.yml b/.github/workflows/pytest-coverage.yml index 1d59c7aa2..7aeebb8e3 100644 --- a/.github/workflows/pytest-coverage.yml +++ b/.github/workflows/pytest-coverage.yml @@ -2,13 +2,23 @@ name: Pytest Coverage Calculation on: [push, pull_request] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: python-version: ["3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v3 + - run: wget https://sqlite.org/2022/sqlite-autoconf-3380500.tar.gz + - run: tar -xvf sqlite-autoconf-3380500.tar.gz + - run: ./configure + working-directory: sqlite-autoconf-3380500 + - run: make + working-directory: sqlite-autoconf-3380500 + - run: sudo make install + working-directory: sqlite-autoconf-3380500 + - run: export PATH="/usr/local/lib:$PATH" + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 633cf223e..a90675284 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -15,10 +15,21 @@ permissions: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + + - run: wget https://sqlite.org/2022/sqlite-autoconf-3380500.tar.gz + - run: tar -xvf sqlite-autoconf-3380500.tar.gz + - run: ./configure + working-directory: sqlite-autoconf-3380500 + - run: make + working-directory: sqlite-autoconf-3380500 + - run: sudo make install + working-directory: sqlite-autoconf-3380500 + - run: export PATH="/usr/local/lib:$PATH" + - name: Set up Python 3.10 uses: actions/setup-python@v3 with: From f0f444c32708c30085d6b09620e12575e394a40c Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Thu, 27 Oct 2022 00:56:33 -0500 Subject: [PATCH 11/28] Actually fix the github runner probably --- .github/workflows/pytest-coverage.yml | 2 ++ .github/workflows/python-app.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/pytest-coverage.yml b/.github/workflows/pytest-coverage.yml index 7aeebb8e3..436d11154 100644 --- a/.github/workflows/pytest-coverage.yml +++ b/.github/workflows/pytest-coverage.yml @@ -34,6 +34,8 @@ jobs: - name: Build coverage file run: | pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=. | tee pytest-coverage.txt + env: + LD_LIBRARY_PATH: /usr/local/lib working-directory: src/backend - name: Pytest coverage comment diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index a90675284..033c740b3 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -50,4 +50,6 @@ jobs: - name: Test with pytest run: | pytest + env: + LD_LIBRARY_PATH: /usr/local/lib working-directory: src/backend From 7e39e10a2159c80e8bf6b728f711d2892bfbe191 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 16:57:11 -0500 Subject: [PATCH 12/28] Added mainpage handling --- src/backend/app.py | 49 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/backend/app.py b/src/backend/app.py index 9739cbe4e..c51ed8096 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -1,7 +1,9 @@ """ This is a module docstring """ +import json +import dataclasses from flask import Flask, request from login import Login - +from mainpage import MainPage # from logging import FileHandler, WARNING app = Flask(__name__) @@ -31,6 +33,51 @@ def register(): return result.message, 400 return result.message, 200 +@app.route("/", methods=["POST", "GET"]) +@app.route("/main", methods=["POST", "GET"]) +def mainpage(): + """Handle mainpage requests""" + mainpage = MainPage() + if request.method == "POST": + apt_id = request.json["apt_id"] + username = request.json["username"] + comment = request.json["comment"] + vote = request.json["vote"] + query_result = "" + if None not in (apt_id, username, comment, vote): + reviews = mainpage.write_apartment_review(apt_id, username, comment, vote) + reviews_dict = [dataclasses.asdict(review) for review in reviews] + query_result = json.dumps(reviews_dict) + return query_result, 201 if len(query_result) != 0 else query_result, 400 + + args = request.args + is_search = args.get("search") + is_populate = args.get("populate") + is_review = args.get("review") + is_pictures = args.get("pictures") + num_apts = args.get("numApts", type=int) + apt_id = args.get("aptId", default=0, type=int) + search_query = args.get("searchQuery", type=str) + price_sort = args.get("priceSort", type=int) + rating_sort = args.get("ratingSort", type=int) + query_result = "" + if is_search is not None and search_query is not None: + query_result = json.dumps(mainpage.search_apartments(search_query)) + elif is_populate is not None and num_apts is not None: + apts = [] + if price_sort is not None and rating_sort is not None: + apts = mainpage.apartments_sorted(num_apts, price_sort, rating_sort) + else: + apts = mainpage.apartments_default(num_apts) + apts_dict = [dataclasses.asdict(apt) for apt in apts] + query_result = json.dumps(apts_dict) + elif is_review is not None and apt_id is not None: + reviews = mainpage.get_apartments_reviews(apt_id) + reviews_dict = [dataclasses.asdict(review) for review in reviews] + query_result = json.dumps(reviews_dict) + elif is_pictures is not None and apt_id is not None: + query_result = json.dumps(mainpage.get_apartments_pictures(apt_id)) + return query_result, 200 if len(query_result) != 0 else query_result, 400 if __name__ == "__main__": app.run(debug=True) # pragma: no cover From 41d95b5b5dc124abb4dbe51a8fff9b7275d512ec Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 17:19:31 -0500 Subject: [PATCH 13/28] Split code to be more readable --- src/backend/app.py | 91 ++++++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/src/backend/app.py b/src/backend/app.py index c51ed8096..dd4094dae 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -1,9 +1,12 @@ """ This is a module docstring """ import json import dataclasses +from collections import namedtuple +from werkzeug.datastructures import MultiDict from flask import Flask, request from login import Login from mainpage import MainPage + # from logging import FileHandler, WARNING app = Flask(__name__) @@ -33,51 +36,77 @@ def register(): return result.message, 400 return result.message, 200 + @app.route("/", methods=["POST", "GET"]) @app.route("/main", methods=["POST", "GET"]) def mainpage(): """Handle mainpage requests""" - mainpage = MainPage() + mainpage_obj = MainPage() if request.method == "POST": - apt_id = request.json["apt_id"] - username = request.json["username"] - comment = request.json["comment"] - vote = request.json["vote"] - query_result = "" - if None not in (apt_id, username, comment, vote): - reviews = mainpage.write_apartment_review(apt_id, username, comment, vote) - reviews_dict = [dataclasses.asdict(review) for review in reviews] - query_result = json.dumps(reviews_dict) - return query_result, 201 if len(query_result) != 0 else query_result, 400 - + return mainpage_post(mainpage_obj) + args = request.args - is_search = args.get("search") - is_populate = args.get("populate") - is_review = args.get("review") - is_pictures = args.get("pictures") - num_apts = args.get("numApts", type=int) - apt_id = args.get("aptId", default=0, type=int) - search_query = args.get("searchQuery", type=str) - price_sort = args.get("priceSort", type=int) - rating_sort = args.get("ratingSort", type=int) + return mainpage_get(mainpage_obj, args) + + +def mainpage_get(mainpage_obj: MainPage, args: MultiDict): + """Helper for mainpage get requests""" + action_type = namedtuple( + "action_type", ["is_search", "is_populate", "is_review", "is_pictures"] + ) + action = action_type( + args.get("search"), + args.get("populate"), + args.get("review"), + args.get("pictures"), + ) + + params = namedtuple( + "params", ["num_apts", "apt_id", "search_query", "price_sort", "rating_sort"] + ) + param = params( + args.get("numApts", type=int), + args.get("aptId", default=0, type=int), + args.get("searchQuery", type=str), + args.get("priceSort", type=int), + args.get("ratingSort", type=int), + ) + query_result = "" - if is_search is not None and search_query is not None: - query_result = json.dumps(mainpage.search_apartments(search_query)) - elif is_populate is not None and num_apts is not None: + if action.is_search is not None and param.search_query is not None: + query_result = json.dumps(mainpage_obj.search_apartments(param.search_query)) + elif action.is_populate is not None and param.num_apts is not None: apts = [] - if price_sort is not None and rating_sort is not None: - apts = mainpage.apartments_sorted(num_apts, price_sort, rating_sort) + if param.price_sort is not None and param.rating_sort is not None: + apts = mainpage_obj.apartments_sorted( + param.num_apts, param.price_sort, param.rating_sort + ) else: - apts = mainpage.apartments_default(num_apts) + apts = mainpage_obj.apartments_default(param.num_apts) apts_dict = [dataclasses.asdict(apt) for apt in apts] query_result = json.dumps(apts_dict) - elif is_review is not None and apt_id is not None: - reviews = mainpage.get_apartments_reviews(apt_id) + elif action.is_review is not None and param.apt_id is not None: + reviews = mainpage_obj.get_apartments_reviews(param.apt_id) reviews_dict = [dataclasses.asdict(review) for review in reviews] query_result = json.dumps(reviews_dict) - elif is_pictures is not None and apt_id is not None: - query_result = json.dumps(mainpage.get_apartments_pictures(apt_id)) + elif action.is_pictures is not None and param.apt_id is not None: + query_result = json.dumps(mainpage_obj.get_apartments_pictures(param.apt_id)) return query_result, 200 if len(query_result) != 0 else query_result, 400 + +def mainpage_post(mainpage_obj: MainPage): + """Helper for mainpage post requests""" + apt_id = request.json["apt_id"] + username = request.json["username"] + comment = request.json["comment"] + vote = request.json["vote"] + query_result = "" + if None not in (apt_id, username, comment, vote): + reviews = mainpage_obj.write_apartment_review(apt_id, username, comment, vote) + reviews_dict = [dataclasses.asdict(review) for review in reviews] + query_result = json.dumps(reviews_dict) + return query_result, 201 if len(query_result) != 0 else query_result, 400 + + if __name__ == "__main__": app.run(debug=True) # pragma: no cover From cbe27eb5696cec6a04975b638352a3763efe3cc0 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 17:23:02 -0500 Subject: [PATCH 14/28] Fix aptId autocast --- src/backend/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/app.py b/src/backend/app.py index dd4094dae..4f89e5228 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -66,7 +66,7 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): ) param = params( args.get("numApts", type=int), - args.get("aptId", default=0, type=int), + args.get("aptId", type=int), args.get("searchQuery", type=str), args.get("priceSort", type=int), args.get("ratingSort", type=int), From 88c84458c956314caa797b79da92556aec9e1a20 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 17:26:35 -0500 Subject: [PATCH 15/28] Fix returns --- src/backend/app.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/backend/app.py b/src/backend/app.py index 4f89e5228..d091ee49f 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -75,6 +75,7 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): query_result = "" if action.is_search is not None and param.search_query is not None: query_result = json.dumps(mainpage_obj.search_apartments(param.search_query)) + elif action.is_populate is not None and param.num_apts is not None: apts = [] if param.price_sort is not None and param.rating_sort is not None: @@ -85,13 +86,18 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): apts = mainpage_obj.apartments_default(param.num_apts) apts_dict = [dataclasses.asdict(apt) for apt in apts] query_result = json.dumps(apts_dict) + elif action.is_review is not None and param.apt_id is not None: reviews = mainpage_obj.get_apartments_reviews(param.apt_id) reviews_dict = [dataclasses.asdict(review) for review in reviews] query_result = json.dumps(reviews_dict) + elif action.is_pictures is not None and param.apt_id is not None: query_result = json.dumps(mainpage_obj.get_apartments_pictures(param.apt_id)) - return query_result, 200 if len(query_result) != 0 else query_result, 400 + + if len(query_result) != 0: + return query_result, 200 + return "", 400 def mainpage_post(mainpage_obj: MainPage): @@ -100,12 +106,14 @@ def mainpage_post(mainpage_obj: MainPage): username = request.json["username"] comment = request.json["comment"] vote = request.json["vote"] - query_result = "" + if None not in (apt_id, username, comment, vote): + query_result = "" reviews = mainpage_obj.write_apartment_review(apt_id, username, comment, vote) reviews_dict = [dataclasses.asdict(review) for review in reviews] query_result = json.dumps(reviews_dict) - return query_result, 201 if len(query_result) != 0 else query_result, 400 + return query_result, 201 + return "", 400 if __name__ == "__main__": From 591e6535c368b51afcbeee5d991f08a18c803768 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 22:19:26 -0500 Subject: [PATCH 16/28] Tentative mainpage tests --- src/backend/app.py | 24 +++++++++--------- src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/mainpage.py | 6 +++-- src/backend/tests/test_app.py | 41 +++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/backend/app.py b/src/backend/app.py index d091ee49f..b819a4cf8 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -102,17 +102,19 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): def mainpage_post(mainpage_obj: MainPage): """Helper for mainpage post requests""" - apt_id = request.json["apt_id"] - username = request.json["username"] - comment = request.json["comment"] - vote = request.json["vote"] - - if None not in (apt_id, username, comment, vote): - query_result = "" - reviews = mainpage_obj.write_apartment_review(apt_id, username, comment, vote) - reviews_dict = [dataclasses.asdict(review) for review in reviews] - query_result = json.dumps(reviews_dict) - return query_result, 201 + json_form = request.get_json(force=True) + + if json_form is not None: + apt_id = json_form.get("apt_id") + username = json_form.get("username") + comment = json_form.get("comment") + vote = json_form.get("vote") + if None not in (apt_id, username, comment, vote): + query_result = "" + reviews = mainpage_obj.write_apartment_review(apt_id, username, comment, vote) + reviews_dict = [dataclasses.asdict(review) for review in reviews] + query_result = json.dumps(reviews_dict) + return query_result, 201 return "", 400 diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 2bf4bf0f83e4a83032bb061a20bc89740b27fe55..0acd29bfdaaa3973f7d70814bae2ef900d1dd181 100644 GIT binary patch delta 243 zcmZp8z}WDBae@>Ri|#}jCm^{oVHH0Ie+L9^78I!DXY`!d=sNkLya6x&R|aPOc__vU9}R%CK4NGvMJP0cGQW{Lo+;bLSKX9_J&Eh=UbnM`0s$=W+{NL_na5Rs8(?_u*CoEi|9lXbb@7*gnPp delta 106 zcmZp8z}WDBae@>R1M5T?Cm^{oVHN*oL5Jh~lP}5}a56A3F!Fz6;Qs~`I>$e8f}8*c jBeOVXXmM&$F)vV(K>#YDGs%Htv#`S%pc)ir0$daTj!huO diff --git a/src/backend/mainpage.py b/src/backend/mainpage.py index 66df63033..32c6b583f 100644 --- a/src/backend/mainpage.py +++ b/src/backend/mainpage.py @@ -1,5 +1,6 @@ """Contains Main page class""" import sqlite3 +from datetime import date from typing import List from typing import Tuple from apt import Apt @@ -166,14 +167,15 @@ def write_apartment_review( user_id = cursor.execute( "SELECT user_id FROM Users WHERE username = ?", (username,) ).fetchone()[0] + today = date.today().strftime("%Y-%m-%d") cursor.execute( "INSERT INTO Reviews (apt_id, user_id, date_of_rating, comment, vote) \ - VALUES (?, ?, date(), ?, ?) \ + VALUES (?, ?, ?, ?, ?) \ ON CONFLICT DO UPDATE SET \ date_of_rating = excluded.date_of_rating, \ comment = excluded.comment, \ vote = excluded.vote", - (apt_id, user_id, comment, vote), + (apt_id, user_id, today, comment, vote), ) connection.commit() connection.close() diff --git a/src/backend/tests/test_app.py b/src/backend/tests/test_app.py index c521326a5..07302469f 100644 --- a/src/backend/tests/test_app.py +++ b/src/backend/tests/test_app.py @@ -2,6 +2,7 @@ import sqlite3 import pytest from app import app +from tests.mainpage_staging import MainPageStaging @pytest.fixture(name="config_app") @@ -85,3 +86,43 @@ def test_login_invalid(client): log_info = {"user": "big_finger", "password": "123456789"} res = client.post("/login", json=log_info) assert res.status_code == 404 + + +def test_mainpage_get_valid(client): + """Test mainpage handles valid get request""" + mainpage = MainPageStaging() + mainpage.initialize_all() + connection = sqlite3.connect("database/database.db") + cursor = connection.cursor() + + far_id = cursor.execute( + "SELECT apt_id FROM Apartments WHERE (apt_name = 'FAR')" + ).fetchone()[0] + + res = client.get("/main", query_string={"review": "True", "aptId": far_id}) + sample_json = ('[{"username": "Big_finger", ' + '"date": "2022-10-10", ' + '"comment": "Decent hall", ' + '"vote": true}]') + + connection.close() + mainpage.clean_all() + assert res.status_code == 200 + assert res.text == sample_json + + +def test_mainpage_get_invalid(client): + """Test mainpage handles invalid get request""" + res = client.get("/main", query_string={"search": "True"}) + assert res.status_code == 400 + + +def test_mainpage_post_valid(client): + """Test mainpage handles valid post request""" + + +def test_mainpage_post_invalid(client): + """Test mainpage handles invalid post request""" + sample_review = {"username": "User McUserFace", "comment": "Ho ho ho ho"} + res = client.post("/main", json=sample_review) + assert res.status_code == 400 From e40587efaa0ced8f64322cabf45fc254e2b672cb Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 22:28:48 -0500 Subject: [PATCH 17/28] Added test for mainpage post --- src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/tests/test_app.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 0acd29bfdaaa3973f7d70814bae2ef900d1dd181..b414ee7f829cac84cf921f3b88d4e7d8e4d6a3bf 100644 GIT binary patch delta 389 zcmZp8z}WDBae}nq8wLgjb|8iUo{2ifKtVnK8eZN+1~wj62EM)g{2L3VbIY)?i%Uy0 zwrH0mCgr3CrIux;mKQ@Q_RY#X0*s98lTCPaxS4=vae&NAXPunQE5*n*xsBJ4k!A8# zUIj+h&EI&X86}w6#l^)Lo2|h{g%+n46@v+u$(sDvMYuqE1sE9kZy?}iL5Ds3Jp3;p z1_R|aCN`=}a$wq_dwugL4{g=M%Rgrj*~CS8wi3-1i2~(1~)4T z`0_I@nb^2sk^?8}eSRPWy5SuE!~ix%#fgrxKvC{=ei*a{Dp%)c;a|X@GqF*dapuIv FsQ`35V1NJs delta 390 zcmZp8z}WDBae}m<2Vq)VG5iXd4C;8zJsB9HKKmUEWtw1&A6B~^I D_G4YA diff --git a/src/backend/tests/test_app.py b/src/backend/tests/test_app.py index 07302469f..ff862437a 100644 --- a/src/backend/tests/test_app.py +++ b/src/backend/tests/test_app.py @@ -119,7 +119,24 @@ def test_mainpage_get_invalid(client): def test_mainpage_post_valid(client): """Test mainpage handles valid post request""" + mainpage = MainPageStaging() + mainpage.initialize_all() + connection = sqlite3.connect("database/database.db") + cursor = connection.cursor() + + isr_id = cursor.execute( + "SELECT apt_id FROM Apartments WHERE (apt_name = 'ISR')" + ).fetchone()[0] + sample_review = {"apt_id": isr_id, "username": "Minh Phan", "comment": "Good", "vote": 1} + res = client.post("/main", json=sample_review) + cursor.execute( + "DELETE FROM Reviews WHERE apt_id = ?", (isr_id,) + ) + connection.commit() + connection.close() + mainpage.clean_all() + assert res.status_code == 201 def test_mainpage_post_invalid(client): """Test mainpage handles invalid post request""" From 54afbfcd96c38ae0f2aee2d96b75296cd40501ea Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 22:32:58 -0500 Subject: [PATCH 18/28] Ran black --- src/backend/tests/test_app.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/backend/tests/test_app.py b/src/backend/tests/test_app.py index ff862437a..aa0c3b768 100644 --- a/src/backend/tests/test_app.py +++ b/src/backend/tests/test_app.py @@ -100,10 +100,12 @@ def test_mainpage_get_valid(client): ).fetchone()[0] res = client.get("/main", query_string={"review": "True", "aptId": far_id}) - sample_json = ('[{"username": "Big_finger", ' - '"date": "2022-10-10", ' - '"comment": "Decent hall", ' - '"vote": true}]') + sample_json = ( + '[{"username": "Big_finger", ' + '"date": "2022-10-10", ' + '"comment": "Decent hall", ' + '"vote": true}]' + ) connection.close() mainpage.clean_all() @@ -127,17 +129,21 @@ def test_mainpage_post_valid(client): isr_id = cursor.execute( "SELECT apt_id FROM Apartments WHERE (apt_name = 'ISR')" ).fetchone()[0] - sample_review = {"apt_id": isr_id, "username": "Minh Phan", "comment": "Good", "vote": 1} + sample_review = { + "apt_id": isr_id, + "username": "Minh Phan", + "comment": "Good", + "vote": 1, + } res = client.post("/main", json=sample_review) - cursor.execute( - "DELETE FROM Reviews WHERE apt_id = ?", (isr_id,) - ) + cursor.execute("DELETE FROM Reviews WHERE apt_id = ?", (isr_id,)) connection.commit() connection.close() mainpage.clean_all() assert res.status_code == 201 + def test_mainpage_post_invalid(client): """Test mainpage handles invalid post request""" sample_review = {"username": "User McUserFace", "comment": "Ho ho ho ho"} From e5cb2aedba66f5861bf7ff384f3b8b22a0bc8bff Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Fri, 28 Oct 2022 22:37:05 -0500 Subject: [PATCH 19/28] Fix pylint and black errors --- src/backend/app.py | 4 +++- src/backend/tests/test_mainpage.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/backend/app.py b/src/backend/app.py index b819a4cf8..87faf0859 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -111,7 +111,9 @@ def mainpage_post(mainpage_obj: MainPage): vote = json_form.get("vote") if None not in (apt_id, username, comment, vote): query_result = "" - reviews = mainpage_obj.write_apartment_review(apt_id, username, comment, vote) + reviews = mainpage_obj.write_apartment_review( + apt_id, username, comment, vote + ) reviews_dict = [dataclasses.asdict(review) for review in reviews] query_result = json.dumps(reviews_dict) return query_result, 201 diff --git a/src/backend/tests/test_mainpage.py b/src/backend/tests/test_mainpage.py index 94c0aed12..32760506d 100644 --- a/src/backend/tests/test_mainpage.py +++ b/src/backend/tests/test_mainpage.py @@ -39,15 +39,15 @@ def test_apartments_default(self): connection = sqlite3.connect("database/database.db") cursor = connection.cursor() - far_id = cursor.execute( - "SELECT apt_id FROM Apartments WHERE (apt_name = 'FAR')" - ).fetchone()[0] sherman_id = cursor.execute( "SELECT apt_id FROM Apartments WHERE (apt_name = 'Sherman')" ).fetchone()[0] isr_id = cursor.execute( "SELECT apt_id FROM Apartments WHERE (apt_name = 'ISR')" ).fetchone()[0] + far_id = cursor.execute( + "SELECT apt_id FROM Apartments WHERE (apt_name = 'FAR')" + ).fetchone()[0] connection.close() sample_apts_default = [] sample_apts_default.append( From d1d4278c32217332a001b33da75e2115ec0b6f82 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Sat, 29 Oct 2022 13:48:36 -0500 Subject: [PATCH 20/28] Changed login and register to account for the no json case --- src/backend/app.py | 36 +++++++++++++++++++------------ src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/tests/test_app.py | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/backend/app.py b/src/backend/app.py index 87faf0859..6f8586cfb 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -16,26 +16,34 @@ def login(): """Handles login routing""" user_login = Login() - user = request.json["user"] - password = request.json["password"] - if user_login.login(user, password): - return f"welcome {user}", 200 - return "User not found, please try again", 404 + user_login = Login() + json_form = request.get_json(force=True, silent=True) + + if json_form is not None: + user = json_form.get("user") + password = json_form.get("password") + if user_login.login(user, password): + return f"welcome {user}", 200 + return "User not found, please try again", 401 + return "", 404 @app.route("/register", methods=["POST", "GET"]) def register(): """Handles register routing""" user_login = Login() - username = request.json["username"] - email = request.json["email"] - password = request.json["password"] - phone = request.json["phone"] - result = user_login.register(username, email, password, phone) - if not result.status: - return result.message, 400 - return result.message, 200 + json_form = request.get_json(force=True, silent=True) + if json_form is not None: + username = json_form.get("username", "") + email = json_form.get("email", "") + password = json_form.get("password", "") + phone = json_form.get("phone", "") + result = user_login.register(username, email, password, phone) + if not result.status: + return result.message, 400 + return result.message, 200 + return "", 404 @app.route("/", methods=["POST", "GET"]) @app.route("/main", methods=["POST", "GET"]) @@ -102,7 +110,7 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): def mainpage_post(mainpage_obj: MainPage): """Helper for mainpage post requests""" - json_form = request.get_json(force=True) + json_form = request.get_json(force=True, silent=True) if json_form is not None: apt_id = json_form.get("apt_id") diff --git a/src/backend/database/database.db b/src/backend/database/database.db index b414ee7f829cac84cf921f3b88d4e7d8e4d6a3bf..47d011501b1fe02f649b95eaf0c4772d24e5a6e3 100644 GIT binary patch delta 240 zcmZp8z}WDBae@>R+suhFPC#;F!b*Pbll*YFSy5mWKR^F{25ts8FrV0H%*V_Bl!2N5 z7z6(o{$u>tHwy;r;P>HYVpe2wEJ!RW$xY2GDQ0xwVP=+Q3Q8@@Of4^F^Z**b4>yE? zfs2t@oGG+8wWyd;k{2k>$bXT6{~~_}1OhFtqP+!YI6o8u#Xa~LS59nP vIw^pY^$tG}0xiGD&!{r7Q2{8#9l;NSnn1Op{4D&_8T2PM>M^dE*ti4$DJ?(i delta 238 zcmZp8z}WDBae@>R%bSTZPC#;F!b*Pb8~kv%SukJ^KM(&41`Y-Y(3se$GAV$Ci<$oh z1OHe4r~Eg7N_O&l^M7SffGUAd+|10a#Q=axf} diff --git a/src/backend/tests/test_app.py b/src/backend/tests/test_app.py index aa0c3b768..54b30470e 100644 --- a/src/backend/tests/test_app.py +++ b/src/backend/tests/test_app.py @@ -85,7 +85,7 @@ def test_login_invalid(client): """Test login returns invalid (404) network code""" log_info = {"user": "big_finger", "password": "123456789"} res = client.post("/login", json=log_info) - assert res.status_code == 404 + assert res.status_code == 401 def test_mainpage_get_valid(client): From 0349e65fee309d560a5cb91ed0637dfdc65455da Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Sat, 29 Oct 2022 15:34:47 -0500 Subject: [PATCH 21/28] Added all tests for login and register --- src/backend/app.py | 30 ++++++++++++++++----------- src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/tests/test_app.py | 34 +++++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/backend/app.py b/src/backend/app.py index 6f8586cfb..1558ad728 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -17,24 +17,24 @@ def login(): """Handles login routing""" user_login = Login() user_login = Login() - json_form = request.get_json(force=True, silent=True) + json_form = request.get_json(force=True) - if json_form is not None: - user = json_form.get("user") - password = json_form.get("password") + if isinstance(json_form, dict): + user = json_form.get("user", "") + password = json_form.get("password", "") if user_login.login(user, password): return f"welcome {user}", 200 return "User not found, please try again", 401 - return "", 404 + return "", 400 @app.route("/register", methods=["POST", "GET"]) def register(): """Handles register routing""" user_login = Login() - json_form = request.get_json(force=True, silent=True) + json_form = request.get_json(force=True) - if json_form is not None: + if isinstance(json_form, dict): username = json_form.get("username", "") email = json_form.get("email", "") password = json_form.get("password", "") @@ -42,8 +42,9 @@ def register(): result = user_login.register(username, email, password, phone) if not result.status: return result.message, 400 - return result.message, 200 - return "", 404 + return result.message, 201 + return "", 400 + @app.route("/", methods=["POST", "GET"]) @app.route("/main", methods=["POST", "GET"]) @@ -110,14 +111,19 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): def mainpage_post(mainpage_obj: MainPage): """Helper for mainpage post requests""" - json_form = request.get_json(force=True, silent=True) + json_form = request.get_json(force=True) - if json_form is not None: + if isinstance(json_form, dict): apt_id = json_form.get("apt_id") username = json_form.get("username") comment = json_form.get("comment") vote = json_form.get("vote") - if None not in (apt_id, username, comment, vote): + if ( + apt_id is not None + and username is not None + and comment is not None + and vote is not None + ): query_result = "" reviews = mainpage_obj.write_apartment_review( apt_id, username, comment, vote diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 47d011501b1fe02f649b95eaf0c4772d24e5a6e3..1db1060326713fcf0ac5a9327e18d15db183d71e 100644 GIT binary patch delta 68 zcmZp8z}WDBae@>RSJOlpCm^{op;TX+xqye6S(+&*wJbBWyqLL-fq{V^h+%+>ky)H6 Ov^ce>n3;Kzfdc?otq#Zl delta 68 zcmZp8z}WDBae@>R+suhFPC#;FLaDwsqXQ2!voupsYFTD#c`>600|Ns;5W@f$BeOVD OXmM&$F{9)n0|x+5j}DLk diff --git a/src/backend/tests/test_app.py b/src/backend/tests/test_app.py index 54b30470e..048b4de01 100644 --- a/src/backend/tests/test_app.py +++ b/src/backend/tests/test_app.py @@ -32,11 +32,14 @@ def test_register_valid(client): cursor.execute("DELETE FROM Users WHERE username = ?", ("big_finger",)) connection.commit() connection.close() - assert res.status_code == 200 + assert res.status_code == 201 -def test_register_invalid(client): - """Test register returns invalid (400) network code""" +def test_register_invalid_input(client): + """ + Test register returns invalid (400) network code + for invalid register attempt + """ reg_info = { "username": "big_finger", "email": "junk@gmail.com", @@ -58,9 +61,17 @@ def test_register_invalid(client): cursor.execute("DELETE FROM Users WHERE username = ?", ("big_finger",)) connection.commit() connection.close() - + assert res_2.text == "big_finger already registered, please try again" assert res_2.status_code == 400 +def test_register_not_json(client): + """ + Test register returns invalid (400) + network code for a non-json + """ + res = client.post("/register", json = 0) + assert res.text == "" + assert res.status_code == 400 def test_login_valid(client): """Test login returns valid (200) network code""" @@ -81,12 +92,23 @@ def test_login_valid(client): assert res.status_code == 200 -def test_login_invalid(client): - """Test login returns invalid (404) network code""" +def test_login_invalid_user(client): + """ + Test login returns invalid (401) network code + for non-existant user + """ log_info = {"user": "big_finger", "password": "123456789"} res = client.post("/login", json=log_info) assert res.status_code == 401 +def test_login_not_json(client): + """ + Test login returns invalid (400) network code + for a non-json + """ + res = client.post("/register", json = 0) + assert res.status_code == 400 + def test_mainpage_get_valid(client): """Test mainpage handles valid get request""" From 45fd5ba41f34f04856bd057c92f4a47e3d8ced87 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Sat, 29 Oct 2022 16:49:40 -0500 Subject: [PATCH 22/28] Restructured backend --- src/backend/.coveragerc | 3 +- src/backend/app.py | 25 +++--- src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/dataholders/__init__.py | 0 src/backend/{ => dataholders}/apt.py | 0 src/backend/{ => dataholders}/review.py | 0 src/backend/pages/__init__.py | 0 src/backend/{ => pages}/login.py | 0 src/backend/{ => pages}/mainpage.py | 12 +-- src/backend/tests/test_app.py | 111 ++++++++++++++++++------ src/backend/tests/test_apt.py | 2 +- src/backend/tests/test_login.py | 2 +- src/backend/tests/test_mainpage.py | 6 +- src/backend/tests/test_review.py | 2 +- 14 files changed, 114 insertions(+), 49 deletions(-) create mode 100644 src/backend/dataholders/__init__.py rename src/backend/{ => dataholders}/apt.py (100%) rename src/backend/{ => dataholders}/review.py (100%) create mode 100644 src/backend/pages/__init__.py rename src/backend/{ => pages}/login.py (100%) rename src/backend/{ => pages}/mainpage.py (95%) diff --git a/src/backend/.coveragerc b/src/backend/.coveragerc index 1cb8c51ca..78751e734 100644 --- a/src/backend/.coveragerc +++ b/src/backend/.coveragerc @@ -1,2 +1,3 @@ [run] -omit = *tests* \ No newline at end of file +omit = *tests* + *init* \ No newline at end of file diff --git a/src/backend/app.py b/src/backend/app.py index 1558ad728..1f2da7f05 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -4,8 +4,8 @@ from collections import namedtuple from werkzeug.datastructures import MultiDict from flask import Flask, request -from login import Login -from mainpage import MainPage +from pages.login import Login +from pages.mainpage import MainPage # from logging import FileHandler, WARNING @@ -16,7 +16,6 @@ def login(): """Handles login routing""" user_login = Login() - user_login = Login() json_form = request.get_json(force=True) if isinstance(json_form, dict): @@ -64,10 +63,10 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): "action_type", ["is_search", "is_populate", "is_review", "is_pictures"] ) action = action_type( - args.get("search"), - args.get("populate"), - args.get("review"), - args.get("pictures"), + args.get("search", default=False, type=bool), + args.get("populate", default=False, type=bool), + args.get("review", default=False, type=bool), + args.get("pictures", default=False, type=bool), ) params = namedtuple( @@ -82,10 +81,12 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): ) query_result = "" - if action.is_search is not None and param.search_query is not None: - query_result = json.dumps(mainpage_obj.search_apartments(param.search_query)) + if action.is_search is True and param.search_query is not None: + apts = mainpage_obj.search_apartments(param.search_query) + apts_dict = [dataclasses.asdict(apt) for apt in apts] + query_result = json.dumps(apts_dict) - elif action.is_populate is not None and param.num_apts is not None: + elif action.is_populate is True and param.num_apts is not None: apts = [] if param.price_sort is not None and param.rating_sort is not None: apts = mainpage_obj.apartments_sorted( @@ -96,12 +97,12 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): apts_dict = [dataclasses.asdict(apt) for apt in apts] query_result = json.dumps(apts_dict) - elif action.is_review is not None and param.apt_id is not None: + elif action.is_review is True and param.apt_id is not None: reviews = mainpage_obj.get_apartments_reviews(param.apt_id) reviews_dict = [dataclasses.asdict(review) for review in reviews] query_result = json.dumps(reviews_dict) - elif action.is_pictures is not None and param.apt_id is not None: + elif action.is_pictures is True and param.apt_id is not None: query_result = json.dumps(mainpage_obj.get_apartments_pictures(param.apt_id)) if len(query_result) != 0: diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 1db1060326713fcf0ac5a9327e18d15db183d71e..e772cb1cc3e72b72b16391ea3e7fd2d7e6d88657 100644 GIT binary patch delta 111 zcmZp8z}WDBae|bPgcSn=13M7IfXYN2BPI!}g$cZjESnQ~k25kdP1fYUzR7@5U^An_ x6@G0Nc^+nFX{Mmmvdq--Vit3tMt+!PAkD?dEY1{KoLW@OJav-*NRz{VeE`6;75o4I delta 111 zcmZp8z}WDBae|Z(R}%vR13M7I0MA4nBPOn List[Apt]: cursor = connection.cursor() apt_query = cursor.execute( "SELECT Apartments.apt_id, Apartments.apt_name, Apartments.apt_address, \ - COALESCE(SUM(Reviews.vote), 0), Apartments.price_min, Apartments.price_max \ - FROM Apartments, Reviews WHERE LOWER(Apartments.apt_name) LIKE ? \ - AND Apartments.apt_id = Reviews.apt_id", + COALESCE(SUM(Reviews.vote), 0) AS 'total_vote', \ + Apartments.price_min, Apartments.price_max \ + FROM Apartments LEFT JOIN Reviews ON Apartments.apt_id = Reviews.apt_id \ + WHERE LOWER(Apartments.apt_name) LIKE ? \ + GROUP BY Apartments.apt_id", (query_sql,), ).fetchall() apts = [] diff --git a/src/backend/tests/test_app.py b/src/backend/tests/test_app.py index 048b4de01..c4efe11d7 100644 --- a/src/backend/tests/test_app.py +++ b/src/backend/tests/test_app.py @@ -19,7 +19,7 @@ def fixture_client(config_app): def test_register_valid(client): - """Test register returns valid (200) network code""" + """Test register handles valid request""" reg_info = { "username": "big_finger", "email": "junk@gmail.com", @@ -36,10 +36,7 @@ def test_register_valid(client): def test_register_invalid_input(client): - """ - Test register returns invalid (400) network code - for invalid register attempt - """ + """Test register handles invalid register attempt""" reg_info = { "username": "big_finger", "email": "junk@gmail.com", @@ -64,17 +61,16 @@ def test_register_invalid_input(client): assert res_2.text == "big_finger already registered, please try again" assert res_2.status_code == 400 + def test_register_not_json(client): - """ - Test register returns invalid (400) - network code for a non-json - """ - res = client.post("/register", json = 0) + """Test register handles non-json""" + res = client.post("/register", json="") assert res.text == "" assert res.status_code == 400 + def test_login_valid(client): - """Test login returns valid (200) network code""" + """Test login handles valid request""" connection = sqlite3.connect("database/database.db") cursor = connection.cursor() cursor.execute( @@ -93,25 +89,20 @@ def test_login_valid(client): def test_login_invalid_user(client): - """ - Test login returns invalid (401) network code - for non-existant user - """ + """Test handles non-existant user""" log_info = {"user": "big_finger", "password": "123456789"} res = client.post("/login", json=log_info) assert res.status_code == 401 + def test_login_not_json(client): - """ - Test login returns invalid (400) network code - for a non-json - """ - res = client.post("/register", json = 0) + """Test login handles non-json""" + res = client.post("login", json="") assert res.status_code == 400 -def test_mainpage_get_valid(client): - """Test mainpage handles valid get request""" +def test_mainpage_get_valid_review(client): + """Test mainpage handles valid reviwew request""" mainpage = MainPageStaging() mainpage.initialize_all() connection = sqlite3.connect("database/database.db") @@ -120,8 +111,8 @@ def test_mainpage_get_valid(client): far_id = cursor.execute( "SELECT apt_id FROM Apartments WHERE (apt_name = 'FAR')" ).fetchone()[0] - - res = client.get("/main", query_string={"review": "True", "aptId": far_id}) + query = {"review": "True", "aptId": far_id} + res = client.get("/main", query_string=query) sample_json = ( '[{"username": "Big_finger", ' '"date": "2022-10-10", ' @@ -135,7 +126,77 @@ def test_mainpage_get_valid(client): assert res.text == sample_json -def test_mainpage_get_invalid(client): +def test_mainpage_get_valid_search(client): + """Test mainpage handles valid search request""" + mainpage = MainPageStaging() + mainpage.initialize_all() + + connection = sqlite3.connect("database/database.db") + cursor = connection.cursor() + + isr_id = cursor.execute( + "SELECT apt_id FROM Apartments WHERE (apt_name = 'ISR')" + ).fetchone()[0] + + query = {"search": "True", "searchQuery": "is"} + res = client.get("/main", query_string=query) + sample_json = ( + f'[{{"apt_id": {isr_id}, ' + '"name": "ISR", ' + '"address": "918 W Illinois", ' + '"rating": 0, ' + '"price_min": 6000, ' + '"price_max": 7000}]' + ) + + connection.close() + mainpage.clean_all() + assert res.status_code == 200 + assert res.text == sample_json + + +def test_mainpage_get_valid_pictures(client): + """Test mainpage handles valid picture query""" + mainpage = MainPageStaging() + mainpage.initialize_all() + + connection = sqlite3.connect("database/database.db") + cursor = connection.cursor() + + sherman_id = cursor.execute( + "SELECT apt_id FROM Apartments WHERE (apt_name = 'Sherman')" + ).fetchone()[0] + + query = {"pictures": "True", "aptId": sherman_id} + res = client.get("/main", query_string=query) + + sample_json = '["Link1", "Link2", "Link3"]' + + connection.close() + mainpage.clean_all() + + assert res.status_code == 200 + assert res.text == sample_json + + +def test_mainpage_get_valid_populate(client): + """Test mainpage handles valid populate query""" + mainpage = MainPageStaging() + mainpage.initialize_all() + + query_1 = {"populate": "True", "numApts": 1} + query_2 = {"populate": "True", "numApts": 1, "priceSort": 0, "ratingSort": 0} + + res_1 = client.get("/main", query_string=query_1) + res_2 = client.get("/main", query_string=query_2) + mainpage.clean_all() + + assert res_1.status_code == 200 + assert res_2.status_code == 200 + assert res_1.text == res_2.text + + +def test_mainpage_get_invalid_query(client): """Test mainpage handles invalid get request""" res = client.get("/main", query_string={"search": "True"}) assert res.status_code == 400 diff --git a/src/backend/tests/test_apt.py b/src/backend/tests/test_apt.py index 2dd06db38..594982082 100644 --- a/src/backend/tests/test_apt.py +++ b/src/backend/tests/test_apt.py @@ -1,5 +1,5 @@ """Test apt.py""" -from apt import Apt +from dataholders.apt import Apt class TestApt: diff --git a/src/backend/tests/test_login.py b/src/backend/tests/test_login.py index 90726e4cb..d9e398bf9 100644 --- a/src/backend/tests/test_login.py +++ b/src/backend/tests/test_login.py @@ -1,6 +1,6 @@ """Test login.py""" import sqlite3 -from login import Login +from pages.login import Login class TestLogin: diff --git a/src/backend/tests/test_mainpage.py b/src/backend/tests/test_mainpage.py index 32760506d..825702c89 100644 --- a/src/backend/tests/test_mainpage.py +++ b/src/backend/tests/test_mainpage.py @@ -1,9 +1,9 @@ """Test mainpage.py""" import sqlite3 from datetime import date -from mainpage import MainPage -from apt import Apt -from review import Review +from pages.mainpage import MainPage +from dataholders.apt import Apt +from dataholders.review import Review from tests.mainpage_staging import MainPageStaging diff --git a/src/backend/tests/test_review.py b/src/backend/tests/test_review.py index 65a0492b7..1d1578b04 100644 --- a/src/backend/tests/test_review.py +++ b/src/backend/tests/test_review.py @@ -1,5 +1,5 @@ """Test review.py""" -from review import Review +from dataholders.review import Review class TestReview: From 4212ea307094277f059b20c836f3265dca0e483e Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Sat, 29 Oct 2022 17:11:55 -0500 Subject: [PATCH 23/28] Fix pylint error --- src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/tests/test_app.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/database/database.db b/src/backend/database/database.db index e772cb1cc3e72b72b16391ea3e7fd2d7e6d88657..131afa2501e8a83aff2a8c46c9413720fef23ce4 100644 GIT binary patch delta 65 zcmZp8z}WDBae_4C!-+D^j1M;^l<8~L@h~$>GX*%VBiO07~o=L7H0}A NPAw{Ce!j@S0RVJ65Oe?l delta 65 zcmZp8z}WDBae_3X)kGO*MyrhpW%^q3Jj~3}OhKt-nW^Q)EanUh4E#V016+*E;!L5% NsYS)iQx_RH000oQ4io?Y diff --git a/src/backend/tests/test_app.py b/src/backend/tests/test_app.py index c4efe11d7..8256f940d 100644 --- a/src/backend/tests/test_app.py +++ b/src/backend/tests/test_app.py @@ -163,11 +163,11 @@ def test_mainpage_get_valid_pictures(client): connection = sqlite3.connect("database/database.db") cursor = connection.cursor() - sherman_id = cursor.execute( + sample_id = cursor.execute( "SELECT apt_id FROM Apartments WHERE (apt_name = 'Sherman')" ).fetchone()[0] - query = {"pictures": "True", "aptId": sherman_id} + query = {"pictures": "True", "aptId": sample_id} res = client.get("/main", query_string=query) sample_json = '["Link1", "Link2", "Link3"]' From fdbc9e2b31a1adef446b7f8bd5ed3ccc15d6469e Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Mon, 31 Oct 2022 12:03:05 -0500 Subject: [PATCH 24/28] Modified docstrings to be more descriptive --- src/backend/app.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/backend/app.py b/src/backend/app.py index 1f2da7f05..5c720aa21 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -58,7 +58,14 @@ def mainpage(): def mainpage_get(mainpage_obj: MainPage, args: MultiDict): - """Helper for mainpage get requests""" + """ + Helper for mainpage get requests + Actions that use get requests: + - Searching apartments + - Populating the mainpage with apartments + - Getting reviews of an apartment + - Getting pictures of an apartment + """ action_type = namedtuple( "action_type", ["is_search", "is_populate", "is_review", "is_pictures"] ) @@ -111,7 +118,11 @@ def mainpage_get(mainpage_obj: MainPage, args: MultiDict): def mainpage_post(mainpage_obj: MainPage): - """Helper for mainpage post requests""" + """ + Helper for mainpage post requests + Actions that use post request: + - Writing a review to an apartment + """ json_form = request.get_json(force=True) if isinstance(json_form, dict): From dfabf3c3b5e79fb0030b61bd2b90a6327d814987 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Mon, 31 Oct 2022 14:38:21 -0500 Subject: [PATCH 25/28] Add a DB decorator to reduce db open/close repetition --- .github/workflows/pytest-coverage.yml | 2 +- src/backend/.coveragerc | 3 +- src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/decorators.py | 23 +++++++++ src/backend/pages/login.py | 19 +++----- src/backend/pages/mainpage.py | 66 ++++++++++++-------------- 6 files changed, 63 insertions(+), 50 deletions(-) create mode 100644 src/backend/decorators.py diff --git a/.github/workflows/pytest-coverage.yml b/.github/workflows/pytest-coverage.yml index 436d11154..50402a7c6 100644 --- a/.github/workflows/pytest-coverage.yml +++ b/.github/workflows/pytest-coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Build coverage file run: | - pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=. | tee pytest-coverage.txt + pytest --junitxml=pytest.xml --cov-report=term-missing --cov=. | tee pytest-coverage.txt env: LD_LIBRARY_PATH: /usr/local/lib working-directory: src/backend diff --git a/src/backend/.coveragerc b/src/backend/.coveragerc index 78751e734..c3fbb1154 100644 --- a/src/backend/.coveragerc +++ b/src/backend/.coveragerc @@ -1,3 +1,4 @@ [run] omit = *tests* - *init* \ No newline at end of file + *init* + *decorators* \ No newline at end of file diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 131afa2501e8a83aff2a8c46c9413720fef23ce4..51549e6f9925e594f04811d5e3c57d2a41ae65e2 100644 GIT binary patch delta 256 zcmZp8z}WDBae|bPMlS;c13M7IK*>ZMBPNaBjR~vy*?aiGaI>I7EkC2{#74)-7v&9j zfhw8#Z!qwG<$ub5W3ynu4t_uWrwoD+1B{{Oa5FPYGX|xWWu}%FC-N~dD>6D3Bo>wA zrskCt$8$0=i!+86rxq0lbAi+gFfj1nKtQ0Gd-!?yUodb$4bzy|s4~fclQV`N3O6eX z`0_I@nb^1hD9n1F9|(b(&hbwSU}IFA=qL*mL diff --git a/src/backend/decorators.py b/src/backend/decorators.py new file mode 100644 index 000000000..9258c0176 --- /dev/null +++ b/src/backend/decorators.py @@ -0,0 +1,23 @@ +"""Stores decorators for common functionalities""" +import sqlite3 +import functools +def use_database(func): + + @functools.wraps(func) + def wrapped(*args): + connection = sqlite3.connect("database/database.db") + cursor = connection.cursor() + wrapped.cursor = cursor + wrapped.connection = connection + try: + caller = func(*args) + except Exception: + connection.rollback() + raise + else: + connection.commit() + finally: + connection.close() + return caller + + return wrapped \ No newline at end of file diff --git a/src/backend/pages/login.py b/src/backend/pages/login.py index 5228cec77..b2ea9d32b 100644 --- a/src/backend/pages/login.py +++ b/src/backend/pages/login.py @@ -1,7 +1,7 @@ """ Contains Login class """ from dataclasses import dataclass import re -import sqlite3 +from decorators import use_database @dataclass(frozen=True) @@ -19,6 +19,7 @@ class Login: def __init__(self) -> None: """Constructor""" + @use_database def register( self, username: str, email: str, password: str, phone: str ) -> RegisterResult: @@ -40,33 +41,27 @@ def register( if len(password) < 8: return RegisterResult("Password is too short, please try again", False) - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - check = cursor.execute( + + check = self.register.cursor.execute( "SELECT username FROM Users WHERE username = ?", (username,) ).fetchone() if check is None: # valid - cursor.execute( + self.register.cursor.execute( "INSERT INTO Users (username, email, password, phone, apt_id) \ VALUES (?, ?, ?, ?, 0)", (username, email, password, phone), ) - connection.commit() - connection.close() return RegisterResult(f"Register successful, welcome {username}", True) - connection.close() return RegisterResult(f"{username} already registered, please try again", False) + @use_database def login(self, user_id: str, password: str) -> bool: """Login function, returns false if combination not found""" - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - user = cursor.execute( + user = self.login.cursor.execute( "SELECT username, email, password FROM Users WHERE (username = ? OR email = ?) \ AND password = ?", (user_id, user_id, password), ).fetchall() - connection.close() return len(user) > 0 def logout(self) -> None: diff --git a/src/backend/pages/mainpage.py b/src/backend/pages/mainpage.py index 99b0ef9d6..1150667e9 100644 --- a/src/backend/pages/mainpage.py +++ b/src/backend/pages/mainpage.py @@ -5,7 +5,7 @@ from typing import Tuple from dataholders.apt import Apt from dataholders.review import Review - +from decorators import use_database class MainPage: """Mainpage class, interacts with the mainpage frontend""" @@ -13,12 +13,11 @@ class MainPage: def __init__(self) -> None: """Constructor""" + @use_database def search_apartments(self, query: str) -> List[Apt]: """Returns a list of apartments with name matching query""" query_sql = "%" + query.lower() + "%" - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - apt_query = cursor.execute( + apt_query = self.search_apartments.cursor.execute( "SELECT Apartments.apt_id, Apartments.apt_name, Apartments.apt_address, \ COALESCE(SUM(Reviews.vote), 0) AS 'total_vote', \ Apartments.price_min, Apartments.price_max \ @@ -33,14 +32,12 @@ def search_apartments(self, query: str) -> List[Apt]: apts.append( Apt(entry[0], entry[1], entry[2], entry[3], entry[4], entry[5]) ) - connection.close() return apts + @use_database def apartments_default(self, num_apts: int) -> List[Apt]: """Returns num_apts apartments to populate the mainpage""" - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - apt_query = cursor.execute( + apt_query = self.apartments_default.cursor.execute( "SELECT Apartments.apt_id, Apartments.apt_name, Apartments.apt_address, \ COALESCE(SUM(Reviews.vote), 0) AS 'total_vote', \ Apartments.price_min, Apartments.price_max \ @@ -52,27 +49,23 @@ def apartments_default(self, num_apts: int) -> List[Apt]: apts = [] for entry in apt_query: apts.append(Apt(entry[0], entry[1], entry[2], entry[3], entry[4], entry[5])) - connection.close() return apts + @use_database def apartments_sorted( self, num_apts: int, price_sort: int, rating_sort: int ) -> List[Apt]: """Returns num_apts apartments with sorting criterias""" - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() apts = [] apt_query = [] if price_sort == 0: - apt_query = self.rating_sort_helper(num_apts, rating_sort, cursor) + apt_query = self.rating_sort_helper(num_apts, rating_sort, self.apartments_sorted.cursor) elif rating_sort == 0 and price_sort != 0: - apt_query = self.price_sort_helper(num_apts, price_sort, cursor) + apt_query = self.price_sort_helper(num_apts, price_sort, self.apartments_sorted.cursor) else: - apt_query = self.both_sort_helper(num_apts, price_sort, rating_sort, cursor) - - connection.close() + apt_query = self.both_sort_helper(num_apts, price_sort, rating_sort, self.apartments_sorted.cursor) for entry in apt_query: apts.append(Apt(entry[0], entry[1], entry[2], entry[3], entry[4], entry[5])) @@ -147,30 +140,27 @@ def both_sort_helper( return apt_query + @use_database def get_apartments_pictures(self, apt_id: int) -> List[str]: """Returns pictures related to an apartment""" - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - pic_query = cursor.execute( + pic_query = self.get_apartments_pictures.cursor.execute( "SELECT link FROM AptPics WHERE apt_id = ?", (apt_id,) ).fetchall() res = [] for entry in pic_query: res.append(entry[0]) - cursor.close() return res + @use_database def write_apartment_review( self, apt_id: int, username: str, comment: str, vote: int ) -> List[Review]: """Write a new review for apartment""" - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - user_id = cursor.execute( + user_id = self.write_apartment_review.cursor.execute( "SELECT user_id FROM Users WHERE username = ?", (username,) ).fetchone()[0] today = date.today().strftime("%Y-%m-%d") - cursor.execute( + self.write_apartment_review.cursor.execute( "INSERT INTO Reviews (apt_id, user_id, date_of_rating, comment, vote) \ VALUES (?, ?, ?, ?, ?) \ ON CONFLICT DO UPDATE SET \ @@ -179,20 +169,22 @@ def write_apartment_review( vote = excluded.vote", (apt_id, user_id, today, comment, vote), ) - connection.commit() - connection.close() - new_reviews = self.get_apartments_reviews(apt_id) - new_review_ind = [ - i for i, x in enumerate(new_reviews) if x.username == username - ][0] - new_reviews.insert(0, new_reviews.pop(new_review_ind)) - return new_reviews + self.write_apartment_review.connection.commit() + ratings_query = self.write_apartment_review.cursor.execute( + "SELECT Users.username, Reviews.date_of_rating, Reviews.comment, Reviews.vote \ + FROM Users INNER JOIN Reviews \ + ON Users.user_id = Reviews.user_id \ + WHERE Reviews.apt_id = ? \ + ORDER BY Users.username = ? DESC, Reviews.date_of_rating DESC", + (apt_id, username), + ).fetchall() + return self.create_reviews_helper(ratings_query) + + @use_database def get_apartments_reviews(self, apt_id: int) -> List[Review]: """Returns a list of apartment reviews""" - connection = sqlite3.connect("database/database.db") - cursor = connection.cursor() - ratings_query = cursor.execute( + ratings_query = self.get_apartments_reviews.cursor.execute( "SELECT Users.username, Reviews.date_of_rating, Reviews.comment, Reviews.vote \ FROM Users INNER JOIN Reviews \ ON Users.user_id = Reviews.user_id \ @@ -200,6 +192,9 @@ def get_apartments_reviews(self, apt_id: int) -> List[Review]: ORDER BY Reviews.date_of_rating DESC", (apt_id,), ).fetchall() + return self.create_reviews_helper(ratings_query) + + def create_reviews_helper(self, ratings_query: List[Tuple]) -> List[Review]: reviews = [] for entry in ratings_query: if entry[0] is not None: @@ -207,5 +202,4 @@ def get_apartments_reviews(self, apt_id: int) -> List[Review]: if entry[3] == 1: vote = True reviews.append(Review(entry[0], entry[1], entry[2], vote)) - cursor.close() return reviews From 65001cb258228eb6dfc52a9dab6c5788f91cc2f5 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Mon, 31 Oct 2022 14:53:38 -0500 Subject: [PATCH 26/28] Add ignore instructions for pylint --- src/backend/decorators.py | 6 +++++- src/backend/pages/login.py | 3 +++ src/backend/pages/mainpage.py | 25 +++++++++++++++++++++---- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/backend/decorators.py b/src/backend/decorators.py index 9258c0176..129a40bdd 100644 --- a/src/backend/decorators.py +++ b/src/backend/decorators.py @@ -1,10 +1,14 @@ """Stores decorators for common functionalities""" import sqlite3 import functools + + def use_database(func): + """Decorator to utilize DB connections""" @functools.wraps(func) def wrapped(*args): + """Wrapper about the actual function""" connection = sqlite3.connect("database/database.db") cursor = connection.cursor() wrapped.cursor = cursor @@ -20,4 +24,4 @@ def wrapped(*args): connection.close() return caller - return wrapped \ No newline at end of file + return wrapped diff --git a/src/backend/pages/login.py b/src/backend/pages/login.py index b2ea9d32b..d051c47a1 100644 --- a/src/backend/pages/login.py +++ b/src/backend/pages/login.py @@ -42,10 +42,12 @@ def register( if len(password) < 8: return RegisterResult("Password is too short, please try again", False) + # pylint: disable=no-member check = self.register.cursor.execute( "SELECT username FROM Users WHERE username = ?", (username,) ).fetchone() if check is None: # valid + # pylint: disable=no-member self.register.cursor.execute( "INSERT INTO Users (username, email, password, phone, apt_id) \ VALUES (?, ?, ?, ?, 0)", @@ -57,6 +59,7 @@ def register( @use_database def login(self, user_id: str, password: str) -> bool: """Login function, returns false if combination not found""" + # pylint: disable=no-member user = self.login.cursor.execute( "SELECT username, email, password FROM Users WHERE (username = ? OR email = ?) \ AND password = ?", diff --git a/src/backend/pages/mainpage.py b/src/backend/pages/mainpage.py index 1150667e9..6f25417b5 100644 --- a/src/backend/pages/mainpage.py +++ b/src/backend/pages/mainpage.py @@ -7,6 +7,7 @@ from dataholders.review import Review from decorators import use_database + class MainPage: """Mainpage class, interacts with the mainpage frontend""" @@ -17,6 +18,7 @@ def __init__(self) -> None: def search_apartments(self, query: str) -> List[Apt]: """Returns a list of apartments with name matching query""" query_sql = "%" + query.lower() + "%" + # pylint: disable=no-member apt_query = self.search_apartments.cursor.execute( "SELECT Apartments.apt_id, Apartments.apt_name, Apartments.apt_address, \ COALESCE(SUM(Reviews.vote), 0) AS 'total_vote', \ @@ -37,6 +39,7 @@ def search_apartments(self, query: str) -> List[Apt]: @use_database def apartments_default(self, num_apts: int) -> List[Apt]: """Returns num_apts apartments to populate the mainpage""" + # pylint: disable=no-member apt_query = self.apartments_default.cursor.execute( "SELECT Apartments.apt_id, Apartments.apt_name, Apartments.apt_address, \ COALESCE(SUM(Reviews.vote), 0) AS 'total_vote', \ @@ -61,11 +64,20 @@ def apartments_sorted( apt_query = [] if price_sort == 0: - apt_query = self.rating_sort_helper(num_apts, rating_sort, self.apartments_sorted.cursor) + apt_query = self.rating_sort_helper( + # pylint: disable=no-member + num_apts, rating_sort, self.apartments_sorted.cursor + ) elif rating_sort == 0 and price_sort != 0: - apt_query = self.price_sort_helper(num_apts, price_sort, self.apartments_sorted.cursor) + apt_query = self.price_sort_helper( + # pylint: disable=no-member + num_apts, price_sort, self.apartments_sorted.cursor + ) else: - apt_query = self.both_sort_helper(num_apts, price_sort, rating_sort, self.apartments_sorted.cursor) + apt_query = self.both_sort_helper( + # pylint: disable=no-member + num_apts, price_sort, rating_sort, self.apartments_sorted.cursor + ) for entry in apt_query: apts.append(Apt(entry[0], entry[1], entry[2], entry[3], entry[4], entry[5])) @@ -143,6 +155,7 @@ def both_sort_helper( @use_database def get_apartments_pictures(self, apt_id: int) -> List[str]: """Returns pictures related to an apartment""" + # pylint: disable=no-member pic_query = self.get_apartments_pictures.cursor.execute( "SELECT link FROM AptPics WHERE apt_id = ?", (apt_id,) ).fetchall() @@ -156,6 +169,7 @@ def write_apartment_review( self, apt_id: int, username: str, comment: str, vote: int ) -> List[Review]: """Write a new review for apartment""" + # pylint: disable=no-member user_id = self.write_apartment_review.cursor.execute( "SELECT user_id FROM Users WHERE username = ?", (username,) ).fetchone()[0] @@ -169,8 +183,9 @@ def write_apartment_review( vote = excluded.vote", (apt_id, user_id, today, comment, vote), ) + # pylint: disable=no-member self.write_apartment_review.connection.commit() - + # pylint: disable=no-member ratings_query = self.write_apartment_review.cursor.execute( "SELECT Users.username, Reviews.date_of_rating, Reviews.comment, Reviews.vote \ FROM Users INNER JOIN Reviews \ @@ -184,6 +199,7 @@ def write_apartment_review( @use_database def get_apartments_reviews(self, apt_id: int) -> List[Review]: """Returns a list of apartment reviews""" + # pylint: disable=no-member ratings_query = self.get_apartments_reviews.cursor.execute( "SELECT Users.username, Reviews.date_of_rating, Reviews.comment, Reviews.vote \ FROM Users INNER JOIN Reviews \ @@ -195,6 +211,7 @@ def get_apartments_reviews(self, apt_id: int) -> List[Review]: return self.create_reviews_helper(ratings_query) def create_reviews_helper(self, ratings_query: List[Tuple]) -> List[Review]: + """Create a list of reviews out of a query""" reviews = [] for entry in ratings_query: if entry[0] is not None: From 922ccdc2e637ee95ba019cfbcb366dd0bc2d59a1 Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Mon, 31 Oct 2022 15:20:01 -0500 Subject: [PATCH 27/28] Fuck pylint, PoS cant handle decorators --- src/backend/.pylintrc | 619 +++++++++++++++++++++++++++++++ src/backend/database/database.db | Bin 28672 -> 28672 bytes src/backend/pages/login.py | 3 - src/backend/pages/mainpage.py | 10 - 4 files changed, 619 insertions(+), 13 deletions(-) create mode 100644 src/backend/.pylintrc diff --git a/src/backend/.pylintrc b/src/backend/.pylintrc new file mode 100644 index 000000000..cbc3a3759 --- /dev/null +++ b/src/backend/.pylintrc @@ -0,0 +1,619 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\' represents the directory delimiter on Windows systems, it +# can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.8 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + no-member + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=BaseException, + Exception + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= diff --git a/src/backend/database/database.db b/src/backend/database/database.db index 51549e6f9925e594f04811d5e3c57d2a41ae65e2..83eda088f81b3dcee772d9ec66bc404e088135ab 100644 GIT binary patch delta 240 zcmZp8z}WDBae@?+rpZJZCm^{oVKqN{2R|5Y7F4L@XY`!d=sNkLya6x&QwC=KV+{OX z_>b{l-z*rggWr#ziCK}!u^_RiBsVp$q?lnJ4>PkgQ&4JIW@>pc!yyI+27Vxh0WL;n zai-AX)S_aBrCcCE0igUz1O(c*il3kVJ_9#Q2Lpro#75&u4xF6f{7|@AQNV+raplCu ur9ffUJN!ThbjwA4MwN+;3P2(52!0sU1gaI~XW^gDpg*xuk8#Ds#w7rwn?Jb# delta 240 zcmZp8z}WDBae@?+M(;!!Cm^{oVKqN{4?h@g7F4L^XLOy|=s5YJya6u*0|PVv4F>+N z{7?CBY!(dI!SBcaltBIG78hUVwpt{{{jA&D_J!!~cST18SJY#731#4xF4Z{7|@AQNWj bool: """Login function, returns false if combination not found""" - # pylint: disable=no-member user = self.login.cursor.execute( "SELECT username, email, password FROM Users WHERE (username = ? OR email = ?) \ AND password = ?", diff --git a/src/backend/pages/mainpage.py b/src/backend/pages/mainpage.py index 6f25417b5..e2ecba294 100644 --- a/src/backend/pages/mainpage.py +++ b/src/backend/pages/mainpage.py @@ -18,7 +18,6 @@ def __init__(self) -> None: def search_apartments(self, query: str) -> List[Apt]: """Returns a list of apartments with name matching query""" query_sql = "%" + query.lower() + "%" - # pylint: disable=no-member apt_query = self.search_apartments.cursor.execute( "SELECT Apartments.apt_id, Apartments.apt_name, Apartments.apt_address, \ COALESCE(SUM(Reviews.vote), 0) AS 'total_vote', \ @@ -39,7 +38,6 @@ def search_apartments(self, query: str) -> List[Apt]: @use_database def apartments_default(self, num_apts: int) -> List[Apt]: """Returns num_apts apartments to populate the mainpage""" - # pylint: disable=no-member apt_query = self.apartments_default.cursor.execute( "SELECT Apartments.apt_id, Apartments.apt_name, Apartments.apt_address, \ COALESCE(SUM(Reviews.vote), 0) AS 'total_vote', \ @@ -65,17 +63,14 @@ def apartments_sorted( if price_sort == 0: apt_query = self.rating_sort_helper( - # pylint: disable=no-member num_apts, rating_sort, self.apartments_sorted.cursor ) elif rating_sort == 0 and price_sort != 0: apt_query = self.price_sort_helper( - # pylint: disable=no-member num_apts, price_sort, self.apartments_sorted.cursor ) else: apt_query = self.both_sort_helper( - # pylint: disable=no-member num_apts, price_sort, rating_sort, self.apartments_sorted.cursor ) @@ -155,7 +150,6 @@ def both_sort_helper( @use_database def get_apartments_pictures(self, apt_id: int) -> List[str]: """Returns pictures related to an apartment""" - # pylint: disable=no-member pic_query = self.get_apartments_pictures.cursor.execute( "SELECT link FROM AptPics WHERE apt_id = ?", (apt_id,) ).fetchall() @@ -169,7 +163,6 @@ def write_apartment_review( self, apt_id: int, username: str, comment: str, vote: int ) -> List[Review]: """Write a new review for apartment""" - # pylint: disable=no-member user_id = self.write_apartment_review.cursor.execute( "SELECT user_id FROM Users WHERE username = ?", (username,) ).fetchone()[0] @@ -183,9 +176,7 @@ def write_apartment_review( vote = excluded.vote", (apt_id, user_id, today, comment, vote), ) - # pylint: disable=no-member self.write_apartment_review.connection.commit() - # pylint: disable=no-member ratings_query = self.write_apartment_review.cursor.execute( "SELECT Users.username, Reviews.date_of_rating, Reviews.comment, Reviews.vote \ FROM Users INNER JOIN Reviews \ @@ -199,7 +190,6 @@ def write_apartment_review( @use_database def get_apartments_reviews(self, apt_id: int) -> List[Review]: """Returns a list of apartment reviews""" - # pylint: disable=no-member ratings_query = self.get_apartments_reviews.cursor.execute( "SELECT Users.username, Reviews.date_of_rating, Reviews.comment, Reviews.vote \ FROM Users INNER JOIN Reviews \ From f085bf553bfc5fd4d7e93d2cb85dbeb9ea6c299e Mon Sep 17 00:00:00 2001 From: minhnp2 Date: Mon, 31 Oct 2022 16:24:28 -0500 Subject: [PATCH 28/28] Fix pylint action --- .github/workflows/pylint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 20ed9f6b8..c2c5f3b9e 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -23,3 +23,4 @@ jobs: - name: Analysing the code with pylint run: | pylint $(git ls-files '*.py') + working-directory: src/backend