From 963750a04aa89c26474c37931e76e51a386746ef Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Wed, 19 Sep 2018 18:09:31 +0200 Subject: [PATCH 01/67] Change db models to join the relationships as defined in model. --- .../db_models/blacklist.py | 2 +- total_tolles_ferleihsystem/db_models/item.py | 51 ++++++++++--------- .../db_models/itemType.py | 17 ++++--- total_tolles_ferleihsystem/db_models/tag.py | 2 +- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/total_tolles_ferleihsystem/db_models/blacklist.py b/total_tolles_ferleihsystem/db_models/blacklist.py index 028b9e2..bd5a22b 100644 --- a/total_tolles_ferleihsystem/db_models/blacklist.py +++ b/total_tolles_ferleihsystem/db_models/blacklist.py @@ -31,7 +31,7 @@ class BlacklistToItemType (DB.Model): end_time = DB.Column(DB.DateTime, nullable=True) reason = DB.Column(DB.Text, nullable=True) - user = DB.relationship('Blacklist', backref=DB.backref('_item_types', lazy='joined', + user = DB.relationship('Blacklist', lazy='select', backref=DB.backref('_item_types', lazy='select', single_parent=True, cascade="all, delete-orphan")) item_type = DB.relationship('ItemType', lazy='joined') diff --git a/total_tolles_ferleihsystem/db_models/item.py b/total_tolles_ferleihsystem/db_models/item.py index e85f10e..5eeced1 100644 --- a/total_tolles_ferleihsystem/db_models/item.py +++ b/total_tolles_ferleihsystem/db_models/item.py @@ -36,7 +36,7 @@ class Item(DB.Model): name = DB.Column(DB.String(STD_STRING_SIZE)) update_name_from_schema = DB.Column(DB.Boolean, default=True, nullable=False) type_id = DB.Column(DB.Integer, DB.ForeignKey('ItemType.id')) - lending_duration = DB.Column(DB.Integer, nullable=True) # in seconds + lending_duration = DB.Column(DB.Integer, nullable=True) # in seconds deleted = DB.Column(DB.Boolean, default=False) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) @@ -47,7 +47,7 @@ class Item(DB.Model): ) def __init__(self, update_name_from_schema: bool, name: str, type_id: int, lending_duration: int = -1, - visible_for: str = ''): + visible_for: str = ''): self.update_name_from_schema = update_name_from_schema self.name = name @@ -61,7 +61,7 @@ def __init__(self, update_name_from_schema: bool, name: str, type_id: int, lendi self.visible_for = visible_for def update(self, update_name_from_schema: bool, name: str, type_id: int, lending_duration: int = -1, - visible_for: str = ''): + visible_for: str = ''): """ Function to update the objects data """ @@ -184,20 +184,20 @@ def get_attribute_changes(self, definition_ids, remove: bool = False): if(remove): # Check if multiple sources bring it, if yes don't delete it. sources = 0 - if(def_id in [ittad.attribute_definition_id for ittad in self.type._item_type_to_attribute_definitions if not ittad.attribute_definition.deleted ]): + if(def_id in [ittad.attribute_definition_id for ittad in self.type._item_type_to_attribute_definitions if not ittad.attribute_definition.deleted]): sources += 1 for tag in [itt.tag for itt in self._tags]: if(def_id in [ttad.attribute_definition_id for ttad in tag._tag_to_attribute_definitions if not ttad.attribute_definition.deleted]): sources += 1 - if sources == 1 : + if sources == 1: attributes_to_delete.append(itad) elif(itad.deleted): attributes_to_undelete.append(itad) if not exists and not remove: attributes_to_add.append(ItemToAttributeDefinition(self.id, - def_id, - "")) #TODO: Get default if possible. + def_id, + "")) # TODO: Get default if possible. return attributes_to_add, attributes_to_delete, attributes_to_undelete def get_new_attributes_from_type(self, type_id: int): @@ -209,7 +209,8 @@ def get_new_attributes_from_type(self, type_id: int): .query .filter(itemType.ItemTypeToAttributeDefinition.item_type_id == type_id) .all()) - attributes_to_add, _, _ = self.get_attribute_changes([ittad.attribute_definition_id for ittad in item_type_attribute_definitions if not ittad.item_type.deleted], False) + attributes_to_add, _, _ = self.get_attribute_changes( + [ittad.attribute_definition_id for ittad in item_type_attribute_definitions if not ittad.item_type.deleted], False) return attributes_to_add @@ -268,11 +269,11 @@ class File(DB.Model): invalidation = DB.Column(DB.DateTime, nullable=True) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) - item = DB.relationship('Item', lazy='joined', backref=DB.backref('_files', lazy='select', + item = DB.relationship('Item', lazy='select', backref=DB.backref('_files', lazy='select', single_parent=True, cascade="all, delete-orphan")) - def __init__(self, name: str, file_type: str, file_hash: str, item_id: int=None, visible_for: str = ''): + def __init__(self, name: str, file_type: str, file_hash: str, item_id: int = None, visible_for: str = ''): if item_id is not None: self.item_id = item_id self.name = name @@ -327,13 +328,12 @@ class ItemToItem(DB.Model): parent_id = DB.Column(DB.Integer, DB.ForeignKey('Item.id'), primary_key=True) item_id = DB.Column(DB.Integer, DB.ForeignKey('Item.id'), primary_key=True) - parent = DB.relationship('Item', foreign_keys=[parent_id], + parent = DB.relationship('Item', foreign_keys=[parent_id], lazy='select', backref=DB.backref('_contained_items', lazy='select', single_parent=True, cascade="all, delete-orphan")) - item = DB.relationship('Item', foreign_keys=[item_id], backref=DB.backref('_parents', lazy='select', - single_parent=True, - cascade="all, delete-orphan"), - lazy='joined') + item = DB.relationship('Item', foreign_keys=[item_id], lazy='select', + backref=DB.backref('_parents', lazy='select', + single_parent=True, cascade="all, delete-orphan")) def __init__(self, parent_id: int, item_id: int): self.parent_id = parent_id @@ -348,11 +348,11 @@ class ItemToLending (DB.Model): lending_id = DB.Column(DB.Integer, DB.ForeignKey('Lending.id'), primary_key=True) due = DB.Column(DB.DateTime) - item = DB.relationship('Item', backref=DB.backref('_lending', lazy='joined', - single_parent=True, cascade="all, delete-orphan")) - lending = DB.relationship('Lending', backref=DB.backref('itemLendings', lazy='joined', - single_parent=True, - cascade="all, delete-orphan"), lazy='select') + item = DB.relationship('Item', lazy='select', backref=DB.backref('_lending', lazy='select', + single_parent=True, cascade="all, delete-orphan")) + lending = DB.relationship('Lending', lazy='select', backref=DB.backref('itemLendings', lazy='select', + single_parent=True, + cascade="all, delete-orphan")) def __init__(self, item: Item, lending: Lending): self.item = item @@ -367,8 +367,8 @@ class ItemToTag (DB.Model): item_id = DB.Column(DB.Integer, DB.ForeignKey('Item.id'), primary_key=True) tag_id = DB.Column(DB.Integer, DB.ForeignKey('Tag.id'), primary_key=True) - item = DB.relationship('Item', backref=DB.backref('_tags', lazy='joined', - single_parent=True, cascade="all, delete-orphan")) + item = DB.relationship('Item', lazy='select', backref=DB.backref('_tags', lazy='select', + single_parent=True, cascade="all, delete-orphan")) tag = DB.relationship('Tag', lazy='joined') def __init__(self, item_id: int, tag_id: int): @@ -385,9 +385,10 @@ class ItemToAttributeDefinition (DB.Model): value = DB.Column(DB.String(STD_STRING_SIZE)) deleted = DB.Column(DB.Boolean, default=False) - item = DB.relationship('Item', backref=DB.backref('_attributes', lazy='select', - single_parent=True, cascade="all, delete-orphan")) - attribute_definition = DB.relationship('AttributeDefinition', backref=DB.backref('_item_to_attribute_definitions', lazy='joined')) + item = DB.relationship('Item', lazy='select', backref=DB.backref('_attributes', lazy='select', + single_parent=True, cascade="all, delete-orphan")) + attribute_definition = DB.relationship('AttributeDefinition', lazy='select', + backref=DB.backref('_item_to_attribute_definitions', lazy='select')) def __init__(self, item_id: int, attribute_definition_id: int, value: str): self.item_id = item_id diff --git a/total_tolles_ferleihsystem/db_models/itemType.py b/total_tolles_ferleihsystem/db_models/itemType.py index f124889..d53d28c 100644 --- a/total_tolles_ferleihsystem/db_models/itemType.py +++ b/total_tolles_ferleihsystem/db_models/itemType.py @@ -3,7 +3,8 @@ from .attributeDefinition import AttributeDefinition from . import item -__all__= [ 'ItemType', 'ItemTypeToItemType', 'ItemTypeToAttributeDefinition' ] +__all__ = ['ItemType', 'ItemTypeToItemType', 'ItemTypeToAttributeDefinition'] + class ItemType (DB.Model): @@ -29,7 +30,7 @@ def __init__(self, name: str, name_schema: str, lending_duration: int, visible_f if how_to != '' and how_to != None: self.how_to = how_to - def update(self, name: str, name_schema: str, lendable: bool, lending_duration: int, visible_for: str, how_to:str): + def update(self, name: str, name_schema: str, lendable: bool, lending_duration: int, visible_for: str, how_to: str): self.name = name self.name_schema = name_schema self.lendable = lendable @@ -52,7 +53,8 @@ def unassociate_attr_def(self, attribute_definition_id): if association is None: return(204, '', False) - itads = item.ItemToAttributeDefinition.query.filter(item.ItemToAttributeDefinition.attribute_definition_id == attribute_definition_id).all() + itads = item.ItemToAttributeDefinition.query.filter( + item.ItemToAttributeDefinition.attribute_definition_id == attribute_definition_id).all() items = [itad.item for itad in itads] @@ -72,10 +74,12 @@ class ItemTypeToItemType (DB.Model): parent_id = DB.Column(DB.Integer, DB.ForeignKey('ItemType.id', ondelete='CASCADE'), primary_key=True) item_type_id = DB.Column(DB.Integer, DB.ForeignKey('ItemType.id'), primary_key=True) - parent = DB.relationship('ItemType', foreign_keys=[parent_id], + parent = DB.relationship('ItemType', foreign_keys=[parent_id], lazy='select', backref=DB.backref('_contained_item_types', lazy='select', single_parent=True, cascade="all, delete-orphan")) - item_type = DB.relationship('ItemType', foreign_keys=[item_type_id], lazy='joined') + item_type = DB.relationship('ItemType', foreign_keys=[item_type_id], lazy='select', + backref=DB.backref('_possible_parent_item_types', lazy='select', + single_parent=True, cascade="all, delete-orphan")) def __init__(self, parent_id: int, item_type_id: int): self.parent_id = parent_id @@ -89,7 +93,8 @@ class ItemTypeToAttributeDefinition (DB.Model): item_type_id = DB.Column(DB.Integer, DB.ForeignKey('ItemType.id'), primary_key=True) attribute_definition_id = DB.Column(DB.Integer, DB.ForeignKey('AttributeDefinition.id'), primary_key=True) - item_type = DB.relationship('ItemType', backref=DB.backref('_item_type_to_attribute_definitions', lazy='select')) + item_type = DB.relationship('ItemType', lazy='select', + backref=DB.backref('_item_type_to_attribute_definitions', lazy='select')) attribute_definition = DB.relationship('AttributeDefinition', lazy='joined') def __init__(self, item_type_id: int, attribute_definition_id: int): diff --git a/total_tolles_ferleihsystem/db_models/tag.py b/total_tolles_ferleihsystem/db_models/tag.py index 55c5423..6385dd3 100644 --- a/total_tolles_ferleihsystem/db_models/tag.py +++ b/total_tolles_ferleihsystem/db_models/tag.py @@ -67,7 +67,7 @@ class TagToAttributeDefinition (DB.Model): tag_id = DB.Column(DB.Integer, DB.ForeignKey('Tag.id'), primary_key=True) attribute_definition_id = DB.Column(DB.Integer, DB.ForeignKey('AttributeDefinition.id'), primary_key=True) - tag = DB.relationship(Tag, backref=DB.backref('_tag_to_attribute_definitions', lazy='select')) + tag = DB.relationship(Tag, lazy='select' backref=DB.backref('_tag_to_attribute_definitions', lazy='select')) attribute_definition = DB.relationship('AttributeDefinition', lazy='joined') def __init__(self, tag_id: int, attribute_definition_id: int): From 29b002f0c08840bdf93c8364d75f1def05761fd9 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Wed, 19 Sep 2018 18:10:45 +0200 Subject: [PATCH 02/67] Add lendable parameter to ItemType.init --- total_tolles_ferleihsystem/db_models/itemType.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/total_tolles_ferleihsystem/db_models/itemType.py b/total_tolles_ferleihsystem/db_models/itemType.py index d53d28c..b96fdc9 100644 --- a/total_tolles_ferleihsystem/db_models/itemType.py +++ b/total_tolles_ferleihsystem/db_models/itemType.py @@ -19,7 +19,7 @@ class ItemType (DB.Model): visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) how_to = DB.Column(DB.Text, nullable=True) - def __init__(self, name: str, name_schema: str, lending_duration: int, visible_for: str = '', how_to: str = ''): + def __init__(self, name: str, name_schema: str, lendable: bool, lending_duration: int, visible_for: str = '', how_to: str = ''): self.name = name self.name_schema = name_schema self.lending_duration = lending_duration From fd1987d0a618ca7bbee940534156a304b0eeeb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Wed, 19 Sep 2018 19:11:57 +0200 Subject: [PATCH 03/67] First major Model clean up --- .../api/catalog/item.py | 37 ++- total_tolles_ferleihsystem/api/models.py | 247 +++++++++++------- 2 files changed, 181 insertions(+), 103 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index 2f25ac1..16f204c 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -9,7 +9,7 @@ from sqlalchemy.orm import noload, joinedload from .. import API, satisfies_role -from ..models import ITEM_GET, ITEM_POST, ID, ITEM_PUT, ITEM_TAG_GET, ATTRIBUTE_PUT, ATTRIBUTE_GET, FILE_GET, ITEM_GET_WITH_PARENTS +from ..models import ITEM_GET, ITEM_POST, ID, ITEM_PUT, ITEM_TAG_GET, ATTRIBUTE_PUT, ATTRIBUTE_GET, FILE_GET from ... import DB from ...login import UserRole from ...performance import record_view_performance @@ -94,7 +94,7 @@ class ItemDetail(Resource): @jwt_required @ANS.response(404, 'Requested item not found!') - @API.marshal_with(ITEM_GET_WITH_PARENTS) + @API.marshal_with(ITEM_GET) # pylint: disable=R0201 def get(self, item_id): """ @@ -403,6 +403,37 @@ def put(self, item_id, attribute_definition_id): except IntegrityError: abort(500) + +@ANS.route('//parent/') +class ItemParentItems(Resource): + """ + The parent items of this item object + """ + + @jwt_required + @ANS.response(404, 'Requested item not found!') + @API.marshal_list_with(ITEM_GET) + # pylint: disable=R0201 + def get(self, item_id): + """ + Get all contained items of this item. + """ + base_query = Item.query + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((Item.visible_for == 'all') | (Item.visible_for == 'moderator')) + else: + base_query = base_query.filter(Item.visible_for == 'all') + + if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + abort(404, 'Requested item not found!') + + associations = ItemToItem.query.filter(ItemToItem.item_id == item_id).all() + return [e.parent for e in associations if not e.item.deleted] + + @ANS.route('//contained/') class ItemContainedItems(Resource): """ @@ -411,7 +442,7 @@ class ItemContainedItems(Resource): @jwt_required @ANS.response(404, 'Requested item not found!') - @API.marshal_with(ITEM_GET) + @API.marshal_list_with(ITEM_GET) # pylint: disable=R0201 def get(self, item_id): """ diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index 6e26fee..da4fbbc 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -7,15 +7,33 @@ from ..hal_field import HaLUrl, UrlData, NestedFields from ..db_models import STD_STRING_SIZE + +# +# --- Helper Models --- +# These modules are here to provide some consistency across all models. +# + WITH_CURIES = API.model('WithCuries', { 'curies': HaLUrl(UrlData('api.doc', templated=True, hashtag='!{rel}', name='rel')), }) ID = API.model('Id', { - 'id': fields.Integer(min=1, example=1, readonly=True), + 'id': fields.Integer(min=1, example=1, readonly=True, title='Internal Identifier'), +}) +ID_LIST = API.model('IdList', { + 'ids': fields.List(fields.Integer(min=1, example=1)), +}) + +VISIBLE_FOR = API.model('VisibleFor', { + 'visible_for': fields.String(enum=('all', 'moderator', 'administrator'), max_length=STD_STRING_SIZE, title='Access Rights'), }) + +# +# --- Root --- +# + ROOT_LINKS = API.inherit('RootLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.default_root_resource')), 'auth': HaLUrl(UrlData('api.auth_authentication_routes')), @@ -25,10 +43,16 @@ 'spec': HaLUrl(UrlData('api.specs')), 'lending': HaLUrl(UrlData('api.lending_lending_list')), }) + ROOT_MODEL = API.model('RootModel', { '_links': NestedFields(ROOT_LINKS), }) + +# +# --- Authentication Routes --- +# + AUTHENTICATION_ROUTES_LINKS = API.inherit('AuthenticationRoutesLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.auth_authentication_routes')), 'login': HaLUrl(UrlData('api.auth_login')), @@ -38,10 +62,16 @@ 'check': HaLUrl(UrlData('api.auth_check')), 'settings': HaLUrl(UrlData('api.auth_settings_resource')), }) + AUTHENTICATION_ROUTES_MODEL = API.model('AuthenticationRoutesModel', { '_links': NestedFields(AUTHENTICATION_ROUTES_LINKS), }) + +# +# --- Catalog --- +# + CATALOG_LINKS = API.inherit('CatalogLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.default_catalog_resource')), 'items': HaLUrl(UrlData('api.item_item_list')), @@ -50,76 +80,66 @@ 'files': HaLUrl(UrlData('api.file_file_list')), 'attribute_definitions': HaLUrl(UrlData('api.attribute_definition_attribute_definition_list')), }) + CATALOG_MODEL = API.model('CatalogModel', { '_links': NestedFields(CATALOG_LINKS), }) -ITEM_TYPE_LINKS = API.inherit('ItemTypeLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_type_item_type_detail', url_data={'type_id': 'id'}), - required=False), - 'attributes': HaLUrl(UrlData('api.item_type_item_type_attributes', url_data={'type_id' : 'id'})), - 'can_contain': HaLUrl(UrlData('api.item_type_item_type_can_contain_types', - url_data={'type_id' : 'id'})), -}) -ITEM_TYPE_LIST_LINKS = API.inherit('ItemTypeLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_type_item_type_list')), -}) +# +# --- Item Type --- +# -ITEM_TYPE_POST = API.model('ItemTypePOST', { +ITEM_TYPE_BASIC = API.inherit('ItemTypeBasic', VISIBLE_FOR, { 'name': fields.String(max_length=STD_STRING_SIZE, title='Name'), 'name_schema': fields.String(max_length=STD_STRING_SIZE, title='Name Schema'), + 'lendable': fields.Boolean(default=True), 'lending_duration': fields.Integer(title='Lending Duration'), - 'visible_for': fields.String(enum=('all', 'moderator', 'administrator'), title='Access Rights'), 'how_to': fields.String(nullable=True, title='How to'), }) - -ITEM_TYPE_PUT = API.inherit('ItemTypePUT', ITEM_TYPE_POST, { - 'lendable': fields.Boolean(default=True), +ITEM_TYPE_LINKS = API.inherit('ItemTypeLinks', WITH_CURIES, { + 'self': HaLUrl(UrlData('api.item_type_item_type_detail', url_data={'type_id': 'id'}), + required=False), + 'attributes': HaLUrl(UrlData('api.item_type_item_type_attributes', url_data={'type_id' : 'id'})), + 'can_contain': HaLUrl(UrlData('api.item_type_item_type_can_contain_types', + url_data={'type_id' : 'id'})), }) -ITEM_TYPE_GET = API.inherit('ItemType', ITEM_TYPE_PUT, ID, { +ITEM_TYPE_POST = API.inherit('ItemTypePOST', ITEM_TYPE_BASIC, {}) +ITEM_TYPE_PUT = API.inherit('ItemTypePUT', ITEM_TYPE_BASIC, {}) +ITEM_TYPE_GET = API.inherit('ItemType', ITEM_TYPE_BASIC, ID, { 'deleted': fields.Boolean(readonly=True), '_links': NestedFields(ITEM_TYPE_LINKS), }) -ITEM_TAG_LINKS = API.inherit('ItemTagLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_tag_item_tag_detail', url_data={'tag_id' : 'id'}), - required=False), - 'attributes': HaLUrl(UrlData('api.item_tag_item_tag_attributes', url_data={'tag_id' : 'id'})), -}) -ITEM_TAG_LIST_LINKS = API.inherit('ItemTagLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_tag_item_tag_list')), -}) +# +# --- Item Tag --- +# -ITEM_TAG_POST = API.model('ItemTagPOST', { +ITEM_TAG_BASIC = API.inherit('ItemTagBasic', VISIBLE_FOR, { 'name': fields.String(max_length=STD_STRING_SIZE, title='Name'), 'lending_duration': fields.Integer(nullable=True, title='Lending Duration'), - 'visible_for': fields.String(enum=('all', 'moderator', 'administrator'), max_length=STD_STRING_SIZE, - title='Access Rights'), }) - -ITEM_TAG_PUT = API.inherit('ItemTagPUT', ITEM_TAG_POST, { +ITEM_TAG_LINKS = API.inherit('ItemTagLinks', WITH_CURIES, { + 'self': HaLUrl(UrlData('api.item_tag_item_tag_detail', url_data={'tag_id' : 'id'}), + required=False), + 'attributes': HaLUrl(UrlData('api.item_tag_item_tag_attributes', url_data={'tag_id' : 'id'})), }) -ITEM_TAG_GET = API.inherit('ItemTagGET', ITEM_TAG_PUT, ID, { +ITEM_TAG_POST = API.inherit('ItemTagPOST', ITEM_TAG_BASIC, {}) +ITEM_TAG_PUT = API.inherit('ItemTagPUT', ITEM_TAG_BASIC, {}) +ITEM_TAG_GET = API.inherit('ItemTagGET', ITEM_TAG_BASIC, ID, { 'deleted': fields.Boolean(readonly=True), '_links': NestedFields(ITEM_TAG_LINKS), }) -ATTRIBUTE_DEFINITION_LINKS = API.inherit('AttributeDefinitionLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.attribute_definition_attribute_definition_detail', - url_data={'definition_id' : 'id'}), required=False), - 'autocomplete': HaLUrl(UrlData('api.attribute_definition_attribute_definition_values', - url_data={'definition_id' : 'id'}), required=False), -}) -ATTRIBUTE_DEFINITION_LIST_LINKS = API.inherit('AttributeDefinitionLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.attribute_definition_attribute_definition_list')), -}) +# +# --- Attribute Definition --- +# -ATTRIBUTE_DEFINITION_POST = API.model('AttributeDefinitionPOST', { +ATTRIBUTE_DEFINITION_BASIC = API.inherit('AttributeDefinitionBasic', VISIBLE_FOR, { 'name': fields.String(max_length=STD_STRING_SIZE), 'type': fields.String(enum=('string', 'integer', 'number', 'boolean'), max_length=STD_STRING_SIZE), 'jsonschema': fields.String(nullable=True, default='{\n \n}', valueType='json', description="""Subset of jsonschema v4 @@ -129,13 +149,17 @@ * string: "minLength", "maxLength", "pattern", "enum", "format" ["date"|"date-time"] * integer: "minimum", "maximum" * float: "minimum", "maxinum" """), - 'visible_for': fields.String(enum=('all', 'moderator', 'administrator'), max_length=STD_STRING_SIZE), }) - -ATTRIBUTE_DEFINITION_PUT = API.inherit('AttributeDefinitionPUT', ATTRIBUTE_DEFINITION_POST, { +ATTRIBUTE_DEFINITION_LINKS = API.inherit('AttributeDefinitionLinks', WITH_CURIES, { + 'self': HaLUrl(UrlData('api.attribute_definition_attribute_definition_detail', + url_data={'definition_id' : 'id'}), required=False), + 'autocomplete': HaLUrl(UrlData('api.attribute_definition_attribute_definition_values', + url_data={'definition_id' : 'id'}), required=False), }) -ATTRIBUTE_DEFINITION_GET = API.inherit('AttributeDefinitionGET', ATTRIBUTE_DEFINITION_PUT, ID, { +ATTRIBUTE_DEFINITION_POST = API.inherit('AttributeDefinitionPOST', ATTRIBUTE_DEFINITION_BASIC, {}) +ATTRIBUTE_DEFINITION_PUT = API.inherit('AttributeDefinitionPUT', ATTRIBUTE_DEFINITION_BASIC, {}) +ATTRIBUTE_DEFINITION_GET = API.inherit('AttributeDefinitionGET', ATTRIBUTE_DEFINITION_BASIC, ID, { 'deleted': fields.Boolean(readonly=True), '_links': NestedFields(ATTRIBUTE_DEFINITION_LINKS), }) @@ -147,35 +171,29 @@ } }) -ID_LIST = API.model('IdList', { - 'ids': fields.List(fields.Integer(min=1, example=1)), -}) - -ITEM_LINKS = API.inherit('ItemLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_item_detail', url_data={'item_id' : 'id'}), required=False), - 'tags': HaLUrl(UrlData('api.item_item_item_tags', url_data={'item_id' : 'id'})), - 'attributes': HaLUrl(UrlData('api.item_item_attribute_list', url_data={'item_id' : 'id'})), - 'contained_items': HaLUrl(UrlData('api.item_item_contained_items', url_data={'item_id' : 'id'})), - 'files': HaLUrl(UrlData('api.item_item_file', url_data={'item_id' : 'id'})) -}) -ITEM_LIST_LINKS = API.inherit('ItemLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_item_list')), -}) +# +# --- Item --- +# -ITEM_POST = API.model('ItemPOST', { +ITEM_BASIC = API.inherit('ItemBasic', VISIBLE_FOR, { 'name': fields.String(max_length=STD_STRING_SIZE, title='Name'), 'update_name_from_schema': fields.Boolean(default=True, title='Schema Name'), 'type_id': fields.Integer(min=1, title='Type'), 'lending_duration': fields.Integer(nullable=True, title='Lending Duration'), - 'visible_for': fields.String(enum=('all', 'moderator', 'administrator'), max_length=STD_STRING_SIZE, - title='Access Rights'), }) - -ITEM_PUT = API.inherit('ItemPUT', ITEM_POST, { +ITEM_LINKS = API.inherit('ItemLinks', WITH_CURIES, { + 'self': HaLUrl(UrlData('api.item_item_detail', url_data={'item_id' : 'id'}), required=False), + 'tags': HaLUrl(UrlData('api.item_item_item_tags', url_data={'item_id' : 'id'})), + 'attributes': HaLUrl(UrlData('api.item_item_attribute_list', url_data={'item_id' : 'id'})), + 'parent_items': HaLUrl(UrlData('api.item_item_parent_items', url_data={'item_id' : 'id'})), + 'contained_items': HaLUrl(UrlData('api.item_item_contained_items', url_data={'item_id' : 'id'})), + 'files': HaLUrl(UrlData('api.item_item_file', url_data={'item_id' : 'id'})) }) -ITEM_GET = API.inherit('ItemGET', ITEM_PUT, ID, { +ITEM_POST = API.inherit('ItemPOST', ITEM_BASIC, {}) +ITEM_PUT = API.inherit('ItemPUT', ITEM_BASIC, {}) +ITEM_GET = API.inherit('ItemGET', ITEM_BASIC, ID, { 'deleted': fields.Boolean(readonly=True), 'type': fields.Nested(ITEM_TYPE_GET), 'is_currently_lent': fields.Boolean(readonly=True), @@ -185,31 +203,37 @@ '_links': NestedFields(ITEM_LINKS) }) -ITEM_GET_WITH_PARENTS = API.inherit('ItemGET_PARENTS', ITEM_GET, { - 'parent': fields.Nested(ITEM_GET, attribute='parent') -}) +# +# --- Attribute --- +# +ATTRIBUTE_BASIC = API.model('AttributeBasic', { + 'value': fields.String(max_length=STD_STRING_SIZE), +}) ATTRIBUTE_LINKS = API.inherit('AttributeLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.item_item_attribute_detail', url_data={'item_id': 'item_id', 'attribute_definition_id': 'attribute_definition_id'}), required=False), }) -ATTRIBUTE_LIST_LINKS = API.inherit('AttributeLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_item_attribute_list')), -}) - -ATTRIBUTE_PUT = API.model('AttributePUT', { - 'value': fields.String(max_length=STD_STRING_SIZE), -}) - -ATTRIBUTE_GET = API.inherit('AttributeGET', ATTRIBUTE_PUT, { +ATTRIBUTE_PUT = API.inherit('AttributePUT', ATTRIBUTE_BASIC, {}) +ATTRIBUTE_GET = API.inherit('AttributeGET', ATTRIBUTE_BASIC, { 'attribute_definition_id': fields.Integer(), 'attribute_definition': fields.Nested(ATTRIBUTE_DEFINITION_GET), '_links': NestedFields(ATTRIBUTE_LINKS) }) + +# +# --- FIle --- +# + +FILE_BASIC = API.inherit('FileBASIC', VISIBLE_FOR, { + 'name': fields.String(max_length=STD_STRING_SIZE, nullable=True), + 'file_type': fields.String(max_length=20), + 'invalidation': fields.DateTime(nullable=True), +}) FILE_LINKS = API.inherit('FileLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.file_file_detail', url_data={'file_id': 'id'}), required=False), @@ -217,13 +241,7 @@ url_data={'file_hash': 'file_hash'}), required=False), }) -FILE_BASIC = API.model('FileBASIC', { - 'name': fields.String(max_length=STD_STRING_SIZE, nullable=True), - 'file_type': fields.String(max_length=20), - 'invalidation': fields.DateTime(nullable=True), - 'visible_for': fields.String(enum=('all', 'moderator', 'administrator'), title='Access Rights'), -}) - +FILE_PUT = API.inherit('FilePUT', FILE_BASIC, {}) FILE_GET = API.inherit('FileGET', FILE_BASIC, ID, { 'item': fields.Nested(ITEM_GET), 'file_hash': fields.String(max_length=STD_STRING_SIZE), @@ -231,34 +249,63 @@ '_links': NestedFields(FILE_LINKS) }) -FILE_PUT = API.inherit('FilePUT', FILE_BASIC, { -}) - -LENDING_LINKS = API.inherit('LendingLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.lending_lending_detail', - url_data={'lending_id' : 'id'}), required=False), -}) -ITEM_LENDING = API.model('ItemLending', { - 'due': fields.DateTime(), - 'item': fields.Nested(ITEM_GET), -}) +# +# --- Lending --- +# LENDING_BASIC = API.model('LendingBASIC', { 'moderator': fields.String(max_length=STD_STRING_SIZE), 'user': fields.String(max_length=STD_STRING_SIZE), 'deposit': fields.String(example="Studentenausweis", max_length=STD_STRING_SIZE), }) +LENDING_ITEM = API.model('LendingItem', { + 'due': fields.DateTime(), + 'item': fields.Nested(ITEM_GET), +}) +LENDING_LINKS = API.inherit('LendingLinks', WITH_CURIES, { + 'self': HaLUrl(UrlData('api.lending_lending_detail', + url_data={'lending_id' : 'id'}), required=False), +}) LENDING_POST = API.inherit('LendingPOST', LENDING_BASIC, { 'item_ids': fields.List(fields.Integer(min=1)) }) - -LENDING_PUT = API.inherit('LendingPUT', LENDING_POST, { -}) - +LENDING_PUT = API.inherit('LendingPUT', LENDING_POST, {}) LENDING_GET = API.inherit('LendingGET', LENDING_BASIC, ID, { '_links': NestedFields(LENDING_LINKS), 'date': fields.DateTime(), - 'itemLendings': fields.Nested(ITEM_LENDING) + 'itemLendings': fields.Nested(LENDING_ITEM), +}) + + +# +# --- Blacklist --- +# + +BLACKLIST_BASIC = API.model('BlacklistBasic', { + 'name': fields.String(max_length=STD_STRING_SIZE), + 'system_wide': fields.Boolean(default=False), + 'reason': fields.String(), +}) +BLACKLIST_ITEM_TYPE_BASIC = API.model('BlacklistItemTypeBasic', { + 'end_time': fields.DateTime(), + 'reason': fields.String(), +}) +BLACKLIST_LINKS = API.inherit('BlacklistLinks', WITH_CURIES, { + 'self': HaLUrl(UrlData('', url_data={'id' : 'id'})), + 'blockedItemTypes': HaLUrl(UrlData('', url_data={'id' : 'id'})) +}) + +BLACKLIST_POST = API.inherit('BlacklistPOST', BLACKLIST_ITEM_TYPE_BASIC, { + 'item_type_id': fields.Integer(min=1, example=1, readonly=True, title='Internal Identifier'), +}) +BLACKLIST_PUT = API.inherit('BlacklistPUT', BLACKLIST_BASIC, {}) +BLACKLIST_GET = API.inherit('BlacklistGET', BLACKLIST_BASIC, ID, { + '_links': NestedFields(BLACKLIST_LINKS), + 'blocked_types': HaLUrl(UrlData('', url_data={'id' : 'id'})), +}) + +BLACKLIST_ITEM_TYPE_GET = API.inherit('BlacklistItemTypeGet', BLACKLIST_ITEM_TYPE_BASIC, { + 'item_type': NestedFields(ITEM_TYPE_GET), }) From d6ec786a5e577d62d43c52515aa51bebb93c5fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 20 Sep 2018 12:38:03 +0200 Subject: [PATCH 04/67] Fix a minor missdesign --- total_tolles_ferleihsystem/api/models.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index da4fbbc..28fce32 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -275,7 +275,7 @@ LENDING_GET = API.inherit('LendingGET', LENDING_BASIC, ID, { '_links': NestedFields(LENDING_LINKS), 'date': fields.DateTime(), - 'itemLendings': fields.Nested(LENDING_ITEM), + 'item_lendings': fields.Nested(LENDING_ITEM), }) @@ -288,13 +288,12 @@ 'system_wide': fields.Boolean(default=False), 'reason': fields.String(), }) -BLACKLIST_ITEM_TYPE_BASIC = API.model('BlacklistItemTypeBasic', { +BLACKLIST_ITEM_TYPE = API.model('BlacklistItemType', { 'end_time': fields.DateTime(), 'reason': fields.String(), }) BLACKLIST_LINKS = API.inherit('BlacklistLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('', url_data={'id' : 'id'})), - 'blockedItemTypes': HaLUrl(UrlData('', url_data={'id' : 'id'})) }) BLACKLIST_POST = API.inherit('BlacklistPOST', BLACKLIST_ITEM_TYPE_BASIC, { @@ -303,9 +302,5 @@ BLACKLIST_PUT = API.inherit('BlacklistPUT', BLACKLIST_BASIC, {}) BLACKLIST_GET = API.inherit('BlacklistGET', BLACKLIST_BASIC, ID, { '_links': NestedFields(BLACKLIST_LINKS), - 'blocked_types': HaLUrl(UrlData('', url_data={'id' : 'id'})), -}) - -BLACKLIST_ITEM_TYPE_GET = API.inherit('BlacklistItemTypeGet', BLACKLIST_ITEM_TYPE_BASIC, { - 'item_type': NestedFields(ITEM_TYPE_GET), + 'blocked_types': fields.Nested(BLACKLIST_ITEM_TYPE) }) From 224c1ab90dcb9b56c7cfba71cd4d7b212780f3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 20 Sep 2018 12:51:34 +0200 Subject: [PATCH 05/67] Add another endpoint to the system --- .../api/catalog/item.py | 38 ++++++++++++++++--- total_tolles_ferleihsystem/api/models.py | 4 +- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index 7879f0f..ec60a8e 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -2,25 +2,25 @@ This module contains all API endpoints for the namespace 'item' """ +import logging + from flask import request from flask_restplus import Resource, abort, marshal from flask_jwt_extended import jwt_required, get_jwt_claims from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import noload, joinedload +from sqlalchemy.orm import joinedload from .. import API, satisfies_role -from ..models import ITEM_GET, ITEM_POST, ID, ITEM_PUT, ITEM_TAG_GET, ATTRIBUTE_PUT, ATTRIBUTE_GET, FILE_GET +from ..models import ITEM_GET, ITEM_POST, ID, ITEM_PUT, ITEM_TAG_GET, ATTRIBUTE_GET, FILE_GET, LENDING_GET from ... import DB from ...login import UserRole from ...performance import record_view_performance -from ...db_models.item import Item, ItemToTag, ItemToAttributeDefinition, ItemToItem, File, ItemToLending +from ...db_models.item import Item, ItemToTag, ItemToAttributeDefinition, ItemToItem, File, ItemToLending, Lending from ...db_models.itemType import ItemType, ItemTypeToItemType from ...db_models.tag import Tag from ...db_models.attributeDefinition import AttributeDefinition -import logging - PATH: str = '/catalog/items' ANS = API.namespace('item', description='Items', path=PATH) @@ -567,3 +567,31 @@ def get(self, item_id): abort(404, 'Requested item not found!') return File.query.filter(File.item_id == item_id).all() + + +@ANS.route('//lendings/') +class ItemLendings(Resource): + """ + Current and past lendings of a single item. + """ + + @jwt_required + @ANS.response(404, 'Requested item not found!') + @API.marshal_list_with(LENDING_GET) + def get(self, item_id): + """ + Get the lendings concerning the specific item. + """ + base_query = Item.query + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((Item.visible_for == 'all') | (Item.visible_for == 'moderator')) + else: + base_query = base_query.filter(Item.visible_for == 'all') + + if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + abort(404, 'Requested item not found!') + + return Lending.query.join(ItemToLending).filter(ItemToLending.item_id == item_id).all() diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index 28fce32..3e19586 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -188,7 +188,8 @@ 'attributes': HaLUrl(UrlData('api.item_item_attribute_list', url_data={'item_id' : 'id'})), 'parent_items': HaLUrl(UrlData('api.item_item_parent_items', url_data={'item_id' : 'id'})), 'contained_items': HaLUrl(UrlData('api.item_item_contained_items', url_data={'item_id' : 'id'})), - 'files': HaLUrl(UrlData('api.item_item_file', url_data={'item_id' : 'id'})) + 'files': HaLUrl(UrlData('api.item_item_file', url_data={'item_id' : 'id'})), + 'lendings': HaLUrl(UrlData('', url_data={'item_id' : 'id'})), }) ITEM_POST = API.inherit('ItemPOST', ITEM_BASIC, {}) @@ -198,7 +199,6 @@ 'type': fields.Nested(ITEM_TYPE_GET), 'is_currently_lent': fields.Boolean(readonly=True), 'effective_lending_duration': fields.Integer(readonly=True), - 'lending_id': fields.Integer(readonly=True), 'due': fields.DateTime(attribute='item_lending.due', readonly=True), '_links': NestedFields(ITEM_LINKS) }) From 00070462f01615cae752941cd1ddbf953ee507fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 20 Sep 2018 12:55:45 +0200 Subject: [PATCH 06/67] Add missing pylint disable --- total_tolles_ferleihsystem/api/catalog/item.py | 1 + 1 file changed, 1 insertion(+) diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index ec60a8e..a18c85f 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -578,6 +578,7 @@ class ItemLendings(Resource): @jwt_required @ANS.response(404, 'Requested item not found!') @API.marshal_list_with(LENDING_GET) + # pylint: disable=R0201 def get(self, item_id): """ Get the lendings concerning the specific item. From 713baf581b99d8200e0078b3dc81418c59590cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 20 Sep 2018 12:58:29 +0200 Subject: [PATCH 07/67] Fix for two bugs --- total_tolles_ferleihsystem/api/models.py | 2 +- total_tolles_ferleihsystem/db_models/tag.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index 3e19586..fd69221 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -296,7 +296,7 @@ 'self': HaLUrl(UrlData('', url_data={'id' : 'id'})), }) -BLACKLIST_POST = API.inherit('BlacklistPOST', BLACKLIST_ITEM_TYPE_BASIC, { +BLACKLIST_POST = API.inherit('BlacklistPOST', BLACKLIST_ITEM_TYPE, { 'item_type_id': fields.Integer(min=1, example=1, readonly=True, title='Internal Identifier'), }) BLACKLIST_PUT = API.inherit('BlacklistPUT', BLACKLIST_BASIC, {}) diff --git a/total_tolles_ferleihsystem/db_models/tag.py b/total_tolles_ferleihsystem/db_models/tag.py index 6385dd3..c708824 100644 --- a/total_tolles_ferleihsystem/db_models/tag.py +++ b/total_tolles_ferleihsystem/db_models/tag.py @@ -67,7 +67,7 @@ class TagToAttributeDefinition (DB.Model): tag_id = DB.Column(DB.Integer, DB.ForeignKey('Tag.id'), primary_key=True) attribute_definition_id = DB.Column(DB.Integer, DB.ForeignKey('AttributeDefinition.id'), primary_key=True) - tag = DB.relationship(Tag, lazy='select' backref=DB.backref('_tag_to_attribute_definitions', lazy='select')) + tag = DB.relationship(Tag, lazy='select', backref=DB.backref('_tag_to_attribute_definitions', lazy='select')) attribute_definition = DB.relationship('AttributeDefinition', lazy='joined') def __init__(self, tag_id: int, attribute_definition_id: int): From 5d70024dc3d55bbff989430fee74b8f14fe64893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 20 Sep 2018 13:09:55 +0200 Subject: [PATCH 08/67] Add disable tags --- .../api/catalog/item.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index a18c85f..6edfd78 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -219,6 +219,7 @@ def get(self, item_id): else: base_query = base_query.filter(Item.visible_for == 'all') + # pylint: disable=C0121 if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') @@ -238,9 +239,11 @@ def post(self, item_id): Associate a new tag with the item. """ tag_id = request.get_json()["id"] + # pylint: disable=C0121 item = Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() if item is None: abort(404, 'Requested item not found!') + # pylint: disable=C0121 tag = Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() if tag is None: abort(400, 'Requested item tag not found!') @@ -275,9 +278,11 @@ def delete(self, item_id): """ tag_id = request.get_json()["id"] + # pylint: disable=C0121 item = Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() if item is None: abort(404, 'Requested item not found!') + # pylint: disable=C0121 if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() is None: abort(400, 'Requested item tag not found!') @@ -324,9 +329,11 @@ def get(self, item_id): else: base_query = base_query.filter(Item.visible_for == 'all') + # pylint: disable=C0121 if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') + # pylint: disable=C0121 attributes = ItemToAttributeDefinition.query.filter(ItemToAttributeDefinition.item_id == item_id).filter(ItemToAttributeDefinition.deleted == False).join(ItemToAttributeDefinition.attribute_definition).order_by(AttributeDefinition.name).all() return attributes; # DON'T CHANGE THIS!! # It is necessary because a Flask Bug prehibits log messages on return statements. @@ -355,9 +362,11 @@ def get(self, item_id, attribute_definition_id): else: base_query = base_query.filter(Item.visible_for == 'all') + # pylint: disable=C0121 if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') + # pylint: disable=C0121 attribute = (ItemToAttributeDefinition .query .filter(ItemToAttributeDefinition.item_id == item_id) @@ -382,10 +391,12 @@ def put(self, item_id, attribute_definition_id): Set a single attribute of this item. """ + # pylint: disable=C0121 if Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') value = request.get_json()["value"] + # pylint: disable=C0121 attribute = (ItemToAttributeDefinition .query .filter(ItemToAttributeDefinition.item_id == item_id) @@ -428,6 +439,7 @@ def get(self, item_id): else: base_query = base_query.filter(Item.visible_for == 'all') + # pylint: disable=C0121 if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') @@ -458,6 +470,7 @@ def get(self, item_id): else: base_query = base_query.filter(Item.visible_for == 'all') + # pylint: disable=C0121 if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') @@ -478,7 +491,9 @@ def post(self, item_id): Add a new contained item to this item """ contained_item_id = request.get_json()["id"] + # pylint: disable=C0121 parent = Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() + # pylint: disable=C0121 child = Item.query.filter(Item.id == contained_item_id).filter(Item.deleted == False).first() if parent is None: @@ -520,8 +535,10 @@ def delete(self, item_id): """ contained_item_id = request.get_json()["id"] + # pylint: disable=C0121 if Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item (current) not found!') + # pylint: disable=C0121 if Item.query.filter(Item.id == contained_item_id).filter(Item.deleted == False).first() is None: abort(400, 'Requested item (to be contained) not found!') @@ -563,6 +580,7 @@ def get(self, item_id): else: base_query = base_query.filter(Item.visible_for == 'all') + # pylint: disable=C0121 if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') @@ -592,6 +610,7 @@ def get(self, item_id): else: base_query = base_query.filter(Item.visible_for == 'all') + # pylint: disable=C0121 if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') From 5afbe174028be05bd9db7552b0b1086275c3af38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Wed, 26 Sep 2018 16:42:26 +0200 Subject: [PATCH 09/67] Add changes done during the api check by neumantm and waeltkts --- .../api/catalog/attribute_definition.py | 53 ++++- .../api/catalog/file.py | 60 +++-- .../api/catalog/item.py | 11 +- .../api/catalog/item_tag.py | 77 +++++-- .../api/catalog/item_type.py | 205 +++++++++++++++--- total_tolles_ferleihsystem/api/lending.py | 2 +- total_tolles_ferleihsystem/api/models.py | 15 +- total_tolles_ferleihsystem/api/search.py | 2 +- total_tolles_ferleihsystem/config.py | 1 + total_tolles_ferleihsystem/db_models/item.py | 26 +-- 10 files changed, 327 insertions(+), 125 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py index 0745e09..d36dc6a 100644 --- a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py +++ b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py @@ -4,12 +4,13 @@ from flask import request from flask_restplus import Resource, abort, marshal -from flask_jwt_extended import jwt_required +from flask_jwt_extended import jwt_required, get_jwt_claims +from sqlalchemy.orm import joinedload from sqlalchemy.exc import IntegrityError from .. import API, satisfies_role from ..models import ATTRIBUTE_DEFINITION_GET, ATTRIBUTE_DEFINITION_POST, ATTRIBUTE_DEFINITION_PUT, ATTRIBUTE_DEFINITION_VALUES -from ... import DB +from ... import DB, APP from ...login import UserRole from ...db_models.item import ItemToAttributeDefinition from ...db_models.attributeDefinition import AttributeDefinition @@ -36,7 +37,16 @@ def get(self): Get a list of all attribute definitions currently in the system """ test_for = request.args.get('deleted', 'false') == 'true' - return AttributeDefinition.query.filter(AttributeDefinition.deleted == test_for).order_by(AttributeDefinition.name).all() + base_query = AttributeDefinition.query.filter(AttributeDefinition.deleted == test_for) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((AttributeDefinition.visible_for == 'all') | (AttributeDefinition.visible_for == 'moderator')) + else: + base_query = base_query.filter(AttributeDefinition.visible_for == 'all') + + return base_query.order_by(AttributeDefinition.name).all() @jwt_required @satisfies_role(UserRole.ADMIN) @@ -55,8 +65,10 @@ def post(self): return marshal(new, ATTRIBUTE_DEFINITION_GET), 201 except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Name is not unique.', err) abort(409, 'Name is not unique!') + APP.logger.error('SQL Error', err) abort(500) @ANS.route('//') @@ -73,8 +85,18 @@ def get(self, definition_id): """ Get a single attribute definition """ - attribute = AttributeDefinition.query.filter(AttributeDefinition.id == definition_id).first() + base_query = AttributeDefinition.query.filter(AttributeDefinition.id == definition_id) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((AttributeDefinition.visible_for == 'all') | (AttributeDefinition.visible_for == 'moderator')) + else: + base_query = base_query.filter(AttributeDefinition.visible_for == 'all') + + attribute = base_query.first() if attribute is None: + APP.logger.debug('Requested attribute not found!', definition_id) abort(404, 'Requested attribute not found!') return attribute @@ -126,6 +148,7 @@ def post(self, definition_id): """ attribute = AttributeDefinition.query.filter(AttributeDefinition.id == definition_id).first() if attribute is None: + APP.logger.debug('Requested attribute not found!', definition_id) abort(404, 'Requested attribute not found!') attribute.deleted = False DB.session.commit() @@ -143,6 +166,7 @@ def put(self, definition_id): """ attribute = AttributeDefinition.query.filter(AttributeDefinition.id == definition_id).first() if attribute is None: + APP.logger.debug('Requested attribute not found!', definition_id) abort(404, 'Requested attribute not found!') attribute.update(**request.get_json()) try: @@ -150,8 +174,10 @@ def put(self, definition_id): return marshal(attribute, ATTRIBUTE_DEFINITION_GET), 200 except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Name is not unique.', err) abort(409, 'Name is not unique!') + APP.logger.error('SQL Error', err) abort(500) @@ -168,7 +194,18 @@ def get(self, definition_id): """ Get all values of this attribute definition """ - if AttributeDefinition.query.filter(AttributeDefinition.id == definition_id).first() is None: + base_query = AttributeDefinition.query.options(joinedload('_item_to_attribute_definitions')).filter(AttributeDefinition.id == definition_id) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((AttributeDefinition.visible_for == 'all') | (AttributeDefinition.visible_for == 'moderator')) + else: + base_query = base_query.filter(AttributeDefinition.visible_for == 'all') + + attributeDefinition = base_query.first() + if attributeDefinition is None: + APP.logger.debug('Requested attribute not found!', definition_id) abort(404, 'Requested attribute not found!') - return [item.value for item in DB.session.query(ItemToAttributeDefinition.value).filter(ItemToAttributeDefinition.attribute_definition_id == definition_id).distinct()] + return list(set([itad.value for itad in attributeDefinition._item_to_attribute_definitions])).sort() diff --git a/total_tolles_ferleihsystem/api/catalog/file.py b/total_tolles_ferleihsystem/api/catalog/file.py index 1fc03ea..a120fc6 100644 --- a/total_tolles_ferleihsystem/api/catalog/file.py +++ b/total_tolles_ferleihsystem/api/catalog/file.py @@ -41,9 +41,9 @@ def get(self): # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: if UserRole(get_jwt_claims()) == UserRole.MODERATOR: - base_query = base_query.filter((Item.visible_for == 'all') | (Item.visible_for == 'moderator')) + base_query = base_query.filter((File.visible_for == 'all') | (File.visible_for == 'moderator')) else: - base_query = base_query.filter(Item.visible_for == 'all') + base_query = base_query.filter(File.visible_for == 'all') return base_query.all() @@ -64,7 +64,7 @@ def post(self): file = request.files['file'] if not file: abort(400, 'File Empty!') - if file.filename == None or file.filename == '': + if file.filename is None or file.filename == '': abort(400, 'No file name!') item_id = request.form['item_id'] if item_id is None: @@ -85,12 +85,9 @@ def post(self): file_on_disk.write(file.stream.read()) # add the file to the sql database - try: - DB.session.add(new) - DB.session.commit() - return marshal(new, FILE_GET), 201 - except IntegrityError: - abort(500, 'SQL Error!') + DB.session.add(new) + DB.session.commit() + return marshal(new, FILE_GET), 201 @ANS.route('//') @@ -106,18 +103,20 @@ def get(self, file_id): """ Get a single file object """ - base_query = File.query + base_query = File.query.filter(File.id == file_id) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: if UserRole(get_jwt_claims()) == UserRole.MODERATOR: - base_query = base_query.filter((Item.visible_for == 'all') | (Item.visible_for == 'moderator')) + base_query = base_query.filter((File.visible_for == 'all') | (File.visible_for == 'moderator')) else: - base_query = base_query.filter(Item.visible_for == 'all') + base_query = base_query.filter(File.visible_for == 'all') + + file = base_query.first() - file = base_query.filter(File.id == file_id).first() if file is None: - abort(404, 'Requested item not found!') + APP.logger.debug('Requested file not found!', file_id) + abort(404, 'Requested file not found!') return file @@ -130,8 +129,11 @@ def delete(self, file_id): Delete a file object """ file = File.query.filter(File.id == file_id).first() + if file is None: - abort(404, 'Requested item not found!') + APP.logger.debug('Requested file not found!', file_id) + abort(404, 'Requested file not found!') + DB.session.delete(file) DB.session.commit() return "", 204 @@ -147,16 +149,15 @@ def put(self, file_id): Replace a file object """ file = File.query.filter(File.id == file_id).first() + if file is None: + APP.logger.debug('Requested file not found!', file_id) abort(404, 'Requested file not found!') file.update(**request.get_json()) - try: - DB.session.commit() - return file - except IntegrityError: - abort(500, 'SQL Error!') + DB.session.commit() + return file @ANS.route('/archive') @@ -204,23 +205,32 @@ def map_function(file_id: int) -> Tuple[str, str]: PATH2: str = '/file-store' ANS2 = API.namespace('file', description='The download Endpoint to download any file from the system.', path=PATH2) -@ANS2.route('//') +@ANS2.route('//') class FileData(Resource): """ The endpoints to get the actual stored file """ @jwt_required - @satisfies_role(UserRole.MODERATOR) @ANS.response(404, 'Requested file not found!') @ANS.response(500, 'Something crashed while reading file!') - def get(self, file_hash): + def get(self, file_id): """ Get the actual file """ - file = File.query.filter(File.file_hash == file_hash).first() + base_query = File.query.filter(File.file_id == file_id) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((File.visible_for == 'all') | (File.visible_for == 'moderator')) + else: + base_query = base_query.filter(File.visible_for == 'all') + + file = base_query.first() if file is None: + APP.logger.debug('Requested file not found!', file_id) abort(404, 'Requested file was not found!') headers = { @@ -229,4 +239,6 @@ def get(self, file_hash): with open(os.path.join(APP.config['DATA_DIRECTORY'], file.file_hash), mode='rb') as file_on_disk: return make_response(file_on_disk.read(), headers) + + APP.logger.error('Crash while downloading file.', file_id) abort(500, 'Something crashed while reading file!') diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index 6edfd78..836b680 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -2,21 +2,19 @@ This module contains all API endpoints for the namespace 'item' """ -import logging - from flask import request from flask_restplus import Resource, abort, marshal from flask_jwt_extended import jwt_required, get_jwt_claims -from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload +from sqlalchemy.exc import IntegrityError from .. import API, satisfies_role from ..models import ITEM_GET, ITEM_POST, ID, ITEM_PUT, ITEM_TAG_GET, ATTRIBUTE_GET, FILE_GET, LENDING_GET -from ... import DB +from ... import DB, APP from ...login import UserRole from ...performance import record_view_performance -from ...db_models.item import Item, ItemToTag, ItemToAttributeDefinition, ItemToItem, File, ItemToLending, Lending +from ...db_models.item import Item, ItemToTag, ItemToAttributeDefinition, ItemToItem, File, Lending from ...db_models.itemType import ItemType, ItemTypeToItemType from ...db_models.tag import Tag from ...db_models.attributeDefinition import AttributeDefinition @@ -42,8 +40,7 @@ def get(self): Get a list of all items currently in the system """ test_for = request.args.get('deleted', 'false') == 'true' - - base_query = Item.query.options(joinedload(Item.type)).filter(Item.deleted == test_for) + base_query = Item.query.options(joinedload('_lending')).filter(Item.deleted == test_for) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: diff --git a/total_tolles_ferleihsystem/api/catalog/item_tag.py b/total_tolles_ferleihsystem/api/catalog/item_tag.py index 7b9d11a..7dcf6a8 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_tag.py +++ b/total_tolles_ferleihsystem/api/catalog/item_tag.py @@ -4,7 +4,8 @@ from flask import request from flask_restplus import Resource, abort, marshal -from flask_jwt_extended import jwt_required +from flask_jwt_extended import jwt_required, get_jwt_claims +from sqlalchemy.orm import joinedload from sqlalchemy.exc import IntegrityError from .. import API, satisfies_role @@ -26,7 +27,6 @@ class ItemTagList(Resource): Item tags root item tag """ - @jwt_required @API.param('deleted', 'get all deleted item tags (and only these)', type=bool, required=False, default=False) @API.marshal_list_with(ITEM_TAG_GET) @@ -36,7 +36,16 @@ def get(self): Get a list of all item tags currently in the system """ test_for = request.args.get('deleted', 'false') == 'true' - return Tag.query.filter(Tag.deleted == test_for).order_by(Tag.name).all() + base_query = Tag.query.filter(Tag.deleted == test_for) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((Tag.visible_for == 'all') | (Tag.visible_for == 'moderator')) + else: + base_query = base_query.filter(Tag.visible_for == 'all') + + return base_query.order_by(Tag.name).all() @jwt_required @satisfies_role(UserRole.ADMIN) @@ -55,10 +64,13 @@ def post(self): return marshal(new, ITEM_TAG_GET), 201 except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Name is not unique.', err) abort(409, 'Name is not unique!') + APP.logger.error('SQL Error', err) abort(500) + @ANS.route('//') class ItemTagDetail(Resource): """ @@ -73,9 +85,20 @@ def get(self, tag_id): """ Get a single item tag object """ - item_tag = Tag.query.filter(Tag.id == tag_id).first() + base_query = Tag.query.filter(Tag.id == tag_id) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((Tag.visible_for == 'all') | (Tag.visible_for == 'moderator')) + else: + base_query = base_query.filter(Tag.visible_for == 'all') + + item_tag = base_query.first() + if item_tag is None: abort(404, 'Requested item tag not found!') + return item_tag @jwt_required @@ -89,15 +112,12 @@ def delete(self, tag_id): """ item_tag = Tag.query.filter(Tag.id == tag_id).first() if item_tag is None: + APP.logger.debug('Requested item tag not found.', tag_id) abort(404, 'Requested item tag not found!') itts = ItemToTag.query.filter(ItemToTag.tag_id == tag_id).all() items = [itt.item for itt in itts] - #Not intended -neumantm - #for itt in itts: - # DB.session.delete(itt) - for item in items: _, attributes_to_delete, _ = item.get_attribute_changes_from_tag(tag_id, True) for attr in attributes_to_delete: @@ -119,6 +139,7 @@ def post(self, tag_id): """ item_tag = Tag.query.filter(Tag.id == tag_id).first() if item_tag is None: + APP.logger.debug('Requested item tag not found.', tag_id) abort(404, 'Requested item tag not found!') itts = ItemToTag.query.filter(ItemToTag.tag_id == tag_id).all() @@ -145,20 +166,25 @@ def put(self, tag_id): Replace a item tag object """ item_tag = Tag.query.filter(Tag.id == tag_id).first() + if item_tag is None: + APP.logger.debug('Requested item tag not found.', tag_id) abort(404, 'Requested item tag not found!') + item_tag.update(**request.get_json()) + try: DB.session.commit() return marshal(item_tag, ITEM_TAG_GET), 200 except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Name is not unique.', err) abort(409, 'Name is not unique!') + APP.logger.error('SQL Error', err) abort(500) - @ANS.route('//attributes/') class ItemTagAttributes(Resource): """ @@ -173,16 +199,21 @@ def get(self, tag_id): """ Get all attribute definitions for this tag. """ - if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() is None: + base_query = Tag.query.options(joindload('_tag_to_attribute_definitions')).filter(Tag.id == tag_id).filter(Tag.deleted == False) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((Tag.visible_for == 'all') | (Tag.visible_for == 'moderator')) + else: + base_query = base_query.filter(Tag.visible_for == 'all') + + tag = base_query.first() + if tag is None: + APP.logger.debug('Requested item tag not found.', tag_id) abort(404, 'Requested item tag not found!') - # Two possibilitys: - # return [e.attribute_definition for e in TagToAttributeDefinition.query - # .filter(TagToAttributeDefinition.tag_id == tag_id).all()] - # return [e.attribute_definition for e in Tag.query.filter(Tag.id == tag_id) - # .first()._tag_to_attribute_definitions ] - associations = TagToAttributeDefinition.query.filter(TagToAttributeDefinition.tag_id == tag_id).all() - return [e.attribute_definition for e in associations if not e.tag.deleted] + return [ttad.attribute_definition for ttad in tag._tag_to_attribute_definitions] @jwt_required @satisfies_role(UserRole.ADMIN) @@ -200,8 +231,10 @@ def post(self, tag_id): attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted == False).first() if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() is None: + APP.logger.debug('Requested item tag not found.', tag_id) abort(404, 'Requested item tag not found!') if attribute_definition is None: + APP.logger.debug('Requested attribute definition not found.', attribute_definition_id) abort(400, 'Requested attribute definition not found!') items = [itt.item for itt in ItemToTag.query.filter(ItemToTag.tag_id == tag_id).all()] @@ -219,8 +252,10 @@ def post(self, tag_id): return [e.attribute_definition for e in associations] except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Attribute definition is already asociated with this tag!', err) abort(409, 'Attribute definition is already asociated with this tag!') + APP.logger.error('SQL Error', err) abort(500) @jwt_required @@ -237,6 +272,7 @@ def delete(self, tag_id): attribute_definition_id = request.get_json()["id"] tag = Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() if tag is None: + APP.logger.debug('Requested item tag not found.', tag_id) abort(404, 'Requested item tag not found!') code, msg, commit = tag.unassociate_attr_def(attribute_definition_id) @@ -246,4 +282,5 @@ def delete(self, tag_id): if code == 204: return '', 204 + APP.logger.debug('Error.', code, msg) abort(code, msg) diff --git a/total_tolles_ferleihsystem/api/catalog/item_type.py b/total_tolles_ferleihsystem/api/catalog/item_type.py index 0835bd2..beae668 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_type.py +++ b/total_tolles_ferleihsystem/api/catalog/item_type.py @@ -5,21 +5,22 @@ from flask import request from flask_restplus import Resource, abort, marshal from flask_jwt_extended import jwt_required, get_jwt_claims +from sqlalchemy.orm import joinedload from sqlalchemy.exc import IntegrityError from .. import API, satisfies_role from ..models import ITEM_TYPE_GET, ITEM_TYPE_POST, ATTRIBUTE_DEFINITION_GET, ID, ITEM_TYPE_PUT -from ... import DB +from ... import DB, APP from ...login import UserRole from ...db_models.attributeDefinition import AttributeDefinition from ...db_models.itemType import ItemType, ItemTypeToAttributeDefinition, ItemTypeToItemType from ...db_models.item import Item + PATH: str = '/catalog/item_types' ANS = API.namespace('item_type', description='ItemTypes', path=PATH) - @ANS.route('/') class ItemTypeList(Resource): """ @@ -35,7 +36,7 @@ def get(self): Get a list of all item types currently in the system """ test_for = request.args.get('deleted', 'false') == 'true' - base_query = ItemType.query + base_query = ItemType.query.filter(ItemType.deleted == test_for) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -44,27 +45,31 @@ def get(self): else: base_query = base_query.filter(ItemType.visible_for == 'all') - return base_query.filter(ItemType.deleted == test_for).order_by(ItemType.name).all() + return base_query.order_by(ItemType.name).all() @jwt_required @satisfies_role(UserRole.ADMIN) @ANS.doc(model=ITEM_TYPE_GET, body=ITEM_TYPE_POST) @ANS.response(409, 'Name is not Unique.') @ANS.response(201, 'Created.') + @API.marshal_with(ITEM_TYPE_GET) # pylint: disable=R0201 def post(self): """ Add a new item type to the system """ new = ItemType(**request.get_json()) + try: DB.session.add(new) DB.session.commit() return marshal(new, ITEM_TYPE_GET), 201 except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Name is not unique.', err) abort(409, 'Name is not unique!') + APP.logger.error('SQL Error', err) abort(500) @ANS.route('//') @@ -81,7 +86,7 @@ def get(self, type_id): """ Get a single item type object """ - base_query = ItemType.query + base_query = ItemType.query.filter(ItemType.id == type_id) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -90,9 +95,12 @@ def get(self, type_id): else: base_query = base_query.filter(ItemType.visible_for == 'all') - item_type = base_query.filter(ItemType.id == type_id).first() + item_type = base_query.first() + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') + return item_type @jwt_required @@ -105,17 +113,13 @@ def delete(self, type_id): Delete a item type object """ item_type = ItemType.query.filter(ItemType.id == type_id).first() + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') item_type.deleted = True - # Not intended -neumantm - # for element in item_type._contained_item_types: - # DB.session.delete(element) - # for element in item_type._item_type_to_attribute_definitions: - # DB.session.delete(element) - items = Item.query.filter(Item.type_id == type_id).all() for item in items: code, msg, commit = item.delete() @@ -135,9 +139,13 @@ def post(self, type_id): Undelete a item type object """ item_type = ItemType.query.filter(ItemType.id == type_id).first() + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') + item_type.deleted = False + DB.session.commit() return "", 204 @@ -152,16 +160,22 @@ def put(self, type_id): Replace a item type object """ item_type = ItemType.query.filter(ItemType.id == type_id).first() + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') + item_type.update(**request.get_json()) + try: DB.session.commit() return marshal(item_type, ITEM_TYPE_GET), 200 except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Name is not unique.', err) abort(409, 'Name is not unique!') + APP.logger.error('SQL Error', err) abort(500) @@ -179,14 +193,22 @@ def get(self, type_id): """ Get all attribute definitions for this item type. """ - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + base_query = ItemType.query.options(joinedload('_item_type_to_attribute_definitions')).filter(ItemType.id == type_id).filter(ItemType.deleted == False) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((ItemType.visible_for == 'all') | (ItemType.visible_for == 'moderator')) + else: + base_query = base_query.filter(ItemType.visible_for == 'all') + + item_type = base_query.first() + + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') - associations = (ItemTypeToAttributeDefinition - .query - .filter(ItemTypeToAttributeDefinition.item_type_id == type_id) - .all()) - return [element.attribute_definition for element in associations if not element.item_type.deleted] + return [ittad.attribute_definition for ittad in item_type._item_type_to_attribute_definitions] @jwt_required @satisfies_role(UserRole.ADMIN) @@ -201,16 +223,19 @@ def post(self, type_id): Associate a new attribute definition with the item type. """ attribute_definition_id = request.get_json()["id"] + # pylint: disable=C0121 attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted == False).first() if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') if attribute_definition is None: + APP.logger.debug('Requested item type not found!', type_id) abort(400, 'Requested attribute definition not found!') items = Item.query.filter(Item.type_id == type_id).all() - new = ItemTypeToAttributeDefinition(type_id, attribute_definition_id) + try: DB.session.add(new) for item in items: @@ -226,8 +251,10 @@ def post(self, type_id): return [e.attribute_definition for e in associations] except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Attribute definition is already asociated with item type!', err) abort(409, 'Attribute definition is already asociated with item type!') + APP.logger.error('SQL Error', err) abort(500) @jwt_required @@ -243,7 +270,9 @@ def delete(self, type_id): """ attribute_definition_id = request.get_json()["id"] item_type = ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') code, msg, commit = item_type.unassociate_attr_def(attribute_definition_id) @@ -254,11 +283,12 @@ def delete(self, type_id): if code == 204: return '', 204 + APP.logger.error("Error.", code, msg) abort(code, msg) -@ANS.route('//can_contain/') -class ItemTypeCanContainTypes(Resource): +@ANS.route('//contained_types/') +class ItemTypeContainedTypes(Resource): """ The item types that a item of this type can contain. """ @@ -271,7 +301,7 @@ def get(self, type_id): """ Get all item types, this item_type may contain. """ - base_query = ItemType.query + base_query = ItemType.query.options(joinedload('_contained_item_types').joinedload('item_type')).filter(ItemType.id == type_id).filter(ItemType.deleted == False) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -280,11 +310,12 @@ def get(self, type_id): else: base_query = base_query.filter(ItemType.visible_for == 'all') - if base_query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + item_type = base_query.first() + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') - associations = ItemTypeToItemType.query.filter(ItemTypeToItemType.parent_id == type_id).all() - return [e.item_type for e in associations if not e.parent.deleted] + return [cit.item_type for cit in item_type._contained_item_types] @jwt_required @satisfies_role(UserRole.ADMIN) @@ -300,11 +331,12 @@ def post(self, type_id): """ child_id = request.get_json()["id"] - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted == False).first() is None: - abort(400, 'Requested attribute definition not found!') + APP.logger.debug('Requested contained type not found!', child_id) + abort(400, 'Requested contained type not found!') new = ItemTypeToItemType(type_id, child_id) try: @@ -314,8 +346,10 @@ def post(self, type_id): return [e.item_type for e in associations] except IntegrityError as err: message = str(err) - if 'UNIQUE constraint failed' in message: + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('Item type can already be contained in this item type.', err) abort(409, 'Item type can already be contained in this item type.') + APP.logger.error('SQL Error', err) abort(500) @jwt_required @@ -331,22 +365,121 @@ def delete(self, type_id): """ child_id = request.get_json()["id"] - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + APP.logger.debug('Requested item type not found!', type_id) abort(404, 'Requested item type not found!') if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted == False).first() is None: - abort(400, 'Requested attribute definition not found!') + APP.logger.debug('Requested contained type not found!', child_id) + abort(400, 'Requested contained type not found!') association = (ItemTypeToItemType .query .filter(ItemTypeToItemType.parent_id == type_id) .filter(ItemTypeToItemType.item_type_id == child_id) .first()) + if association is None: return '', 204 + + DB.session.delete(association) + DB.session.commit() + return '', 204 + +@ANS.route('//parent_types/') +class ItemTypeParentTypes(Resource): + """ + The item types that a item of this type can be contained by. + """ + + @jwt_required + @ANS.response(404, 'Requested item type not found!') + @API.marshal_with(ITEM_TYPE_GET) + # pylint: disable=R0201 + def get(self, type_id): + """ + Get all item types, this item_type may be contained in. + """ + base_query = ItemType.query.options(joinedload('_possible_parent_item_types').joinedload('parent')).filter(ItemType.id == type_id).filter(ItemType.deleted == False) + + # auth check + if UserRole(get_jwt_claims()) != UserRole.ADMIN: + if UserRole(get_jwt_claims()) == UserRole.MODERATOR: + base_query = base_query.filter((ItemType.visible_for == 'all') | (ItemType.visible_for == 'moderator')) + else: + base_query = base_query.filter(ItemType.visible_for == 'all') + + item_type = base_query.first() + if item_type is None: + APP.logger.debug('Requested item type not found!', type_id) + abort(404, 'Requested item type not found!') + + return [ppit.parent for ppit in item_type._possible_parent_item_types] + + @jwt_required + @satisfies_role(UserRole.ADMIN) + @ANS.doc(body=ID) + @ANS.response(404, 'Requested item type not found!') + @ANS.response(400, 'Requested parent item type not found!') + @ANS.response(409, 'Item type can already be contained in this item type.') + @API.marshal_with(ITEM_TYPE_GET) + # pylint: disable=R0201 + def post(self, type_id): + """ + Add new item type which can contain this item type. + """ + parent_id = request.get_json()["id"] + + if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + APP.logger.debug('Requested item type not found!', type_id) + abort(404, 'Requested item type not found!') + if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted == False).first() is None: + APP.logger.debug('Requested parent type not found!', parent_id) + abort(400, 'Requested parent type not found!') + + new = ItemTypeToItemType(parent_id, type_id) + try: - DB.session.delete(association) + DB.session.add(new) DB.session.commit() - return '', 204 - except IntegrityError: + associations = ItemTypeToItemType.query.filter(ItemTypeToItemType.parent_id == type_id).all() + return [e.item_type for e in associations] + except IntegrityError as err: + message = str(err) + if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: + APP.logger.info('This item type can already contain the given item type.', err) + abort(409, 'This item type can already contain the given item type.') + APP.logger.error('SQL Error', err) abort(500) + + @jwt_required + @satisfies_role(UserRole.ADMIN) + @ANS.doc(body=ID) + @ANS.response(404, 'Requested item type not found!') + @ANS.response(400, 'Requested child item type not found!') + @ANS.response(204, 'Success.') + # pylint: disable=R0201 + def delete(self, type_id): + """ + Remove item type which can contain this item type + """ + parent_id = request.get_json()["id"] + + if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + APP.logger.debug('Requested item type not found!', type_id) + abort(404, 'Requested item type not found!') + if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted == False).first() is None: + APP.logger.debug('Requested parent type not found!', parent_id) + abort(400, 'Requested parent type not found!') + + association = (ItemTypeToItemType + .query + .filter(ItemTypeToItemType.parent_id == type_id) + .filter(ItemTypeToItemType.item_type_id == parent_id) + .first()) + + if association is None: + return '', 204 + + DB.session.delete(association) + DB.session.commit() + return '', 204 diff --git a/total_tolles_ferleihsystem/api/lending.py b/total_tolles_ferleihsystem/api/lending.py index 2c5ec51..65f92bc 100644 --- a/total_tolles_ferleihsystem/api/lending.py +++ b/total_tolles_ferleihsystem/api/lending.py @@ -13,7 +13,7 @@ from .models import LENDING_GET, LENDING_POST, LENDING_PUT, ID_LIST from ..login import UserRole -from ..db_models.item import Lending, ItemToLending, Item +from ..db_models.item import Lending, Item PATH: str = '/lending' ANS = API.namespace('lending', description='Lendings', path=PATH) diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index fd69221..41fe85f 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -98,16 +98,17 @@ 'how_to': fields.String(nullable=True, title='How to'), }) ITEM_TYPE_LINKS = API.inherit('ItemTypeLinks', WITH_CURIES, { - 'self': HaLUrl(UrlData('api.item_type_item_type_detail', url_data={'type_id': 'id'}), - required=False), + 'self': HaLUrl(UrlData('api.item_type_item_type_detail', url_data={'type_id': 'id'})), 'attributes': HaLUrl(UrlData('api.item_type_item_type_attributes', url_data={'type_id' : 'id'})), - 'can_contain': HaLUrl(UrlData('api.item_type_item_type_can_contain_types', + 'parent_types': HaLUrl(UrlData('api.item_type_item_type_can_contain_types', + url_data={'type_id' : 'id'})), + 'contained_types': HaLUrl(UrlData('api.item_type_item_type_can_contain_types', url_data={'type_id' : 'id'})), }) ITEM_TYPE_POST = API.inherit('ItemTypePOST', ITEM_TYPE_BASIC, {}) ITEM_TYPE_PUT = API.inherit('ItemTypePUT', ITEM_TYPE_BASIC, {}) -ITEM_TYPE_GET = API.inherit('ItemType', ITEM_TYPE_BASIC, ID, { +ITEM_TYPE_GET = API.inherit('ItemTypeGET', ITEM_TYPE_BASIC, ID, { 'deleted': fields.Boolean(readonly=True), '_links': NestedFields(ITEM_TYPE_LINKS), }) @@ -184,12 +185,13 @@ }) ITEM_LINKS = API.inherit('ItemLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.item_item_detail', url_data={'item_id' : 'id'}), required=False), + 'item_type': HaLUrl(UrlData('api.item_type_item_type_detail', url_data={'type_id': 'type_id'})), 'tags': HaLUrl(UrlData('api.item_item_item_tags', url_data={'item_id' : 'id'})), 'attributes': HaLUrl(UrlData('api.item_item_attribute_list', url_data={'item_id' : 'id'})), 'parent_items': HaLUrl(UrlData('api.item_item_parent_items', url_data={'item_id' : 'id'})), 'contained_items': HaLUrl(UrlData('api.item_item_contained_items', url_data={'item_id' : 'id'})), 'files': HaLUrl(UrlData('api.item_item_file', url_data={'item_id' : 'id'})), - 'lendings': HaLUrl(UrlData('', url_data={'item_id' : 'id'})), + 'lendings': HaLUrl(UrlData('api.item_item_lendings', url_data={'item_id' : 'id'})), }) ITEM_POST = API.inherit('ItemPOST', ITEM_BASIC, {}) @@ -236,7 +238,8 @@ }) FILE_LINKS = API.inherit('FileLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.file_file_detail', - url_data={'file_id': 'id'}), required=False), + url_data={'file_id': 'id'})), + 'item': HaLUrl(UrlData('api.item_item_detail', url_data={'file_id': 'id'}), required=False), 'download': HaLUrl(UrlData('api.file_file_data', url_data={'file_hash': 'file_hash'}), required=False), }) diff --git a/total_tolles_ferleihsystem/api/search.py b/total_tolles_ferleihsystem/api/search.py index 6da86e5..b531098 100644 --- a/total_tolles_ferleihsystem/api/search.py +++ b/total_tolles_ferleihsystem/api/search.py @@ -6,7 +6,7 @@ from flask_restplus import Resource from flask_jwt_extended import jwt_optional, get_jwt_claims from . import API -from ..db_models.item import Item, ItemToTag, ItemToAttributeDefinition, ItemToLending +from ..db_models.item import Item, ItemToTag, ItemToAttributeDefinition from ..db_models.tag import Tag from .models import ITEM_GET from ..login import UserRole diff --git a/total_tolles_ferleihsystem/config.py b/total_tolles_ferleihsystem/config.py index 74456ce..7a7f1d6 100644 --- a/total_tolles_ferleihsystem/config.py +++ b/total_tolles_ferleihsystem/config.py @@ -13,6 +13,7 @@ class Config(object): JWT_SECRET_KEY = ''.join(hex(randint(0, 255))[2:] for i in range(16)) SQLALCHEMY_DATABASE_URI = 'sqlite://:memory:' SQLALCHEMY_TRACK_MODIFICATIONS = False + DB_UNIQUE_CONSTRAIN_FAIL = 'UNIQUE constraint failed' WEBPACK_MANIFEST_PATH = './build/manifest.json' LOGGING = { 'version': 1, diff --git a/total_tolles_ferleihsystem/db_models/item.py b/total_tolles_ferleihsystem/db_models/item.py index 5eeced1..09e7db5 100644 --- a/total_tolles_ferleihsystem/db_models/item.py +++ b/total_tolles_ferleihsystem/db_models/item.py @@ -20,7 +20,6 @@ 'File', 'Lending', 'ItemToItem', - 'ItemToLending', 'ItemToTag', 'ItemToAttributeDefinition' ] @@ -36,11 +35,14 @@ class Item(DB.Model): name = DB.Column(DB.String(STD_STRING_SIZE)) update_name_from_schema = DB.Column(DB.Boolean, default=True, nullable=False) type_id = DB.Column(DB.Integer, DB.ForeignKey('ItemType.id')) + lending_id = DB.Column(DB.Integer, DB.ForeignKey('Lending.id'), default=None, nullable=True) lending_duration = DB.Column(DB.Integer, nullable=True) # in seconds + due = DB.Column(DB.DateTime, default=None, nullable=True) deleted = DB.Column(DB.Boolean, default=False) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) type = DB.relationship('ItemType', lazy='joined') + lending = DB.relationship('Lending', lazy='joined') __table_args__ = ( UniqueConstraint('name', 'type_id', name='_name_type_id_uc'), @@ -264,7 +266,7 @@ class File(DB.Model): item_id = DB.Column(DB.Integer, DB.ForeignKey('Item.id'), nullable=True) name = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) file_type = DB.Column(DB.String(STD_STRING_SIZE)) - file_hash = DB.Column(DB.String(STD_STRING_SIZE), nullable=True, index=True) + file_hash = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) creation = DB.Column(DB.DateTime, server_default=func.now()) invalidation = DB.Column(DB.DateTime, nullable=True) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) @@ -340,26 +342,6 @@ def __init__(self, parent_id: int, item_id: int): self.item_id = item_id -class ItemToLending (DB.Model): - - __tablename__ = 'ItemToLending' - - item_id = DB.Column(DB.Integer, DB.ForeignKey('Item.id'), primary_key=True) - lending_id = DB.Column(DB.Integer, DB.ForeignKey('Lending.id'), primary_key=True) - due = DB.Column(DB.DateTime) - - item = DB.relationship('Item', lazy='select', backref=DB.backref('_lending', lazy='select', - single_parent=True, cascade="all, delete-orphan")) - lending = DB.relationship('Lending', lazy='select', backref=DB.backref('itemLendings', lazy='select', - single_parent=True, - cascade="all, delete-orphan")) - - def __init__(self, item: Item, lending: Lending): - self.item = item - self.lending = lending - self.due = lending.date + datetime.timedelta(0, item.effective_lending_duration) - - class ItemToTag (DB.Model): __tablename__ = 'ItemToTag' From 9a7fc81c25a0e95c82ca3e62d0be8f91aa7a8076 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Thu, 21 Feb 2019 15:32:41 +0100 Subject: [PATCH 10/67] Add more local query options to do joinedload. Catalog should now be done. And fix ItemLending Endpoint. --- .../api/catalog/attribute_definition.py | 2 +- .../api/catalog/file.py | 9 ++++---- .../api/catalog/item.py | 22 ++++++++++--------- .../api/catalog/item_tag.py | 4 ++-- .../api/catalog/item_type.py | 4 ++-- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py index d36dc6a..a16d7a4 100644 --- a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py +++ b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py @@ -194,7 +194,7 @@ def get(self, definition_id): """ Get all values of this attribute definition """ - base_query = AttributeDefinition.query.options(joinedload('_item_to_attribute_definitions')).filter(AttributeDefinition.id == definition_id) + base_query = AttributeDefinition.query.options(joinedload('_item_to_attribute_definitions').joinedload('item')).filter(AttributeDefinition.id == definition_id) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: diff --git a/total_tolles_ferleihsystem/api/catalog/file.py b/total_tolles_ferleihsystem/api/catalog/file.py index a120fc6..4cefe52 100644 --- a/total_tolles_ferleihsystem/api/catalog/file.py +++ b/total_tolles_ferleihsystem/api/catalog/file.py @@ -9,6 +9,7 @@ from flask import request, make_response from flask_restplus import Resource, abort, marshal from flask_jwt_extended import jwt_required, get_jwt_claims +from sqlalchemy.orm import joinedload from sqlalchemy.exc import IntegrityError from total_tolles_ferleihsystem.tasks.file import create_archive @@ -36,7 +37,7 @@ def get(self): """ Get a list of files """ - base_query = File.query + base_query = File.query.options(joinedload('item')) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -103,7 +104,7 @@ def get(self, file_id): """ Get a single file object """ - base_query = File.query.filter(File.id == file_id) + base_query = File.query.filter(File.id == file_id).options(joinedload('item')) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -148,7 +149,7 @@ def put(self, file_id): """ Replace a file object """ - file = File.query.filter(File.id == file_id).first() + file = File.query.filter(File.id == file_id).options(joinedload('item')).first() if file is None: APP.logger.debug('Requested file not found!', file_id) @@ -218,7 +219,7 @@ def get(self, file_id): """ Get the actual file """ - base_query = File.query.filter(File.file_id == file_id) + base_query = File.query.filter(File.file_id == file_id).options(joinedload('item')) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index 836b680..de6d1ae 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -369,6 +369,7 @@ def get(self, item_id, attribute_definition_id): .filter(ItemToAttributeDefinition.item_id == item_id) .filter(ItemToAttributeDefinition.deleted == False) .filter(ItemToAttributeDefinition.attribute_definition_id == attribute_definition_id) + .options(joinedload('attribute_definition')) .first()) if attribute is None: @@ -399,6 +400,7 @@ def put(self, item_id, attribute_definition_id): .filter(ItemToAttributeDefinition.item_id == item_id) .filter(ItemToAttributeDefinition.attribute_definition_id == attribute_definition_id) .filter(ItemToAttributeDefinition.deleted == False) + .options(joinedload('attribute_definition')) .first()) if attribute is None: @@ -440,7 +442,7 @@ def get(self, item_id): if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') - associations = ItemToItem.query.filter(ItemToItem.item_id == item_id).all() + associations = ItemToItem.query.filter(ItemToItem.item_id == item_id).options(joinedload('parent')).all() return [e.parent for e in associations if not e.item.deleted] @@ -471,7 +473,7 @@ def get(self, item_id): if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') - associations = ItemToItem.query.filter(ItemToItem.parent_id == item_id).all() + associations = ItemToItem.query.filter(ItemToItem.parent_id == item_id).options(joinedload('item')).all() return [e.item for e in associations if not e.item.deleted] @jwt_required @@ -511,7 +513,7 @@ def post(self, item_id): try: DB.session.add(new) DB.session.commit() - associations = ItemToItem.query.filter(ItemToItem.parent_id == item_id).all() + associations = ItemToItem.query.filter(ItemToItem.parent_id == item_id).options(joinedload('item')).all() return [e.item for e in associations] except IntegrityError as err: message = str(err) @@ -581,13 +583,12 @@ def get(self, item_id): if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: abort(404, 'Requested item not found!') - return File.query.filter(File.item_id == item_id).all() + return File.query.filter(File.item_id == item_id).options(joinedload('item')).all() - -@ANS.route('//lendings/') +@ANS.route('//lending/') class ItemLendings(Resource): """ - Current and past lendings of a single item. + Current lending of a single item. """ @jwt_required @@ -596,7 +597,7 @@ class ItemLendings(Resource): # pylint: disable=R0201 def get(self, item_id): """ - Get the lendings concerning the specific item. + Get the lending concerning the specific item. """ base_query = Item.query @@ -608,7 +609,8 @@ def get(self, item_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + item = base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() + if item is None: abort(404, 'Requested item not found!') - return Lending.query.join(ItemToLending).filter(ItemToLending.item_id == item_id).all() + return item.lending diff --git a/total_tolles_ferleihsystem/api/catalog/item_tag.py b/total_tolles_ferleihsystem/api/catalog/item_tag.py index 7dcf6a8..9dcf00e 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_tag.py +++ b/total_tolles_ferleihsystem/api/catalog/item_tag.py @@ -199,7 +199,7 @@ def get(self, tag_id): """ Get all attribute definitions for this tag. """ - base_query = Tag.query.options(joindload('_tag_to_attribute_definitions')).filter(Tag.id == tag_id).filter(Tag.deleted == False) + base_query = Tag.query.options(joinedload('_tag_to_attribute_definitions')).filter(Tag.id == tag_id).filter(Tag.deleted == False) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -237,7 +237,7 @@ def post(self, tag_id): APP.logger.debug('Requested attribute definition not found.', attribute_definition_id) abort(400, 'Requested attribute definition not found!') - items = [itt.item for itt in ItemToTag.query.filter(ItemToTag.tag_id == tag_id).all()] + items = [itt.item for itt in ItemToTag.query.filter(ItemToTag.tag_id == tag_id).options(joinedload('item')).all()] new = TagToAttributeDefinition(tag_id, attribute_definition_id) try: diff --git a/total_tolles_ferleihsystem/api/catalog/item_type.py b/total_tolles_ferleihsystem/api/catalog/item_type.py index beae668..9dd62b4 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_type.py +++ b/total_tolles_ferleihsystem/api/catalog/item_type.py @@ -342,7 +342,7 @@ def post(self, type_id): try: DB.session.add(new) DB.session.commit() - associations = ItemTypeToItemType.query.filter(ItemTypeToItemType.parent_id == type_id).all() + associations = ItemTypeToItemType.query.filter(ItemTypeToItemType.parent_id == type_id).options(joinedload('item_type')).all() return [e.item_type for e in associations] except IntegrityError as err: message = str(err) @@ -441,7 +441,7 @@ def post(self, type_id): try: DB.session.add(new) DB.session.commit() - associations = ItemTypeToItemType.query.filter(ItemTypeToItemType.parent_id == type_id).all() + associations = ItemTypeToItemType.query.filter(ItemTypeToItemType.parent_id == type_id).options(joinedload('item_type')).all() return [e.item_type for e in associations] except IntegrityError as err: message = str(err) From c344c00a3b55a2ff1ae7bbdd30cf5d125ba6f4fb Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Sat, 30 Mar 2019 13:37:17 +0000 Subject: [PATCH 11/67] Finish lending rework. Lendings are now one Table and don't have a NxM Table to items, but one item has a reference to (up to) one lending directly. Old lendings are deleted. All lending related changes are logged via the lending logger. Also lending dates and due dates are now unix time stamps. --- total_tolles_ferleihsystem/__init__.py | 1 + .../api/catalog/item.py | 4 +- total_tolles_ferleihsystem/api/lending.py | 110 +++++------------ total_tolles_ferleihsystem/api/models.py | 16 +-- total_tolles_ferleihsystem/db_models/item.py | 113 ++++++++++++------ 5 files changed, 116 insertions(+), 128 deletions(-) diff --git a/total_tolles_ferleihsystem/__init__.py b/total_tolles_ferleihsystem/__init__.py index b2d4724..e6e253a 100644 --- a/total_tolles_ferleihsystem/__init__.py +++ b/total_tolles_ferleihsystem/__init__.py @@ -44,6 +44,7 @@ APP.logger.info('Connecting to database %s.', APP.config['SQLALCHEMY_DATABASE_URI']) AUTH_LOGGER = getLogger('flask.app.auth') # type: Logger +LENDING_LOGGER = getLogger('ttf.lending') # type: Logger # Setup DB with Migrations and bcrypt DB: SQLAlchemy diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index de6d1ae..79748dc 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -40,7 +40,7 @@ def get(self): Get a list of all items currently in the system """ test_for = request.args.get('deleted', 'false') == 'true' - base_query = Item.query.options(joinedload('_lending')).filter(Item.deleted == test_for) + base_query = Item.query.options(joinedload('lending')).filter(Item.deleted == test_for) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -50,7 +50,7 @@ def get(self): base_query = base_query.filter(Item.visible_for == 'all') if request.args.get('lent', 'false') == 'true': - base_query = base_query.join(ItemToLending) + base_query = base_query.filter(Item.lending_id != None) return base_query.order_by(Item.name).all() diff --git a/total_tolles_ferleihsystem/api/lending.py b/total_tolles_ferleihsystem/api/lending.py index 65f92bc..2c97594 100644 --- a/total_tolles_ferleihsystem/api/lending.py +++ b/total_tolles_ferleihsystem/api/lending.py @@ -7,6 +7,7 @@ from flask_restplus import Resource, abort, marshal from flask_jwt_extended import jwt_required from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import joinedload from . import API, satisfies_role from .. import DB @@ -21,23 +22,18 @@ @ANS.route('/') class LendingList(Resource): """ - Lendings root item tag + List of all active lendings """ @jwt_required - @API.param('active', 'get only active lendings', type=bool, required=False, default=True) @API.marshal_list_with(LENDING_GET) # pylint: disable=R0201 def get(self): """ Get a list of all lendings currently in the system """ - active = request.args.get('active', 'true') == 'true' - base_query = Lending.query - if active: - base_query = base_query.join(ItemToLending).distinct() - return base_query.all() + return Lending.query.options(joinedload('_items')).all() @jwt_required @satisfies_role(UserRole.MODERATOR) @@ -51,35 +47,14 @@ def post(self): """ Add a new lending to the system """ - json = request.get_json() - item_ids = json.pop('item_ids') - items = [] - item_to_lendings = [] - - for element in item_ids: - item = Item.query.filter(Item.id == element).first() - if item is None: - abort(400, "Item not found:" + str(element)) - if not item.type.lendable: - abort(400, "Item not lendable:" + str(element)) - if item.is_currently_lent: - abort(400, "Item already lent:" + str(element)) - items.append(item) - - new = Lending(**json) try: - DB.session.add(new) - DB.session.commit() - for element in items: - item_to_lendings.append(ItemToLending(element, new)) - DB.session.add_all(item_to_lendings) - DB.session.commit() - return marshal(new, LENDING_GET), 201 - except IntegrityError as err: - message = str(err) - if 'UNIQUE constraint failed' in message: - abort(409, 'Name is not unique!') - abort(500) + new = Lending(** request.get_json()) + except ValueError as err: + abort(400, str(err)) + + DB.session.add(new) + DB.session.commit() + return marshal(new, LENDING_GET), 201 @ANS.route('//') class LendingDetail(Resource): @@ -96,7 +71,7 @@ def get(self, lending_id): """ Get a single lending object """ - lending = Lending.query.filter(Lending.id == lending_id).first() + lending = Lending.query.filter(Lending.id == lending_id).options(joinedload('_items')).first() if lending is None: abort(404, 'Requested lending not found!') return lending @@ -113,6 +88,7 @@ def delete(self, lending_id): lending = Lending.query.filter(Lending.id == lending_id).first() if lending is None: abort(404, 'Requested lending not found!') + lending.pre_delete() DB.session.delete(lending) DB.session.commit() return "", 204 @@ -120,71 +96,43 @@ def delete(self, lending_id): @jwt_required @satisfies_role(UserRole.MODERATOR) @ANS.doc(model=LENDING_GET, body=LENDING_PUT) - @ANS.response(409, 'Name is not Unique.') @ANS.response(404, 'Requested lending not found!') + @ANS.response(400, "Item not found") + @ANS.response(400, "Item not lendable") + @ANS.response(400, "Item already lent") # pylint: disable=R0201 def put(self, lending_id): """ Replace a lending object """ - lending = Lending.query.filter(Lending.id == lending_id).first() + lending = Lending.query.filter(Lending.id == lending_id).options(joinedload('_items')).first() if lending is None: abort(404, 'Requested lending not found!') - - json = request.get_json() - item_ids = json.pop('item_ids') - items = [] - item_to_lendings = [] - - for element in item_ids: - item = Item.query.filter(Item.id == element).first() - if item is None: - abort(400, "Item not found:" + str(element)) - if not item.type.lendable: - abort(400, "Item not lendable:" + str(element)) - if item.is_currently_lent: - abort(400, "Item already lent:" + str(element)) - items.append(item) - - lending.update(**request.get_json()) try: - DB.session.commit() - for element in ItemToLending.query.filter(ItemToLending.lending_id == lending_id).all(): - DB.session.delete(element) - for element in items: - item_to_lendings.append(ItemToLending(element, lending)) - DB.session.add_all(item_to_lendings) - DB.session.commit() - return marshal(lending, LENDING_GET), 200 - except IntegrityError as err: - message = str(err) - if 'UNIQUE constraint failed' in message: - abort(409, 'Name is not unique!') - abort(500) + lending.update(**request.get_json()) + except ValueError as err: + abort(400, str(err)) + + DB.session.commit() + return marshal(lending, LENDING_GET), 200 @jwt_required @satisfies_role(UserRole.MODERATOR) @ANS.doc(body=ID_LIST) @ANS.response(404, 'Requested lending not found!') - @ANS.response(400, 'Requested item is not part of this lending.') + @ANS.response(400, "Item not found") @API.marshal_with(LENDING_GET) # pylint: disable=R0201 def post(self, lending_id): """ Give back a list of items. """ - lending = Lending.query.filter(Lending.id == lending_id).first() + lending = Lending.query.filter(Lending.id == lending_id).options(joinedload('_items')).first() if lending is None: abort(404, 'Requested lending not found!') - - ids = request.get_json()["ids"] try: - for element in ids: - to_delete = ItemToLending.query.filter(ItemToLending.item_id == element).first() - if to_delete is None: - abort(400, "Requested item is not part of this lending:" + str(element)) - DB.session.delete(to_delete) - DB.session.commit() - return lending - except IntegrityError: - abort(500) + lending.remove_items(request.get_json()["ids"]) + except ValueError as err: + abort(400, str(err)) + DB.session.commit() + return lending diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index 41fe85f..0964e68 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -201,11 +201,10 @@ 'type': fields.Nested(ITEM_TYPE_GET), 'is_currently_lent': fields.Boolean(readonly=True), 'effective_lending_duration': fields.Integer(readonly=True), - 'due': fields.DateTime(attribute='item_lending.due', readonly=True), - '_links': NestedFields(ITEM_LINKS) + 'due': fields.Integer(readonly=True, title="Due date", description="[Unix time]"), + '_links': NestedFields(ITEM_LINKS), }) - # # --- Attribute --- # @@ -228,7 +227,7 @@ # -# --- FIle --- +# --- File --- # FILE_BASIC = API.inherit('FileBASIC', VISIBLE_FOR, { @@ -262,10 +261,6 @@ 'user': fields.String(max_length=STD_STRING_SIZE), 'deposit': fields.String(example="Studentenausweis", max_length=STD_STRING_SIZE), }) -LENDING_ITEM = API.model('LendingItem', { - 'due': fields.DateTime(), - 'item': fields.Nested(ITEM_GET), -}) LENDING_LINKS = API.inherit('LendingLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.lending_lending_detail', url_data={'lending_id' : 'id'}), required=False), @@ -277,11 +272,10 @@ LENDING_PUT = API.inherit('LendingPUT', LENDING_POST, {}) LENDING_GET = API.inherit('LendingGET', LENDING_BASIC, ID, { '_links': NestedFields(LENDING_LINKS), - 'date': fields.DateTime(), - 'item_lendings': fields.Nested(LENDING_ITEM), + 'date': fields.Integer(title="Lending date", description="[Unix time]"), + 'items': fields.Nested(ITEM_GET, attribute='_items'), }) - # # --- Blacklist --- # diff --git a/total_tolles_ferleihsystem/db_models/item.py b/total_tolles_ferleihsystem/db_models/item.py index 09e7db5..6d0d3e9 100644 --- a/total_tolles_ferleihsystem/db_models/item.py +++ b/total_tolles_ferleihsystem/db_models/item.py @@ -1,15 +1,14 @@ """ The database models of the item and all connected tables """ - -import datetime from sqlalchemy.sql import func from sqlalchemy.schema import UniqueConstraint import string from json import loads from datetime import date +import time -from .. import DB +from .. import DB, LENDING_LOGGER from . import STD_STRING_SIZE from . import itemType @@ -37,12 +36,13 @@ class Item(DB.Model): type_id = DB.Column(DB.Integer, DB.ForeignKey('ItemType.id')) lending_id = DB.Column(DB.Integer, DB.ForeignKey('Lending.id'), default=None, nullable=True) lending_duration = DB.Column(DB.Integer, nullable=True) # in seconds - due = DB.Column(DB.DateTime, default=None, nullable=True) + due = DB.Column(DB.Integer, default=-1) # unix time deleted = DB.Column(DB.Boolean, default=False) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) type = DB.relationship('ItemType', lazy='joined') - lending = DB.relationship('Lending', lazy='joined') + lending = DB.relationship('Lending', lazy='select', + backref=DB.backref('_items', lazy='select')) __table_args__ = ( UniqueConstraint('name', 'type_id', name='_name_type_id_uc'), @@ -78,34 +78,12 @@ def update(self, update_name_from_schema: bool, name: str, type_id: int, lending self.lending_duration = lending_duration self.visible_for = visible_for - @property - def lending_id(self): - """ - The lending_id this item is currently associated with. -1 if not lent. - """ - if self._lending: - return self._lending[0].lending_to_item.lending_id - return -1 - - @property - def item_lending(self): - """ - The lending this item is currently associated with. - """ - if self._lending: - return self._lending[0] - return None - @property def is_currently_lent(self): """ If the item is currently lent. """ - return self.lending_id != -1 - - @property - def due(self): - return -1 if self.lending_id is not None else self.item_lending.due + return self.lending is not None @property def parent(self): @@ -305,23 +283,90 @@ class Lending(DB.Model): id = DB.Column(DB.Integer, primary_key=True) moderator = DB.Column(DB.String(STD_STRING_SIZE)) user = DB.Column(DB.String(STD_STRING_SIZE)) - date = DB.Column(DB.DateTime) + date = DB.Column(DB.Integer) #unix time deposit = DB.Column(DB.String(STD_STRING_SIZE)) - def __init__(self, moderator: str, user: str, deposit: str): + def __repr__(self): + ret = "Lending by " + ret += self.moderator + ret += " to " + ret += self.user + ret += " at " + ret += str(self.date) + ret += " with " + ret += self.deposit + ret += ". Items:" + ret += str([str(item.id) for item in self._items]) + return ret + + def __init__(self, moderator: str, user: str, deposit: str, item_ids: list): self.moderator = moderator self.user = user - self.date = datetime.datetime.now() + self.date = int(time.time()) self.deposit = deposit - - def update(self, moderator: str, user: str, deposit: str): + for element in item_ids: + item = Item.query.filter(Item.id == element).filter(Item.deleted == False).first() + if item is None: + raise ValueError("Item not found:" + str(element)) + if not item.type.lendable: + raise ValueError("Item not lendable:" + str(element)) + if item.is_currently_lent: + raise ValueError("Item already lent:" + str(element)) + item.lending = self + item.due = self.date + item.effective_lending_duration + LENDING_LOGGER.info("New lending: %s", repr(self)) + + + def update(self, moderator: str, user: str, deposit: str, item_ids: list): """ Function to update the objects data """ self.moderator = moderator self.user = user self.deposit = deposit - + old_items = self._items + new_items = [] + + for element in item_ids: + item = Item.query.filter(Item.id == element).filter(Item.deleted == False).first() + if item is None: + raise ValueError("Item not found:" + str(element)) + new_items.append(item) + + items_to_remove = [item for item in old_items if item not in new_items] + items_to_add = [item for item in new_items if item not in old_items] + + for item in items_to_remove: + item.lending = None + item.due = -1 + + for item in items_to_add: + if not item.type.lendable: + raise ValueError("Item not lendable:" + str(item)) + if item.is_currently_lent: + raise ValueError("Item already lent:" + str(item)) + item.lending = self + item.due = self.date + item.effective_lending_duration + LENDING_LOGGER.info("Updated lending: %s", repr(self)) + + + def remove_items(self, item_ids: list): + """ + Function to remove a list of items from this lending + """ + for element in item_ids: + item = Item.query.filter(Item.id == element).first() + if item is None: + raise ValueError("Item not found:" + str(element)) + item.lending = None + item.due = -1 + LENDING_LOGGER.info("Updated lending(remove items): %s", repr(self)) + + def pre_delete(self): + LENDING_LOGGER.info("Deleting lending: %s", repr(self)) + for item in list(self._items): + item.lending = None + item.due = -1 class ItemToItem(DB.Model): From a16620fd1a1f313429db43ba0facee31c6643193 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Sat, 30 Mar 2019 14:41:32 +0000 Subject: [PATCH 12/67] Fix some bugs in earlier commits. --- total_tolles_ferleihsystem/api/catalog/item_type.py | 1 - total_tolles_ferleihsystem/api/models.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/item_type.py b/total_tolles_ferleihsystem/api/catalog/item_type.py index 9dd62b4..be1fa11 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_type.py +++ b/total_tolles_ferleihsystem/api/catalog/item_type.py @@ -52,7 +52,6 @@ def get(self): @ANS.doc(model=ITEM_TYPE_GET, body=ITEM_TYPE_POST) @ANS.response(409, 'Name is not Unique.') @ANS.response(201, 'Created.') - @API.marshal_with(ITEM_TYPE_GET) # pylint: disable=R0201 def post(self): """ diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index 0964e68..4ea8add 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -100,9 +100,9 @@ ITEM_TYPE_LINKS = API.inherit('ItemTypeLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.item_type_item_type_detail', url_data={'type_id': 'id'})), 'attributes': HaLUrl(UrlData('api.item_type_item_type_attributes', url_data={'type_id' : 'id'})), - 'parent_types': HaLUrl(UrlData('api.item_type_item_type_can_contain_types', + 'parent_types': HaLUrl(UrlData('api.item_type_item_type_parent_types', url_data={'type_id' : 'id'})), - 'contained_types': HaLUrl(UrlData('api.item_type_item_type_can_contain_types', + 'contained_types': HaLUrl(UrlData('api.item_type_item_type_contained_types', url_data={'type_id' : 'id'})), }) From 8d67a5b44d257d17f3502c6550d116b568d36369 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Thu, 11 Apr 2019 10:17:20 +0200 Subject: [PATCH 13/67] Add more small improvements and fixes. --- total_tolles_ferleihsystem/api/catalog/item.py | 2 +- total_tolles_ferleihsystem/api/search.py | 5 +++-- total_tolles_ferleihsystem/db_models/item.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index 79748dc..85832fc 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -40,7 +40,7 @@ def get(self): Get a list of all items currently in the system """ test_for = request.args.get('deleted', 'false') == 'true' - base_query = Item.query.options(joinedload('lending')).filter(Item.deleted == test_for) + base_query = Item.query.options(joinedload('lending'), joinedload("_tags")).filter(Item.deleted == test_for) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: diff --git a/total_tolles_ferleihsystem/api/search.py b/total_tolles_ferleihsystem/api/search.py index b531098..2de5010 100644 --- a/total_tolles_ferleihsystem/api/search.py +++ b/total_tolles_ferleihsystem/api/search.py @@ -5,6 +5,7 @@ from flask import request from flask_restplus import Resource from flask_jwt_extended import jwt_optional, get_jwt_claims +from sqlalchemy.orm import joinedload from . import API from ..db_models.item import Item, ItemToTag, ItemToAttributeDefinition from ..db_models.tag import Tag @@ -44,7 +45,7 @@ def get(self): lent = request.args.get('lent', default=False, type=lambda x: x == 'true') search_string = '%' + search + '%' - search_result = Item.query + search_result = Item.query.options(joinedload('lending'), joinedload("_tags")) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -69,7 +70,7 @@ def get(self): search_result = search_result.filter(~Item.deleted) if not lent: - search_result = search_result.join(ItemToLending, isouter=True).filter(ItemToLending.lending_id.is_(None)) + search_result = search_result.filter(Item.lending == None) if item_type != -1: search_result = search_result.filter(Item.type_id == item_type) diff --git a/total_tolles_ferleihsystem/db_models/item.py b/total_tolles_ferleihsystem/db_models/item.py index 6d0d3e9..79fdfac 100644 --- a/total_tolles_ferleihsystem/db_models/item.py +++ b/total_tolles_ferleihsystem/db_models/item.py @@ -99,7 +99,7 @@ def effective_lending_duration(self): if self.lending_duration and (self.lending_duration >= 0): return self.lending_duration - tag_lending_duration = min((t.lending_duration for t in self._tags if t.lending_duration > 0), default=-1) + tag_lending_duration = min((t.tag.lending_duration for t in self._tags if t.tag.lending_duration > 0), default=-1) if tag_lending_duration >= 0: return tag_lending_duration From 300f067ee5f969a3d81cc073dc3ef2bd72371c7a Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Thu, 11 Apr 2019 10:50:29 +0200 Subject: [PATCH 14/67] Change all DateTimes to Integers with Unix Time and change deleted to time stamps. --- total_tolles_ferleihsystem/api/models.py | 6 ++--- .../db_models/attributeDefinition.py | 8 +++++-- .../db_models/blacklist.py | 11 ++++----- total_tolles_ferleihsystem/db_models/item.py | 23 ++++++++++--------- .../db_models/itemType.py | 13 ++++++++++- total_tolles_ferleihsystem/db_models/tag.py | 6 ++++- 6 files changed, 42 insertions(+), 25 deletions(-) diff --git a/total_tolles_ferleihsystem/api/models.py b/total_tolles_ferleihsystem/api/models.py index 4ea8add..5341478 100644 --- a/total_tolles_ferleihsystem/api/models.py +++ b/total_tolles_ferleihsystem/api/models.py @@ -233,7 +233,7 @@ FILE_BASIC = API.inherit('FileBASIC', VISIBLE_FOR, { 'name': fields.String(max_length=STD_STRING_SIZE, nullable=True), 'file_type': fields.String(max_length=20), - 'invalidation': fields.DateTime(nullable=True), + 'invalidation': fields.Integer(nullable=True, title="Invalidation date", description="[Unix time]"), }) FILE_LINKS = API.inherit('FileLinks', WITH_CURIES, { 'self': HaLUrl(UrlData('api.file_file_detail', @@ -247,7 +247,7 @@ FILE_GET = API.inherit('FileGET', FILE_BASIC, ID, { 'item': fields.Nested(ITEM_GET), 'file_hash': fields.String(max_length=STD_STRING_SIZE), - 'creation': fields.DateTime(), + 'creation': fields.Integer(title="Creation date", description="[Unix time]"), '_links': NestedFields(FILE_LINKS) }) @@ -286,7 +286,7 @@ 'reason': fields.String(), }) BLACKLIST_ITEM_TYPE = API.model('BlacklistItemType', { - 'end_time': fields.DateTime(), + 'end_time': fields.Integer(title="Date of end of blacklisting", description="[Unix time]"), 'reason': fields.String(), }) BLACKLIST_LINKS = API.inherit('BlacklistLinks', WITH_CURIES, { diff --git a/total_tolles_ferleihsystem/db_models/attributeDefinition.py b/total_tolles_ferleihsystem/db_models/attributeDefinition.py index 98b1cbb..e52336f 100644 --- a/total_tolles_ferleihsystem/db_models/attributeDefinition.py +++ b/total_tolles_ferleihsystem/db_models/attributeDefinition.py @@ -12,7 +12,7 @@ class AttributeDefinition (DB.Model): type = DB.Column(DB.String(STD_STRING_SIZE)) jsonschema = DB.Column(DB.Text) visible_for = DB.Column(DB.String(STD_STRING_SIZE)) - deleted = DB.Column(DB.Boolean, default=False) + deleted_time = DB.Column(DB.Integer, default=None) def __init__(self, name: str, type: str, jsonschema: str, visible_for: str): self.name = name @@ -24,4 +24,8 @@ def update(self, name: str, type: str, jsonschema: str, visible_for: str): self.name = name self.type = type self.jsonschema = jsonschema - self.visible_for = visible_for \ No newline at end of file + self.visible_for = visible_for + + @property + def deleted(self): + return self.deleted_time is not None \ No newline at end of file diff --git a/total_tolles_ferleihsystem/db_models/blacklist.py b/total_tolles_ferleihsystem/db_models/blacklist.py index bd5a22b..fe29ca1 100644 --- a/total_tolles_ferleihsystem/db_models/blacklist.py +++ b/total_tolles_ferleihsystem/db_models/blacklist.py @@ -28,18 +28,15 @@ class BlacklistToItemType (DB.Model): user_id = DB.Column(DB.Integer, DB.ForeignKey('Blacklist.id'), primary_key=True) item_type_id = DB.Column(DB.Integer, DB.ForeignKey('ItemType.id'), primary_key=True) - end_time = DB.Column(DB.DateTime, nullable=True) + end_time = DB.Column(DB.Integer, nullable=True) reason = DB.Column(DB.Text, nullable=True) user = DB.relationship('Blacklist', lazy='select', backref=DB.backref('_item_types', lazy='select', single_parent=True, cascade="all, delete-orphan")) item_type = DB.relationship('ItemType', lazy='joined') - def __init__(self, user: Blacklist, item_type: ItemType, end_time: any=None, reason: str=None): - #TODO Fabi pls FIX duration time + def __init__(self, user: Blacklist, item_type: ItemType, end_time: int=None, reason: str=None): self.user = user self.item_type = item_type - - if self.end_time != None: - self.end_time = end_time - self.reason = reason + self.reason = reason + self.end_time = end_time diff --git a/total_tolles_ferleihsystem/db_models/item.py b/total_tolles_ferleihsystem/db_models/item.py index 79fdfac..66132ed 100644 --- a/total_tolles_ferleihsystem/db_models/item.py +++ b/total_tolles_ferleihsystem/db_models/item.py @@ -5,7 +5,6 @@ from sqlalchemy.schema import UniqueConstraint import string from json import loads -from datetime import date import time from .. import DB, LENDING_LOGGER @@ -37,7 +36,7 @@ class Item(DB.Model): lending_id = DB.Column(DB.Integer, DB.ForeignKey('Lending.id'), default=None, nullable=True) lending_duration = DB.Column(DB.Integer, nullable=True) # in seconds due = DB.Column(DB.Integer, default=-1) # unix time - deleted = DB.Column(DB.Boolean, default=False) + deleted_time = DB.Column(DB.Integer, default=None) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) type = DB.relationship('ItemType', lazy='joined') @@ -78,6 +77,10 @@ def update(self, update_name_from_schema: bool, name: str, type_id: int, lending self.lending_duration = lending_duration self.visible_for = visible_for + @property + def deleted(self): + return self.deleted_time is not None + @property def is_currently_lent(self): """ @@ -245,8 +248,8 @@ class File(DB.Model): name = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) file_type = DB.Column(DB.String(STD_STRING_SIZE)) file_hash = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) - creation = DB.Column(DB.DateTime, server_default=func.now()) - invalidation = DB.Column(DB.DateTime, nullable=True) + creation = DB.Column(DB.Integer) + invalidation = DB.Column(DB.Integer, nullable=True) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) item = DB.relationship('Item', lazy='select', backref=DB.backref('_files', lazy='select', @@ -259,11 +262,12 @@ def __init__(self, name: str, file_type: str, file_hash: str, item_id: int = Non self.name = name self.file_type = file_type self.file_hash = file_hash + self.creation = int(time.time()) if visible_for != '' and visible_for != None: self.visible_for = visible_for - def update(self, name: str, file_type: str, invalidation, item_id: int, visible_for: str = '') -> None: + def update(self, name: str, file_type: str, invalidation: int, item_id: int, visible_for: str = '') -> None: """ Function to update the objects data """ @@ -410,7 +414,7 @@ class ItemToAttributeDefinition (DB.Model): item_id = DB.Column(DB.Integer, DB.ForeignKey('Item.id'), primary_key=True) attribute_definition_id = DB.Column(DB.Integer, DB.ForeignKey('AttributeDefinition.id'), primary_key=True) value = DB.Column(DB.String(STD_STRING_SIZE)) - deleted = DB.Column(DB.Boolean, default=False) + deleted_time = DB.Column(DB.Integer, default=None) item = DB.relationship('Item', lazy='select', backref=DB.backref('_attributes', lazy='select', single_parent=True, cascade="all, delete-orphan")) @@ -444,8 +448,5 @@ def undelete(self) -> None: self.deleted = False @property - def is_deleted(self) -> bool: - """ - Checks whether this association is currently soft deleted - """ - return self.deleted + def deleted(self): + return self.deleted_time is not None diff --git a/total_tolles_ferleihsystem/db_models/itemType.py b/total_tolles_ferleihsystem/db_models/itemType.py index b96fdc9..4c6b9b0 100644 --- a/total_tolles_ferleihsystem/db_models/itemType.py +++ b/total_tolles_ferleihsystem/db_models/itemType.py @@ -15,7 +15,7 @@ class ItemType (DB.Model): name_schema = DB.Column(DB.String(STD_STRING_SIZE)) lendable = DB.Column(DB.Boolean, default=True) lending_duration = DB.Column(DB.Integer, nullable=True) - deleted = DB.Column(DB.Boolean, default=False) + deleted_time = DB.Column(DB.Integer, default=None) visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) how_to = DB.Column(DB.Text, nullable=True) @@ -38,6 +38,17 @@ def update(self, name: str, name_schema: str, lendable: bool, lending_duration: self.visible_for = visible_for self.how_to = how_to + @property + def deleted(self): + return self.deleted_time is not None + + @deleted.setter() + def deleted(self, value: bool): + if value: + self.deleted_time = int(time.time()) + else + self.deleted_time = None + def unassociate_attr_def(self, attribute_definition_id): """ Does all necessary changes to the database for unassociating a attribute definition from this type. diff --git a/total_tolles_ferleihsystem/db_models/tag.py b/total_tolles_ferleihsystem/db_models/tag.py index c708824..7ed442b 100644 --- a/total_tolles_ferleihsystem/db_models/tag.py +++ b/total_tolles_ferleihsystem/db_models/tag.py @@ -19,7 +19,7 @@ class Tag(DB.Model): id = DB.Column(DB.Integer, primary_key=True) name = DB.Column(DB.String(STD_STRING_SIZE), unique=True) lending_duration = DB.Column(DB.Integer) - deleted = DB.Column(DB.Boolean, default=False) + deleted_time = DB.Column(DB.Integer, default=None) visible_for = DB.Column(DB.String(STD_STRING_SIZE)) def __init__(self, name: str, lending_duration: int, visible_for: str): @@ -32,6 +32,10 @@ def update(self, name: str, lending_duration: int, visible_for: str): self.lending_duration = lending_duration self.visible_for = visible_for + @property + def deleted(self): + return self.deleted_time is not None + def unassociate_attr_def(self, attribute_definition_id): """ Does all necessary changes to the database for unassociating a attribute definition from this tag. From 2a4b79c4169769a83c01bee2257eb2f55c0b7d0d Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Thu, 11 Apr 2019 11:20:58 +0200 Subject: [PATCH 15/67] Fix logging problems. --- .../api/catalog/attribute_definition.py | 16 ++--- .../api/catalog/file.py | 10 ++-- .../api/catalog/item_tag.py | 28 ++++----- .../api/catalog/item_type.py | 58 +++++++++---------- 4 files changed, 56 insertions(+), 56 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py index a16d7a4..04df013 100644 --- a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py +++ b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py @@ -66,9 +66,9 @@ def post(self): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Name is not unique.', err) + APP.logger.info('Name is not unique. %s', err) abort(409, 'Name is not unique!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error: %s', err) abort(500) @ANS.route('//') @@ -96,7 +96,7 @@ def get(self, definition_id): attribute = base_query.first() if attribute is None: - APP.logger.debug('Requested attribute not found!', definition_id) + APP.logger.debug('Requested attribute not found for id: %s !', definition_id) abort(404, 'Requested attribute not found!') return attribute @@ -148,7 +148,7 @@ def post(self, definition_id): """ attribute = AttributeDefinition.query.filter(AttributeDefinition.id == definition_id).first() if attribute is None: - APP.logger.debug('Requested attribute not found!', definition_id) + APP.logger.debug('Requested attribute not found for id: %s !', definition_id) abort(404, 'Requested attribute not found!') attribute.deleted = False DB.session.commit() @@ -166,7 +166,7 @@ def put(self, definition_id): """ attribute = AttributeDefinition.query.filter(AttributeDefinition.id == definition_id).first() if attribute is None: - APP.logger.debug('Requested attribute not found!', definition_id) + APP.logger.debug('Requested attribute not found for id: %s !', definition_id) abort(404, 'Requested attribute not found!') attribute.update(**request.get_json()) try: @@ -175,9 +175,9 @@ def put(self, definition_id): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Name is not unique.', err) + APP.logger.info('Name is not unique. %s', err) abort(409, 'Name is not unique!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error: %s', err) abort(500) @@ -205,7 +205,7 @@ def get(self, definition_id): attributeDefinition = base_query.first() if attributeDefinition is None: - APP.logger.debug('Requested attribute not found!', definition_id) + APP.logger.debug('Requested attribute not found for id: %s !', definition_id) abort(404, 'Requested attribute not found!') return list(set([itad.value for itad in attributeDefinition._item_to_attribute_definitions])).sort() diff --git a/total_tolles_ferleihsystem/api/catalog/file.py b/total_tolles_ferleihsystem/api/catalog/file.py index 4cefe52..4b2b811 100644 --- a/total_tolles_ferleihsystem/api/catalog/file.py +++ b/total_tolles_ferleihsystem/api/catalog/file.py @@ -116,7 +116,7 @@ def get(self, file_id): file = base_query.first() if file is None: - APP.logger.debug('Requested file not found!', file_id) + APP.logger.debug('Requested file not found for id: %s !', file_id) abort(404, 'Requested file not found!') return file @@ -132,7 +132,7 @@ def delete(self, file_id): file = File.query.filter(File.id == file_id).first() if file is None: - APP.logger.debug('Requested file not found!', file_id) + APP.logger.debug('Requested file not found for id: %s !', file_id) abort(404, 'Requested file not found!') DB.session.delete(file) @@ -152,7 +152,7 @@ def put(self, file_id): file = File.query.filter(File.id == file_id).options(joinedload('item')).first() if file is None: - APP.logger.debug('Requested file not found!', file_id) + APP.logger.debug('Requested file not found for id: %s !', file_id) abort(404, 'Requested file not found!') file.update(**request.get_json()) @@ -231,7 +231,7 @@ def get(self, file_id): file = base_query.first() if file is None: - APP.logger.debug('Requested file not found!', file_id) + APP.logger.debug('Requested file not found for id: %s !', file_id) abort(404, 'Requested file was not found!') headers = { @@ -241,5 +241,5 @@ def get(self, file_id): with open(os.path.join(APP.config['DATA_DIRECTORY'], file.file_hash), mode='rb') as file_on_disk: return make_response(file_on_disk.read(), headers) - APP.logger.error('Crash while downloading file.', file_id) + APP.logger.error('Crash while downloading file: %s !', file_id) abort(500, 'Something crashed while reading file!') diff --git a/total_tolles_ferleihsystem/api/catalog/item_tag.py b/total_tolles_ferleihsystem/api/catalog/item_tag.py index 9dcf00e..19ba7ee 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_tag.py +++ b/total_tolles_ferleihsystem/api/catalog/item_tag.py @@ -65,9 +65,9 @@ def post(self): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Name is not unique.', err) + APP.logger.info('Name is not unique. %s', err) abort(409, 'Name is not unique!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error: %s', err) abort(500) @@ -112,7 +112,7 @@ def delete(self, tag_id): """ item_tag = Tag.query.filter(Tag.id == tag_id).first() if item_tag is None: - APP.logger.debug('Requested item tag not found.', tag_id) + APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') itts = ItemToTag.query.filter(ItemToTag.tag_id == tag_id).all() @@ -139,7 +139,7 @@ def post(self, tag_id): """ item_tag = Tag.query.filter(Tag.id == tag_id).first() if item_tag is None: - APP.logger.debug('Requested item tag not found.', tag_id) + APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') itts = ItemToTag.query.filter(ItemToTag.tag_id == tag_id).all() @@ -168,7 +168,7 @@ def put(self, tag_id): item_tag = Tag.query.filter(Tag.id == tag_id).first() if item_tag is None: - APP.logger.debug('Requested item tag not found.', tag_id) + APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') item_tag.update(**request.get_json()) @@ -179,9 +179,9 @@ def put(self, tag_id): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Name is not unique.', err) + APP.logger.info('Name is not unique. %s', err) abort(409, 'Name is not unique!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error: %s', err) abort(500) @@ -210,7 +210,7 @@ def get(self, tag_id): tag = base_query.first() if tag is None: - APP.logger.debug('Requested item tag not found.', tag_id) + APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') return [ttad.attribute_definition for ttad in tag._tag_to_attribute_definitions] @@ -231,10 +231,10 @@ def post(self, tag_id): attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted == False).first() if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() is None: - APP.logger.debug('Requested item tag not found.', tag_id) + APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') if attribute_definition is None: - APP.logger.debug('Requested attribute definition not found.', attribute_definition_id) + APP.logger.debug('Requested attribute definition not found for id: %s !', attribute_definition_id) abort(400, 'Requested attribute definition not found!') items = [itt.item for itt in ItemToTag.query.filter(ItemToTag.tag_id == tag_id).options(joinedload('item')).all()] @@ -253,9 +253,9 @@ def post(self, tag_id): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Attribute definition is already asociated with this tag!', err) + APP.logger.info('Attribute definition is already asociated with this tag! %s', err) abort(409, 'Attribute definition is already asociated with this tag!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error: %s', err) abort(500) @jwt_required @@ -272,7 +272,7 @@ def delete(self, tag_id): attribute_definition_id = request.get_json()["id"] tag = Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() if tag is None: - APP.logger.debug('Requested item tag not found.', tag_id) + APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') code, msg, commit = tag.unassociate_attr_def(attribute_definition_id) @@ -282,5 +282,5 @@ def delete(self, tag_id): if code == 204: return '', 204 - APP.logger.debug('Error.', code, msg) + APP.logger.debug('Error: %s, %s', code, msg) abort(code, msg) diff --git a/total_tolles_ferleihsystem/api/catalog/item_type.py b/total_tolles_ferleihsystem/api/catalog/item_type.py index be1fa11..24f7c8d 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_type.py +++ b/total_tolles_ferleihsystem/api/catalog/item_type.py @@ -66,9 +66,9 @@ def post(self): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Name is not unique.', err) + APP.logger.info('Name is not unique. %s', err) abort(409, 'Name is not unique!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error, %s', err) abort(500) @ANS.route('//') @@ -97,7 +97,7 @@ def get(self, type_id): item_type = base_query.first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') return item_type @@ -114,7 +114,7 @@ def delete(self, type_id): item_type = ItemType.query.filter(ItemType.id == type_id).first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') item_type.deleted = True @@ -140,7 +140,7 @@ def post(self, type_id): item_type = ItemType.query.filter(ItemType.id == type_id).first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') item_type.deleted = False @@ -161,7 +161,7 @@ def put(self, type_id): item_type = ItemType.query.filter(ItemType.id == type_id).first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') item_type.update(**request.get_json()) @@ -172,9 +172,9 @@ def put(self, type_id): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Name is not unique.', err) + APP.logger.info('Name is not unique. %s', err) abort(409, 'Name is not unique!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error %s', err) abort(500) @@ -204,7 +204,7 @@ def get(self, type_id): item_type = base_query.first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') return [ittad.attribute_definition for ittad in item_type._item_type_to_attribute_definitions] @@ -226,10 +226,10 @@ def post(self, type_id): attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted == False).first() if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') if attribute_definition is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(400, 'Requested attribute definition not found!') items = Item.query.filter(Item.type_id == type_id).all() @@ -251,9 +251,9 @@ def post(self, type_id): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Attribute definition is already asociated with item type!', err) + APP.logger.info('Attribute definition is already asociated with item type! %s', err) abort(409, 'Attribute definition is already asociated with item type!') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error %s', err) abort(500) @jwt_required @@ -271,7 +271,7 @@ def delete(self, type_id): item_type = ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') code, msg, commit = item_type.unassociate_attr_def(attribute_definition_id) @@ -282,7 +282,7 @@ def delete(self, type_id): if code == 204: return '', 204 - APP.logger.error("Error.", code, msg) + APP.logger.error("Error. %s, %s", code, msg) abort(code, msg) @@ -311,7 +311,7 @@ def get(self, type_id): item_type = base_query.first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') return [cit.item_type for cit in item_type._contained_item_types] @@ -331,10 +331,10 @@ def post(self, type_id): child_id = request.get_json()["id"] if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested contained type not found!', child_id) + APP.logger.debug('Requested contained type (id: %s) not found!', child_id) abort(400, 'Requested contained type not found!') new = ItemTypeToItemType(type_id, child_id) @@ -346,9 +346,9 @@ def post(self, type_id): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('Item type can already be contained in this item type.', err) + APP.logger.info('Item type can already be contained in this item type. %s', err) abort(409, 'Item type can already be contained in this item type.') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error %s', err) abort(500) @jwt_required @@ -365,10 +365,10 @@ def delete(self, type_id): child_id = request.get_json()["id"] if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested contained type not found!', child_id) + APP.logger.debug('Requested contained type (id: %s) not found!', child_id) abort(400, 'Requested contained type not found!') association = (ItemTypeToItemType @@ -409,7 +409,7 @@ def get(self, type_id): item_type = base_query.first() if item_type is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') return [ppit.parent for ppit in item_type._possible_parent_item_types] @@ -429,10 +429,10 @@ def post(self, type_id): parent_id = request.get_json()["id"] if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested parent type not found!', parent_id) + APP.logger.debug('Requested parent type (id: %s) not found!', parent_id) abort(400, 'Requested parent type not found!') new = ItemTypeToItemType(parent_id, type_id) @@ -445,9 +445,9 @@ def post(self, type_id): except IntegrityError as err: message = str(err) if APP.config['DB_UNIQUE_CONSTRAIN_FAIL'] in message: - APP.logger.info('This item type can already contain the given item type.', err) + APP.logger.info('This item type can already contain the given item type. %s', err) abort(409, 'This item type can already contain the given item type.') - APP.logger.error('SQL Error', err) + APP.logger.error('SQL Error %s', err) abort(500) @jwt_required @@ -464,10 +464,10 @@ def delete(self, type_id): parent_id = request.get_json()["id"] if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested item type not found!', type_id) + APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted == False).first() is None: - APP.logger.debug('Requested parent type not found!', parent_id) + APP.logger.debug('Requested parent type (id: %s) not found!', parent_id) abort(400, 'Requested parent type not found!') association = (ItemTypeToItemType From aba07cab3b49cff5e6e677e6033a7da594f504ec Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Thu, 11 Apr 2019 11:59:34 +0200 Subject: [PATCH 16/67] Fixes for deleted timestamps. --- .../api/catalog/attribute_definition.py | 6 ++- .../api/catalog/item.py | 44 ++++++++++--------- .../api/catalog/item_tag.py | 14 +++--- .../api/catalog/item_type.py | 36 ++++++++------- total_tolles_ferleihsystem/api/search.py | 2 +- total_tolles_ferleihsystem/db_models/item.py | 4 +- .../db_models/itemType.py | 6 +-- total_tolles_ferleihsystem/db_models/tag.py | 2 +- 8 files changed, 65 insertions(+), 49 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py index 04df013..ca40a61 100644 --- a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py +++ b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py @@ -36,8 +36,12 @@ def get(self): """ Get a list of all attribute definitions currently in the system """ + base_query = AttributeDefinition.query test_for = request.args.get('deleted', 'false') == 'true' - base_query = AttributeDefinition.query.filter(AttributeDefinition.deleted == test_for) + if test_for: + base_query = base_query.filter(AttributeDefinition.deleted_time != None) + else: + base_query = base_query.filter(AttributeDefinition.deleted_time == None) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: diff --git a/total_tolles_ferleihsystem/api/catalog/item.py b/total_tolles_ferleihsystem/api/catalog/item.py index 85832fc..b9e2131 100644 --- a/total_tolles_ferleihsystem/api/catalog/item.py +++ b/total_tolles_ferleihsystem/api/catalog/item.py @@ -39,8 +39,12 @@ def get(self): """ Get a list of all items currently in the system """ + base_query = Item.query.options(joinedload('lending'), joinedload("_tags")) test_for = request.args.get('deleted', 'false') == 'true' - base_query = Item.query.options(joinedload('lending'), joinedload("_tags")).filter(Item.deleted == test_for) + if test_for: + base_query = base_query.filter(Item.deleted_time != None) + else: + base_query = base_query.filter(Item.deleted_time == None) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -217,7 +221,7 @@ def get(self, item_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if base_query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item not found!') associations = ItemToTag.query.filter(ItemToTag.item_id == item_id).all() @@ -237,11 +241,11 @@ def post(self, item_id): """ tag_id = request.get_json()["id"] # pylint: disable=C0121 - item = Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() + item = Item.query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() if item is None: abort(404, 'Requested item not found!') # pylint: disable=C0121 - tag = Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() + tag = Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted_time == None).first() if tag is None: abort(400, 'Requested item tag not found!') @@ -276,11 +280,11 @@ def delete(self, item_id): tag_id = request.get_json()["id"] # pylint: disable=C0121 - item = Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() + item = Item.query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() if item is None: abort(404, 'Requested item not found!') # pylint: disable=C0121 - if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() is None: + if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted_time == None).first() is None: abort(400, 'Requested item tag not found!') association = (ItemToTag @@ -327,11 +331,11 @@ def get(self, item_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if base_query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item not found!') # pylint: disable=C0121 - attributes = ItemToAttributeDefinition.query.filter(ItemToAttributeDefinition.item_id == item_id).filter(ItemToAttributeDefinition.deleted == False).join(ItemToAttributeDefinition.attribute_definition).order_by(AttributeDefinition.name).all() + attributes = ItemToAttributeDefinition.query.filter(ItemToAttributeDefinition.item_id == item_id).filter(ItemToAttributeDefinition.deleted_time == None).join(ItemToAttributeDefinition.attribute_definition).order_by(AttributeDefinition.name).all() return attributes; # DON'T CHANGE THIS!! # It is necessary because a Flask Bug prehibits log messages on return statements. @@ -360,14 +364,14 @@ def get(self, item_id, attribute_definition_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if base_query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item not found!') # pylint: disable=C0121 attribute = (ItemToAttributeDefinition .query .filter(ItemToAttributeDefinition.item_id == item_id) - .filter(ItemToAttributeDefinition.deleted == False) + .filter(ItemToAttributeDefinition.deleted_time == None) .filter(ItemToAttributeDefinition.attribute_definition_id == attribute_definition_id) .options(joinedload('attribute_definition')) .first()) @@ -390,7 +394,7 @@ def put(self, item_id, attribute_definition_id): """ # pylint: disable=C0121 - if Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if Item.query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item not found!') value = request.get_json()["value"] @@ -399,7 +403,7 @@ def put(self, item_id, attribute_definition_id): .query .filter(ItemToAttributeDefinition.item_id == item_id) .filter(ItemToAttributeDefinition.attribute_definition_id == attribute_definition_id) - .filter(ItemToAttributeDefinition.deleted == False) + .filter(ItemToAttributeDefinition.deleted_time == None) .options(joinedload('attribute_definition')) .first()) @@ -439,7 +443,7 @@ def get(self, item_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if base_query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item not found!') associations = ItemToItem.query.filter(ItemToItem.item_id == item_id).options(joinedload('parent')).all() @@ -470,7 +474,7 @@ def get(self, item_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if base_query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item not found!') associations = ItemToItem.query.filter(ItemToItem.parent_id == item_id).options(joinedload('item')).all() @@ -491,9 +495,9 @@ def post(self, item_id): """ contained_item_id = request.get_json()["id"] # pylint: disable=C0121 - parent = Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() + parent = Item.query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() # pylint: disable=C0121 - child = Item.query.filter(Item.id == contained_item_id).filter(Item.deleted == False).first() + child = Item.query.filter(Item.id == contained_item_id).filter(Item.deleted_time == None).first() if parent is None: abort(404, 'Requested item (current) not found!') @@ -535,10 +539,10 @@ def delete(self, item_id): contained_item_id = request.get_json()["id"] # pylint: disable=C0121 - if Item.query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if Item.query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item (current) not found!') # pylint: disable=C0121 - if Item.query.filter(Item.id == contained_item_id).filter(Item.deleted == False).first() is None: + if Item.query.filter(Item.id == contained_item_id).filter(Item.deleted_time == None).first() is None: abort(400, 'Requested item (to be contained) not found!') association = (ItemToItem @@ -580,7 +584,7 @@ def get(self, item_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - if base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() is None: + if base_query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() is None: abort(404, 'Requested item not found!') return File.query.filter(File.item_id == item_id).options(joinedload('item')).all() @@ -609,7 +613,7 @@ def get(self, item_id): base_query = base_query.filter(Item.visible_for == 'all') # pylint: disable=C0121 - item = base_query.filter(Item.id == item_id).filter(Item.deleted == False).first() + item = base_query.filter(Item.id == item_id).filter(Item.deleted_time == None).first() if item is None: abort(404, 'Requested item not found!') diff --git a/total_tolles_ferleihsystem/api/catalog/item_tag.py b/total_tolles_ferleihsystem/api/catalog/item_tag.py index 19ba7ee..196328e 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_tag.py +++ b/total_tolles_ferleihsystem/api/catalog/item_tag.py @@ -35,8 +35,12 @@ def get(self): """ Get a list of all item tags currently in the system """ + base_query = Tag.query test_for = request.args.get('deleted', 'false') == 'true' - base_query = Tag.query.filter(Tag.deleted == test_for) + if test_for: + base_query = base_query.filter(Tag.deleted_time != None) + else: + base_query = base_query.filter(Tag.deleted_time == None) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -199,7 +203,7 @@ def get(self, tag_id): """ Get all attribute definitions for this tag. """ - base_query = Tag.query.options(joinedload('_tag_to_attribute_definitions')).filter(Tag.id == tag_id).filter(Tag.deleted == False) + base_query = Tag.query.options(joinedload('_tag_to_attribute_definitions')).filter(Tag.id == tag_id).filter(Tag.deleted_time == None) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -228,9 +232,9 @@ def post(self, tag_id): Associate a new attribute definition with the tag. """ attribute_definition_id = request.get_json()["id"] - attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted == False).first() + attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted_time == None).first() - if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() is None: + if Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted_time == None).first() is None: APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') if attribute_definition is None: @@ -270,7 +274,7 @@ def delete(self, tag_id): Remove association of a attribute definition with the tag. """ attribute_definition_id = request.get_json()["id"] - tag = Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted == False).first() + tag = Tag.query.filter(Tag.id == tag_id).filter(Tag.deleted_time == None).first() if tag is None: APP.logger.debug('Requested item tag not found for id: %s !', tag_id) abort(404, 'Requested item tag not found!') diff --git a/total_tolles_ferleihsystem/api/catalog/item_type.py b/total_tolles_ferleihsystem/api/catalog/item_type.py index 24f7c8d..c491970 100644 --- a/total_tolles_ferleihsystem/api/catalog/item_type.py +++ b/total_tolles_ferleihsystem/api/catalog/item_type.py @@ -35,9 +35,13 @@ def get(self): """ Get a list of all item types currently in the system """ + base_query = ItemType.query test_for = request.args.get('deleted', 'false') == 'true' - base_query = ItemType.query.filter(ItemType.deleted == test_for) - + if test_for: + base_query = base_query.filter(ItemType.deleted_time != None) + else: + base_query = base_query.filter(ItemType.deleted_time == None) + # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: if UserRole(get_jwt_claims()) == UserRole.MODERATOR: @@ -192,7 +196,7 @@ def get(self, type_id): """ Get all attribute definitions for this item type. """ - base_query = ItemType.query.options(joinedload('_item_type_to_attribute_definitions')).filter(ItemType.id == type_id).filter(ItemType.deleted == False) + base_query = ItemType.query.options(joinedload('_item_type_to_attribute_definitions')).filter(ItemType.id == type_id).filter(ItemType.deleted_time == None) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -223,9 +227,9 @@ def post(self, type_id): """ attribute_definition_id = request.get_json()["id"] # pylint: disable=C0121 - attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted == False).first() + attribute_definition = AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted_time == None).first() - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') if attribute_definition is None: @@ -268,7 +272,7 @@ def delete(self, type_id): Remove association of a attribute definition with the item type. """ attribute_definition_id = request.get_json()["id"] - item_type = ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() + item_type = ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted_time == None).first() if item_type is None: APP.logger.debug('Requested item type (id: %s) not found!', type_id) @@ -300,7 +304,7 @@ def get(self, type_id): """ Get all item types, this item_type may contain. """ - base_query = ItemType.query.options(joinedload('_contained_item_types').joinedload('item_type')).filter(ItemType.id == type_id).filter(ItemType.deleted == False) + base_query = ItemType.query.options(joinedload('_contained_item_types').joinedload('item_type')).filter(ItemType.id == type_id).filter(ItemType.deleted_time == None) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -330,10 +334,10 @@ def post(self, type_id): """ child_id = request.get_json()["id"] - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') - if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested contained type (id: %s) not found!', child_id) abort(400, 'Requested contained type not found!') @@ -364,10 +368,10 @@ def delete(self, type_id): """ child_id = request.get_json()["id"] - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') - if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == child_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested contained type (id: %s) not found!', child_id) abort(400, 'Requested contained type not found!') @@ -398,7 +402,7 @@ def get(self, type_id): """ Get all item types, this item_type may be contained in. """ - base_query = ItemType.query.options(joinedload('_possible_parent_item_types').joinedload('parent')).filter(ItemType.id == type_id).filter(ItemType.deleted == False) + base_query = ItemType.query.options(joinedload('_possible_parent_item_types').joinedload('parent')).filter(ItemType.id == type_id).filter(ItemType.deleted_time == None) # auth check if UserRole(get_jwt_claims()) != UserRole.ADMIN: @@ -428,10 +432,10 @@ def post(self, type_id): """ parent_id = request.get_json()["id"] - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') - if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested parent type (id: %s) not found!', parent_id) abort(400, 'Requested parent type not found!') @@ -463,10 +467,10 @@ def delete(self, type_id): """ parent_id = request.get_json()["id"] - if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == type_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested item type (id: %s) not found!', type_id) abort(404, 'Requested item type not found!') - if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted == False).first() is None: + if ItemType.query.filter(ItemType.id == parent_id).filter(ItemType.deleted_time == None).first() is None: APP.logger.debug('Requested parent type (id: %s) not found!', parent_id) abort(400, 'Requested parent type not found!') diff --git a/total_tolles_ferleihsystem/api/search.py b/total_tolles_ferleihsystem/api/search.py index 2de5010..9f999c3 100644 --- a/total_tolles_ferleihsystem/api/search.py +++ b/total_tolles_ferleihsystem/api/search.py @@ -67,7 +67,7 @@ def get(self): search_result = search_result.filter(search_condition) if not deleted: - search_result = search_result.filter(~Item.deleted) + search_result = search_result.filter(Item.deleted_time == None) if not lent: search_result = search_result.filter(Item.lending == None) diff --git a/total_tolles_ferleihsystem/db_models/item.py b/total_tolles_ferleihsystem/db_models/item.py index 66132ed..9eb1af4 100644 --- a/total_tolles_ferleihsystem/db_models/item.py +++ b/total_tolles_ferleihsystem/db_models/item.py @@ -309,7 +309,7 @@ def __init__(self, moderator: str, user: str, deposit: str, item_ids: list): self.date = int(time.time()) self.deposit = deposit for element in item_ids: - item = Item.query.filter(Item.id == element).filter(Item.deleted == False).first() + item = Item.query.filter(Item.id == element).filter(Item.deleted_time == None).first() if item is None: raise ValueError("Item not found:" + str(element)) if not item.type.lendable: @@ -332,7 +332,7 @@ def update(self, moderator: str, user: str, deposit: str, item_ids: list): new_items = [] for element in item_ids: - item = Item.query.filter(Item.id == element).filter(Item.deleted == False).first() + item = Item.query.filter(Item.id == element).filter(Item.deleted_time == None).first() if item is None: raise ValueError("Item not found:" + str(element)) new_items.append(item) diff --git a/total_tolles_ferleihsystem/db_models/itemType.py b/total_tolles_ferleihsystem/db_models/itemType.py index 4c6b9b0..3ea7945 100644 --- a/total_tolles_ferleihsystem/db_models/itemType.py +++ b/total_tolles_ferleihsystem/db_models/itemType.py @@ -42,11 +42,11 @@ def update(self, name: str, name_schema: str, lendable: bool, lending_duration: def deleted(self): return self.deleted_time is not None - @deleted.setter() + @deleted.setter def deleted(self, value: bool): if value: self.deleted_time = int(time.time()) - else + else: self.deleted_time = None def unassociate_attr_def(self, attribute_definition_id): @@ -54,7 +54,7 @@ def unassociate_attr_def(self, attribute_definition_id): Does all necessary changes to the database for unassociating a attribute definition from this type. Does not commit the changes. """ - if AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted == False).first() is None: + if AttributeDefinition.query.filter(AttributeDefinition.id == attribute_definition_id).filter(AttributeDefinition.deleted_time == None).first() is None: return(400, 'Requested attribute definition not found!', False) association = (ItemTypeToAttributeDefinition .query diff --git a/total_tolles_ferleihsystem/db_models/tag.py b/total_tolles_ferleihsystem/db_models/tag.py index 7ed442b..108abe2 100644 --- a/total_tolles_ferleihsystem/db_models/tag.py +++ b/total_tolles_ferleihsystem/db_models/tag.py @@ -41,7 +41,7 @@ def unassociate_attr_def(self, attribute_definition_id): Does all necessary changes to the database for unassociating a attribute definition from this tag. Does not commit the changes. """ - if attributeDefinition.AttributeDefinition.query.filter(attributeDefinition.AttributeDefinition.id == attribute_definition_id).filter(attributeDefinition.AttributeDefinition.deleted == False).first() is None: + if attributeDefinition.AttributeDefinition.query.filter(attributeDefinition.AttributeDefinition.id == attribute_definition_id).filter(attributeDefinition.AttributeDefinition.deleted_time == None).first() is None: return(400, 'Requested attribute definition not found!', False) association = (TagToAttributeDefinition .query From 5a28e8ff4c2e637f69c0c7b63996032412b49f56 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Tue, 16 Apr 2019 10:14:25 +0200 Subject: [PATCH 17/67] Fix more bugs with deletion time. --- .../api/catalog/attribute_definition.py | 1 + total_tolles_ferleihsystem/config.py | 1 + .../db_models/attributeDefinition.py | 9 ++++++++- total_tolles_ferleihsystem/db_models/item.py | 9 ++++++++- total_tolles_ferleihsystem/db_models/itemType.py | 2 ++ total_tolles_ferleihsystem/db_models/tag.py | 8 ++++++++ 6 files changed, 28 insertions(+), 2 deletions(-) diff --git a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py index ca40a61..bd30aa3 100644 --- a/total_tolles_ferleihsystem/api/catalog/attribute_definition.py +++ b/total_tolles_ferleihsystem/api/catalog/attribute_definition.py @@ -1,6 +1,7 @@ """ This module contains all API endpoints for the namespace 'attribute_definition' """ +import time from flask import request from flask_restplus import Resource, abort, marshal diff --git a/total_tolles_ferleihsystem/config.py b/total_tolles_ferleihsystem/config.py index 7a7f1d6..1f0b06b 100644 --- a/total_tolles_ferleihsystem/config.py +++ b/total_tolles_ferleihsystem/config.py @@ -145,6 +145,7 @@ class DebugConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/test.db' JWT_SECRET_KEY = 'debug' + JWT_ACCESS_TOKEN_EXPIRES = False LOGIN_PROVIDERS = ['Debug'] Config.LOGGING['loggers']['flask.app.auth']['level'] = logging.DEBUG Config.LOGGING['loggers']['flask.app.db']['level'] = logging.DEBUG diff --git a/total_tolles_ferleihsystem/db_models/attributeDefinition.py b/total_tolles_ferleihsystem/db_models/attributeDefinition.py index e52336f..15bc285 100644 --- a/total_tolles_ferleihsystem/db_models/attributeDefinition.py +++ b/total_tolles_ferleihsystem/db_models/attributeDefinition.py @@ -28,4 +28,11 @@ def update(self, name: str, type: str, jsonschema: str, visible_for: str): @property def deleted(self): - return self.deleted_time is not None \ No newline at end of file + return self.deleted_time is not None + + @deleted.setter + def deleted(self, value: bool): + if value: + self.deleted_time = int(time.time()) + else: + self.deleted_time = None \ No newline at end of file diff --git a/total_tolles_ferleihsystem/db_models/item.py b/total_tolles_ferleihsystem/db_models/item.py index 9eb1af4..96b4daf 100644 --- a/total_tolles_ferleihsystem/db_models/item.py +++ b/total_tolles_ferleihsystem/db_models/item.py @@ -40,7 +40,7 @@ class Item(DB.Model): visible_for = DB.Column(DB.String(STD_STRING_SIZE), nullable=True) type = DB.relationship('ItemType', lazy='joined') - lending = DB.relationship('Lending', lazy='select', + lending = DB.relationship('Lending', lazy='select', backref=DB.backref('_items', lazy='select')) __table_args__ = ( @@ -81,6 +81,13 @@ def update(self, update_name_from_schema: bool, name: str, type_id: int, lending def deleted(self): return self.deleted_time is not None + @deleted.setter + def deleted(self, value: bool): + if value: + self.deleted_time = int(time.time()) + else: + self.deleted_time = None + @property def is_currently_lent(self): """ diff --git a/total_tolles_ferleihsystem/db_models/itemType.py b/total_tolles_ferleihsystem/db_models/itemType.py index 3ea7945..1faec6b 100644 --- a/total_tolles_ferleihsystem/db_models/itemType.py +++ b/total_tolles_ferleihsystem/db_models/itemType.py @@ -1,3 +1,5 @@ +import time + from .. import DB from . import STD_STRING_SIZE from .attributeDefinition import AttributeDefinition diff --git a/total_tolles_ferleihsystem/db_models/tag.py b/total_tolles_ferleihsystem/db_models/tag.py index 108abe2..af0024b 100644 --- a/total_tolles_ferleihsystem/db_models/tag.py +++ b/total_tolles_ferleihsystem/db_models/tag.py @@ -1,6 +1,7 @@ """ Module containing database models for everything concerning Item-Tags. """ +import time from .. import DB from . import STD_STRING_SIZE @@ -36,6 +37,13 @@ def update(self, name: str, lending_duration: int, visible_for: str): def deleted(self): return self.deleted_time is not None + @deleted.setter + def deleted(self, value: bool): + if value: + self.deleted_time = int(time.time()) + else: + self.deleted_time = None + def unassociate_attr_def(self, attribute_definition_id): """ Does all necessary changes to the database for unassociating a attribute definition from this tag. From 1462637b0d77f25008ef4a672aec340da4498d92 Mon Sep 17 00:00:00 2001 From: Tim Neumann Date: Tue, 16 Apr 2019 10:51:33 +0200 Subject: [PATCH 18/67] Update requirements. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b3e8617..d0e03e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Flask==1.0.2 -flask-restplus==0.11.0 +flask-restplus==0.12.1 Flask-SQLAlchemy==2.3.1 Flask-Migrate==2.2.1 Flask-Webpack==0.1.0 From 8df229eed4704e4d50e79137d4e6d38e9c1b9f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 18 Apr 2019 14:29:59 +0200 Subject: [PATCH 19/67] Add the preliminary migration --- migrations/versions/53482a1da62a_.py | 87 ++++++++++++++++++++ total_tolles_ferleihsystem/package-lock.json | 58 ++++--------- 2 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 migrations/versions/53482a1da62a_.py diff --git a/migrations/versions/53482a1da62a_.py b/migrations/versions/53482a1da62a_.py new file mode 100644 index 0000000..5d04064 --- /dev/null +++ b/migrations/versions/53482a1da62a_.py @@ -0,0 +1,87 @@ +"""empty message + +Revision ID: 53482a1da62a +Revises: 70ab97044985 +Create Date: 2019-04-18 14:15:20.648676 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '53482a1da62a' +down_revision = '70ab97044985' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('ItemToLending') + with op.batch_alter_table('AttributeDefinition', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + batch_op.drop_column('deleted') + + with op.batch_alter_table('File', schema=None) as batch_op: + batch_op.drop_index('ix_File_file_hash') + + with op.batch_alter_table('Item', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column('due', sa.Integer(), nullable=True)) + batch_op.add_column(sa.Column('lending_id', sa.Integer(), nullable=True)) + batch_op.create_foreign_key(batch_op.f('fk_Item_lending_id'), 'Lending', ['lending_id'], ['id']) + batch_op.drop_column('deleted') + + with op.batch_alter_table('ItemToAttributeDefinition', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + batch_op.drop_column('deleted') + + with op.batch_alter_table('ItemType', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + batch_op.drop_column('deleted') + + with op.batch_alter_table('Tag', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + batch_op.drop_column('deleted') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('Tag', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted', sa.BOOLEAN(), nullable=True)) + batch_op.drop_column('deleted_time') + + with op.batch_alter_table('ItemType', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted', sa.BOOLEAN(), nullable=True)) + batch_op.drop_column('deleted_time') + + with op.batch_alter_table('ItemToAttributeDefinition', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted', sa.BOOLEAN(), nullable=True)) + batch_op.drop_column('deleted_time') + + with op.batch_alter_table('Item', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted', sa.BOOLEAN(), nullable=True)) + batch_op.drop_constraint(batch_op.f('fk_Item_lending_id'), type_='foreignkey') + batch_op.drop_column('lending_id') + batch_op.drop_column('due') + batch_op.drop_column('deleted_time') + + with op.batch_alter_table('File', schema=None) as batch_op: + batch_op.create_index('ix_File_file_hash', ['file_hash'], unique=False) + + with op.batch_alter_table('AttributeDefinition', schema=None) as batch_op: + batch_op.add_column(sa.Column('deleted', sa.BOOLEAN(), nullable=True)) + batch_op.drop_column('deleted_time') + + op.create_table('ItemToLending', + sa.Column('item_id', sa.INTEGER(), nullable=False), + sa.Column('lending_id', sa.INTEGER(), nullable=False), + sa.Column('due', sa.DATETIME(), nullable=True), + sa.ForeignKeyConstraint(['item_id'], ['Item.id'], ), + sa.ForeignKeyConstraint(['lending_id'], ['Lending.id'], ), + sa.PrimaryKeyConstraint('item_id', 'lending_id') + ) + # ### end Alembic commands ### diff --git a/total_tolles_ferleihsystem/package-lock.json b/total_tolles_ferleihsystem/package-lock.json index 5c24ce8..339fbfa 100644 --- a/total_tolles_ferleihsystem/package-lock.json +++ b/total_tolles_ferleihsystem/package-lock.json @@ -582,15 +582,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true, - "optional": true + "dev": true }, "are-we-there-yet": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "dev": true, - "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -1034,7 +1032,6 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, - "optional": true, "requires": { "hoek": "2.x.x" } @@ -1622,8 +1619,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "constants-browserify": { "version": "1.0.0", @@ -1984,8 +1980,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true + "dev": true }, "denodeify": { "version": "1.2.1", @@ -2831,8 +2826,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2875,8 +2869,7 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", @@ -2887,8 +2880,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3005,8 +2997,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3018,7 +3009,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3048,7 +3038,6 @@ "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3067,7 +3056,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3168,7 +3156,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3254,8 +3241,7 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3291,7 +3277,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3311,7 +3296,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3355,14 +3339,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -3381,7 +3363,6 @@ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true, - "optional": true, "requires": { "graceful-fs": "^4.1.2", "inherits": "~2.0.0", @@ -3400,7 +3381,6 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, - "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -3417,7 +3397,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3427,7 +3406,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3473,8 +3451,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "optional": true + "dev": true }, "getpass": { "version": "0.1.7", @@ -3702,8 +3679,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true + "dev": true }, "hash-base": { "version": "2.0.2", @@ -3758,8 +3734,7 @@ "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true, - "optional": true + "dev": true }, "hosted-git-info": { "version": "2.5.0", @@ -5384,8 +5359,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "optional": true + "dev": true }, "matcher-collection": { "version": "1.0.5", @@ -5881,7 +5855,6 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, - "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -9314,7 +9287,6 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "dev": true, - "optional": true, "requires": { "string-width": "^1.0.2" }, @@ -9324,7 +9296,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9334,7 +9305,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", From dcd379f2e9c82bfdb2b708f71e6406571351432c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 18 Apr 2019 16:28:56 +0200 Subject: [PATCH 20/67] Add data migration --- migrations/versions/53482a1da62a_.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/migrations/versions/53482a1da62a_.py b/migrations/versions/53482a1da62a_.py index 5d04064..bfe4233 100644 --- a/migrations/versions/53482a1da62a_.py +++ b/migrations/versions/53482a1da62a_.py @@ -21,6 +21,7 @@ def upgrade(): op.drop_table('ItemToLending') with op.batch_alter_table('AttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + op.execute(' UPDATE "AttributeDefinition" SET "deleted_time" = 1 WHERE "AttributeDefinition"."deleted"') batch_op.drop_column('deleted') with op.batch_alter_table('File', schema=None) as batch_op: @@ -30,19 +31,23 @@ def upgrade(): batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('due', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('lending_id', sa.Integer(), nullable=True)) + op.execute(' UPDATE "Item" SET "deleted_time" = 1 WHERE "Item"."deleted"') batch_op.create_foreign_key(batch_op.f('fk_Item_lending_id'), 'Lending', ['lending_id'], ['id']) batch_op.drop_column('deleted') with op.batch_alter_table('ItemToAttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + op.execute(' UPDATE "ItemToAttributeDefinition" SET "deleted_time" = 1 WHERE "ItemToAttributeDefinition"."deleted"') batch_op.drop_column('deleted') with op.batch_alter_table('ItemType', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + op.execute(' UPDATE "ItemType" SET "deleted_time" = 1 WHERE "ItemType"."deleted"') batch_op.drop_column('deleted') with op.batch_alter_table('Tag', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) + op.execute(' UPDATE "Tag" SET "deleted_time" = 1 WHERE "Tag"."deleted"') batch_op.drop_column('deleted') # ### end Alembic commands ### From b25e57216c3e5842c25fb628ff4589cddf5bcf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 18 Apr 2019 16:31:49 +0200 Subject: [PATCH 21/67] Fix bug --- migrations/versions/53482a1da62a_.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/migrations/versions/53482a1da62a_.py b/migrations/versions/53482a1da62a_.py index bfe4233..e1a6cdf 100644 --- a/migrations/versions/53482a1da62a_.py +++ b/migrations/versions/53482a1da62a_.py @@ -21,7 +21,7 @@ def upgrade(): op.drop_table('ItemToLending') with op.batch_alter_table('AttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "AttributeDefinition" SET "deleted_time" = 1 WHERE "AttributeDefinition"."deleted"') + op.execute(' UPDATE "AttributeDefinition" SET "deleted_time" = 1 WHERE "AttributeDefinition"."deleted" <> 0') batch_op.drop_column('deleted') with op.batch_alter_table('File', schema=None) as batch_op: @@ -31,23 +31,23 @@ def upgrade(): batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('due', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('lending_id', sa.Integer(), nullable=True)) - op.execute(' UPDATE "Item" SET "deleted_time" = 1 WHERE "Item"."deleted"') + op.execute(' UPDATE "Item" SET "deleted_time" = 1 WHERE "Item"."deleted" <> 0') batch_op.create_foreign_key(batch_op.f('fk_Item_lending_id'), 'Lending', ['lending_id'], ['id']) batch_op.drop_column('deleted') with op.batch_alter_table('ItemToAttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "ItemToAttributeDefinition" SET "deleted_time" = 1 WHERE "ItemToAttributeDefinition"."deleted"') + op.execute(' UPDATE "ItemToAttributeDefinition" SET "deleted_time" = 1 WHERE "ItemToAttributeDefinition"."deleted" <> 0') batch_op.drop_column('deleted') with op.batch_alter_table('ItemType', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "ItemType" SET "deleted_time" = 1 WHERE "ItemType"."deleted"') + op.execute(' UPDATE "ItemType" SET "deleted_time" = 1 WHERE "ItemType"."deleted" <> 0') batch_op.drop_column('deleted') with op.batch_alter_table('Tag', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "Tag" SET "deleted_time" = 1 WHERE "Tag"."deleted"') + op.execute(' UPDATE "Tag" SET "deleted_time" = 1 WHERE "Tag"."deleted" <> 0') batch_op.drop_column('deleted') # ### end Alembic commands ### From ac73a6652158d6b6aa30ce503baae16400935630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 18 Apr 2019 16:37:30 +0200 Subject: [PATCH 22/67] Change the sql string escapes --- migrations/versions/53482a1da62a_.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/migrations/versions/53482a1da62a_.py b/migrations/versions/53482a1da62a_.py index e1a6cdf..b9b60ca 100644 --- a/migrations/versions/53482a1da62a_.py +++ b/migrations/versions/53482a1da62a_.py @@ -21,7 +21,7 @@ def upgrade(): op.drop_table('ItemToLending') with op.batch_alter_table('AttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "AttributeDefinition" SET "deleted_time" = 1 WHERE "AttributeDefinition"."deleted" <> 0') + op.execute(' UPDATE `AttributeDefinition` SET `deleted_time` = 1 WHERE `AttributeDefinition`.`deleted` <> 0') batch_op.drop_column('deleted') with op.batch_alter_table('File', schema=None) as batch_op: @@ -31,23 +31,23 @@ def upgrade(): batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('due', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('lending_id', sa.Integer(), nullable=True)) - op.execute(' UPDATE "Item" SET "deleted_time" = 1 WHERE "Item"."deleted" <> 0') + op.execute(' UPDATE `Item` SET `deleted_time` = 1 WHERE `Item`.`deleted` <> 0') batch_op.create_foreign_key(batch_op.f('fk_Item_lending_id'), 'Lending', ['lending_id'], ['id']) batch_op.drop_column('deleted') with op.batch_alter_table('ItemToAttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "ItemToAttributeDefinition" SET "deleted_time" = 1 WHERE "ItemToAttributeDefinition"."deleted" <> 0') + op.execute(' UPDATE `ItemToAttributeDefinition` SET `deleted_time` = 1 WHERE `ItemToAttributeDefinition`.`deleted` <> 0') batch_op.drop_column('deleted') with op.batch_alter_table('ItemType', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "ItemType" SET "deleted_time" = 1 WHERE "ItemType"."deleted" <> 0') + op.execute(' UPDATE `ItemType` SET `deleted_time` = 1 WHERE `ItemType`.`deleted` <> 0') batch_op.drop_column('deleted') with op.batch_alter_table('Tag', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE "Tag" SET "deleted_time" = 1 WHERE "Tag"."deleted" <> 0') + op.execute(' UPDATE `Tag` SET `deleted_time` = 1 WHERE `Tag`.`deleted` <> 0') batch_op.drop_column('deleted') # ### end Alembic commands ### From 5f1982450d29e913ba834e1487fbbf45efd57f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=A4ltken?= Date: Thu, 18 Apr 2019 16:43:11 +0200 Subject: [PATCH 23/67] Fix the batch execution of the current migration --- migrations/versions/53482a1da62a_.py | 30 +++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/migrations/versions/53482a1da62a_.py b/migrations/versions/53482a1da62a_.py index b9b60ca..97657ac 100644 --- a/migrations/versions/53482a1da62a_.py +++ b/migrations/versions/53482a1da62a_.py @@ -21,33 +21,53 @@ def upgrade(): op.drop_table('ItemToLending') with op.batch_alter_table('AttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE `AttributeDefinition` SET `deleted_time` = 1 WHERE `AttributeDefinition`.`deleted` <> 0') + + op.execute(' UPDATE `AttributeDefinition` SET `deleted_time` = 1 WHERE `AttributeDefinition`.`deleted` <> 0') + + with op.batch_alter_table('AttributeDefinition', schema=None) as batch_op: batch_op.drop_column('deleted') + with op.batch_alter_table('File', schema=None) as batch_op: batch_op.drop_index('ix_File_file_hash') + with op.batch_alter_table('Item', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('due', sa.Integer(), nullable=True)) batch_op.add_column(sa.Column('lending_id', sa.Integer(), nullable=True)) - op.execute(' UPDATE `Item` SET `deleted_time` = 1 WHERE `Item`.`deleted` <> 0') + + op.execute(' UPDATE `Item` SET `deleted_time` = 1 WHERE `Item`.`deleted` <> 0') + + with op.batch_alter_table('Item', schema=None) as batch_op: batch_op.create_foreign_key(batch_op.f('fk_Item_lending_id'), 'Lending', ['lending_id'], ['id']) batch_op.drop_column('deleted') + with op.batch_alter_table('ItemToAttributeDefinition', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE `ItemToAttributeDefinition` SET `deleted_time` = 1 WHERE `ItemToAttributeDefinition`.`deleted` <> 0') + + op.execute(' UPDATE `ItemToAttributeDefinition` SET `deleted_time` = 1 WHERE `ItemToAttributeDefinition`.`deleted` <> 0') + + with op.batch_alter_table('ItemToAttributeDefinition', schema=None) as batch_op: batch_op.drop_column('deleted') + with op.batch_alter_table('ItemType', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE `ItemType` SET `deleted_time` = 1 WHERE `ItemType`.`deleted` <> 0') + + op.execute(' UPDATE `ItemType` SET `deleted_time` = 1 WHERE `ItemType`.`deleted` <> 0') + + with op.batch_alter_table('ItemType', schema=None) as batch_op: batch_op.drop_column('deleted') + with op.batch_alter_table('Tag', schema=None) as batch_op: batch_op.add_column(sa.Column('deleted_time', sa.Integer(), nullable=True)) - op.execute(' UPDATE `Tag` SET `deleted_time` = 1 WHERE `Tag`.`deleted` <> 0') + + op.execute(' UPDATE `Tag` SET `deleted_time` = 1 WHERE `Tag`.`deleted` <> 0') + + with op.batch_alter_table('Tag', schema=None) as batch_op: batch_op.drop_column('deleted') # ### end Alembic commands ### From 0894632a6220542c9b9d92c8cbd23cc62aae5937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=BChler?= <17296905+buehlefs@users.noreply.github.com> Date: Sun, 21 Apr 2019 19:34:38 +0200 Subject: [PATCH 24/67] Fix dates and lendings in UI --- .../src/app/home/home.component.html | 2 +- .../src/app/home/home.component.ts | 4 ++-- .../src/app/items/item-detail.component.html | 2 +- .../src/app/items/lending-duration.component.ts | 3 +-- .../src/app/lending/item-lending.component.html | 10 +++++----- .../src/app/lending/item-lending.component.ts | 8 ++++---- .../src/app/lending/lending.component.html | 4 ++-- .../src/app/shared/rest/api.service.ts | 4 ++-- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/total_tolles_ferleihsystem/src/app/home/home.component.html b/total_tolles_ferleihsystem/src/app/home/home.component.html index ee6a120..f2b4bb1 100644 --- a/total_tolles_ferleihsystem/src/app/home/home.component.html +++ b/total_tolles_ferleihsystem/src/app/home/home.component.html @@ -14,7 +14,7 @@ {{item.name}} ({{item.type?.name}}) - {{item.due | date:'short'}} + {{item.due * 1000 | date:'short'}} diff --git a/total_tolles_ferleihsystem/src/app/home/home.component.ts b/total_tolles_ferleihsystem/src/app/home/home.component.ts index 6076deb..405cc64 100644 --- a/total_tolles_ferleihsystem/src/app/home/home.component.ts +++ b/total_tolles_ferleihsystem/src/app/home/home.component.ts @@ -16,7 +16,7 @@ import { Observable } from 'rxjs'; grid-column-gap: 20px; grid-row-gap: 20px; grid-template-columns: repeat(auto-fill, 17rem); - justify-content: space-between; + justify-content: space-between; margin-left: .5rem; margin-right: .5rem; }` @@ -47,7 +47,7 @@ export class HomeComponent implements OnInit { } itemOverdue(item: ApiObject): boolean { - const due = new Date(item.due); + const due = new Date(item.due * 1000); return due < new Date(); } diff --git a/total_tolles_ferleihsystem/src/app/items/item-detail.component.html b/total_tolles_ferleihsystem/src/app/items/item-detail.component.html index d47ff04..71a8f6f 100644 --- a/total_tolles_ferleihsystem/src/app/items/item-detail.component.html +++ b/total_tolles_ferleihsystem/src/app/items/item-detail.component.html @@ -35,7 +35,7 @@

Item "{{item?.name}}" Due: - {{item?.due | date:'short'}} + {{item?.due * 1000 | date:'short'}}

Attributes:

diff --git a/total_tolles_ferleihsystem/src/app/items/lending-duration.component.ts b/total_tolles_ferleihsystem/src/app/items/lending-duration.component.ts index 0c67f63..a7dc534 100644 --- a/total_tolles_ferleihsystem/src/app/items/lending-duration.component.ts +++ b/total_tolles_ferleihsystem/src/app/items/lending-duration.component.ts @@ -11,7 +11,6 @@ export class LendingDurationComponent implements OnChanges { minutes: number; hours: number; days: number; - weeks: number; years: number; get valid(): boolean { @@ -27,7 +26,7 @@ export class LendingDurationComponent implements OnChanges { temp = Math.floor(temp / 60); this.hours = temp % 24; temp = Math.floor(temp / 24); - this.days = temp % 7; + this.days = temp % 364; this.years = Math.floor(temp / 364); } diff --git a/total_tolles_ferleihsystem/src/app/lending/item-lending.component.html b/total_tolles_ferleihsystem/src/app/lending/item-lending.component.html index 2e68e04..d0be6ac 100644 --- a/total_tolles_ferleihsystem/src/app/lending/item-lending.component.html +++ b/total_tolles_ferleihsystem/src/app/lending/item-lending.component.html @@ -3,10 +3,10 @@
- {{itemLending?.item?.name}} (due: {{itemLending?.due | date:'short'}}) + {{item?.name}} (due: {{item?.due * 1000 | date:'short'}})
- @@ -14,13 +14,13 @@

Type: - {{itemLending?.item?.type?.name}} + {{item?.type?.name}}

Tags: {{tag.name}}{{isLast ? '' : ', '}}

-

Lending Duration:

+

Lending Duration:

Attributes:

{{attr.attribute_definition?.name}}: {{attr?.value}}

@@ -29,6 +29,6 @@

How To:

-
{{itemLending?.item?.type?.how_to}}
+
{{item?.type?.how_to}}
diff --git a/total_tolles_ferleihsystem/src/app/lending/item-lending.component.ts b/total_tolles_ferleihsystem/src/app/lending/item-lending.component.ts index 3115bef..b690084 100644 --- a/total_tolles_ferleihsystem/src/app/lending/item-lending.component.ts +++ b/total_tolles_ferleihsystem/src/app/lending/item-lending.component.ts @@ -11,7 +11,7 @@ import { ApiObject } from '../shared/rest/api-base.service'; export class ItemLendingComponent implements OnInit, OnDestroy { - @Input() itemLending: any; + @Input() item: any; @Output() return: EventEmitter = new EventEmitter(); tags: ApiObject[] = []; @@ -23,11 +23,11 @@ export class ItemLendingComponent implements OnInit, OnDestroy { constructor(private api: ApiService) { } ngOnInit(): void { - if (this.itemLending != null && this.itemLending.item != null) { - this.api.getTagsForItem(this.itemLending.item).take(1).subscribe(tags => { + if (this.item != null && this.item != null) { + this.api.getTagsForItem(this.item).take(1).subscribe(tags => { this.tags = tags; }); - this.api.getAttributes(this.itemLending.item).take(1).subscribe(attributes => { + this.api.getAttributes(this.item).take(1).subscribe(attributes => { this.attributes = attributes; }); } diff --git a/total_tolles_ferleihsystem/src/app/lending/lending.component.html b/total_tolles_ferleihsystem/src/app/lending/lending.component.html index 2cbbba6..13ab8d4 100644 --- a/total_tolles_ferleihsystem/src/app/lending/lending.component.html +++ b/total_tolles_ferleihsystem/src/app/lending/lending.component.html @@ -8,8 +8,8 @@

Lending:

Moderator: {{lending?.moderator}}

User: {{lending?.user}}

Deposit: {{lending?.deposit}}

-

Date: {{lending?.date | date:'short'}}

- +

Date: {{lending?.date * 1000 | date:'short'}}

+
diff --git a/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts b/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts index bd547c0..3793d5a 100644 --- a/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts +++ b/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts @@ -804,8 +804,8 @@ export class ApiService implements OnInit { if (b.due == null || b.due == '') { return 1; } - const d_a = new Date(a.due); - const d_b = new Date(b.due); + const d_a = new Date(a.due * 1000); + const d_b = new Date(b.due * 1000); if (d_a < d_b) { return -1; } else if (d_a > d_b) { From 8abf23efb40b6f8f712a0d8438eff546596c3015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=BChler?= <17296905+buehlefs@users.noreply.github.com> Date: Sun, 21 Apr 2019 21:57:45 +0200 Subject: [PATCH 25/67] Fix contained-items api calls in ui Removed old references to can_contain --- .../item-types/item-type-edit.component.ts | 6 +++--- .../src/app/items/item-detail.component.ts | 2 +- .../src/app/shared/rest/api.service.ts | 20 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/total_tolles_ferleihsystem/src/app/item-types/item-type-edit.component.ts b/total_tolles_ferleihsystem/src/app/item-types/item-type-edit.component.ts index 569a573..6cf60ba 100644 --- a/total_tolles_ferleihsystem/src/app/item-types/item-type-edit.component.ts +++ b/total_tolles_ferleihsystem/src/app/item-types/item-type-edit.component.ts @@ -41,7 +41,7 @@ export class ItemTypeEditComponent implements OnChanges, OnDestroy { if (this.canContainSubscription != null) { this.canContainSubscription.unsubscribe(); } - this.canContainSubscription = this.api.getCanContain(this.itemType).subscribe(canContain => { + this.canContainSubscription = this.api.getContainedTypes(this.itemType).subscribe(canContain => { this.canContain = canContain; }); }); @@ -58,13 +58,13 @@ export class ItemTypeEditComponent implements OnChanges, OnDestroy { addCanContain() { if (this.canContainTypeID != null && this.canContainTypeID >= 0) { - this.api.postCanContain(this.itemType, this.canContainTypeID); + this.api.postContainedType(this.itemType, this.canContainTypeID); } } removeCanContain(id) { if (this.canContainTypeID != null && this.canContainTypeID >= 0) { - this.api.deleteCanContain(this.itemType, id); + this.api.deleteContainedType(this.itemType, id); } } diff --git a/total_tolles_ferleihsystem/src/app/items/item-detail.component.ts b/total_tolles_ferleihsystem/src/app/items/item-detail.component.ts index dbac1b6..600b79b 100644 --- a/total_tolles_ferleihsystem/src/app/items/item-detail.component.ts +++ b/total_tolles_ferleihsystem/src/app/items/item-detail.component.ts @@ -116,7 +116,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy { if (this.containedTypeSubscription != null) { this.containedTypeSubscription.unsubscribe(); } - this.containedTypeSubscription = this.api.getCanContain(item.type).subscribe(canContain => { + this.containedTypeSubscription = this.api.getContainedTypes(item.type).subscribe(canContain => { this.canContain = canContain; }); if (this.containedItemsSubscription != null) { diff --git a/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts b/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts index 3793d5a..ed84983 100644 --- a/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts +++ b/total_tolles_ferleihsystem/src/app/shared/rest/api.service.ts @@ -446,12 +446,12 @@ export class ApiService implements OnInit { return (stream.asObservable() as Observable).filter(data => data != null); } - getCanContain(item_type: ApiObject, showErrors: string= 'all'): Observable { - const resource = 'item-types/' + item_type.id + '/can-contain'; + getContainedTypes(item_type: ApiObject, showErrors: string= 'all'): Observable { + const resource = 'item-types/' + item_type.id + '/contained-types'; const stream = this.getStreamSource(resource); this.currentJWT.map(jwt => jwt.token()).subscribe(token => { - this.rest.get(item_type._links.can_contain, token).subscribe(data => { + this.rest.get(item_type._links.contained_types, token).subscribe(data => { stream.next(data); }, error => this.errorHandler(error, resource, 'GET', showErrors)); }); @@ -459,12 +459,12 @@ export class ApiService implements OnInit { return (stream.asObservable() as Observable); } - postCanContain(item_type: ApiObject, itemTypeID, showErrors: string= 'all'): Observable { - const resource = 'item-types/' + item_type.id + '/can-contain'; + postContainedType(item_type: ApiObject, itemTypeID, showErrors: string= 'all'): Observable { + const resource = 'item-types/' + item_type.id + '/contained-types'; const stream = this.getStreamSource(resource); this.currentJWT.map(jwt => jwt.token()).subscribe(token => { - this.rest.post(item_type._links.can_contain, {id: itemTypeID}, token).subscribe(data => { + this.rest.post(item_type._links.contained_types, {id: itemTypeID}, token).subscribe(data => { stream.next(data); }, error => this.errorHandler(error, resource, 'POST', showErrors)); }); @@ -472,13 +472,13 @@ export class ApiService implements OnInit { return (stream.asObservable() as Observable); } - deleteCanContain(item_type: ApiObject, itemTypeID, showErrors: string= 'all'): Observable { - const resource = 'item-types/' + item_type.id + '/can-contain'; + deleteContainedType(item_type: ApiObject, itemTypeID, showErrors: string= 'all'): Observable { + const resource = 'item-types/' + item_type.id + '/contained-types'; const stream = this.getStreamSource(resource); this.currentJWT.map(jwt => jwt.token()).subscribe(token => { - this.rest.delete(item_type._links.can_contain, token, {id: itemTypeID}).subscribe(data => { - this.getCanContain(item_type); + this.rest.delete(item_type._links.contained_types, token, {id: itemTypeID}).subscribe(data => { + this.getContainedTypes(item_type); }, error => this.errorHandler(error, resource, 'DELETE', showErrors)); }); From 249fe130eede28903fc6ec4ee08d53bd6f62012a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20B=C3=BChler?= <17296905+buehlefs@users.noreply.github.com> Date: Sun, 21 Apr 2019 21:58:32 +0200 Subject: [PATCH 26/67] Fix request flood in item overview and search Only request details for items in viewport --- .../src/app/items/item-list.component.html | 2 +- .../src/app/items/item-list.component.ts | 20 +++++++++----- .../src/app/search/search.component.html | 2 +- .../src/app/search/search.component.ts | 20 +++++++++----- .../src/app/shared/is-visible.directive.ts | 27 +++++++++++++++++++ .../src/app/shared/shared.module.ts | 3 +++ 6 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 total_tolles_ferleihsystem/src/app/shared/is-visible.directive.ts diff --git a/total_tolles_ferleihsystem/src/app/items/item-list.component.html b/total_tolles_ferleihsystem/src/app/items/item-list.component.html index 208e550..246abf1 100644 --- a/total_tolles_ferleihsystem/src/app/items/item-list.component.html +++ b/total_tolles_ferleihsystem/src/app/items/item-list.component.html @@ -8,7 +8,7 @@
{{letter}}