From 8db228bf8fe8e3bbd5c5adcc00add9e8a611b268 Mon Sep 17 00:00:00 2001 From: robban64 <carl@schonfelder.se> Date: Fri, 16 Apr 2021 19:23:59 +0200 Subject: [PATCH] fix: component and you now get all types by get misc/types --- server/app/apis/components.py | 17 +++++++- server/app/apis/misc.py | 39 +++++++++-------- server/app/core/dto.py | 3 ++ server/app/core/parsers.py | 5 ++- server/app/core/rich_schemas.py | 1 + server/app/core/schemas.py | 34 +++++++++------ server/app/database/controller/add.py | 16 ++++++- server/app/database/controller/delete.py | 4 ++ server/app/database/controller/edit.py | 17 ++++++++ server/app/database/controller/get.py | 23 +++++++++- server/app/database/models.py | 45 +++++++------------- server/configmodule.py | 3 +- server/populate.py | 25 +++++++---- server/tests/test_app.py | 53 ++++++++++-------------- server/tests/test_helpers.py | 30 ++++++++------ 15 files changed, 194 insertions(+), 121 deletions(-) diff --git a/server/app/apis/components.py b/server/app/apis/components.py index 73e2a907..da211895 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -1,7 +1,8 @@ +import app.core.http_codes as codes import app.database.controller as dbc from app.apis import admin_required, item_response, list_response from app.core.dto import ComponentDTO -from app.core.parsers import component_parser +from app.core.parsers import component_create_parser, component_parser from app.database.models import Competition from flask.globals import request from flask_jwt_extended import jwt_required @@ -20,6 +21,18 @@ class ComponentByID(Resource): item = dbc.get.component(component_id) return item_response(schema.dump(item)) + @jwt_required + def put(self, CID, SID, component_id): + args = component_parser.parse_args() + item = dbc.edit.component(**args) + return item_response(schema.dump(item)) + + @jwt_required + def delete(self, CID, SID, component_id): + item = dbc.get.component(component_id) + dbc.delete.component(item) + return {}, codes.NO_CONTENT + @api.route("/") @api.param("CID, SID") @@ -31,7 +44,7 @@ class ComponentList(Resource): @jwt_required def post(self, CID, SID): - args = component_parser.parse_args() + args = component_create_parser.parse_args() item_slide = dbc.get.slide(CID, SID) item = dbc.add.component(item_slide=item_slide, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py index 7fbb222d..b40f97a8 100644 --- a/server/app/apis/misc.py +++ b/server/app/apis/misc.py @@ -1,7 +1,7 @@ import app.database.controller as dbc from app.apis import admin_required, item_response, list_response from app.core.dto import MiscDTO -from app.database.models import City, MediaType, QuestionType, Role +from app.database.models import City, ComponentType, MediaType, QuestionType, Role, ViewType from flask_jwt_extended import jwt_required from flask_restx import Resource, reqparse @@ -9,6 +9,9 @@ api = MiscDTO.api question_type_schema = MiscDTO.question_type_schema media_type_schema = MiscDTO.media_type_schema +component_type_schema = MiscDTO.component_type_schema +view_type_schema = MiscDTO.view_type_schema + role_schema = MiscDTO.role_schema city_schema = MiscDTO.city_schema @@ -17,27 +20,23 @@ name_parser = reqparse.RequestParser() name_parser.add_argument("name", type=str, required=True, location="json") -@api.route("/media_types") -class MediaTypeList(Resource): - @jwt_required - def get(self): - items = MediaType.query.all() - return list_response(media_type_schema.dump(items)) - - -@api.route("/question_types") -class QuestionTypeList(Resource): +@api.route("/types") +class TypesList(Resource): @jwt_required def get(self): - items = QuestionType.query.all() - return list_response(question_type_schema.dump(items)) + result = {} + result["media_types"] = media_type_schema.dump(dbc.get.all(MediaType)) + result["component_types"] = component_type_schema.dump(dbc.get.all(ComponentType)) + result["question_types"] = question_type_schema.dump(dbc.get.all(QuestionType)) + result["view_types"] = view_type_schema.dump(dbc.get.all(ViewType)) + return result @api.route("/roles") class RoleList(Resource): @jwt_required def get(self): - items = Role.query.all() + items = dbc.get.all(Role) return list_response(role_schema.dump(items)) @@ -45,14 +44,14 @@ class RoleList(Resource): class CitiesList(Resource): @jwt_required def get(self): - items = City.query.all() + items = dbc.get.all(City) return list_response(city_schema.dump(items)) @jwt_required def post(self): args = name_parser.parse_args(strict=True) dbc.add.city(args["name"]) - items = City.query.all() + items = dbc.get.all(City) return list_response(city_schema.dump(items)) @@ -61,16 +60,16 @@ class CitiesList(Resource): class Cities(Resource): @jwt_required def put(self, ID): - item = City.query.filter(City.id == ID).first() + item = dbc.get.one(City, ID) args = name_parser.parse_args(strict=True) item.name = args["name"] dbc.commit_and_refresh(item) - items = City.query.all() + items = dbc.get.all(City) return list_response(city_schema.dump(items)) @jwt_required def delete(self, ID): - item = City.query.filter(City.id == ID).first() + item = dbc.get.one(City, ID) dbc.delete.default(item) - items = City.query.all() + items = dbc.get.all(City) return list_response(city_schema.dump(items)) diff --git a/server/app/core/dto.py b/server/app/core/dto.py index 3e6b0cda..a6899fc7 100644 --- a/server/app/core/dto.py +++ b/server/app/core/dto.py @@ -3,6 +3,7 @@ import app.core.schemas as schemas import marshmallow as ma from flask_restx import Namespace, fields from flask_uploads import IMAGES, UploadSet +from sqlalchemy.sql.expression import true class ComponentDTO: @@ -53,6 +54,8 @@ class MiscDTO: role_schema = schemas.RoleSchema(many=True) question_type_schema = schemas.QuestionTypeSchema(many=True) media_type_schema = schemas.MediaTypeSchema(many=True) + component_type_schema = schemas.ComponentTypeSchema(many=True) + view_type_schema = schemas.ViewTypeSchema(many=True) city_schema = schemas.CitySchema(many=True) diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py index a8136c9c..151924ca 100644 --- a/server/app/core/parsers.py +++ b/server/app/core/parsers.py @@ -77,4 +77,7 @@ component_parser.add_argument("y", type=int, default=None, location="json") component_parser.add_argument("w", type=int, default=None, location="json") component_parser.add_argument("h", type=int, default=None, location="json") component_parser.add_argument("data", type=dict, default=None, location="json") -component_parser.add_argument("type_id", type=int, default=None, location="json") + +component_create_parser = component_parser.copy() +component_create_parser.replace_argument("data", type=dict, required=True, location="json") +component_create_parser.add_argument("type_id", type=int, required=True, location="json") diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py index ab1b3abb..8c220e78 100644 --- a/server/app/core/rich_schemas.py +++ b/server/app/core/rich_schemas.py @@ -53,6 +53,7 @@ class SlideSchemaRich(RichSchema): timer = ma.auto_field() competition_id = ma.auto_field() questions = fields.Nested(QuestionSchemaRich, many=True) + components = fields.Nested(schemas.ComponentSchema, many=True) class CompetitionSchemaRich(RichSchema): diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py index 058efed7..ee909e4b 100644 --- a/server/app/core/schemas.py +++ b/server/app/core/schemas.py @@ -10,12 +10,30 @@ class BaseSchema(ma.SQLAlchemySchema): include_relationships = False -class QuestionTypeSchema(BaseSchema): +class IdNameSchema(BaseSchema): + + id = fields.fields.Integer() + name = fields.fields.String() + + +class QuestionTypeSchema(IdNameSchema): class Meta(BaseSchema.Meta): model = models.QuestionType - id = ma.auto_field() - name = ma.auto_field() + +class MediaTypeSchema(IdNameSchema): + class Meta(BaseSchema.Meta): + model = models.MediaType + + +class ComponentTypeSchema(IdNameSchema): + class Meta(BaseSchema.Meta): + model = models.ComponentType + + +class ViewTypeSchema(IdNameSchema): + class Meta(BaseSchema.Meta): + model = models.ViewType class QuestionSchema(BaseSchema): @@ -40,14 +58,6 @@ class QuestionAnswerSchema(BaseSchema): team_id = ma.auto_field() -class MediaTypeSchema(BaseSchema): - class Meta(BaseSchema.Meta): - model = models.MediaType - - id = ma.auto_field() - name = ma.auto_field() - - class RoleSchema(BaseSchema): class Meta(BaseSchema.Meta): model = models.Role @@ -124,6 +134,6 @@ class ComponentSchema(BaseSchema): y = ma.auto_field() w = ma.auto_field() h = ma.auto_field() - data = ma.auto_field() # TODO: Convert this to dict + data = ma.Function(lambda obj: obj.data) slide_id = ma.auto_field() type_id = ma.auto_field() diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 9e2a8e11..0e955a9e 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -5,6 +5,7 @@ from app.database.models import ( City, Competition, Component, + ComponentType, Media, MediaType, Question, @@ -13,6 +14,7 @@ from app.database.models import ( Slide, Team, User, + ViewType, ) from flask_restx import abort @@ -33,8 +35,8 @@ def db_add(func): @db_add -def component(x, y, w, h, data, item_slide, type_id): - return Component(x, y, w, h, data, item_slide.id, type_id) +def component(type_id, item_slide, data, x=0, y=0, w=0, h=0): + return Component(item_slide.id, type_id, data, x, y, w, h) @db_add @@ -83,6 +85,16 @@ def questionType(name): return QuestionType(name) +@db_add +def componentType(name): + return ComponentType(name) + + +@db_add +def viewType(name): + return ViewType(name) + + @db_add def role(name): return Role(name) diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py index 65527ee9..2eb040c9 100644 --- a/server/app/database/controller/delete.py +++ b/server/app/database/controller/delete.py @@ -8,6 +8,10 @@ def default(item): db.session.commit() +def component(item_component): + default(item_component) + + def slide(item_slide): for item_question in item_slide.questions: question(item_question) diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py index 0a1b190f..3afcb4d4 100644 --- a/server/app/database/controller/edit.py +++ b/server/app/database/controller/edit.py @@ -20,6 +20,23 @@ def switch_order(item1, item2): return item1 +def component(item, x, y, w, h, data): + if x: + item.x = x + if y: + item.y = y + if w: + item.w = w + if h: + item.h = h + if data: + item.data = data + + db.session.commit() + db.session.refresh(item) + return item + + def slide(item, title=None, timer=None): if title: item.title = title diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index fa6914b2..892d251b 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -1,4 +1,25 @@ -from app.database.models import Competition, Component, Question, Slide, Team, User +from app.core import db +from app.database.models import ( + City, + Competition, + Component, + ComponentType, + MediaType, + Question, + QuestionType, + Role, + Slide, + Team, + User, +) + + +def all(db_type): + return db_type.query.all() + + +def one(db_type, id, required=True, error_msg=None): + return db_type.query.filter(db_type.id == id).first_extended(required, error_msg) def user_exists(email): diff --git a/server/app/database/models.py b/server/app/database/models.py index 29cbe3b5..6396e035 100644 --- a/server/app/database/models.py +++ b/server/app/database/models.py @@ -203,7 +203,6 @@ class Dictionary(TypeDecorator): class Component(db.Model): - # __mapper_args__ = {"polymorphic_on": type, "polymorphic_identity": "component"} id = db.Column(db.Integer, primary_key=True) x = db.Column(db.Integer, nullable=False, default=0) y = db.Column(db.Integer, nullable=False, default=0) @@ -213,7 +212,7 @@ class Component(db.Model): slide_id = db.Column(db.Integer, db.ForeignKey("slide.id"), nullable=False) type_id = db.Column(db.Integer, db.ForeignKey("component_type.id"), nullable=False) - def __init__(self, x, y, w, h, data, slide_id, type_id): + def __init__(self, slide_id, type_id, data, x=0, y=0, w=1, h=1): self.x = x self.y = y self.w = w @@ -223,33 +222,6 @@ class Component(db.Model): self.type_id = type_id -""" -class ImageComponent(Component): - __mapper_args__ = {"polymorphic_identity": "image_component"} - - id = db.Column(db.Integer, db.ForeignKey("component.id"), primary_key=True) - # id = db.Column(db.Integer, primary_key=True) - image_id = db.Column(db.Integer, db.ForeignKey("media.id"), nullable=False) - image = db.relationship("Media", uselist=False) - - def __init__(self, id, image_id, x, y, w, h, slide_id, type): - super.__init__(x, y, w, h, slide_id, type) - self.id = id - self.image_id = image_id - - -class TextComponent(Component): - __mapper_args__ = {"polymorphic_identity": "text_component"} - id = db.Column(db.Integer, db.ForeignKey("component.id"), primary_key=True) - text = db.Column(db.Text, default="", nullable=False) - - def __init__(self, id, text, x, y, w, h, slide_id, type): - super.__init__(x, y, w, h, slide_id, type) - self.id = id - self.text = text -""" - - class Code(db.Model): table_args = (db.UniqueConstraint("pointer", "type"),) id = db.Column(db.Integer, primary_key=True) @@ -258,18 +230,29 @@ class Code(db.Model): view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False) + def __init__(self, code, pointer, view_type_id): + self.code = code + self.pointer = pointer + self.view_type_id = view_type_id + class ViewType(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.Text) + name = db.Column(db.String(STRING_SIZE), unique=True) codes = db.relationship("Code", backref="view_type") + def __init__(self, name): + self.name = name + class ComponentType(db.Model): id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.Text) + name = db.Column(db.String(STRING_SIZE), unique=True) components = db.relationship("Component", backref="component_type") + def __init__(self, name): + self.name = name + class MediaType(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/server/configmodule.py b/server/configmodule.py index fcf23cbf..2d525424 100644 --- a/server/configmodule.py +++ b/server/configmodule.py @@ -15,12 +15,13 @@ class Config: JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30) UPLOADED_PHOTOS_DEST = "static/images" # os.getcwd() SECRET_KEY = os.urandom(24) + SQLALCHEMY_ECHO = False class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = "sqlite:///database.db" - SQLALCHEMY_ECHO = True + SQLALCHEMY_ECHO = False class TestingConfig(Config): diff --git a/server/populate.py b/server/populate.py index e2ca4776..45eb6005 100644 --- a/server/populate.py +++ b/server/populate.py @@ -8,21 +8,30 @@ from app.database.models import City, Competition, MediaType, QuestionType, Role def _add_items(): media_types = ["Image", "Video"] question_types = ["Boolean", "Multiple", "Text"] + component_types = ["Text", "Image"] + view_types = ["Team", "Judge", "Audience"] + roles = ["Admin", "Editor"] cities = ["Linköping", "Stockholm", "Norrköping", "Örkelljunga"] teams = ["Gymnasieskola A", "Gymnasieskola B", "Gymnasieskola C"] - for team_name in media_types: - dbc.add.mediaType(team_name) + for name in media_types: + dbc.add.mediaType(name) + + for name in question_types: + dbc.add.questionType(name) + + for name in component_types: + dbc.add.componentType(name) - for team_name in question_types: - dbc.add.questionType(team_name) + for name in view_types: + dbc.add.viewType(name) - for team_name in roles: - dbc.add.role(team_name) + for name in roles: + dbc.add.role(name) - for team_name in cities: - dbc.add.city(team_name) + for name in cities: + dbc.add.city(name) admin_id = Role.query.filter(Role.name == "Admin").one().id editor_id = Role.query.filter(Role.name == "Editor").one().id diff --git a/server/tests/test_app.py b/server/tests/test_app.py index 1cbacc33..67dbac72 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -2,8 +2,7 @@ import app.core.http_codes as codes from app.database.models import Slide from tests import app, client, db -from tests.test_helpers import (add_default_values, change_order_test, delete, - get, post, put) +from tests.test_helpers import add_default_values, change_order_test, delete, get, post, put def test_misc_api(client): @@ -14,6 +13,14 @@ def test_misc_api(client): assert response.status_code == codes.OK headers = {"Authorization": "Bearer " + body["access_token"]} + # Get types + response, body = get(client, "/api/misc/types", headers=headers) + assert response.status_code == codes.OK + assert len(body["media_types"]) >= 2 + assert len(body["question_types"]) >= 3 + assert len(body["component_types"]) >= 2 + assert len(body["view_types"]) >= 2 + ## Get misc response, body = get(client, "/api/misc/roles", headers=headers) assert response.status_code == codes.OK @@ -21,44 +28,33 @@ def test_misc_api(client): response, body = get(client, "/api/misc/cities", headers=headers) assert response.status_code == codes.OK - assert body["count"] == 2 - assert body["items"][0]["name"] == "Linköping" - assert body["items"][1]["name"] == "Testköping" - - response, body = get(client, "/api/misc/media_types", headers=headers) - assert response.status_code == codes.OK assert body["count"] >= 2 - - response, body = get(client, "/api/misc/question_types", headers=headers) - assert response.status_code == codes.OK - assert body["count"] >= 3 + assert body["items"][0]["name"] == "Linköping" and body["items"][1]["name"] == "Testköping" ## Cities response, body = post(client, "/api/misc/cities", {"name": "Göteborg"}, headers=headers) assert response.status_code == codes.OK - assert body["count"] >= 2 - assert body["items"][2]["name"] == "Göteborg" + assert body["count"] >= 2 and body["items"][2]["name"] == "Göteborg" # Rename city response, body = put(client, "/api/misc/cities/3", {"name": "Gbg"}, headers=headers) assert response.status_code == codes.OK - assert body["count"] >= 2 - assert body["items"][2]["name"] == "Gbg" + assert body["count"] >= 2 and body["items"][2]["name"] == "Gbg" # Delete city # First checks current cities response, body = get(client, "/api/misc/cities", headers=headers) assert response.status_code == codes.OK - assert body["count"] == 3 + assert body["count"] >= 3 assert body["items"][0]["name"] == "Linköping" assert body["items"][1]["name"] == "Testköping" assert body["items"][2]["name"] == "Gbg" + # Deletes city response, body = delete(client, "/api/misc/cities/3", headers=headers) assert response.status_code == codes.OK - assert body["count"] == 2 - assert body["items"][0]["name"] == "Linköping" - assert body["items"][1]["name"] == "Testköping" + assert body["count"] >= 2 + assert body["items"][0]["name"] == "Linköping" and body["items"][1]["name"] == "Testköping" def test_competition_api(client): @@ -152,8 +148,7 @@ def test_auth_and_user_api(client): response, body = put(client, "/api/users", {"name": "carl carlsson", "city_id": 2, "role_id": 1}, headers=headers) assert response.status_code == codes.OK assert body["name"] == "Carl Carlsson" - assert body["city"]["id"] == 2 - assert body["role"]["id"] == 1 + assert body["city"]["id"] == 2 and body["role"]["id"] == 1 # Find other user response, body = get( @@ -244,16 +239,14 @@ def test_slide_api(client): # Get slides CID = 2 - num_slides = 3 response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers) assert response.status_code == codes.OK - assert body["count"] == num_slides + assert body["count"] == 3 # Add slide response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers) - num_slides += 1 assert response.status_code == codes.OK - assert body["count"] == num_slides + assert body["count"] == 4 # Get slide SID = 1 @@ -286,21 +279,19 @@ def test_slide_api(client): # Delete slide response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers) - num_slides -= 1 assert response.status_code == codes.NO_CONTENT # Checks that there are fewer slides response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers) assert response.status_code == codes.OK - assert body["count"] == num_slides + assert body["count"] == 3 # Tries to delete slide again response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers) assert response.status_code == codes.NOT_FOUND # Changes the order to the same order - i = 0 - SID = body["items"][i]["id"] - order = body["items"][i]["order"] + SID = body["items"][0]["id"] + order = body["items"][0]["order"] response, _ = put(client, f"/api/competitions/{CID}/slides/{SID}/order", {"order": order}, headers=headers) assert response.status_code == codes.OK diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py index 7cbdec87..6b6bf7a0 100644 --- a/server/tests/test_helpers.py +++ b/server/tests/test_helpers.py @@ -9,23 +9,29 @@ from app.database.models import City, Role def add_default_values(): media_types = ["Image", "Video"] question_types = ["Boolean", "Multiple", "Text"] + component_types = ["Text", "Image"] + view_types = ["Team", "Judge", "Audience"] + roles = ["Admin", "Editor"] cities = ["Linköping", "Testköping"] - # Add media types - for item in media_types: - dbc.add.mediaType(item) + for name in media_types: + dbc.add.mediaType(name) + + for name in question_types: + dbc.add.questionType(name) + + for name in component_types: + dbc.add.componentType(name) + + for name in view_types: + dbc.add.viewType(name) - # Add question types - for item in question_types: - dbc.add.questionType(item) + for name in roles: + dbc.add.role(name) - # Add roles - for item in roles: - dbc.add.role(item) - # Add cities - for item in cities: - dbc.add.city(item) + for name in cities: + dbc.add.city(name) item_admin = Role.query.filter(Role.name == "Admin").one() item_city = City.query.filter(City.name == "Linköping").one() -- GitLab