сбой сериализации таблиц зефира фляги

У меня есть моя модель, которая представляет 4 таблицы: пользователь, тест, область, проблема. Один пользователь имеет несколько тестов. Каждый тест имеет несколько областей, и каждая область имеет несколько проблем. Я хочу реорганизовать код (который работает нормально), чтобы использовать зефир для сериализовать мои объекты SLQ-Alchemy

Первый метод с использованием зефира работает нормально. Однако у меня возникают проблемы при попытке вернуть один тест со всеми его областями и проблемами.

Итак, подробности:

Это мой файл model.py

db = SQLAlchemy()
ma = Marshmallow()


class User(db.Model):

    __tablename__ = 'user'

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

class Test(db.Model):

    __tablename__ = 'test'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, ForeignKey('user.id'))
    user = relationship(User, backref=backref('tests'))

class Area(db.Model):

    __tablename__ = 'area'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), nullable=False)
    test_id = db.Column(db.Integer, ForeignKey('test.id'))
    test = relationship(Test,
                        backref=backref('areas', cascade='all, delete-orphan')
                        )
    user_id = db.Column(db.Integer, ForeignKey('user.id'))
    user = relationship(User, backref=backref('areas'))

class Issue(db.Model):

    __tablename__ = 'issue'
    name = db.Column(db.String(80), nullable=False)
    id = db.Column(db.Integer, primary_key=True)
    area_id = db.Column(db.Integer, ForeignKey('area.id'))
    area = relationship(Area,
                        backref=backref('issues', cascade='all, delete-orphan')
                        )
    user_id = db.Column(db.Integer, ForeignKey('user.id'))
    user = relationship(User, backref=backref('issues'))


class UserSchema(ma.ModelSchema):
    class Meta:
        model = User
    # A User has a list of tests
    test = ma.Nested('TestSchema', many=True)


class TestSchema(ma.ModelSchema):
    class Meta:
        model = Test
    # Each test belongs to one user
    user = ma.Nested('UserSchema')
    # Each test has a list of areas
    area = ma.Nested('AreaSchema', many=True)


class AreaSchema(ma.ModelSchema):
    class Meta:
        model = Area
    # Each Area belongs to one test
    test = ma.Nested('TestSchema')
    # Each Area has a list of issues
    issue = ma.Nested('IssueSchema', many=True)


class IssueSchema(ma.ModelSchema):
    class Meta:
        model = Issue
    # Each issue belongs to one area
    area = ma.Nested('AreaSchema')

Итак, вот Ресурс, который отлично работает с зефиром. Это возвращает все тесты БЕЗ его областей и БЕЗ его проблем. Это работает нормально:

# This works perfectly .
# It returns all tests for the user without its Areas and without Issues
# See line 26 the use of tests = tests_schema.dump(results).data

tests_schema = TestSchema(many=True)

class Tests(Resource):
    """ This method return the list of tests for the user
    We expect a valid JWT token from the user   that was already
    validated thorugh the decorator we created: token_required"""
    @token_required
    def get(self, *args, **kwargs):
        jwt_data = kwargs['jwt_data']
        if jwt_data:
            # The JWT payload contains the "user"
            user_name = jwt_data["user"]
            logger.info("User from JWT payload data is  %s" % user_name)
            userId = modelUser.getUserIDFromName(user_name)
            user = modelUser.getUserInfo(userId)
            results = modelTest.getAllTestsForUser(userId)
            logger.info(results)
            tests = tests_schema.dump(results).data
            logger.info(tests)
            if tests:
                return jsonify(tests)
            else:
                response = make_response(json.dumps(
                                        'You do not have any test'), 204)
                response.headers['Content-Type'] = 'application/json'
                return response

Вот где у меня проблема. Я получаю пустой словарь:

результат = test_schema.dump(testWithAreasAndIssues).data

# This returns just one test with ALL its Areas and ALL ISSUES
# The value returned from the DB is correct.I'm just refactoring to use Marshmallow
# line  19 returns empty

class Test(Resource):
    """ GET DELETE AND PUT(modifies) a Test /api/test/<int:test_id>"""
    @token_required
    def get(self, test_id, *args, **kwargs):
        logger.info("GET for /api/test/test_id= %s" % test_id)
        jwt_data = kwargs['jwt_data']
        test = getTestForJWT(jwt_data, test_id)
        logger.info('test.id= %s for jwt=%s is %s' % (test_id, jwt_data, test))
        logger.info('test= %s' % test)
        logger.info('Calling getTestWithAreasAndIssues')
        testWithAreasAndIssues = modelTest.getTestWithAreasAndIssues(
                                        test.id)
        logger.info('FullTest for testid =%s is %s' % (
                                    test.id, testWithAreasAndIssues))
        result = test_schema.dump(testWithAreasAndIssues).data
        logger.info(jsonify(result))

Почему я получаю пустой словарь в
result = test_schema.dump(testWithAreasAndIssues).data
?

Вот функция в модели, которая получает тест со всей его областью и выдачей.

def getTestWithAreasAndIssues(id):
        """ This method will return a table containing a test
        with all its areas and each areas with all its issues.
        The result are unserialized object in a table with 3 columns
        So we need to later serialize them and convert them
        to a herarquick view using a python dictionary"""
        test = (db.session.query(Test, Area, Issue)
                .join(Area)
                .join(Issue)
                .options(
                    joinedload(Test.areas)
                    .joinedload(Area.issues)
                )
                .filter(Test.id == id)
                .filter(Test.id == Area.test_id)
                .filter(Area.id == Issue.area_id)
                ).all()
         return test

Это вывод этой функции:

[(<Test 4>, <Area 28>, <Issue 17>), 
 (<Test 4>, <Area 29>, <Issue 18>), 
 (<Test 4>, <Area 36>, <Issue 19>), 
 (<Test 4>, <Area 36>, <Issue 20>), 
 (<Test 4>, <Area 36>, <Issue 21>)]

Прежде чем использовать marshmallow, я создал функцию, которая брала эту таблицу SQLAlchemy и превращала ее в объект Python.


person MasterOfTheHouse    schedule 28.09.2018    source источник
comment
Можете показать код функций getAllTestsForUser() и getTestWithAreasAndIssues()? Во втором фрагменте кода, где вы получаете пустой словарь, в предыдущей строке testWithAreasAndIssues тоже содержит пустой словарь?   -  person CloC    schedule 28.09.2018
comment
Я отредактировал вопрос и добавил функцию getTestWithAreasAndIssues(id)   -  person MasterOfTheHouse    schedule 28.09.2018
comment
Кажется, что результат getTestWithAreasAndIssues() нельзя сопоставить с TestSchema, он имеет другую структуру. Подумайте о другой схеме для этого конкретного запроса.   -  person CloC    schedule 28.09.2018
comment
Я добавил вывод getTestWithAreasAndIssues() . Итак, похоже, мне нужно создать схему, которая возвращает то, что я хочу, то есть имеет ту же структуру, что и возвращаемый запрос. Я хотел бы прочитать больше об этом в Интернете, особенно о том, как создать разыскиваемую схему. Спасибо.   -  person MasterOfTheHouse    schedule 28.09.2018
comment
Спасибо, что направили меня в правильном направлении. Ваше предложение правильное. Я реализую это завтра. Следуя вашей рекомендации, я нашел эти примеры очень полезными: marshmallow.readthedocs. io/en/3.0/examples.html   -  person MasterOfTheHouse    schedule 28.09.2018


Ответы (3)


Вы должны использовать другую схему для использования getTestWithAreasAndIssues(). Начнем с TestSchema, который правильно соответствует вашей модели Test:

class TestSchema(ma.ModelSchema):
    class Meta:
        model = Test

Я бы также рекомендовал вам пересмотреть свои модели, ваша модель User не содержит отношений с Test, Area или Issue. Посмотрите здесь, чтобы правильно определить отношения с SQLAlchemy.

Затем у вас может быть схема для результатов, возвращаемых getTestWithAreasAndIssues():

class TestSchema(ma.ModelSchema):
    test = ma.Nested('TestSchema')
    user = ma.Nested('UserSchema')
    area = ma.Nested('AreaSchema')
person CloC    schedule 28.09.2018

Мне удалось создать что-то похожее на то, что я хочу, внеся некоторые изменения в модель (я добавил в модель обратную ссылку и отношение от Issue к Test). Итак, вот мои схемы сейчас

class UserSchema(ma.ModelSchema):

    class Meta:
        model = User

class TestSchema(ma.ModelSchema):

    class Meta:
        model = Test

class AreaSchema(ma.ModelSchema):

    class Meta:
        model = Area

class IssueSchema(ma.ModelSchema):

    class Meta:
        model = Issue

class Test_DetailedSchema(ma.ModelSchema):
    test = ma.Nested('self')
    areas = ma.Nested('AreaSchema', many=True, exclude=('test', 'user',))
    issues = ma.Nested('IssueSchema', many=True,
                       include=('name', 'id', 'reference_number', 'status',))

Так что теперь в моих взглядах я

from models.model import TestSchema
from models.model import IssueSchema
from models.model import AreaSchema
from models.model import Test_DetailedSchema

# Schemas
test_detailed_schema = Test_DetailedSchema()
test_schema = TestSchema(exclude=('user',))
areas_schema = AreaSchema(many=True, exclude=('test', 'user',))
issues_schema = IssueSchema(many=True)

И в маршруте я делаю что-то вроде этого:

class Test(Resource):
    """ GET DELETE AND PUT(modifies) a Test /api/test/<int:test_id>"""
    @token_required
    def get(self, test_id, *args, **kwargs):

        test_result = modelTest.getTest(test_id)
        test_details, error = test_detailed_schema.dump(test_result)
        pprint.pprint({'test_details': test_details})

Это результат, который я получил:

{'test': {'areas': [
                            {'id': 10, 'issues': [7, 8], 'name': 'Name1'},
                            {'id': 11, 'issues': [9], 'name': 'NameX'},
                            {'id': 12, 'issues': [], 'name': 'Name2'},
                            {'id': 13,'issues': [],'name': 'Name3'},
                            {'id': 14, 'issues': [], 'name': 'Name4'},
                            {'id': 15,'issues': [],'name': 'Name5'},
                            {'id': 16, 'issues': [], 'name': 'Name6'},
                            {'id': 17, 'issues': [], 'name': 'Name7'},
                            {'id': 18,'issues': [10, 11],'name': 'Name8'}],
     'issues': [{
                              'area': 10,
                              'id': 7,
                              'name': 'This is the issueX',
                              'open_date': None,
                              'reference_number': '701',
                              'status': 'N',
                              'test': 2,
                              'user': 1},
                             {'area': 10,
                              'id': 8,
                              'name': 'This is the issueY',
                              'open_date': None,
                              'reference_number': '702',
                              'status': 'N',
                              'test': 2,
                              'user': 1},
                             {'area': 11,
                              'id': 9,
                              'name': 'This is the issueZ',
                              'open_date': None,
                              'reference_number': '703',
                              'status': 'N',
                              'test': 2,
                              'user': 1},
                             {'area': 18,
                              'id': 10,
                              'name': 'This is the issueZZ',
                              'open_date': None,
                              'reference_number': '786',
                              'status': 'N',
                              'test': 2,
                              'user': 1},
                             {'area': 18,
                              'id': 11,
                              'name': 'This is the issueXXC',
                              'open_date': None,
                              'reference_number': '787',
                              'status': 'N',
                              'test': 2,
                              'user': 1}]}}

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

'id': 10, 'issues': [7, 8], 'name': 'Name1'}

и вместо этого

{'test': {'areas': [
                                { 'id': 10,
                                 'name': 'Name1' 
                                 'issues':[
                                  {'area': 10,
                                  'id': 7,
                                  'name': 'This is the issueX',
                                  'open_date': None,
                                  'reference_number': '701',
                                  'status': 'N',
                                  'test': 2,
                                  'user': 1},
                                 {'area': 10,
                                  'id': 8,
                                  'name': 'This is the issueY',
                                  'open_date': None,
                                  'reference_number': '702',
                                  'status': 'N',
                                  'test': 2,
                                  'user': 1}
                                 ] 

Почему вопросы не расширяются внутри областей?

person MasterOfTheHouse    schedule 28.09.2018

Я получил то, что хотел:

test_schema = TestSchema(exclude=('user',))
areas_schema = AreaSchema(many=True, exclude=('test', 'user',))
issues_schema = IssueSchema(many=True, exclude=('test', 'user',))

и позже:

test, error = test_schema.dump(test_result)
areas, error = areas_schema.dump(test_result.areas)
issues, error = issues_schema.dump(test_result.issues)
return jsonify({'test': test, 'areas': areas, 'issues': issues})

Если кто-то узнает, почему

test_detailed_schema = Test_DetailedSchema()

с

class Test_DetailedSchema(ma.ModelSchema):
    test = ma.Nested('TesSchema')
    areas = ma.Nested('AreaSchema', many=True, exclude=('test', 'user',))
    issues = ma.Nested('IssueSchema', many=True, exclude=('test', 'user',))

Не возвращайте тот же результат, пожалуйста, дайте ответ здесь

person MasterOfTheHouse    schedule 28.09.2018