From d862fae5bbfb52ab7aeaad5a59b3d8b27b30c8f9 Mon Sep 17 00:00:00 2001 From: Daniel Pyrathon Date: Thu, 19 Jun 2014 16:03:36 +0200 Subject: [PATCH] Refs #12663 -- Added tests for methods in db.models.options. Thanks Russell Keith-Magee and Tim Graham for reviews. --- AUTHORS | 1 + tests/model_meta/__init__.py | 0 tests/model_meta/models.py | 120 +++++++ tests/model_meta/test.py | 656 +++++++++++++++++++++++++++++++++++ 4 files changed, 777 insertions(+) create mode 100644 tests/model_meta/__init__.py create mode 100644 tests/model_meta/models.py create mode 100644 tests/model_meta/test.py diff --git a/AUTHORS b/AUTHORS index 2a4494293f..4bdd5048e9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -517,6 +517,7 @@ answer newbie questions, and generally made Django that much better: Matthias Pronk Jyrki Pulliainen Thejaswi Puthraya + Daniel Pyrathon Johann Queuniet Ram Rachum Jan Rademaker diff --git a/tests/model_meta/__init__.py b/tests/model_meta/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/model_meta/models.py b/tests/model_meta/models.py new file mode 100644 index 0000000000..ab9713dc38 --- /dev/null +++ b/tests/model_meta/models.py @@ -0,0 +1,120 @@ +from django.db import models + +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.models import ContentType + + +class Relation(models.Model): + pass + + +class AbstractPerson(models.Model): + # DATA fields + data_abstract = models.CharField(max_length=10) + fk_abstract = models.ForeignKey(Relation, related_name='fk_abstract_rel') + + # M2M fields + m2m_abstract = models.ManyToManyField(Relation, related_name='m2m_abstract_rel') + friends_abstract = models.ManyToManyField('self', related_name='friends_abstract', symmetrical=True) + following_abstract = models.ManyToManyField('self', related_name='followers_abstract', symmetrical=False) + + # VIRTUAL fields + data_not_concrete_abstract = models.ForeignObject( + Relation, + from_fields=['abstract_non_concrete_id'], + to_fields=['id'], + related_name='fo_abstract_rel', + ) + + # GFK fields + content_type_abstract = models.ForeignKey(ContentType, related_name='+') + object_id_abstract = models.PositiveIntegerField() + content_object_abstract = GenericForeignKey('content_type_abstract', 'object_id_abstract') + + # GR fields + generic_relation_abstract = GenericRelation(Relation) + + class Meta: + abstract = True + + +class BasePerson(AbstractPerson): + # DATA fields + data_base = models.CharField(max_length=10) + fk_base = models.ForeignKey(Relation, related_name='fk_base_rel') + + # M2M fields + m2m_base = models.ManyToManyField(Relation, related_name='m2m_base_rel') + friends_base = models.ManyToManyField('self', related_name='friends_base', symmetrical=True) + following_base = models.ManyToManyField('self', related_name='followers_base', symmetrical=False) + + # VIRTUAL fields + data_not_concrete_base = models.ForeignObject( + Relation, + from_fields=['base_non_concrete_id'], + to_fields=['id'], + related_name='fo_base_rel', + ) + + # GFK fields + content_type_base = models.ForeignKey(ContentType, related_name='+') + object_id_base = models.PositiveIntegerField() + content_object_base = GenericForeignKey('content_type_base', 'object_id_base') + + # GR fields + generic_relation_base = GenericRelation(Relation) + + +class Person(BasePerson): + # DATA fields + data_inherited = models.CharField(max_length=10) + fk_inherited = models.ForeignKey(Relation, related_name='fk_concrete_rel') + + # M2M Fields + m2m_inherited = models.ManyToManyField(Relation, related_name='m2m_concrete_rel') + friends_inherited = models.ManyToManyField('self', related_name='friends_concrete', symmetrical=True) + following_inherited = models.ManyToManyField('self', related_name='followers_concrete', symmetrical=False) + + # VIRTUAL fields + data_not_concrete_inherited = models.ForeignObject( + Relation, + from_fields=['model_non_concrete_id'], + to_fields=['id'], + related_name='fo_concrete_rel', + ) + + # GFK fields + content_type_concrete = models.ForeignKey(ContentType, related_name='+') + object_id_concrete = models.PositiveIntegerField() + content_object_concrete = GenericForeignKey('content_type_concrete', 'object_id_concrete') + + # GR fields + generic_relation_concrete = GenericRelation(Relation) + + +class ProxyPerson(Person): + class Meta: + proxy = True + + +class Relating(models.Model): + + # ForeignKey to BasePerson + baseperson = models.ForeignKey(BasePerson, related_name='relating_baseperson') + baseperson_hidden = models.ForeignKey(BasePerson, related_name='+') + + # ForeignKey to Person + person = models.ForeignKey(Person, related_name='relating_person') + person_hidden = models.ForeignKey(Person, related_name='+') + + # ForeignKey to ProxyPerson + proxyperson = models.ForeignKey(ProxyPerson, related_name='relating_proxyperson') + proxyperson_hidden = models.ForeignKey(ProxyPerson, related_name='+') + + # ManyToManyField to BasePerson + basepeople = models.ManyToManyField(BasePerson, related_name='relating_basepeople') + basepeople_hidden = models.ManyToManyField(BasePerson, related_name='+') + + # ManyToManyField to Person + people = models.ManyToManyField(Person, related_name='relating_people') + people_hidden = models.ManyToManyField(Person, related_name='+') diff --git a/tests/model_meta/test.py b/tests/model_meta/test.py new file mode 100644 index 0000000000..89a5575665 --- /dev/null +++ b/tests/model_meta/test.py @@ -0,0 +1,656 @@ +from django import test + +from django.db.models.fields import related, CharField, Field +from django.contrib.contenttypes.fields import GenericForeignKey + +from .models import ( + AbstractPerson, BasePerson, Person, Relating, Relation +) + +TEST_RESULTS = { + 'fields': { + Person: [ + 'id', + 'data_abstract', + 'fk_abstract_id', + 'data_not_concrete_abstract', + 'content_type_abstract_id', + 'object_id_abstract', + 'data_base', + 'fk_base_id', + 'data_not_concrete_base', + 'content_type_base_id', + 'object_id_base', + 'baseperson_ptr_id', + 'data_inherited', + 'fk_inherited_id', + 'data_not_concrete_inherited', + 'content_type_concrete_id', + 'object_id_concrete', + ], + BasePerson: [ + 'id', + 'data_abstract', + 'fk_abstract_id', + 'data_not_concrete_abstract', + 'content_type_abstract_id', + 'object_id_abstract', + 'data_base', + 'fk_base_id', + 'data_not_concrete_base', + 'content_type_base_id', + 'object_id_base', + ], + AbstractPerson: [ + 'data_abstract', + 'fk_abstract_id', + 'data_not_concrete_abstract', + 'content_type_abstract_id', + 'object_id_abstract', + ], + Relating: [ + 'id', + 'baseperson_id', + 'baseperson_hidden_id', + 'person_id', + 'person_hidden_id', + 'proxyperson_id', + 'proxyperson_hidden_id', + ], + }, + 'local_fields': { + Person: [ + 'baseperson_ptr_id', + 'data_inherited', + 'fk_inherited_id', + 'data_not_concrete_inherited', + 'content_type_concrete_id', + 'object_id_concrete', + ], + BasePerson: [ + 'id', + 'data_abstract', + 'fk_abstract_id', + 'data_not_concrete_abstract', + 'content_type_abstract_id', + 'object_id_abstract', + 'data_base', + 'fk_base_id', + 'data_not_concrete_base', + 'content_type_base_id', + 'object_id_base', + ], + AbstractPerson: [ + 'data_abstract', + 'fk_abstract_id', + 'data_not_concrete_abstract', + 'content_type_abstract_id', + 'object_id_abstract', + ], + Relating: [ + 'id', + 'baseperson_id', + 'baseperson_hidden_id', + 'person_id', + 'person_hidden_id', + 'proxyperson_id', + 'proxyperson_hidden_id', + ], + }, + 'local_concrete_fields': { + Person: [ + 'baseperson_ptr_id', + 'data_inherited', + 'fk_inherited_id', + 'content_type_concrete_id', + 'object_id_concrete', + ], + BasePerson: [ + 'id', + 'data_abstract', + 'fk_abstract_id', + 'content_type_abstract_id', + 'object_id_abstract', + 'data_base', + 'fk_base_id', + 'content_type_base_id', + 'object_id_base', + ], + AbstractPerson: [ + 'data_abstract', + 'fk_abstract_id', + 'content_type_abstract_id', + 'object_id_abstract', + ], + Relating: [ + 'id', + 'baseperson_id', + 'baseperson_hidden_id', + 'person_id', + 'person_hidden_id', + 'proxyperson_id', + 'proxyperson_hidden_id', + ], + }, + 'many_to_many': { + Person: [ + 'm2m_abstract', + 'friends_abstract', + 'following_abstract', + 'm2m_base', + 'friends_base', + 'following_base', + 'm2m_inherited', + 'friends_inherited', + 'following_inherited', + ], + BasePerson: [ + 'm2m_abstract', + 'friends_abstract', + 'following_abstract', + 'm2m_base', + 'friends_base', + 'following_base', + ], + AbstractPerson: [ + 'm2m_abstract', + 'friends_abstract', + 'following_abstract', + ], + Relating: [ + 'basepeople', + 'basepeople_hidden', + 'people', + 'people_hidden', + ], + }, + 'many_to_many_with_model': { + Person: [ + BasePerson, + BasePerson, + BasePerson, + BasePerson, + BasePerson, + BasePerson, + None, + None, + None, + ], + BasePerson: [ + None, + None, + None, + None, + None, + None, + ], + AbstractPerson: [ + None, + None, + None, + ], + Relating: [ + None, + None, + None, + None, + ], + }, + 'get_all_related_objects_with_model': { + Person: ( + ('relating_baseperson', BasePerson), + ('relating_person', None), + ), + BasePerson: ( + ('person', None), + ('relating_baseperson', None), + ), + Relation: ( + ('fk_abstract_rel', None), + ('fo_abstract_rel', None), + ('fk_base_rel', None), + ('fo_base_rel', None), + ('fk_concrete_rel', None), + ('fo_concrete_rel', None), + ), + }, + 'get_all_related_objects_with_model_local': { + Person: ( + ('relating_person', None), + ), + BasePerson: ( + ('person', None), + ('relating_baseperson', None) + ), + Relation: ( + ('fk_abstract_rel', None), + ('fo_abstract_rel', None), + ('fk_base_rel', None), + ('fo_base_rel', None), + ('fk_concrete_rel', None), + ('fo_concrete_rel', None), + ), + }, + 'get_all_related_objects_with_model_hidden': { + BasePerson: ( + ('model_meta:baseperson_friends_base', None), + ('model_meta:baseperson_friends_base', None), + ('model_meta:baseperson_m2m_base', None), + ('model_meta:baseperson_following_base', None), + ('model_meta:baseperson_following_base', None), + ('model_meta:baseperson_m2m_abstract', None), + ('model_meta:baseperson_friends_abstract', None), + ('model_meta:baseperson_friends_abstract', None), + ('model_meta:baseperson_following_abstract', None), + ('model_meta:baseperson_following_abstract', None), + ('model_meta:person', None), + ('model_meta:relating_basepeople', None), + ('model_meta:relating_basepeople_hidden', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ), + Person: ( + ('model_meta:baseperson_friends_base', BasePerson), + ('model_meta:baseperson_friends_base', BasePerson), + ('model_meta:baseperson_m2m_base', BasePerson), + ('model_meta:baseperson_following_base', BasePerson), + ('model_meta:baseperson_following_base', BasePerson), + ('model_meta:baseperson_m2m_abstract', BasePerson), + ('model_meta:baseperson_friends_abstract', BasePerson), + ('model_meta:baseperson_friends_abstract', BasePerson), + ('model_meta:baseperson_following_abstract', BasePerson), + ('model_meta:baseperson_following_abstract', BasePerson), + ('model_meta:relating_basepeople', BasePerson), + ('model_meta:relating_basepeople_hidden', BasePerson), + ('model_meta:relating', BasePerson), + ('model_meta:relating', BasePerson), + ('model_meta:person_m2m_inherited', None), + ('model_meta:person_friends_inherited', None), + ('model_meta:person_friends_inherited', None), + ('model_meta:person_following_inherited', None), + ('model_meta:person_following_inherited', None), + ('model_meta:relating_people', None), + ('model_meta:relating_people_hidden', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ), + Relation: ( + ('model_meta:baseperson_m2m_base', None), + ('model_meta:baseperson_m2m_abstract', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:person_m2m_inherited', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:proxyperson', None), + ('model_meta:proxyperson', None), + ('model_meta:proxyperson', None), + ), + }, + 'get_all_related_objects_with_model_hidden_local': { + BasePerson: ( + ('model_meta:baseperson_friends_base', None), + ('model_meta:baseperson_friends_base', None), + ('model_meta:baseperson_m2m_base', None), + ('model_meta:baseperson_following_base', None), + ('model_meta:baseperson_following_base', None), + ('model_meta:baseperson_m2m_abstract', None), + ('model_meta:baseperson_friends_abstract', None), + ('model_meta:baseperson_friends_abstract', None), + ('model_meta:baseperson_following_abstract', None), + ('model_meta:baseperson_following_abstract', None), + ('model_meta:person', None), + ('model_meta:relating_basepeople', None), + ('model_meta:relating_basepeople_hidden', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ), + Person: ( + ('model_meta:person_m2m_inherited', None), + ('model_meta:person_friends_inherited', None), + ('model_meta:person_friends_inherited', None), + ('model_meta:person_following_inherited', None), + ('model_meta:person_following_inherited', None), + ('model_meta:relating_people', None), + ('model_meta:relating_people_hidden', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ), + Relation: ( + ('model_meta:baseperson_m2m_base', None), + ('model_meta:baseperson_m2m_abstract', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:person_m2m_inherited', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:proxyperson', None), + ('model_meta:proxyperson', None), + ('model_meta:proxyperson', None), + ), + }, + 'get_all_related_objects_with_model_proxy': { + BasePerson: ( + ('person', None), + ('relating_baseperson', None), + ), + Person: ( + ('relating_baseperson', BasePerson), + ('relating_person', None), ('relating_proxyperson', None), + ), + Relation: ( + ('fk_abstract_rel', None), ('fo_abstract_rel', None), + ('fk_base_rel', None), ('fo_base_rel', None), + ('fk_concrete_rel', None), ('fo_concrete_rel', None), + ), + }, + 'get_all_related_objects_with_model_proxy_hidden': { + BasePerson: ( + ('model_meta:baseperson_friends_base', None), + ('model_meta:baseperson_friends_base', None), + ('model_meta:baseperson_m2m_base', None), + ('model_meta:baseperson_following_base', None), + ('model_meta:baseperson_following_base', None), + ('model_meta:baseperson_m2m_abstract', None), + ('model_meta:baseperson_friends_abstract', None), + ('model_meta:baseperson_friends_abstract', None), + ('model_meta:baseperson_following_abstract', None), + ('model_meta:baseperson_following_abstract', None), + ('model_meta:person', None), + ('model_meta:relating_basepeople', None), + ('model_meta:relating_basepeople_hidden', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ), + Person: ( + ('model_meta:baseperson_friends_base', BasePerson), + ('model_meta:baseperson_friends_base', BasePerson), + ('model_meta:baseperson_m2m_base', BasePerson), + ('model_meta:baseperson_following_base', BasePerson), + ('model_meta:baseperson_following_base', BasePerson), + ('model_meta:baseperson_m2m_abstract', BasePerson), + ('model_meta:baseperson_friends_abstract', BasePerson), + ('model_meta:baseperson_friends_abstract', BasePerson), + ('model_meta:baseperson_following_abstract', BasePerson), + ('model_meta:baseperson_following_abstract', BasePerson), + ('model_meta:relating_basepeople', BasePerson), + ('model_meta:relating_basepeople_hidden', BasePerson), + ('model_meta:relating', BasePerson), + ('model_meta:relating', BasePerson), + ('model_meta:person_m2m_inherited', None), + ('model_meta:person_friends_inherited', None), + ('model_meta:person_friends_inherited', None), + ('model_meta:person_following_inherited', None), + ('model_meta:person_following_inherited', None), + ('model_meta:relating_people', None), + ('model_meta:relating_people_hidden', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ('model_meta:relating', None), + ), + Relation: ( + ('model_meta:baseperson_m2m_base', None), + ('model_meta:baseperson_m2m_abstract', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:baseperson', None), + ('model_meta:person_m2m_inherited', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:person', None), + ('model_meta:proxyperson', None), + ('model_meta:proxyperson', None), + ('model_meta:proxyperson', None), + ), + }, + 'get_all_related_many_to_many_with_model': { + BasePerson: ( + ('friends_abstract_rel_+', None), + ('followers_abstract', None), + ('friends_base_rel_+', None), + ('followers_base', None), + ('relating_basepeople', None), + ('+', None), + ), + Person: ( + ('friends_abstract_rel_+', BasePerson), + ('followers_abstract', BasePerson), + ('friends_base_rel_+', BasePerson), + ('followers_base', BasePerson), + ('relating_basepeople', BasePerson), + ('+', BasePerson), + ('friends_inherited_rel_+', None), + ('followers_concrete', None), + ('relating_people', None), + ('+', None), + ), + Relation: ( + ('m2m_abstract_rel', None), + ('m2m_base_rel', None), + ('m2m_concrete_rel', None), + ), + }, + 'get_all_related_many_to_many_local': { + BasePerson: [ + 'friends_abstract_rel_+', + 'followers_abstract', + 'friends_base_rel_+', + 'followers_base', + 'relating_basepeople', + '+', + ], + Person: [ + 'friends_inherited_rel_+', + 'followers_concrete', + 'relating_people', + '+', + ], + Relation: [ + 'm2m_abstract_rel', + 'm2m_base_rel', + 'm2m_concrete_rel', + ], + }, + 'virtual_fields': { + AbstractPerson: [ + 'generic_relation_abstract', + 'content_object_abstract', + ], + BasePerson: [ + 'generic_relation_base', + 'content_object_base', + 'generic_relation_abstract', + 'content_object_abstract', + ], + Person: [ + 'content_object_concrete', + 'generic_relation_concrete', + 'generic_relation_base', + 'content_object_base', + 'generic_relation_abstract', + 'content_object_abstract', + ], + }, +} + + +class OptionsBaseTests(test.TestCase): + + def _map_rq_names(self, res): + return tuple([(o.field.related_query_name(), m) for o, m in res]) + + def _map_names(self, res): + return tuple([(f.name, m) for f, m in res]) + + +class DataTests(OptionsBaseTests): + + def test_fields(self): + for model, expected_result in TEST_RESULTS['fields'].items(): + fields = model._meta.fields + self.assertEqual([f.attname for f in fields], expected_result) + + def test_local_fields(self): + is_data_field = lambda f: isinstance(f, Field) and not isinstance(f, related.ManyToManyField) + + for model, expected_result in TEST_RESULTS['local_fields'].items(): + fields = model._meta.local_fields + self.assertEqual([f.attname for f in fields], expected_result) + self.assertTrue(all([f.model is model for f in fields])) + self.assertTrue(all([is_data_field(f) for f in fields])) + + def test_local_concrete_fields(self): + for model, expected_result in TEST_RESULTS['local_concrete_fields'].items(): + fields = model._meta.local_concrete_fields + self.assertEqual([f.attname for f in fields], expected_result) + self.assertTrue(all([f.column is not None for f in fields])) + + +class M2MTests(OptionsBaseTests): + + def test_many_to_many(self): + for model, expected_result in TEST_RESULTS['many_to_many'].items(): + fields = model._meta.many_to_many + self.assertEqual([f.attname for f in fields], expected_result) + self.assertTrue(all([isinstance(f.rel, related.ManyToManyRel) for f in fields])) + + def test_many_to_many_with_model(self): + for model, expected_result in TEST_RESULTS['many_to_many_with_model'].items(): + models = [model for field, model in model._meta.get_m2m_with_model()] + self.assertEqual(models, expected_result) + + +class RelatedObjectsTests(OptionsBaseTests): + def setUp(self): + self.key_name = lambda r: r[0] + + def test_related_objects(self): + result_key = 'get_all_related_objects_with_model' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_objects_with_model() + self.assertEqual(self._map_rq_names(objects), expected) + + def test_related_objects_local(self): + result_key = 'get_all_related_objects_with_model_local' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_objects_with_model(local_only=True) + self.assertEqual(self._map_rq_names(objects), expected) + + def test_related_objects_include_hidden(self): + result_key = 'get_all_related_objects_with_model_hidden' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_objects_with_model(include_hidden=True) + self.assertEqual( + sorted(self._map_names(objects), key=self.key_name), + sorted(expected, key=self.key_name) + ) + + def test_related_objects_include_hidden_local_only(self): + result_key = 'get_all_related_objects_with_model_hidden_local' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_objects_with_model( + include_hidden=True, local_only=True) + self.assertEqual( + sorted(self._map_names(objects), key=self.key_name), + sorted(expected, key=self.key_name) + ) + + def test_related_objects_proxy(self): + result_key = 'get_all_related_objects_with_model_proxy' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_objects_with_model( + include_proxy_eq=True) + self.assertEqual(self._map_rq_names(objects), expected) + + def test_related_objects_proxy_hidden(self): + result_key = 'get_all_related_objects_with_model_proxy_hidden' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_objects_with_model( + include_proxy_eq=True, include_hidden=True) + self.assertEqual( + sorted(self._map_names(objects), key=self.key_name), + sorted(expected, key=self.key_name) + ) + + +class RelatedM2MTests(OptionsBaseTests): + + def test_related_m2m_with_model(self): + result_key = 'get_all_related_many_to_many_with_model' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_m2m_objects_with_model() + self.assertEqual(self._map_rq_names(objects), expected) + + def test_related_m2m_local_only(self): + result_key = 'get_all_related_many_to_many_local' + for model, expected in TEST_RESULTS[result_key].items(): + objects = model._meta.get_all_related_many_to_many_objects(local_only=True) + self.assertEqual([o.field.related_query_name() for o in objects], expected) + + def test_related_m2m_asymmetrical(self): + m2m = Person._meta.many_to_many + self.assertTrue('following_base' in [f.attname for f in m2m]) + related_m2m = Person._meta.get_all_related_many_to_many_objects() + self.assertTrue('followers_base' in [o.field.related_query_name() for o in related_m2m]) + + def test_related_m2m_symmetrical(self): + m2m = Person._meta.many_to_many + self.assertTrue('friends_base' in [f.attname for f in m2m]) + related_m2m = Person._meta.get_all_related_many_to_many_objects() + self.assertIn('friends_inherited_rel_+', [o.field.related_query_name() for o in related_m2m]) + + +class VirtualFieldsTests(OptionsBaseTests): + + def test_virtual_fields(self): + for model, expected_names in TEST_RESULTS['virtual_fields'].items(): + objects = model._meta.virtual_fields + self.assertEqual(sorted([f.name for f in objects]), sorted(expected_names)) + + +class GetFieldByNameTests(OptionsBaseTests): + + def test_get_data_field(self): + field_info = Person._meta.get_field_by_name('data_abstract') + self.assertEqual(field_info[1:], (BasePerson, True, False)) + self.assertIsInstance(field_info[0], CharField) + + def test_get_m2m_field(self): + field_info = Person._meta.get_field_by_name('m2m_base') + self.assertEqual(field_info[1:], (BasePerson, True, True)) + self.assertIsInstance(field_info[0], related.ManyToManyField) + + def test_get_related_object(self): + field_info = Person._meta.get_field_by_name('relating_baseperson') + self.assertEqual(field_info[1:], (BasePerson, False, False)) + self.assertIsInstance(field_info[0], related.RelatedObject) + + def test_get_related_m2m(self): + field_info = Person._meta.get_field_by_name('relating_people') + self.assertEqual(field_info[1:], (None, False, True)) + self.assertIsInstance(field_info[0], related.RelatedObject) + + def test_get_virtual_field(self): + field_info = Person._meta.get_field_by_name('content_object_base') + self.assertEqual(field_info[1:], (None, True, False)) + self.assertIsInstance(field_info[0], GenericForeignKey)