コード例 #1
0
class Tag(db.Model):
    __tablename__ = 'tags'
    __updateable_fields__ = {'name': str, 'is_for_sale': bool}

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(24), unique=True, nullable=False)
    created_by = db.Column(db.Integer,
                           db.ForeignKey('users.id'),
                           nullable=False)
    is_for_sale = db.Column(db.Boolean, nullable=False, default=True)

    def __repr__(self):
        return f'<Tag {self.name}>'
コード例 #2
0
class Rank(db.Model):
    __tablename__ = 'ranks'
    __updateable_fields__ = {
        'name': str,
        'is_system_user': bool,
        'debt_limit': int
    }

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), unique=True, nullable=False)
    active = db.Column(db.Boolean, nullable=False, default=True)
    debt_limit = db.Column(db.Integer, nullable=True)
    is_system_user = db.Column(db.Boolean, nullable=False, default=False)

    def __repr__(self):
        return f'<Rank {self.name}>'
コード例 #3
0
ファイル: replenishment.py プロジェクト: g3n35i5/shop-db2
class ReplenishmentCollection(db.Model):
    __tablename__ = 'replenishmentcollections'
    __updateable_fields__ = {'revoked': bool, 'comment': str, 'timestamp': str}

    id = db.Column(db.Integer, primary_key=True)
    timestamp = db.Column(db.DateTime, default=func.now(), nullable=False)
    admin_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    seller_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    revoked = db.Column(db.Boolean, nullable=False, default=False)
    comment = db.Column(db.String(64), nullable=False)
    replenishments = db.relationship('Replenishment', lazy='dynamic', foreign_keys='Replenishment.replcoll_id')

    price = column_property(select([func.coalesce(func.sum(Replenishment.total_price), 0)])
                            .where(Replenishment.replcoll_id == id)
                            .where(Replenishment.revoked.is_(False))
                            .as_scalar())

    @hybrid_method
    def set_revoked(self, revoked, admin_id):
        # Which replenishments are not revoked?
        non_revoked_replenishments = self.replenishments.filter_by(revoked=False).all()
        if not revoked and not non_revoked_replenishments:
            raise EntryNotRevocable()

        dr = ReplenishmentCollectionRevoke(revoked=revoked, admin_id=admin_id, replcoll_id=self.id)
        self.revoked = revoked
        db.session.add(dr)

    @hybrid_method
    def set_timestamp(self, timestamp: str):
        data = parse_timestamp({'timestamp': timestamp}, required=True)
        self.timestamp = data['timestamp']

    @hybrid_property
    def revokehistory(self):
        res = (ReplenishmentCollectionRevoke.query
               .filter(ReplenishmentCollectionRevoke.replcoll_id == self.id)
               .all())
        revokehistory = []
        for revoke in res:
            revokehistory.append({
                'id': revoke.id,
                'timestamp': revoke.timestamp,
                'revoked': revoke.revoked
            })
        return revokehistory
コード例 #4
0
ファイル: upload.py プロジェクト: g3n35i5/shop-db2
class Upload(db.Model):
    __tablename__ = 'uploads'
    id = db.Column(db.Integer, primary_key=True)
    timestamp = db.Column(db.DateTime, default=func.now(), nullable=False)
    admin_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    filename = db.Column(db.String(64), unique=True, nullable=False)
コード例 #5
0
class Product(db.Model):
    __tablename__ = 'products'
    __updateable_fields__ = {
        'name': str, 'price': int, 'barcode': str, 'tags': list,
        'countable': bool, 'revocable': bool, 'imagename': dict
    }
    # that "imagename" is a dict and not a string is because the update compares
    # whether the values have changed. But since a product has no "image" but only an
    # "imagename", the data for the new image must be called "imagename" and thus be a dict.

    from .product_price import ProductPrice
    from .product_tag_assignment import product_tag_assignments
    from .purchase import Purchase
    from .replenishment import Replenishment

    id = db.Column(db.Integer, primary_key=True)
    creation_date = db.Column(db.DateTime, default=func.now(), nullable=False)
    created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    name = db.Column(db.String(64), unique=True, nullable=False)
    barcode = db.Column(db.String(32), unique=True, nullable=True)
    active = db.Column(db.Boolean, nullable=False, default=True)
    countable = db.Column(db.Boolean, nullable=False, default=True)
    revocable = db.Column(db.Boolean, nullable=False, default=True)
    image_upload_id = db.Column(db.Integer, db.ForeignKey('uploads.id'), nullable=True)
    tags = db.relationship('Tag', secondary=product_tag_assignments, backref=db.backref('products', lazy='dynamic'))

    # Column property for the price
    price = column_property(select([ProductPrice.price])
                            .where(ProductPrice.product_id == id)
                            .order_by(ProductPrice.id.desc())
                            .limit(1)
                            .as_scalar())

    # Column property for the purchase sum
    purchase_sum = column_property(select([func.coalesce(func.sum(Purchase.price), 0)])
                                   .where(Purchase.product_id == id)
                                   .where(Purchase.revoked.is_(False))
                                   .as_scalar())

    # Column property for the replenishment sum
    replenishment_sum = column_property(select([func.coalesce(func.sum(Replenishment.total_price), 0)])
                                        .where(Replenishment.product_id == id)
                                        .where(Replenishment.revoked.is_(False))
                                        .as_scalar())

    @validates('created_by')
    def validate_admin(self, key, created_by):
        from .user import User
        user = User.query.filter(User.id == created_by).first()
        if not user or not user.is_admin:
            raise UnauthorizedAccess()

        return created_by

    @property
    def is_for_sale(self):
        """
        Returns whether this product is for sale for unprivileged users
        """
        return all(map(lambda tag: tag.is_for_sale, self.tags))

    @hybrid_property
    def imagename(self):
        from .upload import Upload
        upload = Upload.query.filter_by(id=self.image_upload_id).first()
        if upload:
            return upload.filename
        return None

    @hybrid_method
    def get_pricehistory(self, start_date=None, end_date=None):

        # If the time range parameters are not set, we use the creation date
        # and the current date as range.

        try:
            if start_date:
                start = datetime.datetime.fromtimestamp(start_date)
            else:
                start = self.creation_date

            if end_date:
                end = datetime.datetime.fromtimestamp(end_date)
            else:
                end = datetime.datetime.now()
        except ValueError:
            raise InvalidData()

        # Make sure that we select the whole day by shifting the selected
        # range to the very beginning of the start day and to the end of the
        # end day.
        start = start.replace(hour=0, minute=0, second=0, microsecond=0)
        end = end.replace(hour=23, minute=59, second=59, microsecond=999999)

        from .product_price import ProductPrice

        # Query the pricehistory in the given range
        res = (db.session.query(ProductPrice)
               .filter(ProductPrice.product_id == self.id)
               .filter(ProductPrice.timestamp.between(start, end))
               .all())

        # Map the result to a dictionary containing all price changes.
        return list(map(lambda p: {
            'id': p.id, 'timestamp': p.timestamp, 'price': p.price
        }, res))

    @hybrid_method
    def set_price(self, price, admin_id):
        from .product_price import ProductPrice
        productprice = ProductPrice(
            price=price,
            product_id=self.id,
            admin_id=admin_id
        )
        db.session.add(productprice)

    @hybrid_method
    def set_barcode(self, barcode):
        if Product.query.filter_by(barcode=barcode).first():
            raise EntryAlreadyExists()
        self.barcode = barcode

    @hybrid_method
    def set_imagename(self, image, admin_id):
        filename = insert_image(image)
        # Create an upload
        try:
            from .upload import Upload
            u = Upload(filename=filename, admin_id=admin_id)
            db.session.add(u)
            db.session.flush()
            self.image_upload_id = u.id
        except IntegrityError:
            raise CouldNotCreateEntry()

    @hybrid_method
    def set_tags(self, tags):
        from .tag import Tag
        # All tag ids must be int
        if not all([isinstance(tag_id, int) for tag_id in tags]):
            raise InvalidData()

        # No changes?
        if sorted(tags) == sorted(self.tag_ids):
            raise NothingHasChanged()

        # If there are no remaining tags after the update, the request is invalid.
        if len(tags) == 0:
            raise NoRemainingTag()

        # Get a list of all new tag ids and a list of all removed tag ids
        added_tag_ids = [x for x in tags if x not in self.tag_ids]
        removed_tag_ids = [x for x in self.tag_ids if x not in tags]

        # Add all new tags in the added tag ids list
        for tag_id in added_tag_ids:
            tag = Tag.query.filter_by(id=tag_id).first()
            if tag is None:
                raise EntryNotFound()
            self.tags.append(tag)

        # Remove all tags in the remove tag ids list
        for tag_id in removed_tag_ids:
            tag = Tag.query.filter_by(id=tag_id).first()
            if tag is None:
                raise EntryNotFound()
            self.tags.remove(tag)

    @property
    def tag_ids(self):
        return [tag.id for tag in self.tags]

    def __repr__(self):
        return f'<Product {self.name}>'
コード例 #6
0
class User(db.Model):
    __tablename__ = 'users'
    __updateable_fields__ = {
        'firstname': str,
        'lastname': str,
        'password': bytes,
        'is_admin': bool,
        'rank_id': int,
        'imagename': dict
    }

    from .rank import Rank
    from .rank_update import RankUpdate
    from .user_verification import UserVerification
    from .purchase import Purchase
    from .deposit import Deposit
    from .replenishment import ReplenishmentCollection

    id = db.Column(db.Integer, primary_key=True)
    creation_date = db.Column(db.DateTime, default=func.now(), nullable=False)
    firstname = db.Column(db.String(32), unique=False, nullable=True)
    lastname = db.Column(db.String(32), unique=False, nullable=False)
    password = db.Column(db.String(256), unique=False, nullable=True)
    is_verified = db.Column(db.Boolean, nullable=False, default=False)
    image_upload_id = db.Column(db.Integer,
                                db.ForeignKey('uploads.id'),
                                nullable=True)

    # Column property for the full name
    fullname = column_property(
        func.trim(func.coalesce(firstname, "") + " " + lastname))

    # Column property for the active state
    active = column_property(
        select([Rank.active]).where(
            and_(RankUpdate.user_id == id,
                 Rank.id == RankUpdate.rank_id)).order_by(
                     RankUpdate.id.desc()).limit(1).as_scalar())

    # Column property for the is system user property
    is_system_user = column_property(
        select([Rank.is_system_user]).where(
            and_(RankUpdate.user_id == id,
                 Rank.id == RankUpdate.rank_id)).order_by(
                     RankUpdate.id.desc()).limit(1).as_scalar())

    # Column property for the verification_date
    verification_date = column_property(
        select([UserVerification.timestamp
                ]).where(UserVerification.user_id == id).limit(1).as_scalar())

    # Column property for the rank_id
    rank_id = column_property(
        select([Rank.id]).where(
            and_(RankUpdate.user_id == id,
                 Rank.id == RankUpdate.rank_id)).order_by(
                     RankUpdate.id.desc()).limit(1).as_scalar())

    # Select statement for the sum of all non revoked purchases referring this user.
    # NOTE: func.coalesce(a, b) returns the first non-null value of (a, b). If there aren't any purchases
    #       (or deposits, ...) yet, the purchase (deposit, ...) sum is NULL. In this case, 0 gets returned.
    _purchase_sum = column_property(
        select([func.coalesce(func.sum(Purchase.price),
                              0)]).where(Purchase.user_id == id).where(
                                  Purchase.revoked.is_(False)).as_scalar())

    # Select statement for the sum of all non revoked deposits referring this user.
    _deposit_sum = column_property(
        select([func.coalesce(func.sum(Deposit.amount),
                              0)]).where(Deposit.user_id == id).where(
                                  Deposit.revoked.is_(False)).as_scalar())

    # Select statement for the sum of all non revoked refunds referring this user.
    _replenishmentcollection_sum = column_property(
        select([func.coalesce(func.sum(ReplenishmentCollection.price), 0)
                ]).where(ReplenishmentCollection.seller_id == id).where(
                    ReplenishmentCollection.revoked.is_(False)).as_scalar())

    # A users credit is the sum of all amounts that increase his credit (Deposits, ReplenishmentCollections)
    # and all amounts that decrease it (Purchases)
    credit = column_property(_replenishmentcollection_sum.expression +
                             _deposit_sum.expression -
                             _purchase_sum.expression)

    # Link to all purchases of a user.
    purchases = db.relationship('Purchase',
                                lazy='dynamic',
                                foreign_keys='Purchase.user_id')
    # Link to all deposits of a user.
    deposits = db.relationship('Deposit',
                               lazy='dynamic',
                               foreign_keys='Deposit.user_id')
    # Link to all deposits of a user.
    replenishmentcollections = db.relationship(
        'ReplenishmentCollection',
        lazy='dynamic',
        foreign_keys='ReplenishmentCollection.seller_id')

    def __repr__(self):
        return f'<User {self.id}: {self.lastname}, {self.firstname}>'

    @hybrid_property
    def imagename(self):
        from .upload import Upload
        upload = Upload.query.filter_by(id=self.image_upload_id).first()
        if upload:
            return upload.filename
        return None

    @hybrid_method
    def set_imagename(self, image, admin_id):
        filename = insert_image(image)
        # Create an upload
        try:
            from .upload import Upload
            u = Upload(filename=filename, admin_id=admin_id)
            db.session.add(u)
            db.session.flush()
            self.image_upload_id = u.id
        except IntegrityError:
            raise CouldNotCreateEntry()

    @hybrid_property
    def is_admin(self):
        from .admin_update import AdminUpdate
        au = (AdminUpdate.query.filter_by(user_id=self.id).order_by(
            AdminUpdate.id.desc()).first())
        if au is None:
            return False
        return au.is_admin

    @hybrid_method
    def set_admin(self, is_admin, admin_id):
        from .admin_update import AdminUpdate
        if is_admin and self.password is None:
            raise UserNeedsPassword()
        if self.is_admin == is_admin:
            raise NothingHasChanged()
        au = AdminUpdate(is_admin=is_admin, admin_id=admin_id, user_id=self.id)
        db.session.add(au)

    @hybrid_method
    def verify(self, admin_id, rank_id):
        from .user_verification import UserVerification
        if self.is_verified:
            raise UserAlreadyVerified()
        self.is_verified = True
        uv = UserVerification(user_id=self.id, admin_id=admin_id)
        self.set_rank_id(rank_id, admin_id)
        db.session.add(uv)

    @hybrid_method
    def set_rank_id(self, rank_id, admin_id):
        from .rank_update import RankUpdate
        if self.is_verified:
            ru = RankUpdate(rank_id=rank_id,
                            admin_id=admin_id,
                            user_id=self.id)
            db.session.add(ru)
        else:
            self.verify(admin_id=admin_id, rank_id=rank_id)

    @hybrid_method
    def set_is_admin(self, is_admin, admin_id):
        self.set_admin(is_admin=is_admin, admin_id=admin_id)
        if not self.is_admin:
            users = User.query.all()
            admins = list(filter(lambda x: x.is_admin, users))
            if not admins:
                raise NoRemainingAdmin()

    @hybrid_property
    def rank(self):
        from .rank import Rank
        if self.rank_id:
            rank = Rank.query.filter(Rank.id == self.rank_id).first()
            if rank:
                return rank
        return None

    @hybrid_property
    def favorites(self):
        """
        Returns the product ids of the user's favorite products in
        descending order of number. Inactive products those who are
        not for sale are ignored.
        Args:
            self: self
        Returns:
            ids: A list of the favorite product ids in descending order.
        """
        from .tag import Tag
        from .product_tag_assignment import product_tag_assignments
        from .purchase import Purchase
        from .product import Product
        # Get a list of all invalid tag ids (as SQL subquery)
        invalid_tag_ids = db.session.query(Tag.id).filter(
            Tag.is_for_sale.is_(False)).subquery()
        # Get a list of all products to which this tag is assigned
        invalid_product_ids = (db.session.query(
            product_tag_assignments.c.product_id).filter(
                product_tag_assignments.c.tag_id.in_(
                    invalid_tag_ids)).subquery())
        # Get a list of all inactive product ids
        inactive_product_ids = db.session.query(Product.id).filter(
            Product.active.is_(False)).subquery()

        result = (
            db.session.query(Purchase.product_id).filter(
                Purchase.user_id == self.id)  # Get only user purchases
            .group_by(Purchase.product_id)  # Group by products
            .filter(Purchase.product_id.notin_(
                invalid_product_ids))  # Get only products which are for sale
            .filter(Purchase.product_id.notin_(
                inactive_product_ids))  # Get only products which are active
            .filter(
                Purchase.revoked.is_(False))  # Get only non revoked purchases
            .order_by(func.sum(
                Purchase.amount).desc())  # Order by the sum of purchase amount
            .all())
        return [item.product_id for item in result]