def test_build_filter(self): path = 'a__b__c' value = 1337 query_path = QueryPath(path) condition = query_path.build_filter(value) assert isinstance(condition, Q) assert len(condition.children) == 1 assert condition.children[0] == (path, value)
class TestQueryablePropertyReference(object): @pytest.mark.parametrize('relation_path, expected_result', [ (QueryPath(), QueryPath('dummy')), (QueryPath('application'), QueryPath('application__dummy')), ]) def test_full_path(self, relation_path, expected_result): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'dummy') ref = QueryablePropertyReference(prop, prop.model, relation_path) assert ref.full_path == expected_result def test_descriptor(self): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'dummy') ref = QueryablePropertyReference(prop, prop.model, QueryPath()) assert ref.descriptor == ApplicationWithClassBasedProperties.dummy @pytest.mark.parametrize('lookups, relation_path, expected_filter', [ (QueryPath(), QueryPath(), 'version_count__exact'), (QueryPath('lt'), QueryPath(), 'version_count__lt'), (QueryPath('date__year'), QueryPath('application'), 'application__version_count__date__year'), ]) def test_get_filter(self, lookups, relation_path, expected_filter): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'version_count') ref = QueryablePropertyReference(prop, prop.model, relation_path) q = ref.get_filter(lookups, 1337) assert isinstance(q, Q) assert q.children == [(expected_filter, 1337)] def test_get_filter_exception(self): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'dummy') ref = QueryablePropertyReference(prop, prop.model, QueryPath()) with pytest.raises(QueryablePropertyError): ref.get_filter(QueryPath(), None) def test_get_annotation(self): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'version_count') ref = QueryablePropertyReference(prop, prop.model, QueryPath()) assert isinstance(ref.get_annotation(), Count) def test_get_annotation_exception(self): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'dummy') ref = QueryablePropertyReference(prop, prop.model, QueryPath()) with pytest.raises(QueryablePropertyError): ref.get_annotation()
def test_base_condition(self, path): prop = RelatedExistenceCheckProperty(path) condition = prop._base_condition assert isinstance(condition, Q) assert len(condition.children) == 1 assert condition.children[0] == ( six.text_type(QueryPath(path) + 'isnull'), False)
def refs(): model = ApplicationWithClassBasedProperties return { prop_name: QueryablePropertyReference(get_queryable_property(model, prop_name), model, QueryPath()) for prop_name in ('major_sum', 'version_count') }
def test_constructor(self, path, expected_result): query_path = QueryPath(path) assert query_path == expected_result
class TestQueryPath(object): @pytest.mark.parametrize('path, expected_result', [ ([], QueryPath()), (('a', 'b'), QueryPath(('a', 'b'))), ('a__b', QueryPath(('a', 'b'))), ]) def test_constructor(self, path, expected_result): query_path = QueryPath(path) assert query_path == expected_result @pytest.mark.parametrize('query_path, addition, expected_result', [ (QueryPath(), QueryPath(['a']), QueryPath(('a', ))), (QueryPath('a'), ('b', 'c'), QueryPath(('a', 'b', 'c'))), (QueryPath('a'), ['b'], QueryPath(('a', 'b'))), (QueryPath(('a', 'b')), 'c__d', QueryPath(('a', 'b', 'c', 'd'))), ]) def test_add(self, query_path, addition, expected_result): result = query_path + addition assert isinstance(result, QueryPath) assert result == expected_result @pytest.mark.parametrize('query_path, item, expected_result', [ (QueryPath('a__b'), 0, 'a'), (QueryPath('a__b'), slice(0, 1), QueryPath('a')), (QueryPath('a__b'), slice(5, 10), QueryPath()), ]) def test_get_item(self, query_path, item, expected_result): result = query_path[item] assert isinstance(result, expected_result.__class__) assert result == expected_result def test_string_representation(self): query_path = QueryPath(('a', 'b', 'c')) assert six.text_type(query_path) == 'a__b__c' def test_representation(self): query_path = QueryPath(('a', 'b')) assert repr(query_path) == '<QueryPath: a__b>' def test_build_filter(self): path = 'a__b__c' value = 1337 query_path = QueryPath(path) condition = query_path.build_filter(value) assert isinstance(condition, Q) assert len(condition.children) == 1 assert condition.children[0] == (path, value)
def test_unsuccessful(self, model, query_path): assert resolve_queryable_property(model, query_path) == (None, QueryPath())
class TestResolveQueryableProperty(object): @pytest.mark.parametrize( 'model, query_path, expected_property, expected_lookups', [ # No relation involved (VersionWithClassBasedProperties, QueryPath('version'), get_queryable_property(VersionWithClassBasedProperties, 'version'), QueryPath()), (VersionWithDecoratorBasedProperties, QueryPath('version'), get_queryable_property(VersionWithDecoratorBasedProperties, 'version'), QueryPath()), (VersionWithClassBasedProperties, QueryPath('version__lower__exact'), get_queryable_property(VersionWithClassBasedProperties, 'version'), QueryPath('lower__exact')), (VersionWithDecoratorBasedProperties, QueryPath('version__lower__exact'), get_queryable_property(VersionWithDecoratorBasedProperties, 'version'), QueryPath('lower__exact')), # FK forward relation (VersionWithClassBasedProperties, QueryPath('application__version_count'), get_queryable_property(ApplicationWithClassBasedProperties, 'version_count'), QueryPath()), (VersionWithDecoratorBasedProperties, QueryPath('application__version_count'), get_queryable_property(ApplicationWithDecoratorBasedProperties, 'version_count'), QueryPath()), (VersionWithClassBasedProperties, QueryPath('application__major_sum__gt'), get_queryable_property(ApplicationWithClassBasedProperties, 'major_sum'), QueryPath('gt')), (VersionWithDecoratorBasedProperties, QueryPath('application__major_sum__gt'), get_queryable_property(ApplicationWithDecoratorBasedProperties, 'major_sum'), QueryPath('gt')), # FK reverse relation (ApplicationWithClassBasedProperties, QueryPath('versions__major_minor'), get_queryable_property(VersionWithClassBasedProperties, 'major_minor'), QueryPath()), (ApplicationWithDecoratorBasedProperties, QueryPath('versions__major_minor'), get_queryable_property(VersionWithDecoratorBasedProperties, 'major_minor'), QueryPath()), (ApplicationWithClassBasedProperties, QueryPath('versions__version__lower__contains'), get_queryable_property(VersionWithClassBasedProperties, 'version'), QueryPath('lower__contains')), (ApplicationWithDecoratorBasedProperties, QueryPath('versions__version__lower__contains'), get_queryable_property(VersionWithDecoratorBasedProperties, 'version'), QueryPath('lower__contains')), # M2M forward relation (ApplicationWithClassBasedProperties, QueryPath('categories__circular'), get_queryable_property(CategoryWithClassBasedProperties, 'circular'), QueryPath()), (ApplicationWithDecoratorBasedProperties, QueryPath('categories__circular'), get_queryable_property(CategoryWithDecoratorBasedProperties, 'circular'), QueryPath()), (ApplicationWithClassBasedProperties, QueryPath('categories__circular__exact'), get_queryable_property(CategoryWithClassBasedProperties, 'circular'), QueryPath('exact')), (ApplicationWithDecoratorBasedProperties, QueryPath('categories__circular__exact'), get_queryable_property(CategoryWithDecoratorBasedProperties, 'circular'), QueryPath('exact')), # M2M reverse relation (CategoryWithClassBasedProperties, QueryPath('applications__major_sum'), get_queryable_property(ApplicationWithClassBasedProperties, 'major_sum'), QueryPath()), (CategoryWithDecoratorBasedProperties, QueryPath('applications__major_sum'), get_queryable_property(ApplicationWithDecoratorBasedProperties, 'major_sum'), QueryPath()), (CategoryWithClassBasedProperties, QueryPath('applications__version_count__lt'), get_queryable_property(ApplicationWithClassBasedProperties, 'version_count'), QueryPath('lt')), (CategoryWithDecoratorBasedProperties, QueryPath('applications__version_count__lt'), get_queryable_property(ApplicationWithDecoratorBasedProperties, 'version_count'), QueryPath('lt')), # Multiple relations (CategoryWithClassBasedProperties, QueryPath( 'applications__versions__application__categories__circular'), get_queryable_property(CategoryWithClassBasedProperties, 'circular'), QueryPath()), (CategoryWithDecoratorBasedProperties, QueryPath( 'applications__versions__application__categories__circular'), get_queryable_property(CategoryWithDecoratorBasedProperties, 'circular'), QueryPath()), (VersionWithClassBasedProperties, QueryPath('application__categories__circular__in'), get_queryable_property(CategoryWithClassBasedProperties, 'circular'), QueryPath('in')), (VersionWithDecoratorBasedProperties, QueryPath('application__categories__circular__in'), get_queryable_property(CategoryWithDecoratorBasedProperties, 'circular'), QueryPath('in')), ]) def test_successful(self, model, query_path, expected_property, expected_lookups): expected_ref = QueryablePropertyReference( expected_property, expected_property.model, query_path[:-len(expected_lookups) - 1]) assert resolve_queryable_property(model, query_path) == (expected_ref, expected_lookups) @pytest.mark.parametrize( 'model, query_path', [ # No relation involved (VersionWithClassBasedProperties, QueryPath('non_existent')), (VersionWithDecoratorBasedProperties, QueryPath('non_existent')), (VersionWithClassBasedProperties, QueryPath('major')), (VersionWithDecoratorBasedProperties, QueryPath('major')), # FK forward relation (VersionWithClassBasedProperties, QueryPath('application__non_existent__exact')), (VersionWithDecoratorBasedProperties, QueryPath('application__non_existent__exact')), (VersionWithClassBasedProperties, QueryPath('application__name')), (VersionWithDecoratorBasedProperties, QueryPath('application__name')), # FK reverse relation (ApplicationWithClassBasedProperties, QueryPath('versions__non_existent')), (ApplicationWithDecoratorBasedProperties, QueryPath('versions__non_existent')), (ApplicationWithClassBasedProperties, QueryPath('versions__minor__gt')), (ApplicationWithDecoratorBasedProperties, QueryPath('versions__minor__gt')), # M2M forward relation (ApplicationWithClassBasedProperties, QueryPath('categories__non_existent')), (ApplicationWithDecoratorBasedProperties, QueryPath('categories__non_existent')), (ApplicationWithClassBasedProperties, QueryPath('categories__name')), (ApplicationWithDecoratorBasedProperties, QueryPath('categories__name')), # M2M reverse relation (CategoryWithClassBasedProperties, QueryPath('applications__non_existent')), (CategoryWithDecoratorBasedProperties, QueryPath('applications__non_existent')), (CategoryWithClassBasedProperties, QueryPath('applications__name')), (CategoryWithDecoratorBasedProperties, QueryPath('applications__name')), # Non existent relation (VersionWithClassBasedProperties, QueryPath('non_existent_relation__non_existent__in')), (VersionWithDecoratorBasedProperties, QueryPath('non_existent_relation__non_existent__in')), ]) def test_unsuccessful(self, model, query_path): assert resolve_queryable_property(model, query_path) == (None, QueryPath())
def test_get_annotation_exception(self): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'dummy') ref = QueryablePropertyReference(prop, prop.model, QueryPath()) with pytest.raises(QueryablePropertyError): ref.get_annotation()
def test_get_annotation(self): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'version_count') ref = QueryablePropertyReference(prop, prop.model, QueryPath()) assert isinstance(ref.get_annotation(), Count)
def test_descriptor(self): prop = get_queryable_property(ApplicationWithClassBasedProperties, 'dummy') ref = QueryablePropertyReference(prop, prop.model, QueryPath()) assert ref.descriptor == ApplicationWithClassBasedProperties.dummy
class TestModelAttributeGetter(object): @pytest.mark.parametrize('path, expected_query_path', [ ('attr', QueryPath('attr')), ('attr1.attr2', QueryPath('attr1__attr2')), ('attr1.attr2.attr3', QueryPath('attr1__attr2__attr3')), (['attr1', 'attr2'], QueryPath('attr1__attr2')), (('attr1', 'attr2', 'attr3'), QueryPath('attr1__attr2__attr3')), ]) def test_initializer(self, path, expected_query_path): getter = ModelAttributeGetter(path) assert getter.query_path == expected_query_path @pytest.mark.django_db @pytest.mark.parametrize('attribute_name, expected_value', [ ('major', 1), ('changes', None), ]) def test_get_attribute(self, versions, attribute_name, expected_value): getter = ModelAttributeGetter(()) assert getter._get_attribute(versions[0], attribute_name) == expected_value def test_get_attribute_catch_attribute_error_on_none(self): getter = ModelAttributeGetter(()) assert getter._get_attribute(None, 'non_existent') is MISSING_OBJECT @pytest.mark.django_db def test_get_attribute_bubble_attribute_error(self, versions): getter = ModelAttributeGetter(()) with pytest.raises(AttributeError): getter._get_attribute(versions[0], 'non_existent') @pytest.mark.django_db def test_get_attribute_catch_object_does_not_exist(self, applications, versions): obj = versions[0].__class__.objects.get( pk=versions[0].pk) # Refresh from DB applications[0].delete() getter = ModelAttributeGetter(()) assert getter._get_attribute(obj, 'application') is MISSING_OBJECT @pytest.mark.django_db @pytest.mark.parametrize('path, expected_value', [ ('major', 1), ('changes', None), ('application.name', 'My cool App'), ]) def test_get_value(self, versions, path, expected_value): obj = versions[0] getter = ModelAttributeGetter(path) assert getter.get_value(obj) == expected_value @pytest.mark.django_db def test_get_value_missing_object(self, applications, versions): obj = versions[0].__class__.objects.get( pk=versions[0].pk) # Refresh from DB applications[0].delete() getter = ModelAttributeGetter('application.name') assert getter.get_value(obj) is MISSING_OBJECT @pytest.mark.django_db @pytest.mark.usefixtures('versions') def test_get_values(self, categories): getter = ModelAttributeGetter('applications.versions.major') assert Counter(getter.get_values(categories[0])) == Counter({ 2: 2, 1: 6 }) @pytest.mark.django_db @pytest.mark.usefixtures('versions') def test_get_values_missing_object(self, categories): getter = ModelAttributeGetter('applications.versions.major') VersionWithClassBasedProperties.objects.all().delete() assert getter.get_values(categories[0]) == [] ApplicationWithClassBasedProperties.objects.all().delete() assert getter.get_values(categories[0]) == [] @pytest.mark.parametrize('path, lookup, value, expected_query_name', [ ('attr', 'exact', 1337, 'attr__exact'), ('attr1.attr2', 'in', ('test', 'value'), 'attr1__attr2__in'), ('attr1.attr2.attr3', 'gt', 1337, 'attr1__attr2__attr3__gt'), ]) def test_build_filter(self, path, lookup, value, expected_query_name): getter = ModelAttributeGetter(path) condition = getter.build_filter(lookup, value) assert isinstance(condition, Q) assert len(condition.children) == 1 assert condition.children[0] == (expected_query_name, value)
def test_representation(self): query_path = QueryPath(('a', 'b')) assert repr(query_path) == '<QueryPath: a__b>'
def test_string_representation(self): query_path = QueryPath(('a', 'b', 'c')) assert six.text_type(query_path) == 'a__b__c'
class TestRelatedExistenceCheckProperty(object): @pytest.mark.parametrize('path, kwargs, expected_query_path', [ ('my_field', {}, QueryPath('my_field__isnull')), ('my_relation__my_field', { 'negated': True }, QueryPath('my_relation__my_field__isnull')), ('my_field', { 'cached': True }, QueryPath('my_field__isnull')), ]) def test_initializer(self, path, kwargs, expected_query_path): prop = RelatedExistenceCheckProperty(path, **kwargs) assert prop.query_path == expected_query_path assert prop.negated is kwargs.get('negated', False) assert prop.cached is kwargs.get('cached', RelatedExistenceCheckProperty.cached) @pytest.mark.parametrize('path', ['my_field', 'my_relation__my_field']) def test_base_condition(self, path): prop = RelatedExistenceCheckProperty(path) condition = prop._base_condition assert isinstance(condition, Q) assert len(condition.children) == 1 assert condition.children[0] == ( six.text_type(QueryPath(path) + 'isnull'), False) @pytest.mark.parametrize('negated', [False, True]) def test_getter(self, monkeypatch, categories, applications, negated): monkeypatch.setattr( get_queryable_property(CategoryWithClassBasedProperties, 'has_versions'), 'negated', negated) assert categories[0].has_versions is not negated assert categories[1].has_versions is not negated applications[1].versions.all().delete() assert categories[0].has_versions is not negated assert categories[1].has_versions is negated @pytest.mark.parametrize('negated', [False, True]) def test_getter_based_on_non_relation_field(self, monkeypatch, applications, negated): monkeypatch.setattr( get_queryable_property(ApplicationWithClassBasedProperties, 'has_version_with_changelog'), 'negated', negated) assert applications[0].has_version_with_changelog is not negated assert applications[1].has_version_with_changelog is not negated applications[0].versions.filter(major=2).delete() assert applications[0].has_version_with_changelog is negated assert applications[1].has_version_with_changelog is not negated @pytest.mark.parametrize('negated', [False, True]) def test_filter(self, monkeypatch, categories, applications, negated): monkeypatch.setattr( get_queryable_property(CategoryWithClassBasedProperties, 'has_versions'), 'negated', negated) queryset = CategoryWithClassBasedProperties.objects.all() assert set(queryset.filter(has_versions=not negated)) == set( categories[:2]) assert not queryset.filter(has_versions=negated).exists() applications[1].versions.all().delete() assert queryset.get(has_versions=not negated) == categories[0] assert queryset.get(has_versions=negated) == categories[1] @pytest.mark.parametrize('negated', [False, True]) def test_filter_based_on_non_relation_field(self, monkeypatch, categories, applications, negated): monkeypatch.setattr( get_queryable_property(ApplicationWithClassBasedProperties, 'has_version_with_changelog'), 'negated', negated) app_queryset = ApplicationWithClassBasedProperties.objects.all() category_queryset = CategoryWithClassBasedProperties.objects.all() assert set(app_queryset.filter( has_version_with_changelog=not negated)) == set(applications[:2]) assert not app_queryset.filter( has_version_with_changelog=negated).exists() assert (set( category_queryset.filter( applications__has_version_with_changelog=not negated)) == set( categories[:2])) assert not category_queryset.filter( applications__has_version_with_changelog=negated).exists() applications[1].versions.filter(major=2).delete() assert app_queryset.get( has_version_with_changelog=not negated) == applications[0] assert app_queryset.get( has_version_with_changelog=negated) == applications[1] assert category_queryset.get( applications__has_version_with_changelog=not negated ) == categories[0] assert category_queryset.get( applications__has_version_with_changelog=negated) == categories[1] @pytest.mark.skipif( DJANGO_VERSION < (1, 8), reason="Expression-based annotations didn't exist before Django 1.8") def test_annotation(self, categories, applications): queryset = CategoryWithClassBasedProperties.objects.all() assert list(queryset.order_by('has_versions', 'pk')) == categories[:2] applications[1].versions.all().delete() assert list(queryset.order_by('has_versions', 'pk')) == [categories[1], categories[0]] @pytest.mark.skipif( DJANGO_VERSION < (1, 8), reason="Expression-based annotations didn't exist before Django 1.8") def test_annotation_based_on_non_relation_field(self, categories, applications): app_queryset = ApplicationWithClassBasedProperties.objects.all() category_queryset = CategoryWithClassBasedProperties.objects.all() assert list(app_queryset.order_by( '-has_version_with_changelog', '-pk')) == [applications[1], applications[0]] assert list( category_queryset.order_by( '-applications__has_version_with_changelog', '-pk')) == [categories[1], categories[0], categories[0]] applications[1].versions.filter(major=2).delete() assert list(app_queryset.order_by('-has_version_with_changelog', '-pk')) == applications[:2] assert list( category_queryset.order_by( '-applications__has_version_with_changelog', '-pk')) == [categories[0], categories[1], categories[0]]