From ccfd1cd17adac3ea0a07bd104e5d08c6c74a7118 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Sat, 18 Jun 2022 17:17:47 +0300 Subject: [PATCH 01/12] Orm, repository amendings (little changes). Flask app configuration (for future db) Added tests for API --- web-app/adapters/orm.py | 30 ------------------- web-app/adapters/repository.py | 2 +- web-app/config.py | 2 ++ web-app/entrypoints/api/api_initialization.py | 2 ++ web-app/entrypoints/flask_app.py | 8 +++-- web-app/requirements.txt | 1 + web-app/tests/test_api.py | 15 ++++++---- 7 files changed, 22 insertions(+), 38 deletions(-) diff --git a/web-app/adapters/orm.py b/web-app/adapters/orm.py index e7c1826..130f68f 100644 --- a/web-app/adapters/orm.py +++ b/web-app/adapters/orm.py @@ -56,33 +56,3 @@ def start_mappers(): if __name__ == '__main__': start_mappers() -# Draft -# class User(Base): -# __tablename__ = 'user' -# -# id = Column(Integer, primary_key=True, autoincrement=True) -# nickname = Column(Text, unique=True) -# email = Column(Text, unique=True) -# password = Column(Text) -# -# -# class Quiz(Base): -# __tablename__ = 'quiz' -# -# id = Column(Integer, primary_key=True, autoincrement=True) -# user_id = Column(Integer, ForeignKey('user.id')) -# quiz_name = Column(Text) -# -# -# class Question(Base): -# __tablename__ = 'question' -# -# id = Column(Integer, primary_key=True, autoincrement=True) -# quiz_id = Column(Integer, ForeignKey('quiz.id')) -# type = Column(Enum(QuestionTypes)) -# description = Column(Text) -# possible_answers = () -# -# -# class Answers(Base): -# pass diff --git a/web-app/adapters/repository.py b/web-app/adapters/repository.py index 5846708..f8b80cc 100644 --- a/web-app/adapters/repository.py +++ b/web-app/adapters/repository.py @@ -24,7 +24,7 @@ class SqlAlchemyRepository: def __init__(self, session): self.session = session - def add_user(self, user: model.Quiz): + def add_user(self, user: model.User): self.session.add(user) self.session.commit() diff --git a/web-app/config.py b/web-app/config.py index e69de29..cf9f1d3 100644 --- a/web-app/config.py +++ b/web-app/config.py @@ -0,0 +1,2 @@ +def get_postgres_uri(): + return "postgresql://test:test@postgres_db:5432/test" diff --git a/web-app/entrypoints/api/api_initialization.py b/web-app/entrypoints/api/api_initialization.py index 1d23479..099b23b 100644 --- a/web-app/entrypoints/api/api_initialization.py +++ b/web-app/entrypoints/api/api_initialization.py @@ -10,4 +10,6 @@ def api_initialization(app) -> Api: api.add_resource(QuizResource.QuizResource, '/api/user//quiz/') api.add_resource(QuizResource.QuizListResource, '/api/user//quiz') + api.add_resource(QuestionResource.QuestionResource, '/api/quiz//question/') + return api diff --git a/web-app/entrypoints/flask_app.py b/web-app/entrypoints/flask_app.py index 18eb1e2..4c783fa 100644 --- a/web-app/entrypoints/flask_app.py +++ b/web-app/entrypoints/flask_app.py @@ -9,8 +9,12 @@ from entrypoints.api import api_initialization orm.start_mappers() -# get_session = sessionmaker(bind=create_engine(config.get_postgres_uri())) -# initialize_repo(get_session()) +get_session = sessionmaker(bind=create_engine(config.get_postgres_uri())) +repository.initialize_repo(get_session()) app = Flask(__name__) api = api_initialization.api_initialization(app) + +@app.route('/') +def index_page(): + return "

Hello, world

" diff --git a/web-app/requirements.txt b/web-app/requirements.txt index 556c972..287cc0f 100644 --- a/web-app/requirements.txt +++ b/web-app/requirements.txt @@ -13,3 +13,4 @@ SQLAlchemy==1.4.37 waitress==2.1.2 Werkzeug==2.1.2 zipp==3.8.0 +psycopg2==2.9.3 diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index 8eb91b7..bdd2087 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -5,13 +5,12 @@ def test_quiz_post_request(): quiz_post_request_body = { - "id": 500, "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, "name": "Quiz name" } - url = "http://127.0.0.1:8080/api/user/1/quiz" - return post(url, data=quiz_post_request_body).text + url = "http://127.0.0.1:8888/api/user/1/quiz" + return post(url, data=json.dumps(quiz_post_request_body), headers={"Content-Type": "application/json"}).text def test_quiz_get_request(): @@ -25,16 +24,22 @@ def test_user_get_request(): def test_user_post_request(): - url = "http://127.0.0.1:8080/api/user" + url = "http://127.0.0.1:8888/api/user" user_post_request_body = { 'nickname': 'Il', 'email': 'il@k.r', 'password': 'strong_pass' } - post(url, data=user_post_request_body) + return post(url, data=json.dumps(user_post_request_body), headers={"Content-Type": "application/json"}).text + + +def test_question_get_request(): + url = "http://127.0.0.1:8888/api/quiz/1/question/2" + return get(url).text # print(test_quiz_post_request()) # print(test_quiz_get_request()) print(test_user_post_request()) +# print(test_question_get_request()) From bb7639db503045ba0a904e260213446ce6c30ac7 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Mon, 20 Jun 2022 13:57:33 +0300 Subject: [PATCH 02/12] Test_api: Added tests for quizzes and questions Flask app: switches to fake repository --- web-app/entrypoints/flask_app.py | 4 +-- web-app/tests/test_api.py | 42 +++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/web-app/entrypoints/flask_app.py b/web-app/entrypoints/flask_app.py index 4c783fa..1d62f10 100644 --- a/web-app/entrypoints/flask_app.py +++ b/web-app/entrypoints/flask_app.py @@ -9,8 +9,8 @@ from entrypoints.api import api_initialization orm.start_mappers() -get_session = sessionmaker(bind=create_engine(config.get_postgres_uri())) -repository.initialize_repo(get_session()) +# get_session = sessionmaker(bind=create_engine(config.get_postgres_uri())) +# repository.initialize_repo(get_session()) app = Flask(__name__) api = api_initialization.api_initialization(app) diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index bdd2087..aaff181 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -1,9 +1,9 @@ import json -from requests import get, post +from requests import get, post, delete -def test_quiz_post_request(): +def test_quizzes_post_request(): quiz_post_request_body = { "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, "name": "Quiz name" @@ -14,10 +14,39 @@ def test_quiz_post_request(): def test_quiz_get_request(): - url = "http://127.0.0.1:8080/api/user/1/quiz" + url = "http://127.0.0.1:8080/api/user/1/quiz/1" return get(url).text +def test_quiz_delete_request(): + quiz_post_request_body = { + "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, + "name": "Quiz name" + } + url = "http://127.0.0.1:8888/api/user/1/quiz" + + post(url, data=json.dumps(quiz_post_request_body), headers={"Content-Type": "application/json"}) + + return delete(url + "/1").text + + +def test_quizzes_get_request(): + url = "http://127.0.0.1:8888/api/user/1/quiz" + + return get(url).text + + +def test_quiz_put_request(): + url = "http://127.0.0.1:8888/api/user/1/quiz" + + quiz_put_request_body = { + "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, + "name": "Quiz name" + } + + return post(url, json=json.dumps(quiz_put_request_body), headers={"Content-Type": "application/json"}).text + + def test_user_get_request(): url = "http://127.0.0.1:8080/api/user/1" return get(url).text @@ -39,6 +68,13 @@ def test_question_get_request(): return get(url).text +def test_question_post_request(): + url = "http://127.0.0.1:8888/api/quiz/1/question/2" + + question_post_request_body = {"answers": ["Incorrect answer", "Correct answer"]} + return post(url, data=json.dumps(question_post_request_body), headers={"Content-Type": "application/json"}).text + + # print(test_quiz_post_request()) # print(test_quiz_get_request()) print(test_user_post_request()) From 497e75e97d6f66a6974ddd90742a7ff199474090 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Tue, 21 Jun 2022 16:12:31 +0300 Subject: [PATCH 03/12] Webhandlers for routes: '/' '/lobby' '/login' '/register' Other changes: flask_app: webhandlers for pages and tests model: Added 'to_dict' operation repository: Added debug operations ('get_users') orm: Some tables corrected Creation of tables tests: test_api - small changes test_orm - first test for orm (user_creation) --- web-app/adapters/orm.py | 17 +++++----- web-app/adapters/repository.py | 6 +++- web-app/domain/model.py | 35 +++++++++++++++++---- web-app/entrypoints/flask_app.py | 54 ++++++++++++++++++++++++++++---- web-app/tests/test_api.py | 14 +++++---- web-app/tests/test_orm.py | 16 ++++++++++ 6 files changed, 115 insertions(+), 27 deletions(-) diff --git a/web-app/adapters/orm.py b/web-app/adapters/orm.py index 130f68f..f48eafb 100644 --- a/web-app/adapters/orm.py +++ b/web-app/adapters/orm.py @@ -4,11 +4,6 @@ from domain import model -# Заглушка -# To distinguish between question types -# class QuestionTypes: -# pass - # Base = declarative_base() mapper_registry = registry() @@ -30,20 +25,21 @@ # Table for quizzes quiz_table = Table("quizzes", metadata, - Column('user_id', Integer, ForeignKey('user.id')), + Column('id', Integer, primary_key=True, autoincrement=True), + Column('user_id', Integer, ForeignKey('users.id')), Column('name', Text)) # Table for questions question_table = Table("questions", metadata, Column('id', Integer, primary_key=True, autoincrement=True), - Column('quiz_id', Integer, ForeignKey('quiz.id')), + Column('quiz_id', Integer, ForeignKey('quizzes.id')), Column('type', Enum(model.QuestionTypes)), Column('description', Text)) # Table for answers answer_table = Table("answers", metadata, Column('id', Integer, primary_key=True, autoincrement=True), - Column('question_id', ForeignKey('question.id')), + Column('question_id', ForeignKey('questions.id')), Column('text', Text), Column('correct_answer', Boolean)) @@ -54,5 +50,10 @@ def start_mappers(): answer_mapper = mapper_registry.map_imperatively(model.Answer, answer_table) +def create_all(engine): + metadata.bind = engine + metadata.create_all() + + if __name__ == '__main__': start_mappers() diff --git a/web-app/adapters/repository.py b/web-app/adapters/repository.py index f8b80cc..9a89147 100644 --- a/web-app/adapters/repository.py +++ b/web-app/adapters/repository.py @@ -29,7 +29,7 @@ def add_user(self, user: model.User): self.session.commit() def get_user(self, user_id): - return self.session.query(model.User).filter_by(id=user_id).one() + return self.session.query(model.User).filter_by(id=user_id).one().__dict__() # Working with quizzes def add_quiz(self, quiz: model.Quiz): @@ -73,6 +73,10 @@ def get_answer(self, answer_id): def list_answers(self, question_id): return self.session.query(model.Answer).filter_by(question_id=question_id) + # -- for test + def get_users(self): + return self.session.query(model.User).all() + # Testing Repository class FakeRepository: diff --git a/web-app/domain/model.py b/web-app/domain/model.py index 7418dfb..01eecd6 100644 --- a/web-app/domain/model.py +++ b/web-app/domain/model.py @@ -18,9 +18,17 @@ class Answer: - def __init__(self, text: QuestionAnswer, is_correct: bool): + def __init__(self, question_id: int, text: QuestionAnswer, correct_answer: bool): self.text = text - self.is_correct = is_correct + self.correct_answer = correct_answer + self.question_id = question_id + + def to_dict(self): + return { + "text": self.text, + "correct_answer": self.correct_answer, + "question_id": self.question_id + } # Possible types of question in a quiz @@ -37,10 +45,12 @@ def __init__(self, question_type: QuestionTypes, text: QuestionDescription, quiz self.text = text self.quiz_id = quiz_id - # [maybe deleted] - # return next question - def next(self): - pass + def to_dict(self): + return { + "quiz_id": self.quiz_id, + "text": self.text, + "question_type": self.question_type + } # Quiz class, @@ -49,6 +59,12 @@ def __init__(self, quiz_name: QuizName, user_id: int = 0): self.quiz_name = quiz_name self.user_id = user_id + def to_dict(self): + return { + "user_id": self.user_id, + "quiz_name": self.quiz_name + } + # User class, to manage stored in db information through nickname/email class User: @@ -56,3 +72,10 @@ def __init__(self, nickname: NickName, email: Email, password: Password): self.nickname = nickname self.email = email self.password = password + + def to_dict(self): + return { + "nickname": self.nickname, + "email": self.email, + "password": self.password + } diff --git a/web-app/entrypoints/flask_app.py b/web-app/entrypoints/flask_app.py index 1d62f10..47a947d 100644 --- a/web-app/entrypoints/flask_app.py +++ b/web-app/entrypoints/flask_app.py @@ -1,4 +1,6 @@ -from flask import Flask, request +import json + +from flask import Flask, request, render_template from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker @@ -7,14 +9,54 @@ from adapters import orm, repository from service_layer import services from entrypoints.api import api_initialization +from tests import test_orm +engine = create_engine(config.get_postgres_uri()) +get_session = sessionmaker(bind=engine) +repository.initialize_repo(get_session()) orm.start_mappers() -# get_session = sessionmaker(bind=create_engine(config.get_postgres_uri())) -# repository.initialize_repo(get_session()) -app = Flask(__name__) +orm.create_all(engine) +app = Flask(__name__, template_folder='./../static/templates') api = api_initialization.api_initialization(app) @app.route('/') -def index_page(): - return "

Hello, world

" +def main_page(): + return render_template("main_page.html") + + +@app.route('/login') +def login_page(): + return render_template('authorization_page.html') + + +@app.route('/register') +def registration_page(): + return render_template("registration_page.html") + + +# [will be deleted] +@app.route('/lobby') +def lobby_page(): + return render_template("waiting_hall.html") + + +# For tests through docker +@app.route('/tests') +def test(): + repo = repository.get_repo() + + new_items = list() + for item in repo.get_users(): + new_items.append(item.to_dict()) + + return json.dumps({"items": new_items}) + + +@app.route('/tests/user_creation') +def test_user_creation( ): + repo = repository.get_repo() + + test_orm.test_add_user(repo) + + return "

OK

" diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index aaff181..9b94f6e 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -10,12 +10,13 @@ def test_quizzes_post_request(): } url = "http://127.0.0.1:8888/api/user/1/quiz" - return post(url, data=json.dumps(quiz_post_request_body), headers={"Content-Type": "application/json"}).text + assert post(url, data=json.dumps(quiz_post_request_body), + headers={"Content-Type": "application/json"}).status_code == 200 def test_quiz_get_request(): - url = "http://127.0.0.1:8080/api/user/1/quiz/1" - return get(url).text + url = "http://127.0.0.1:8888/api/user/1/quiz/1" + assert get(url) == 200 def test_quiz_delete_request(): @@ -27,7 +28,7 @@ def test_quiz_delete_request(): post(url, data=json.dumps(quiz_post_request_body), headers={"Content-Type": "application/json"}) - return delete(url + "/1").text + assert delete(url + "/1").status_code == 200 def test_quizzes_get_request(): @@ -48,7 +49,7 @@ def test_quiz_put_request(): def test_user_get_request(): - url = "http://127.0.0.1:8080/api/user/1" + url = "http://127.0.0.1:8888/api/user/1" return get(url).text @@ -77,5 +78,6 @@ def test_question_post_request(): # print(test_quiz_post_request()) # print(test_quiz_get_request()) -print(test_user_post_request()) +# print(test_user_post_request()) # print(test_question_get_request()) +print(test_user_get_request()) diff --git a/web-app/tests/test_orm.py b/web-app/tests/test_orm.py index e69de29..ec9a9ad 100644 --- a/web-app/tests/test_orm.py +++ b/web-app/tests/test_orm.py @@ -0,0 +1,16 @@ +from adapters import repository +from domain import model + + +def test_add_user(repo): + new_user = model.User("Fedor", "fedya@gg.ru", "CoolPass") + + repo.add_user(new_user) + db_user = repo.session.query(model.User).filter_by(nickname=new_user.nickname).one() + + assert db_user == new_user + + repo.session.query(model.User).filter_by(nickname=new_user.nickname).delete() + repo.session.commit() + + print("OK") From 8b912ccc2b485ad59aaca16a2e42a65cf51a29b3 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Thu, 23 Jun 2022 20:21:27 +0300 Subject: [PATCH 04/12] Structure of Model changed: user: -nickname, +name, +surname question: +time_limit --- web-app/domain/model.py | 21 ++++++++++++------- .../entrypoints/api/resource/UserResource.py | 3 ++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/web-app/domain/model.py b/web-app/domain/model.py index 01eecd6..c9add43 100644 --- a/web-app/domain/model.py +++ b/web-app/domain/model.py @@ -4,7 +4,8 @@ # Types declaration # For user -NickName = NewType('NickName', str) +Name = NewType('Name', str) +Surname = NewType('Surname', str) Email = NewType('Email', str) Password = NewType('Password', str) @@ -33,23 +34,25 @@ def to_dict(self): # Possible types of question in a quiz class QuestionTypes(enum.Enum): - poll = "polls" + poll = "poll" quiz = "quiz" # Question class, to store information about question: # question type, description, possible answers, and correct answers class Question: - def __init__(self, question_type: QuestionTypes, text: QuestionDescription, quiz_id: int): + def __init__(self, question_type: QuestionTypes, text: QuestionDescription, quiz_id: int, time_limit: int): self.question_type = question_type self.text = text self.quiz_id = quiz_id + self.time = time_limit def to_dict(self): return { "quiz_id": self.quiz_id, "text": self.text, - "question_type": self.question_type + "question_type": self.question_type, + "time": self.time } @@ -62,20 +65,22 @@ def __init__(self, quiz_name: QuizName, user_id: int = 0): def to_dict(self): return { "user_id": self.user_id, - "quiz_name": self.quiz_name + "name": self.quiz_name } # User class, to manage stored in db information through nickname/email class User: - def __init__(self, nickname: NickName, email: Email, password: Password): - self.nickname = nickname + def __init__(self, name: Name, surname: Surname, email: Email, password: Password): + self.name = name + self.surname = surname self.email = email self.password = password def to_dict(self): return { - "nickname": self.nickname, + "name": self.name, + "surname": self.surname, "email": self.email, "password": self.password } diff --git a/web-app/entrypoints/api/resource/UserResource.py b/web-app/entrypoints/api/resource/UserResource.py index 2e65f1e..239fcfd 100644 --- a/web-app/entrypoints/api/resource/UserResource.py +++ b/web-app/entrypoints/api/resource/UserResource.py @@ -4,7 +4,8 @@ from domain.model import User parser = reqparse.RequestParser() -parser.add_argument('nickname', required=True) +parser.add_argument('name', required=True) +parser.add_argument('surname', required=True) parser.add_argument('email', required=True) parser.add_argument('password', required=True) From e14411b43a23c6b0ddb96ec3b1150395da9fc719 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Thu, 23 Jun 2022 20:34:08 +0300 Subject: [PATCH 05/12] Quiz and User Resources changes: \nAll requests require user credentials (email, password) --- .../entrypoints/api/resource/QuizResource.py | 22 ++++++++++++++----- .../entrypoints/api/resource/UserResource.py | 20 ++++++++++++----- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index 7f63be5..36479cb 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -3,19 +3,29 @@ from adapters.repository import SqlAlchemyRepository, get_repo from domain.model import Quiz -parser = reqparse.RequestParser() -parser.add_argument('name', required=True) -parser.add_argument('questions', required=True) +quiz_creation_parser = reqparse.RequestParser() +quiz_creation_parser.add_argument('name', required=True) +quiz_creation_parser.add_argument('questions', required=True) +quiz_creation_parser.add_argument('email', required=True) +quiz_creation_parser.add_argument('password', required=True) + +quiz_get_parser = reqparse.RequestParser() +quiz_get_parser.add_argument('email', required=True) +quiz_get_parser.add_argument('password', required=True) + + +# TODO: checking credentials class QuizResource(Resource): def get(self, quiz_id): + args = quiz_get_parser.parse_args() repo = get_repo() quiz = repo.get_quiz(quiz_id) return quiz def put(self, quiz_id): - args = parser.parse_args() + args = quiz_creation_parser.parse_args() repo = get_repo() repo.put_quiz(Quiz(quiz_id=quiz_id, name=args['name'], user_id=args['user_id'])) @@ -24,6 +34,7 @@ def put(self, quiz_id): return jsonify({"success": "OK"}) def delete(self, quiz_id): + args = quiz_get_parser.parse_args() repo = get_repo() quiz = repo.delete_quiz(quiz_id) @@ -34,12 +45,13 @@ def delete(self, quiz_id): class QuizListResource(Resource): def get(self, user_id): + args = quiz_get_parser.parse_args() repo = get_repo() quizzes = repo.list_quizzes(user_id) return quizzes def post(self, user_id): - args = parser.parse_args() + args = quiz_creation_parser.parse_args() repo = get_repo() repo.add_quiz(Quiz(quiz_name=args['name'], user_id=user_id)) diff --git a/web-app/entrypoints/api/resource/UserResource.py b/web-app/entrypoints/api/resource/UserResource.py index 239fcfd..7d4cb52 100644 --- a/web-app/entrypoints/api/resource/UserResource.py +++ b/web-app/entrypoints/api/resource/UserResource.py @@ -3,11 +3,15 @@ from adapters.repository import SqlAlchemyRepository, get_repo from domain.model import User -parser = reqparse.RequestParser() -parser.add_argument('name', required=True) -parser.add_argument('surname', required=True) -parser.add_argument('email', required=True) -parser.add_argument('password', required=True) +registration_parser = reqparse.RequestParser() +registration_parser.add_argument('name', required=True) +registration_parser.add_argument('surname', required=True) +registration_parser.add_argument('email', required=True) +registration_parser.add_argument('password', required=True) + +login_parser = reqparse.RequestParser() +login_parser.add_argument('email', required=True) +login_parser.add_argument('password', required=True) def abort_if_user_not_found(user_id): @@ -19,6 +23,10 @@ def abort_if_user_not_found(user_id): class UserResource(Resource): def get(self, user_id): + args = login_parser.parse_args() + + # check_user(user_from(args), user_id) TODO: Checking user credentials with db info and requested user page + abort_if_user_not_found(user_id) user = get_repo().get_user(user_id) return user @@ -26,6 +34,6 @@ def get(self, user_id): class UserRegistrationResource(Resource): def post(self): - args = parser.parse_args() + args = registration_parser.parse_args() repo = get_repo() repo.add_user(User(nickname=args['nickname'], email=args['email'], password=args['password'])) From ccfe5bde085c5517b2100bba377f8cc82ca3d9f7 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Sat, 25 Jun 2022 13:04:45 +0300 Subject: [PATCH 06/12] Beggining of Front-end and Back-end sides connection Amendings of Resources of API Creating algorithms to create user, quiz, and add them to DB Implemented support function to process data in services --- web-app/adapters/orm.py | 11 ++- web-app/adapters/repository.py | 18 ++++- .../entrypoints/api/resource/QuizResource.py | 65 +++++++++++++++--- .../entrypoints/api/resource/UserResource.py | 45 +++++++++--- web-app/entrypoints/flask_app.py | 29 +++++++- web-app/requirements.txt | 1 + web-app/service_layer/services.py | 68 +++++++++++++++++++ web-app/tests/test_api.py | 8 ++- 8 files changed, 216 insertions(+), 29 deletions(-) diff --git a/web-app/adapters/orm.py b/web-app/adapters/orm.py index f48eafb..69596f1 100644 --- a/web-app/adapters/orm.py +++ b/web-app/adapters/orm.py @@ -4,7 +4,6 @@ from domain import model - # Base = declarative_base() mapper_registry = registry() metadata = mapper_registry.metadata @@ -19,7 +18,8 @@ # Table for users user_table = Table("users", metadata, Column('id', Integer, primary_key=True, autoincrement=True), - Column('nickname', Text, unique=True), + Column('name', Text), + Column('surname', Text), Column('email', Text, unique=True), Column('password', Text)) @@ -34,7 +34,8 @@ Column('id', Integer, primary_key=True, autoincrement=True), Column('quiz_id', Integer, ForeignKey('quizzes.id')), Column('type', Enum(model.QuestionTypes)), - Column('description', Text)) + Column('description', Text), + Column('time', Integer)) # Table for answers answer_table = Table("answers", metadata, @@ -55,5 +56,9 @@ def create_all(engine): metadata.create_all() +def delete_all(engine): + metadata.drop_all(engine) + + if __name__ == '__main__': start_mappers() diff --git a/web-app/adapters/repository.py b/web-app/adapters/repository.py index 9a89147..eb7951f 100644 --- a/web-app/adapters/repository.py +++ b/web-app/adapters/repository.py @@ -28,13 +28,18 @@ def add_user(self, user: model.User): self.session.add(user) self.session.commit() - def get_user(self, user_id): - return self.session.query(model.User).filter_by(id=user_id).one().__dict__() + def get_user_by_id(self, user_id): + return self.session.query(model.User).filter_by(id=user_id).one() + + def get_user_by_email(self, email): + return self.session.query(model.User).filter_by(email=email).one() # Working with quizzes - def add_quiz(self, quiz: model.Quiz): + def add_quiz(self, quiz: model.Quiz) -> int: self.session.add(quiz) self.session.commit() + self.session.flush() + return quiz.id def get_quiz(self, quiz_id): return self.session.query(model.Quiz).filter_by(id=quiz_id).one() @@ -55,6 +60,8 @@ def list_quizzes(self, user_id): def add_question(self, question: model.Question): self.session.add(question) self.session.commit() + self.session.flush() + return question.id def get_question(self, question_id): return self.session.query(model.Question).filter_by(id=question_id).one() @@ -66,6 +73,8 @@ def list_questions(self, quiz_id): def add_answer(self, answer: model.Answer): self.session.add(answer) self.session.commit() + self.session.flush() + return answer.id def get_answer(self, answer_id): return self.session.query(model.Answer).filter_by(id=answer_id) @@ -77,6 +86,9 @@ def list_answers(self, question_id): def get_users(self): return self.session.query(model.User).all() + def get_quizzes(self): + return self.session.query(model.Quiz).all() + # Testing Repository class FakeRepository: diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index 36479cb..3e514d3 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -1,7 +1,8 @@ -from flask import jsonify +from flask import jsonify, make_response from flask_restful import reqparse, abort, Resource from adapters.repository import SqlAlchemyRepository, get_repo from domain.model import Quiz +from service_layer import services quiz_creation_parser = reqparse.RequestParser() quiz_creation_parser.add_argument('name', required=True) @@ -14,18 +15,51 @@ quiz_get_parser.add_argument('password', required=True) -# TODO: checking credentials +def check_for_credentials(args: dict): + email, password = services.email_and_pass_from(args) + + repo = get_repo() + user = repo.get_user_by_email(email) + + if not user: + abort(404, message="Unauthorized") + + if not user.password == password: + abort(404, message="Unauthorized") + + +def abort_if_user_not_found(user_id: int, args: dict): + email, password = services.email_and_pass_from(args) + + repo = get_repo() + user = repo.get_user_by_id(user_id) + if not user or user.email != email or user.password != password: + abort(404, message="Unauthorized") + + +def abort_if_quiz_not_found(quiz_id): + repo = get_repo() + quiz = repo.get_quiz(quiz_id) + + if not quiz: + abort(404, message="Quiz is not found") class QuizResource(Resource): def get(self, quiz_id): + abort_if_quiz_not_found(quiz_id) + args = quiz_get_parser.parse_args() + check_for_credentials(args) + repo = get_repo() quiz = repo.get_quiz(quiz_id) - return quiz + return make_response(quiz.to_dict(), 200) def put(self, quiz_id): args = quiz_creation_parser.parse_args() + check_for_credentials(args) + repo = get_repo() repo.put_quiz(Quiz(quiz_id=quiz_id, name=args['name'], user_id=args['user_id'])) @@ -35,28 +69,41 @@ def put(self, quiz_id): def delete(self, quiz_id): args = quiz_get_parser.parse_args() + check_for_credentials(args) + repo = get_repo() quiz = repo.delete_quiz(quiz_id) # TODO: Deleting questions + answers - return jsonify({"success": "OK"}) + return make_response("Quiz deleted", 200) class QuizListResource(Resource): def get(self, user_id): args = quiz_get_parser.parse_args() + + abort_if_user_not_found(user_id, args) + repo = get_repo() quizzes = repo.list_quizzes(user_id) - return quizzes + return make_response({"quizzes": [quiz.to_dict() for quiz in quizzes]}, 200) def post(self, user_id): args = quiz_creation_parser.parse_args() + + abort_if_user_not_found(user_id, args) + repo = get_repo() - repo.add_quiz(Quiz(quiz_name=args['name'], user_id=user_id)) + quiz = services.quiz_from(args, user_id) + quiz_id = repo.add_quiz(quiz) - # TODO: parse questions and answers to add them to SQL - # + questions_and_answers = services.questions_of_quiz_creation_from(args, quiz_id) - return jsonify({"success": "OK"}) + for (question, answers_args) in questions_and_answers: + question_id = repo.add_question(question) + for answers in services.answers_for_one_question_of_quiz_creation_from(answers_args, question_id): + repo.add_answer(answers) + + return make_response("Quiz created", 201) diff --git a/web-app/entrypoints/api/resource/UserResource.py b/web-app/entrypoints/api/resource/UserResource.py index 7d4cb52..df53426 100644 --- a/web-app/entrypoints/api/resource/UserResource.py +++ b/web-app/entrypoints/api/resource/UserResource.py @@ -1,7 +1,9 @@ -from flask import jsonify +import flask +from flask import jsonify, make_response from flask_restful import reqparse, abort, Resource from adapters.repository import SqlAlchemyRepository, get_repo from domain.model import User +from service_layer import services registration_parser = reqparse.RequestParser() registration_parser.add_argument('name', required=True) @@ -14,26 +16,49 @@ login_parser.add_argument('password', required=True) -def abort_if_user_not_found(user_id): +def check_for_credentials(args: dict): + email, password = services.email_and_pass_from(args) + repo = get_repo() - user = repo.get_user(user_id) + user = repo.get_user_by_email(email) + if not user: - abort(404, message=f"User {user_id} not found") + abort(404, message="Unauthorized") + + if not user.password == password: + abort(404, message="Unauthorized") + + +def abort_if_user_not_found(user_id: int, args: dict): + email, password = services.email_and_pass_from(args) + + repo = get_repo() + user = repo.get_user_by_id(user_id) + if not user or user.email != email or user.password != password: + abort(404, message="Unauthorized") class UserResource(Resource): def get(self, user_id): args = login_parser.parse_args() + abort_if_user_not_found(user_id, args) + user = get_repo().get_user_by_id(user_id) + return make_response("User exists", 200) - # check_user(user_from(args), user_id) TODO: Checking user credentials with db info and requested user page - abort_if_user_not_found(user_id) - user = get_repo().get_user(user_id) - return user +class UserRegistrationResource(Resource): + def get(self): + args = login_parser.parse_args() + check_for_credentials(args) + + return make_response("User exists", 200) -class UserRegistrationResource(Resource): def post(self): args = registration_parser.parse_args() repo = get_repo() - repo.add_user(User(nickname=args['nickname'], email=args['email'], password=args['password'])) + + user = services.user_from(args) + + repo.add_user(user) + return make_response("User created", 201) diff --git a/web-app/entrypoints/flask_app.py b/web-app/entrypoints/flask_app.py index 47a947d..62e9314 100644 --- a/web-app/entrypoints/flask_app.py +++ b/web-app/entrypoints/flask_app.py @@ -1,6 +1,7 @@ import json from flask import Flask, request, render_template +from flask_cors import CORS from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker @@ -15,9 +16,14 @@ get_session = sessionmaker(bind=engine) repository.initialize_repo(get_session()) orm.start_mappers() + +# To delete all tables +# orm.delete_all(engine) + orm.create_all(engine) app = Flask(__name__, template_folder='./../static/templates') api = api_initialization.api_initialization(app) +cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) @app.route('/') @@ -42,7 +48,7 @@ def lobby_page(): # For tests through docker -@app.route('/tests') +@app.route('/tests/users') def test(): repo = repository.get_repo() @@ -54,9 +60,28 @@ def test(): @app.route('/tests/user_creation') -def test_user_creation( ): +def test_user_creation(): repo = repository.get_repo() test_orm.test_add_user(repo) return "

OK

" + + +@app.route('/tests/quizzes') +def test_quizzes_list(): + repo = repository.get_repo() + + new_items = list() + for item in repo.get_quizzes(): + new_items.append(item.to_dict()) + + return json.dumps({"items": new_items}) + + +@app.route('/tests/get_user_by_email/') +def test_user_by_email(email): + repo = repository.get_repo() + + user = repo.get_user_by_email(email) + return json.dumps({"user": user.to_dict()}) diff --git a/web-app/requirements.txt b/web-app/requirements.txt index 287cc0f..60e6639 100644 --- a/web-app/requirements.txt +++ b/web-app/requirements.txt @@ -14,3 +14,4 @@ waitress==2.1.2 Werkzeug==2.1.2 zipp==3.8.0 psycopg2==2.9.3 +flask-cors==3.0.10 diff --git a/web-app/service_layer/services.py b/web-app/service_layer/services.py index e69de29..ad342f6 100644 --- a/web-app/service_layer/services.py +++ b/web-app/service_layer/services.py @@ -0,0 +1,68 @@ +from domain import model + + +# user_from gets args and creates User class +# user_args should contain following: +# name +# surname +# email +# password +def user_from(user_args: dict) -> model.User: + user_name = user_args['name'] + user_surname = user_args['surname'] + user_email = user_args['email'] + user_password = user_args['password'] + + return model.User( + name=user_name, + surname=user_surname, + email=user_email, + password=user_password + ) + + +def quiz_from(quiz_args: dict, user_id: int) -> model.Quiz: + quiz = model.Quiz( + quiz_name=quiz_args["name"], + user_id=user_id + ) + return quiz + + +def questions_of_quiz_creation_from(quiz_args: dict, quiz_id: int) -> [(model.Question, dict)]: + questions = [ + (model.Question( + question_type=question_args["question_type"], + text=question_args["text"], + time_limit=question_args["time"], + quiz_id=quiz_id + ), question_args["answers"]) + for question_args in quiz_args["questions"] + ] + + return questions + + +def answers_for_one_question_of_quiz_creation_from(answers_args: dict, question_id) -> [model.Answer]: + answers = [ + model.Answer( + text=answer_args["text"], + correct_answer=answer_args["correct_answer"], + question_id=question_id + ) + for answer_args in answers_args + ] + return answers + + +def email_and_pass_from(args: dict) -> (str, str): + return args['email'], args['password'] + + +def user_from(user_args) -> model.User: + return model.User( + name=user_args["name"], + surname=user_args["surname"], + email=user_args["email"], + password=user_args["password"] + ) diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index 9b94f6e..0d84e45 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -49,8 +49,12 @@ def test_quiz_put_request(): def test_user_get_request(): - url = "http://127.0.0.1:8888/api/user/1" - return get(url).text + url = "http://127.0.0.1:8888/api/user" + + body = {"email": "vikochka_kruk@mail.ru", + "password": "123"} + + return get(url, json=json.dumps(body)).text def test_user_post_request(): From 2a4b573f72ad9f46e275828d169fd719f931f6c1 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Mon, 27 Jun 2022 21:00:56 +0300 Subject: [PATCH 07/12] Qui Resource Quiz creation process is working services: Algorithm for quiz collecting model: small amendings UserResource: Rename of api resources to UserLoginResource and UserRegistrationResource api_initialization: changes of login and registration urls test_api: changing test for quiz creations --- web-app/adapters/orm.py | 3 +- web-app/domain/model.py | 6 +-- web-app/entrypoints/api/api_initialization.py | 4 +- .../entrypoints/api/resource/QuizResource.py | 17 +++---- .../entrypoints/api/resource/UserResource.py | 28 +++++------- web-app/service_layer/services.py | 8 ++++ web-app/tests/test_api.py | 45 ++++++++++++++----- 7 files changed, 69 insertions(+), 42 deletions(-) diff --git a/web-app/adapters/orm.py b/web-app/adapters/orm.py index 69596f1..bfd3651 100644 --- a/web-app/adapters/orm.py +++ b/web-app/adapters/orm.py @@ -33,7 +33,7 @@ question_table = Table("questions", metadata, Column('id', Integer, primary_key=True, autoincrement=True), Column('quiz_id', Integer, ForeignKey('quizzes.id')), - Column('type', Enum(model.QuestionTypes)), + Column('type', Text), Column('description', Text), Column('time', Integer)) @@ -49,6 +49,7 @@ def start_mappers(): user_mapper = mapper_registry.map_imperatively(model.User, user_table) question_mapper = mapper_registry.map_imperatively(model.Question, question_table) answer_mapper = mapper_registry.map_imperatively(model.Answer, answer_table) + quiz_mapper = mapper_registry.map_imperatively(model.Quiz, quiz_table) def create_all(engine): diff --git a/web-app/domain/model.py b/web-app/domain/model.py index c9add43..b20ada2 100644 --- a/web-app/domain/model.py +++ b/web-app/domain/model.py @@ -41,7 +41,7 @@ class QuestionTypes(enum.Enum): # Question class, to store information about question: # question type, description, possible answers, and correct answers class Question: - def __init__(self, question_type: QuestionTypes, text: QuestionDescription, quiz_id: int, time_limit: int): + def __init__(self, question_type: str, text: QuestionDescription, quiz_id: int, time_limit: int): self.question_type = question_type self.text = text self.quiz_id = quiz_id @@ -59,13 +59,13 @@ def to_dict(self): # Quiz class, class Quiz: def __init__(self, quiz_name: QuizName, user_id: int = 0): - self.quiz_name = quiz_name + self.name = quiz_name self.user_id = user_id def to_dict(self): return { "user_id": self.user_id, - "name": self.quiz_name + "name": self.name } diff --git a/web-app/entrypoints/api/api_initialization.py b/web-app/entrypoints/api/api_initialization.py index 099b23b..3e49f8b 100644 --- a/web-app/entrypoints/api/api_initialization.py +++ b/web-app/entrypoints/api/api_initialization.py @@ -4,8 +4,8 @@ def api_initialization(app) -> Api: api = Api(app) - api.add_resource(UserResource.UserResource, '/api/user/') - api.add_resource(UserResource.UserRegistrationResource, '/api/user') + api.add_resource(UserResource.UserLoginResource, '/api/user/login') + api.add_resource(UserResource.UserRegistrationResource, '/api/user/register') api.add_resource(QuizResource.QuizResource, '/api/user//quiz/') api.add_resource(QuizResource.QuizListResource, '/api/user//quiz') diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index 3e514d3..49e4ea7 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -6,9 +6,7 @@ quiz_creation_parser = reqparse.RequestParser() quiz_creation_parser.add_argument('name', required=True) -quiz_creation_parser.add_argument('questions', required=True) -quiz_creation_parser.add_argument('email', required=True) -quiz_creation_parser.add_argument('password', required=True) +quiz_creation_parser.add_argument('questions', type=dict, action="append", required=True) quiz_get_parser = reqparse.RequestParser() quiz_get_parser.add_argument('email', required=True) @@ -28,12 +26,10 @@ def check_for_credentials(args: dict): abort(404, message="Unauthorized") -def abort_if_user_not_found(user_id: int, args: dict): - email, password = services.email_and_pass_from(args) - +def abort_if_user_not_found(user_id: int): repo = get_repo() user = repo.get_user_by_id(user_id) - if not user or user.email != email or user.password != password: + if not user: abort(404, message="Unauthorized") @@ -83,7 +79,7 @@ class QuizListResource(Resource): def get(self, user_id): args = quiz_get_parser.parse_args() - abort_if_user_not_found(user_id, args) + abort_if_user_not_found(user_id) repo = get_repo() quizzes = repo.list_quizzes(user_id) @@ -91,8 +87,8 @@ def get(self, user_id): def post(self, user_id): args = quiz_creation_parser.parse_args() - - abort_if_user_not_found(user_id, args) + # return make_response(str(args), 404) + abort_if_user_not_found(user_id) repo = get_repo() @@ -100,6 +96,7 @@ def post(self, user_id): quiz_id = repo.add_quiz(quiz) questions_and_answers = services.questions_of_quiz_creation_from(args, quiz_id) + # return make_response(str(questions_and_answers), 404) for (question, answers_args) in questions_and_answers: question_id = repo.add_question(question) diff --git a/web-app/entrypoints/api/resource/UserResource.py b/web-app/entrypoints/api/resource/UserResource.py index df53426..d551d80 100644 --- a/web-app/entrypoints/api/resource/UserResource.py +++ b/web-app/entrypoints/api/resource/UserResource.py @@ -5,18 +5,18 @@ from domain.model import User from service_layer import services +login_parser = reqparse.RequestParser() +login_parser.add_argument('email', required=True) +login_parser.add_argument('password', required=True) + registration_parser = reqparse.RequestParser() registration_parser.add_argument('name', required=True) registration_parser.add_argument('surname', required=True) registration_parser.add_argument('email', required=True) registration_parser.add_argument('password', required=True) -login_parser = reqparse.RequestParser() -login_parser.add_argument('email', required=True) -login_parser.add_argument('password', required=True) - -def check_for_credentials(args: dict): +def check_for_credentials(args: dict) -> int: email, password = services.email_and_pass_from(args) repo = get_repo() @@ -28,6 +28,8 @@ def check_for_credentials(args: dict): if not user.password == password: abort(404, message="Unauthorized") + return user.id + def abort_if_user_not_found(user_id: int, args: dict): email, password = services.email_and_pass_from(args) @@ -38,22 +40,16 @@ def abort_if_user_not_found(user_id: int, args: dict): abort(404, message="Unauthorized") -class UserResource(Resource): - def get(self, user_id): +class UserLoginResource(Resource): + def post(self): args = login_parser.parse_args() - abort_if_user_not_found(user_id, args) - user = get_repo().get_user_by_id(user_id) - return make_response("User exists", 200) + user_id = check_for_credentials(args) -class UserRegistrationResource(Resource): - def get(self): - args = login_parser.parse_args() - - check_for_credentials(args) + return {"user_id": user_id} - return make_response("User exists", 200) +class UserRegistrationResource(Resource): def post(self): args = registration_parser.parse_args() repo = get_repo() diff --git a/web-app/service_layer/services.py b/web-app/service_layer/services.py index ad342f6..cc207d6 100644 --- a/web-app/service_layer/services.py +++ b/web-app/service_layer/services.py @@ -39,6 +39,14 @@ def questions_of_quiz_creation_from(quiz_args: dict, quiz_id: int) -> [(model.Qu ), question_args["answers"]) for question_args in quiz_args["questions"] ] + # return quiz_args["questions"] + # for question_args in quiz_args["questions"]: + # questions.append((model.Question( + # question_type=question_args["question_type"], + # text=question_args["text"], + # time_limit=question_args["time"], + # quiz_id=quiz_id), + # question_args["answers"])) return questions diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index 0d84e45..067c0c3 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -4,14 +4,37 @@ def test_quizzes_post_request(): + answers1 = [ + {"text": "Hello", "correct_answer": True}, + {"text": "World", "correct_answer": False} + ] + question1 = {"question_type": "poll", + "text": "questions1", + "time": 60, + "answers": answers1} + + answers2 = [ + {"text": "Hello", "correct_answer": True}, + {"text": "World", "correct_answer": False} + ] + + question2 = { + "question_type": "quiz", + "text": "questions2", + "time": 15, + "answers": answers2 + } + quiz_post_request_body = { - "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, + "questions": [question1, question2], "name": "Quiz name" } + # print(type(quiz_post_request_body["questions"])) + url = "http://127.0.0.1:8888/api/user/1/quiz" - assert post(url, data=json.dumps(quiz_post_request_body), - headers={"Content-Type": "application/json"}).status_code == 200 + return post(url, json=quiz_post_request_body, + headers={"Content-Type": "application/json"}).text def test_quiz_get_request(): @@ -48,13 +71,13 @@ def test_quiz_put_request(): return post(url, json=json.dumps(quiz_put_request_body), headers={"Content-Type": "application/json"}).text -def test_user_get_request(): - url = "http://127.0.0.1:8888/api/user" +def test_user_login_request(): + url = "http://127.0.0.1:8888/api/user/login" body = {"email": "vikochka_kruk@mail.ru", "password": "123"} - return get(url, json=json.dumps(body)).text + return post(url, json=body).text def test_user_post_request(): @@ -65,7 +88,7 @@ def test_user_post_request(): 'email': 'il@k.r', 'password': 'strong_pass' } - return post(url, data=json.dumps(user_post_request_body), headers={"Content-Type": "application/json"}).text + return post(url, data=user_post_request_body, headers={"Content-Type": "application/json"}).text def test_question_get_request(): @@ -80,8 +103,10 @@ def test_question_post_request(): return post(url, data=json.dumps(question_post_request_body), headers={"Content-Type": "application/json"}).text -# print(test_quiz_post_request()) +print(test_quizzes_post_request()) # print(test_quiz_get_request()) -# print(test_user_post_request()) +# print(test_user_login_request()) # print(test_question_get_request()) -print(test_user_get_request()) +# print(test_user_get_request()) + +# print(str([{"Hello": True, "World": False}])) From 4b7f5972a6494d3f9d9cdd045d33ba8b04e9f655 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:55:29 +0300 Subject: [PATCH 08/12] Repository: quiz, question, answer delete functions Quiz Resource: deletion, creation test_api: quiz_deletion test orm: mappers for foreign_key update --- web-app/adapters/orm.py | 28 +++++++--- web-app/adapters/repository.py | 23 ++++++-- .../entrypoints/api/resource/QuizResource.py | 52 ++++++++++++++----- web-app/entrypoints/flask_app.py | 12 ++++- web-app/tests/test_api.py | 19 +++++-- 5 files changed, 103 insertions(+), 31 deletions(-) diff --git a/web-app/adapters/orm.py b/web-app/adapters/orm.py index bfd3651..41c8967 100644 --- a/web-app/adapters/orm.py +++ b/web-app/adapters/orm.py @@ -1,6 +1,6 @@ from sqlalchemy import Column, MetaData, Integer, String, ForeignKey, Text, Enum, Table, Boolean from sqlalchemy.orm import registry -from sqlalchemy.orm import mapper +from sqlalchemy.orm import mapper, relationship from domain import model @@ -33,23 +33,37 @@ question_table = Table("questions", metadata, Column('id', Integer, primary_key=True, autoincrement=True), Column('quiz_id', Integer, ForeignKey('quizzes.id')), - Column('type', Text), - Column('description', Text), + Column('question_type', Text), + Column('text', Text), Column('time', Integer)) # Table for answers answer_table = Table("answers", metadata, Column('id', Integer, primary_key=True, autoincrement=True), - Column('question_id', ForeignKey('questions.id')), + Column('question_id', Integer, ForeignKey('questions.id')), Column('text', Text), Column('correct_answer', Boolean)) def start_mappers(): - user_mapper = mapper_registry.map_imperatively(model.User, user_table) - question_mapper = mapper_registry.map_imperatively(model.Question, question_table) + user_mapper = mapper_registry.map_imperatively(model.User, user_table, + properties={ + 'user_id': relationship(model.Quiz, + cascade="all,delete") + }) + question_mapper = mapper_registry.map_imperatively(model.Question, question_table, + properties={ + 'question_id': relationship(model.Answer, + cascade="all,delete") + }) answer_mapper = mapper_registry.map_imperatively(model.Answer, answer_table) - quiz_mapper = mapper_registry.map_imperatively(model.Quiz, quiz_table) + + quiz_mapper = mapper_registry.map_imperatively(model.Quiz, quiz_table, + properties={ + 'quiz_id': relationship(model.Question, + cascade="all,delete") + } + ) def create_all(engine): diff --git a/web-app/adapters/repository.py b/web-app/adapters/repository.py index eb7951f..f028769 100644 --- a/web-app/adapters/repository.py +++ b/web-app/adapters/repository.py @@ -48,9 +48,8 @@ def put_quiz(self, quiz: model.Quiz): # self.session.query(model.Quiz).f raise NotImplemented - def delete_quiz(self, quiz_id): - self.session.query(model.Quiz).filter_by(id=quiz_id).delete(synchronize_session=False) - # TODO: deletion of questions and answers + def delete_quiz(self, quiz: model.Quiz): + self.session.delete(quiz) self.session.commit() def list_quizzes(self, user_id): @@ -66,6 +65,10 @@ def add_question(self, question: model.Question): def get_question(self, question_id): return self.session.query(model.Question).filter_by(id=question_id).one() + def delete_question(self, question: model.Question): + self.session.delete(question) + self.session.commit() + def list_questions(self, quiz_id): return self.session.query(model.Question).filter_by(quiz_id=quiz_id).all() @@ -77,10 +80,14 @@ def add_answer(self, answer: model.Answer): return answer.id def get_answer(self, answer_id): - return self.session.query(model.Answer).filter_by(id=answer_id) + return self.session.query(model.Answer).filter_by(id=answer_id).one() + + def delete_answer(self, answer: model.Answer): + self.session.delete(answer) + self.session.commit() def list_answers(self, question_id): - return self.session.query(model.Answer).filter_by(question_id=question_id) + return self.session.query(model.Answer).filter_by(question_id=question_id).all() # -- for test def get_users(self): @@ -89,6 +96,12 @@ def get_users(self): def get_quizzes(self): return self.session.query(model.Quiz).all() + def get_answers(self): + return self.session.query(model.Answer).all() + + def get_questions(self): + return self.session.query(model.Question).all() + # Testing Repository class FakeRepository: diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index 49e4ea7..4d39f3c 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -42,17 +42,34 @@ def abort_if_quiz_not_found(quiz_id): class QuizResource(Resource): - def get(self, quiz_id): + def get(self, user_id, quiz_id): abort_if_quiz_not_found(quiz_id) - args = quiz_get_parser.parse_args() - check_for_credentials(args) - repo = get_repo() quiz = repo.get_quiz(quiz_id) - return make_response(quiz.to_dict(), 200) - def put(self, quiz_id): + if quiz.user_id != user_id: + abort(403, message="You are not allowed to get this information") + + questions = repo.list_questions(quiz_id) + questions_list = [] + # assert len(questions) != 0 + for question in questions: + questions_list.append(question.to_dict()) + + answers_list = [] + answers = repo.list_answers(question.id) + for answer in answers: + answers_list.append(answer.to_dict()) + questions_list[-1]["answers"] = answers_list + + quiz_dict = quiz.to_dict() + quiz_dict["questions"] = questions_list + return make_response(quiz_dict, 200) + + def put(self, user_id, quiz_id): + abort_if_quiz_not_found(quiz_id) + args = quiz_creation_parser.parse_args() check_for_credentials(args) @@ -63,22 +80,31 @@ def put(self, quiz_id): return jsonify({"success": "OK"}) - def delete(self, quiz_id): - args = quiz_get_parser.parse_args() - check_for_credentials(args) + def delete(self, user_id, quiz_id): + abort_if_quiz_not_found(quiz_id) repo = get_repo() - quiz = repo.delete_quiz(quiz_id) - # TODO: Deleting questions + answers + quiz = repo.get_quiz(quiz_id) + + if quiz.user_id != user_id: + abort(403, message="You are not allowed to perform this action") + + repo.delete_quiz(quiz) + # questions = repo.list_questions(quiz_id) + # + # for question in questions: + # + # answers = repo.list_answers(question.id) + # for answer in answers: + # repo.delete_answer(answer) + # repo.delete_question(question) return make_response("Quiz deleted", 200) class QuizListResource(Resource): def get(self, user_id): - args = quiz_get_parser.parse_args() - abort_if_user_not_found(user_id) repo = get_repo() diff --git a/web-app/entrypoints/flask_app.py b/web-app/entrypoints/flask_app.py index 62e9314..6c57473 100644 --- a/web-app/entrypoints/flask_app.py +++ b/web-app/entrypoints/flask_app.py @@ -18,7 +18,7 @@ orm.start_mappers() # To delete all tables -# orm.delete_all(engine) +orm.delete_all(engine) orm.create_all(engine) app = Flask(__name__, template_folder='./../static/templates') @@ -85,3 +85,13 @@ def test_user_by_email(email): user = repo.get_user_by_email(email) return json.dumps({"user": user.to_dict()}) + + +@app.route('/tests/answers') +def test_answers_list(): + repo = repository.get_repo() + + dict_items = [] + for item in repo.get_answers(): + dict_items.append(item.to_dict()) + return json.dumps({"answers": dict_items}) diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index 067c0c3..64a6741 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -81,14 +81,15 @@ def test_user_login_request(): def test_user_post_request(): - url = "http://127.0.0.1:8888/api/user" + url = "http://127.0.0.1:8888/api/user/register" user_post_request_body = { - 'nickname': 'Il', + 'name': 'Il', + 'surname': 'hello', 'email': 'il@k.r', 'password': 'strong_pass' } - return post(url, data=user_post_request_body, headers={"Content-Type": "application/json"}).text + return post(url, json=user_post_request_body, headers={"Content-Type": "application/json"}).text def test_question_get_request(): @@ -100,10 +101,18 @@ def test_question_post_request(): url = "http://127.0.0.1:8888/api/quiz/1/question/2" question_post_request_body = {"answers": ["Incorrect answer", "Correct answer"]} - return post(url, data=json.dumps(question_post_request_body), headers={"Content-Type": "application/json"}).text + return post(url, json=question_post_request_body, headers={"Content-Type": "application/json"}).text -print(test_quizzes_post_request()) +def test_quiz_delete_request(): + url = "http://127.0.0.1:8888/api/user/1/quiz/2" + + return delete(url).text + + +# print(test_user_post_request()) +print(test_quiz_delete_request()) +# print(test_quizzes_post_request()) # print(test_quiz_get_request()) # print(test_user_login_request()) # print(test_question_get_request()) From 3dc1f225c0062f6f1187b4f53684958cb97bdd08 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Tue, 28 Jun 2022 15:28:08 +0300 Subject: [PATCH 09/12] Orm: Rewrited foreign key mode (To CASCADE mode) QuizResource: Creation of quiz extracted into a function Quiz put request (recreation of quiz) Test_api: quiz put request added --- web-app/adapters/orm.py | 26 ++------- .../entrypoints/api/resource/QuizResource.py | 56 ++++++++----------- web-app/tests/test_api.py | 36 +++++++++--- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/web-app/adapters/orm.py b/web-app/adapters/orm.py index 41c8967..2d379ef 100644 --- a/web-app/adapters/orm.py +++ b/web-app/adapters/orm.py @@ -26,13 +26,13 @@ # Table for quizzes quiz_table = Table("quizzes", metadata, Column('id', Integer, primary_key=True, autoincrement=True), - Column('user_id', Integer, ForeignKey('users.id')), + Column('user_id', Integer, ForeignKey('users.id', ondelete="CASCADE")), Column('name', Text)) # Table for questions question_table = Table("questions", metadata, Column('id', Integer, primary_key=True, autoincrement=True), - Column('quiz_id', Integer, ForeignKey('quizzes.id')), + Column('quiz_id', Integer, ForeignKey('quizzes.id', ondelete="CASCADE")), Column('question_type', Text), Column('text', Text), Column('time', Integer)) @@ -40,30 +40,16 @@ # Table for answers answer_table = Table("answers", metadata, Column('id', Integer, primary_key=True, autoincrement=True), - Column('question_id', Integer, ForeignKey('questions.id')), + Column('question_id', Integer, ForeignKey('questions.id', ondelete="CASCADE")), Column('text', Text), Column('correct_answer', Boolean)) def start_mappers(): - user_mapper = mapper_registry.map_imperatively(model.User, user_table, - properties={ - 'user_id': relationship(model.Quiz, - cascade="all,delete") - }) - question_mapper = mapper_registry.map_imperatively(model.Question, question_table, - properties={ - 'question_id': relationship(model.Answer, - cascade="all,delete") - }) + question_mapper = mapper_registry.map_imperatively(model.Question, question_table) + user_mapper = mapper_registry.map_imperatively(model.User, user_table) answer_mapper = mapper_registry.map_imperatively(model.Answer, answer_table) - - quiz_mapper = mapper_registry.map_imperatively(model.Quiz, quiz_table, - properties={ - 'quiz_id': relationship(model.Question, - cascade="all,delete") - } - ) + quiz_mapper = mapper_registry.map_imperatively(model.Quiz, quiz_table) def create_all(engine): diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index 4d39f3c..e7108a4 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -8,10 +8,6 @@ quiz_creation_parser.add_argument('name', required=True) quiz_creation_parser.add_argument('questions', type=dict, action="append", required=True) -quiz_get_parser = reqparse.RequestParser() -quiz_get_parser.add_argument('email', required=True) -quiz_get_parser.add_argument('password', required=True) - def check_for_credentials(args: dict): email, password = services.email_and_pass_from(args) @@ -41,6 +37,22 @@ def abort_if_quiz_not_found(quiz_id): abort(404, message="Quiz is not found") +def create_quiz(user_id, args): + abort_if_user_not_found(user_id) + + repo = get_repo() + + quiz = services.quiz_from(args, user_id) + quiz_id = repo.add_quiz(quiz) + + questions_and_answers = services.questions_of_quiz_creation_from(args, quiz_id) + + for (question, answers_args) in questions_and_answers: + question_id = repo.add_question(question) + for answers in services.answers_for_one_question_of_quiz_creation_from(answers_args, question_id): + repo.add_answer(answers) + + class QuizResource(Resource): def get(self, user_id, quiz_id): abort_if_quiz_not_found(quiz_id) @@ -71,14 +83,16 @@ def put(self, user_id, quiz_id): abort_if_quiz_not_found(quiz_id) args = quiz_creation_parser.parse_args() - check_for_credentials(args) - repo = get_repo() - repo.put_quiz(Quiz(quiz_id=quiz_id, name=args['name'], user_id=args['user_id'])) + quiz = repo.get_quiz(quiz_id) - # TODO: Putting questions + answers + if quiz.user_id != user_id: + abort(403, message="You are not allowed to perform this action") - return jsonify({"success": "OK"}) + repo.delete_quiz(quiz) + create_quiz(user_id, args) + + return make_response("Quiz recreated", 200) def delete(self, user_id, quiz_id): abort_if_quiz_not_found(quiz_id) @@ -91,14 +105,6 @@ def delete(self, user_id, quiz_id): abort(403, message="You are not allowed to perform this action") repo.delete_quiz(quiz) - # questions = repo.list_questions(quiz_id) - # - # for question in questions: - # - # answers = repo.list_answers(question.id) - # for answer in answers: - # repo.delete_answer(answer) - # repo.delete_question(question) return make_response("Quiz deleted", 200) @@ -113,20 +119,6 @@ def get(self, user_id): def post(self, user_id): args = quiz_creation_parser.parse_args() - # return make_response(str(args), 404) - abort_if_user_not_found(user_id) - - repo = get_repo() - - quiz = services.quiz_from(args, user_id) - quiz_id = repo.add_quiz(quiz) - - questions_and_answers = services.questions_of_quiz_creation_from(args, quiz_id) - # return make_response(str(questions_and_answers), 404) - - for (question, answers_args) in questions_and_answers: - question_id = repo.add_question(question) - for answers in services.answers_for_one_question_of_quiz_creation_from(answers_args, question_id): - repo.add_answer(answers) + create_quiz(user_id, args) return make_response("Quiz created", 201) diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index 64a6741..be674da 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -1,6 +1,6 @@ import json -from requests import get, post, delete +from requests import get, post, delete, put def test_quizzes_post_request(): @@ -61,14 +61,35 @@ def test_quizzes_get_request(): def test_quiz_put_request(): - url = "http://127.0.0.1:8888/api/user/1/quiz" + url = "http://127.0.0.1:8888/api/user/1/quiz/1" + + answers1 = [ + {"text": "Hello", "correct_answer": False}, + {"text": "Worldzzz", "correct_answer": False} + ] + question1 = {"question_type": "poll", + "text": "questions1", + "time": 60, + "answers": answers1} + + answers2 = [ + {"text": "Hello", "correct_answer": True}, + {"text": "World", "correct_answer": False} + ] + + question2 = { + "question_type": "quiz", + "text": "questions2zzz", + "time": 15, + "answers": answers2 + } quiz_put_request_body = { - "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, - "name": "Quiz name" + "questions": [question1, question2], + "name": "Quiz recreated name" } - return post(url, json=json.dumps(quiz_put_request_body), headers={"Content-Type": "application/json"}).text + return put(url, json=quiz_put_request_body, headers={"Content-Type": "application/json"}).text def test_user_login_request(): @@ -105,13 +126,14 @@ def test_question_post_request(): def test_quiz_delete_request(): - url = "http://127.0.0.1:8888/api/user/1/quiz/2" + url = "http://127.0.0.1:8888/api/user/1/quiz/1" return delete(url).text # print(test_user_post_request()) -print(test_quiz_delete_request()) +# print(test_quiz_delete_request()) +print(test_quiz_put_request()) # print(test_quizzes_post_request()) # print(test_quiz_get_request()) # print(test_user_login_request()) From f22670d7e016954165fce516bb136e171303f8f4 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:57:12 +0300 Subject: [PATCH 10/12] User reigtration: Catching error if user creates several accounts with the same email test_orm: Created more tests test_api: added assertions repository: fixed bugs --- web-app/adapters/repository.py | 9 +-- .../entrypoints/api/resource/QuizResource.py | 6 +- .../entrypoints/api/resource/UserResource.py | 20 +++++- web-app/tests/test_api.py | 42 ++++++------- web-app/tests/test_orm.py | 61 +++++++++++++++++-- 5 files changed, 105 insertions(+), 33 deletions(-) diff --git a/web-app/adapters/repository.py b/web-app/adapters/repository.py index f028769..0d249e9 100644 --- a/web-app/adapters/repository.py +++ b/web-app/adapters/repository.py @@ -34,11 +34,14 @@ def get_user_by_id(self, user_id): def get_user_by_email(self, email): return self.session.query(model.User).filter_by(email=email).one() + def delete_user(self, user: model.User): + self.session.delete(user) + self.session.commit() + # Working with quizzes def add_quiz(self, quiz: model.Quiz) -> int: self.session.add(quiz) self.session.commit() - self.session.flush() return quiz.id def get_quiz(self, quiz_id): @@ -53,13 +56,12 @@ def delete_quiz(self, quiz: model.Quiz): self.session.commit() def list_quizzes(self, user_id): - return self.session.query(model.User).filter_by(user_id=user_id).all() + return self.session.query(model.Quiz).filter_by(user_id=user_id).all() # Working with questions def add_question(self, question: model.Question): self.session.add(question) self.session.commit() - self.session.flush() return question.id def get_question(self, question_id): @@ -76,7 +78,6 @@ def list_questions(self, quiz_id): def add_answer(self, answer: model.Answer): self.session.add(answer) self.session.commit() - self.session.flush() return answer.id def get_answer(self, answer_id): diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index e7108a4..8df03b5 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -115,7 +115,11 @@ def get(self, user_id): repo = get_repo() quizzes = repo.list_quizzes(user_id) - return make_response({"quizzes": [quiz.to_dict() for quiz in quizzes]}, 200) + + user = repo.get_user_by_id(user_id) + + return make_response( + {"quizzes": [quiz.to_dict() for quiz in quizzes], "quiz_number": len(quizzes), "user": user.to_dict()}, 200) def post(self, user_id): args = quiz_creation_parser.parse_args() diff --git a/web-app/entrypoints/api/resource/UserResource.py b/web-app/entrypoints/api/resource/UserResource.py index d551d80..8808f8c 100644 --- a/web-app/entrypoints/api/resource/UserResource.py +++ b/web-app/entrypoints/api/resource/UserResource.py @@ -4,6 +4,7 @@ from adapters.repository import SqlAlchemyRepository, get_repo from domain.model import User from service_layer import services +from sqlalchemy.exc import IntegrityError login_parser = reqparse.RequestParser() login_parser.add_argument('email', required=True) @@ -31,6 +32,15 @@ def check_for_credentials(args: dict) -> int: return user.id +def abort_if_user_already_exists(email): + repo = get_repo() + + user = repo.get_user_by_email(email) + + if user: + abort(403, message="User already exists") + + def abort_if_user_not_found(user_id: int, args: dict): email, password = services.email_and_pass_from(args) @@ -56,5 +66,11 @@ def post(self): user = services.user_from(args) - repo.add_user(user) - return make_response("User created", 201) + # abort_if_user_already_exists(user.email) + try: + repo.add_user(user) + return make_response("User created", 201) + + except IntegrityError: + repo.session.rollback() + return make_response("User already exists", 403) diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index be674da..9968a06 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -33,31 +33,31 @@ def test_quizzes_post_request(): # print(type(quiz_post_request_body["questions"])) url = "http://127.0.0.1:8888/api/user/1/quiz" - return post(url, json=quiz_post_request_body, - headers={"Content-Type": "application/json"}).text + assert post(url, json=quiz_post_request_body, + headers={"Content-Type": "application/json"}).status_code == 201 def test_quiz_get_request(): url = "http://127.0.0.1:8888/api/user/1/quiz/1" - assert get(url) == 200 + assert get(url).status_code == 200 -def test_quiz_delete_request(): - quiz_post_request_body = { - "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, - "name": "Quiz name" - } - url = "http://127.0.0.1:8888/api/user/1/quiz" - - post(url, data=json.dumps(quiz_post_request_body), headers={"Content-Type": "application/json"}) - - assert delete(url + "/1").status_code == 200 +# def test_quiz_delete_request(): +# quiz_post_request_body = { +# "questions": {"question1": {"Hello": True, "World": True}, "question2": {"Hello": True, "World": True}}, +# "name": "Quiz name" +# } +# url = "http://127.0.0.1:8888/api/user/1/quiz" +# +# post(url, data=json.dumps(quiz_post_request_body), headers={"Content-Type": "application/json"}) +# +# assert delete(url + "/1").status_code == 200 def test_quizzes_get_request(): url = "http://127.0.0.1:8888/api/user/1/quiz" - return get(url).text + assert get(url).status_code == 200 def test_quiz_put_request(): @@ -89,7 +89,7 @@ def test_quiz_put_request(): "name": "Quiz recreated name" } - return put(url, json=quiz_put_request_body, headers={"Content-Type": "application/json"}).text + assert put(url, json=quiz_put_request_body, headers={"Content-Type": "application/json"}).status_code == 200 def test_user_login_request(): @@ -98,7 +98,7 @@ def test_user_login_request(): body = {"email": "vikochka_kruk@mail.ru", "password": "123"} - return post(url, json=body).text + assert post(url, json=body).status_code == 200 def test_user_post_request(): @@ -110,30 +110,30 @@ def test_user_post_request(): 'email': 'il@k.r', 'password': 'strong_pass' } - return post(url, json=user_post_request_body, headers={"Content-Type": "application/json"}).text + assert post(url, json=user_post_request_body, headers={"Content-Type": "application/json"}).status_code == 201 def test_question_get_request(): url = "http://127.0.0.1:8888/api/quiz/1/question/2" - return get(url).text + assert get(url).status_code == 200 def test_question_post_request(): url = "http://127.0.0.1:8888/api/quiz/1/question/2" question_post_request_body = {"answers": ["Incorrect answer", "Correct answer"]} - return post(url, json=question_post_request_body, headers={"Content-Type": "application/json"}).text + assert post(url, json=question_post_request_body, headers={"Content-Type": "application/json"}).status_code == 201 def test_quiz_delete_request(): url = "http://127.0.0.1:8888/api/user/1/quiz/1" - return delete(url).text + assert delete(url).status_code == 200 # print(test_user_post_request()) # print(test_quiz_delete_request()) -print(test_quiz_put_request()) +# print(test_quiz_put_request()) # print(test_quizzes_post_request()) # print(test_quiz_get_request()) # print(test_user_login_request()) diff --git a/web-app/tests/test_orm.py b/web-app/tests/test_orm.py index ec9a9ad..6f682e7 100644 --- a/web-app/tests/test_orm.py +++ b/web-app/tests/test_orm.py @@ -2,15 +2,66 @@ from domain import model +# Unit tests can be written only for user or quiz (With user) +# Because we use foreign key to link all info in db + def test_add_user(repo): - new_user = model.User("Fedor", "fedya@gg.ru", "CoolPass") + new_user = model.User("Fedor", "Surname", "fedya@gg.ru", "CoolPass") repo.add_user(new_user) - db_user = repo.session.query(model.User).filter_by(nickname=new_user.nickname).one() + db_user = repo.session.query(model.User).filter_by(email=new_user.email).one() assert db_user == new_user - repo.session.query(model.User).filter_by(nickname=new_user.nickname).delete() - repo.session.commit() + repo.delete_user(new_user) + + +def test_add_and_quiz(repo): + new_user = model.User("Fedor", "Surname", "fedya@gg.ru", "CoolPass") + user_id = repo.add_user(new_user) + + new_quiz = model.Quiz(quiz_name="Quiz name", user_id=user_id) + quiz_id = repo.add_quiz(new_quiz) + + db_quiz = repo.get_quiz(quiz_id) + + assert db_quiz == new_quiz + + repo.delete_user(new_user) + + +def test_add_and_question(repo): + new_user = model.User("Fedor", "Surname", "fedya@gg.ru", "CoolPass") + user_id = repo.add_user(new_user) + + new_quiz = model.Quiz(quiz_name="Quiz name", user_id=user_id) + quiz_id = repo.add_quiz(new_quiz) + + question = model.Question(question_type="poll", text="questions1", time_limit=60, quiz_id=quiz_id) + question_id = repo.add_questions(question1) + + db_question = repo.get_question(question_id) + + assert db_question == question + + repo.delete_user(new_user) + + +def test_add_and_answer(repo): + new_user = model.User("Fedor", "Surname", "fedya@gg.ru", "CoolPass") + user_id = repo.add_user(new_user) + + new_quiz = model.Quiz(quiz_name="Quiz name", user_id=user_id) + quiz_id = repo.add_quiz(new_quiz) + + question = model.Question(question_type="poll", text="questions1", time_limit=60, quiz_id=quiz_id) + question_id = repo.add_questions(question) + + answer = model.Answer(text="Hello", correct_answer=True, question_id=question_id) + answer_id = repo.add_answer(answer) + + db_answer = repo.get_answer(answer_id) + + assert db_answer == answer - print("OK") + repo.delete_user(new_user) From 8c44e5cbd26e0a64aeee3a15ee311c750d88db7f Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Thu, 30 Jun 2022 17:16:06 +0300 Subject: [PATCH 11/12] pyanalyze code --- web-app/adapters/__init__.py | 1 + web-app/adapters/orm.py | 2 ++ web-app/adapters/repository.py | 2 +- web-app/app_start.py | 1 + web-app/domain/__init__.py | 1 + web-app/entrypoints/__init__.py | 1 + web-app/entrypoints/api/__init__.py | 1 + web-app/entrypoints/api/api_initialization.py | 2 ++ web-app/entrypoints/api/resource/AnswerResource.py | 1 + web-app/entrypoints/api/resource/QuestionResource.py | 1 + web-app/entrypoints/api/resource/QuizResource.py | 4 ++++ web-app/entrypoints/api/resource/UserResource.py | 4 ++++ web-app/entrypoints/api/resource/__init__.py | 1 + web-app/entrypoints/flask_app.py | 1 + web-app/main.py | 1 + web-app/service_layer/__init__.py | 1 + web-app/service_layer/services.py | 1 + web-app/tests/test_api.py | 1 + web-app/tests/test_orm.py | 1 + 19 files changed, 27 insertions(+), 1 deletion(-) diff --git a/web-app/adapters/__init__.py b/web-app/adapters/__init__.py index eec1779..e5034e2 100644 --- a/web-app/adapters/__init__.py +++ b/web-app/adapters/__init__.py @@ -1 +1,2 @@ +# static analysis: ignore[import_failed] from . import orm, repository \ No newline at end of file diff --git a/web-app/adapters/orm.py b/web-app/adapters/orm.py index 2d379ef..a340c2a 100644 --- a/web-app/adapters/orm.py +++ b/web-app/adapters/orm.py @@ -1,9 +1,11 @@ +# static analysis: ignore[import_failed] from sqlalchemy import Column, MetaData, Integer, String, ForeignKey, Text, Enum, Table, Boolean from sqlalchemy.orm import registry from sqlalchemy.orm import mapper, relationship from domain import model + # Base = declarative_base() mapper_registry = registry() metadata = mapper_registry.metadata diff --git a/web-app/adapters/repository.py b/web-app/adapters/repository.py index 0d249e9..2fc2bf6 100644 --- a/web-app/adapters/repository.py +++ b/web-app/adapters/repository.py @@ -1,4 +1,4 @@ -# from abc import ABC, abstractmethod +# static analysis: ignore[import_failed] from domain import model diff --git a/web-app/app_start.py b/web-app/app_start.py index 7247b59..eb07319 100644 --- a/web-app/app_start.py +++ b/web-app/app_start.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] from entrypoints.flask_app import app if __name__ == "__main__": diff --git a/web-app/domain/__init__.py b/web-app/domain/__init__.py index 36ec720..48d0fae 100644 --- a/web-app/domain/__init__.py +++ b/web-app/domain/__init__.py @@ -1 +1,2 @@ +# static analysis: ignore[import_failed] from . import model \ No newline at end of file diff --git a/web-app/entrypoints/__init__.py b/web-app/entrypoints/__init__.py index e69de29..1a975b4 100644 --- a/web-app/entrypoints/__init__.py +++ b/web-app/entrypoints/__init__.py @@ -0,0 +1 @@ +# static analysis: ignore[import_failed] diff --git a/web-app/entrypoints/api/__init__.py b/web-app/entrypoints/api/__init__.py index e69de29..1a975b4 100644 --- a/web-app/entrypoints/api/__init__.py +++ b/web-app/entrypoints/api/__init__.py @@ -0,0 +1 @@ +# static analysis: ignore[import_failed] diff --git a/web-app/entrypoints/api/api_initialization.py b/web-app/entrypoints/api/api_initialization.py index 3e49f8b..0660342 100644 --- a/web-app/entrypoints/api/api_initialization.py +++ b/web-app/entrypoints/api/api_initialization.py @@ -1,4 +1,6 @@ +# static analysis: ignore[import_failed] from entrypoints.api.resource import AnswerResource, QuizResource, QuestionResource, UserResource + from flask_restful import Api diff --git a/web-app/entrypoints/api/resource/AnswerResource.py b/web-app/entrypoints/api/resource/AnswerResource.py index c46472e..474772f 100644 --- a/web-app/entrypoints/api/resource/AnswerResource.py +++ b/web-app/entrypoints/api/resource/AnswerResource.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] from flask import jsonify from flask_restful import reqparse, abort, Resource from adapters.repository import SqlAlchemyRepository, get_repo diff --git a/web-app/entrypoints/api/resource/QuestionResource.py b/web-app/entrypoints/api/resource/QuestionResource.py index 74ee5fe..edbc3ea 100644 --- a/web-app/entrypoints/api/resource/QuestionResource.py +++ b/web-app/entrypoints/api/resource/QuestionResource.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] from flask import jsonify from flask_restful import reqparse, abort, Resource from adapters.repository import SqlAlchemyRepository, get_repo diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index 8df03b5..d574bc1 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -1,9 +1,13 @@ +# static analysis: ignore[import_failed] from flask import jsonify, make_response from flask_restful import reqparse, abort, Resource from adapters.repository import SqlAlchemyRepository, get_repo + from domain.model import Quiz + from service_layer import services + quiz_creation_parser = reqparse.RequestParser() quiz_creation_parser.add_argument('name', required=True) quiz_creation_parser.add_argument('questions', type=dict, action="append", required=True) diff --git a/web-app/entrypoints/api/resource/UserResource.py b/web-app/entrypoints/api/resource/UserResource.py index 8808f8c..e85f051 100644 --- a/web-app/entrypoints/api/resource/UserResource.py +++ b/web-app/entrypoints/api/resource/UserResource.py @@ -1,9 +1,13 @@ +# static analysis: ignore[import_failed] import flask from flask import jsonify, make_response from flask_restful import reqparse, abort, Resource from adapters.repository import SqlAlchemyRepository, get_repo + from domain.model import User + from service_layer import services + from sqlalchemy.exc import IntegrityError login_parser = reqparse.RequestParser() diff --git a/web-app/entrypoints/api/resource/__init__.py b/web-app/entrypoints/api/resource/__init__.py index e69de29..1a975b4 100644 --- a/web-app/entrypoints/api/resource/__init__.py +++ b/web-app/entrypoints/api/resource/__init__.py @@ -0,0 +1 @@ +# static analysis: ignore[import_failed] diff --git a/web-app/entrypoints/flask_app.py b/web-app/entrypoints/flask_app.py index 6c57473..d9747da 100644 --- a/web-app/entrypoints/flask_app.py +++ b/web-app/entrypoints/flask_app.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] import json from flask import Flask, request, render_template diff --git a/web-app/main.py b/web-app/main.py index 695cac7..18dac90 100644 --- a/web-app/main.py +++ b/web-app/main.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] from entrypoints import flask_app flask_app.main() diff --git a/web-app/service_layer/__init__.py b/web-app/service_layer/__init__.py index e69de29..1a975b4 100644 --- a/web-app/service_layer/__init__.py +++ b/web-app/service_layer/__init__.py @@ -0,0 +1 @@ +# static analysis: ignore[import_failed] diff --git a/web-app/service_layer/services.py b/web-app/service_layer/services.py index cc207d6..aeef318 100644 --- a/web-app/service_layer/services.py +++ b/web-app/service_layer/services.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] from domain import model diff --git a/web-app/tests/test_api.py b/web-app/tests/test_api.py index 9968a06..8410451 100644 --- a/web-app/tests/test_api.py +++ b/web-app/tests/test_api.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] import json from requests import get, post, delete, put diff --git a/web-app/tests/test_orm.py b/web-app/tests/test_orm.py index 6f682e7..40e5d27 100644 --- a/web-app/tests/test_orm.py +++ b/web-app/tests/test_orm.py @@ -1,3 +1,4 @@ +# static analysis: ignore[import_failed] from adapters import repository from domain import model From ee0a1220e38817164375b1a7dacfc91993e19f15 Mon Sep 17 00:00:00 2001 From: IlnurHA <53339563+IlnurHA@users.noreply.github.com> Date: Sat, 2 Jul 2022 14:34:25 +0300 Subject: [PATCH 12/12] quiz_id info for api request --- web-app/entrypoints/api/resource/QuizResource.py | 10 ++++++++-- web-app/entrypoints/flask_app.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/web-app/entrypoints/api/resource/QuizResource.py b/web-app/entrypoints/api/resource/QuizResource.py index d574bc1..827cdee 100644 --- a/web-app/entrypoints/api/resource/QuizResource.py +++ b/web-app/entrypoints/api/resource/QuizResource.py @@ -7,7 +7,6 @@ from service_layer import services - quiz_creation_parser = reqparse.RequestParser() quiz_creation_parser.add_argument('name', required=True) quiz_creation_parser.add_argument('questions', type=dict, action="append", required=True) @@ -122,8 +121,15 @@ def get(self, user_id): user = repo.get_user_by_id(user_id) + quizzes_as_dict = [] + + for quiz in quizzes: + quiz_as_dict = quiz.to_dict + quiz_as_dict["quiz_id"] = quiz.id + quizzes_as_dict.append(dicted_quiz) + return make_response( - {"quizzes": [quiz.to_dict() for quiz in quizzes], "quiz_number": len(quizzes), "user": user.to_dict()}, 200) + {"quizzes": dicted_quizzes, "quiz_number": len(quizzes), "user": user.to_dict()}, 200) def post(self, user_id): args = quiz_creation_parser.parse_args() diff --git a/web-app/entrypoints/flask_app.py b/web-app/entrypoints/flask_app.py index d9747da..a701e53 100644 --- a/web-app/entrypoints/flask_app.py +++ b/web-app/entrypoints/flask_app.py @@ -19,7 +19,7 @@ orm.start_mappers() # To delete all tables -orm.delete_all(engine) +# orm.delete_all(engine) orm.create_all(engine) app = Flask(__name__, template_folder='./../static/templates')