Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Archive recipe and filtering #146

Open
matdrapeau opened this issue Aug 17, 2018 · 4 comments
Open

Using Archive recipe and filtering #146

matdrapeau opened this issue Aug 17, 2018 · 4 comments

Comments

@matdrapeau
Copy link

In order to use soft-deletes, I tried to used the recipe (https://potion.readthedocs.io/en/latest/recipes.html#archivingresource) but filtering resources are not working anymore.

from flask_potion import ModelResource, fields
from flask_potion.contrib.alchemy import SQLAlchemyManager
from flask_potion.exceptions import ItemNotFound
from flask_potion.routes import Route
from flask_potion.instances import Instances
from enum import Enum

class BaseResource(ModelResource):
    class Meta:
        read_only_fields = ['create_date', 'update_date']
    class Schema:
        create_date = fields.DateTimeString()
        update_date = fields.DateTimeString()



class Location(Enum):
    ARCHIVE_ONLY = 1
    INSTANCES_ONLY = 2
    BOTH = 3


class ArchiveManager(SQLAlchemyManager):
    def _query(self, source=Location.INSTANCES_ONLY):
        query = super(ArchiveManager, self)._query()

        if source == Location.BOTH:
            return query
        elif source == Location.ARCHIVE_ONLY:
            return query.filter(getattr(self.model, 'is_archived') == True)
        else:
            return query.filter(getattr(self.model, 'is_archived') == False)

    def instances(self, where=None, sort=None, source=Location.INSTANCES_ONLY):
        query = self._query(source)
        if where:
            expressions = [self._expression_for_condition(condition) for condition in where]
            query = self._query_filter(query, self._and_expression(expressions))
        if sort:
            query = self._query_order_by(query, sort)
        return query

    def archive_instances(self, page, per_page, where=None, sort=None):
        return self.instances(where=where, sort=sort, source=Location.ARCHIVE_ONLY).paginate(page=page, per_page=per_page)

    def read(self, id, source=Location.INSTANCES_ONLY):
        query = self._query(source)
        if query is None:
            raise ItemNotFound(self.resource, id=id)
        return self._query_filter_by_id(query, id)


class ArchivingResource(BaseResource):
    class Meta:
        manager = ArchiveManager
        exclude_routes = ['destroy'] # we're using rel="archive" instead.

    class Schema(BaseResource.Schema):
        is_archived = fields.Boolean(io='r')

    @Route.GET('/<int:id>', rel="self", attribute="instance")
    def read(self, id) -> fields.Inline('self'):
        return self.manager.read(id, source=Location.BOTH)

    @read.PATCH(rel="update")
    def update(self, properties, id):
        item = self.manager.read(id, source=Location.INSTANCES_ONLY)
        updated_item = self.manager.update(item, properties)
        return updated_item

    update.response_schema = update.request_schema = fields.Inline('self', patchable=True)

    @update.DELETE(rel="archive")
    def destroy(self, id):
        item = self.manager.read(id, source=Location.INSTANCES_ONLY)
        self.manager.update(item, {"is_archived": True})
        return None, 204

    @Route.GET("/archive")
    def archive_instances(self, **kwargs):
        return self.manager.archive_instances(**kwargs)

    archive_instances.request_schema = archive_instances.response_schema = Instances()

    @Route.GET('/archive/<int:id>', rel="readArchived")
    def read_archive(self, id) -> fields.Inline('self'):
        return self.manager.read(id, source=Location.ARCHIVE_ONLY)

    @Route.POST('/archive/<int:id>/restore', rel="restoreFromArchive")
    def restore_from_archive(self, id) -> fields.Inline('self'):
        item = self.manager.read(id, source=Location.ARCHIVE_ONLY)
        return self.manager.update(item, {"is_archived": False})


class Base(object):
    id = Column(Integer, primary_key=True, autoincrement=True)
    create_date = Column(DateTime(timezone=True), server_default=func.now())
    update_date = Column(DateTime(timezone=True), onupdate=func.now())
    is_archived = Column(Boolean, default=False)

Base = declarative_base(cls=Base)

class User(Base):
    __tablename__ = 'users'
    name = Column(String(), nullable=True)

class UserResource(ArchivingResource):
    class Meta(ArchivingResource.Meta):
        model = User
        natural_key = ('organization_id', 'foreign_id')
        #write_only_fields = ['organization_id']
    class Schema(ArchivingResource.Schema):
        name = fields.String()

app.api.add_resource(UserResource)

Doing such filters are raising errors:
/users?where={"create_date": {"$gt": "2018-06-10T14:56:15-05:00"}}
/users/archive?where={"create_date": {"$gt": "2018-06-10T14:56:15-05:00"}}

I get: ValidationError {'$gt': '2018-06-10T14:56:15-05:00'} is not valid under any of the given schemas

@lyschoening
Copy link
Contributor

Does /users?where={"create_date": {"$gt": "2018-06-10T19:56:15Z"}} work?

@matdrapeau
Copy link
Author

matdrapeau commented Oct 15, 2018

@lyschoening Actually, that query doesn't work.

It seems the inheritance of the Meta is not picking parent attributes.
create_date and update_date are not exposed for resources which inherit from ArchivingResource based on this definition:

class BaseResource(ModelResource):
    class Meta:
        read_only_fields = ['create_date', 'update_date']
    class Schema:
        create_date = fields.DateTimeString()
        update_date = fields.DateTimeString()


class ArchivingResource(BaseResource):
    class Meta(BaseResource.Meta):
        manager = ArchiveManager
        exclude_routes = ['destroy'] # we're using rel="archive" instead.

    class Schema(BaseResource.Schema):
        is_archived = fields.Boolean(io='r')

@lyschoening
Copy link
Contributor

lyschoening commented Oct 16, 2018

@matdrapeau Have you tried just class Meta: and class Schema:? Sorry, I did not spot that before. There should be no need to subclass those classes in the way you did.

@matdrapeau
Copy link
Author

@lyschoening no, same error as well without subclassing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants