Flask Список целых чисел фильтра SQLalchemy - PullRequest
0 голосов
/ 13 января 2020

В настоящее время пытаюсь внедрить систему рекомендаций для игр, но у меня возникают некоторые проблемы с REST-API с flask / sqlalchemy

Я успешно реализовал маршруты для получения игр и пытаюсь применить больше фильтров. Я хочу фильтровать извлеченные игры по жанрам - что работает, когда я фильтрую по идентификатору одного жанра - проблема возникает, когда я пытаюсь фильтровать по нескольким жанрам.

Маршрут должен выглядеть как / игры? Жанры = 3,7,8

В базе данных есть таблица игр и жанров, и между ними существует связь один ко многим, поэтому одна игра связана с несколькими жанрами.

Это Game db.Model

game_genres = db.Table('game_genres',
                       db.Column('game_id', db.Integer, db.ForeignKey(
                           'games.id'), primary_key=True),
                       db.Column('genre_id', db.Integer, db.ForeignKey(
                           'genres.id'), primary_key=True),
                       db.PrimaryKeyConstraint('game_id', 'genre_id')
                       )

game_platforms = db.Table('game_platforms',
                          db.Column('game_id', db.Integer, db.ForeignKey(
                              'games.id'), primary_key=True),
                          db.Column('platform_id', db.Integer, db.ForeignKey(
                              'platforms.id'), primary_key=True),
                          db.PrimaryKeyConstraint('game_id', 'platform_id')
                          )

class Game(db.Model):
    __tablename__ = 'games'

    id = db.Column(db.Integer, unique=True, primary_key=True)
    title = db.Column(db.String, nullable=False)
    description = db.Column(db.String)
    year = db.Column(db.Integer)
    genres = db.relationship(
        'Genre',
        secondary=game_genres,
        lazy='subquery',
        backref=db.backref('genres', lazy=True, cascade='all, delete')
    )
    platforms = db.relationship(
        'Platform',
        secondary=game_platforms,
        lazy='subquery',
        backref=db.backref('games', lazy=True, cascade='all, delete')
    )

    def __init__(self, title: str):
        self.title = title

    def __repr__(self):
        return f'<Game {self.title}>'

    @property
    def to_json(self):
        genres = []
        for genre in self.genres:
            genres.append(genre.to_json)
        platforms = []
        for platform in self.platforms:
            platforms.append(platform.to_json)
        return {
            'id': self.id,
            'title': self.title,
            'description': self.description,
            'year': self.year,
            'genres': genres,
            'platforms': platforms,
        }
    @classmethod
    def return_all(self, offset, limit):
        return {'games': [g.to_json for g in self.query.order_by(Game.id).offset(offset).limit(limit).all()]}

    @classmethod
    def return_bygenres(self, offset, limit, genres2):
        return {'games': [g.to_json for g in self.query\
                         .join(Game.genres, aliased=True)\
                         .filter(or_(*[id.like(gid)for gid in genres2])).all().order_by(Game.id).offset(offset).limit(limit).all()]}


Это соответствующая модель жанра

class Genre(db.Model):
    __tablename__ = "genres"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f'<Genre {self.name}>'

    @property
    def to_json(self):
        return {
            'id': self.id,
            'name': self.name,
        }

Маршруты выглядят так

GAME_PARSER = reqparse.RequestParser()
GAME_PARSER.add_argument('offset', type=int)
GAME_PARSER.add_argument('limit', type=int)
GAME_PARSER.add_argument('genres')


class AllGames(Resource):
    def get(self):
        args = GAME_PARSER.parse_args()
        offset = 0 if args.offset is None else args.offset
        limit = 100 if args.limit is None else args.limit
        genres = args.genres
        return Game.return_bygenres(offset, limit, genres.split(","))

Я нашел текущий запрос на другой пост, но он выдает ошибку. Я получаю сообщение об ошибке: «У объекта buildtin_function_or_method нет атрибута» вроде «»

Я пробовал другие способы, например использование any, has или in function, но все они возвращают вышеуказанную ошибку .

Использование self.query.join(Game.genres, aliased=True).filter_by(id==1).order_by(Game.id).offset(offset).limit(limit).all() работает только при возврате игр с идентификатором жанра 1

Я не уверен, что в запросе я ошибся или модель не реализована должным образом для такой запрос.

Это мой первый раз кодирование с flask и sqlalchemy, поэтому я был бы признателен за любую форму ввода!

1 Ответ

1 голос
/ 14 января 2020

Методы filter и filter_by принимают разные типы аргументов. Пожалуйста, обратитесь к этому вопросу / ответам 'Разница между filter и filter_by в SQLAlchemy' для объяснения.

Поскольку ваше поле Genre id является целым числом, я бы использовал in_ оператор для фильтрации по этому полю. например,

@classmethod
def return_bygenres(self, offset, limit, genres2):
    # genres2 must be a list of integers, coerce if neccassary, e.g.
    # .filter(Genre.id.in_([int(s) for s in genres2]))

    _query = self.query\
            .join(Game.genres, aliased=True)\
            .filter(Genre.id.in_(genres2))\
            .order_by(Game.id)\
            .offset(offset)\
            .limit(limit)\
    return {'games': [g.to_json for g in _query.all()]}
...