"""base sqlalchemy model.""" from datetime import datetime from dateutil import tz from flask import abort from flask import g from sqlalchemy import func from royalty_common.connectors.database import db from royalty_common.constants import ( error) from royalty_common.utils.users import get_flask_user_id class CRUDMixin: """CRUD model mixin.""" last_modified = None last_modified_by = None @classmethod def find_by_name(cls, name): """Get object from DB by name property. :return: object """ return cls.query.filter_by(name=name).first() @classmethod def get_by_id(cls, obj_id): """Get object from DB by ID property. :return: object """ return cls.query.get(obj_id) @classmethod def get_by_id_or_error(cls, obj_id, error_status=400): """Find object by ID. Abort request if not found.""" obj = cls.get_by_id(obj_id) if not obj: abort( status=error_status, description=error.ERROR_ENTITY_DOES_NOT_EXIST.format( object_type=cls.get_class_name(), object_id=obj_id ) ) return obj @classmethod def delete_by_id_or_error( cls, obj_id, error_status=400, soft_delete=False ): """Load an object by id, then delete it. soft_delete param enforces just updating the row without deleting it. Aborts if the object is not found. """ obj = cls.get_by_id_or_error(obj_id, error_status) if not soft_delete: db.session.delete(obj) else: obj._soft_delete() db.session.commit() @classmethod def get_class_name(cls): """Return name of the subclass.""" return cls.__name__ @classmethod def filter_for(cls, query): """Override to customize filtering on a string query.""" return query and [cls.name.ilike(f'{query}%')] or [] @classmethod def default_order(cls): """Override to customize default ordering.""" return func.lower(cls.name) @classmethod def count(cls, query=None): """Retrieve a count of objects. Filters by the query, if provided. """ return cls.query.filter(*cls.filter_for(query)).count() @classmethod def create(cls, **kwargs): """Persist a new object.""" new_object = cls.build(**kwargs) db.session.commit() return new_object @classmethod def build(cls, **kwargs): """Construct a new object without persisting it.""" new_object = cls(**kwargs) db.session.add(new_object) return new_object @staticmethod def current_timestamp(): """Return correct date for TZ.""" return datetime.now(tz.tzutc()) def commit_changes(*new_objects): """Commit the session, adding any new objects if necessary.""" if new_objects: db.session.add_all(new_objects) db.session.commit() def update_attributes(self, **attrs): """Update attributes.""" date_now = CRUDMixin.current_timestamp() last_modified_by = g.user_details.get('id') for key, val in attrs.items(): setattr(self, key, val) self.last_modified = date_now self.last_modified_by = last_modified_by return self def _soft_delete(self): """Soft delete the instance.""" if not hasattr(self, 'deleted_at') \ or not hasattr(self, 'deleted_by'): abort( description=error.ERROR_SOFT_DELETE_UNAVAILABLE, status=400 ) if self.deleted_at is not None: abort( description=error.ERROR_ENTITY_IS_SOFT_DELETED, status=400 ) self.deleted_at = CRUDMixin.current_timestamp() self.deleted_by = get_flask_user_id() class BaseModel(db.Model, CRUDMixin): """Base model.""" def __init__(self, *args, **kwargs): """Initialize model with default values.""" date_now = CRUDMixin.current_timestamp() user_id = get_flask_user_id() or '' self.created_at = date_now self.created_by = user_id self.last_modified = date_now self.last_modified_by = user_id super().__init__(*args, **kwargs) __abstract__ = True created_at = db.Column(db.DateTime, nullable=False) last_modified = db.Column(db.DateTime, nullable=False) created_by = db.Column(db.String(255), nullable=False, server_default='') last_modified_by = \ db.Column(db.String(255), nullable=False, server_default='')