mirror of
https://github.com/django/django.git
synced 2025-08-04 10:59:45 +00:00
Fixed #12663 -- Formalized the Model._meta API for retrieving fields.
Thanks to Russell Keith-Magee for mentoring this Google Summer of Code 2014 project and everyone else who helped with the patch!
This commit is contained in:
parent
749d23251b
commit
fb48eb0581
58 changed files with 2851 additions and 1195 deletions
|
@ -8,6 +8,11 @@ except ImportError:
|
|||
Image = None
|
||||
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models.fields.related import (
|
||||
ForeignObject, ForeignKey, ManyToManyField, OneToOneField,
|
||||
)
|
||||
from django.db import models
|
||||
from django.db.models.fields.files import ImageFieldFile, ImageField
|
||||
from django.utils import six
|
||||
|
@ -295,6 +300,52 @@ if Image:
|
|||
height_field='headshot_height',
|
||||
width_field='headshot_width')
|
||||
|
||||
|
||||
class AllFieldsModel(models.Model):
|
||||
big_integer = models.BigIntegerField()
|
||||
binary = models.BinaryField()
|
||||
boolean = models.BooleanField(default=False)
|
||||
char = models.CharField(max_length=10)
|
||||
csv = models.CommaSeparatedIntegerField(max_length=10)
|
||||
date = models.DateField()
|
||||
datetime = models.DateTimeField()
|
||||
decimal = models.DecimalField(decimal_places=2, max_digits=2)
|
||||
duration = models.DurationField()
|
||||
email = models.EmailField()
|
||||
file_path = models.FilePathField()
|
||||
floatf = models.FloatField()
|
||||
integer = models.IntegerField()
|
||||
ip_address = models.IPAddressField()
|
||||
generic_ip = models.GenericIPAddressField()
|
||||
null_boolean = models.NullBooleanField()
|
||||
positive_integer = models.PositiveIntegerField()
|
||||
positive_small_integer = models.PositiveSmallIntegerField()
|
||||
slug = models.SlugField()
|
||||
small_integer = models.SmallIntegerField()
|
||||
text = models.TextField()
|
||||
time = models.TimeField()
|
||||
url = models.URLField()
|
||||
uuid = models.UUIDField()
|
||||
|
||||
fo = ForeignObject(
|
||||
'self',
|
||||
from_fields=['abstract_non_concrete_id'],
|
||||
to_fields=['id'],
|
||||
related_name='reverse'
|
||||
)
|
||||
fk = ForeignKey(
|
||||
'self',
|
||||
related_name='reverse2'
|
||||
)
|
||||
m2m = ManyToManyField('self')
|
||||
oto = OneToOneField('self')
|
||||
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_type = models.ForeignKey(ContentType)
|
||||
gfk = GenericForeignKey()
|
||||
gr = GenericRelation(DataModel)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
|
220
tests/model_fields/test_field_flags.py
Normal file
220
tests/model_fields/test_field_flags.py
Normal file
|
@ -0,0 +1,220 @@
|
|||
from django import test
|
||||
|
||||
from django.contrib.contenttypes.fields import (
|
||||
GenericForeignKey, GenericRelation,
|
||||
)
|
||||
from django.db import models
|
||||
from django.db.models.fields.related import (
|
||||
ForeignObject, ForeignKey, OneToOneField, ManyToManyField,
|
||||
ManyToOneRel, ForeignObjectRel,
|
||||
)
|
||||
|
||||
from .models import AllFieldsModel
|
||||
|
||||
|
||||
NON_CONCRETE_FIELDS = (
|
||||
ForeignObject,
|
||||
GenericForeignKey,
|
||||
GenericRelation,
|
||||
)
|
||||
|
||||
NON_EDITABLE_FIELDS = (
|
||||
models.BinaryField,
|
||||
GenericForeignKey,
|
||||
GenericRelation,
|
||||
)
|
||||
|
||||
RELATION_FIELDS = (
|
||||
ForeignKey,
|
||||
ForeignObject,
|
||||
ManyToManyField,
|
||||
OneToOneField,
|
||||
GenericForeignKey,
|
||||
GenericRelation,
|
||||
)
|
||||
|
||||
ONE_TO_MANY_CLASSES = {
|
||||
ForeignObject,
|
||||
ForeignKey,
|
||||
GenericForeignKey,
|
||||
}
|
||||
|
||||
MANY_TO_ONE_CLASSES = {
|
||||
ForeignObjectRel,
|
||||
ManyToOneRel,
|
||||
GenericRelation,
|
||||
}
|
||||
|
||||
MANY_TO_MANY_CLASSES = {
|
||||
ManyToManyField,
|
||||
}
|
||||
|
||||
ONE_TO_ONE_CLASSES = {
|
||||
OneToOneField,
|
||||
}
|
||||
|
||||
FLAG_PROPERTIES = (
|
||||
'concrete',
|
||||
'editable',
|
||||
'is_relation',
|
||||
'model',
|
||||
'hidden',
|
||||
'one_to_many',
|
||||
'many_to_one',
|
||||
'many_to_many',
|
||||
'one_to_one',
|
||||
'related_model',
|
||||
)
|
||||
|
||||
FLAG_PROPERTIES_FOR_RELATIONS = (
|
||||
'one_to_many',
|
||||
'many_to_one',
|
||||
'many_to_many',
|
||||
'one_to_one',
|
||||
)
|
||||
|
||||
|
||||
class FieldFlagsTests(test.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(FieldFlagsTests, cls).setUpClass()
|
||||
cls.fields = (
|
||||
list(AllFieldsModel._meta.fields) +
|
||||
list(AllFieldsModel._meta.virtual_fields)
|
||||
)
|
||||
|
||||
cls.all_fields = (
|
||||
cls.fields +
|
||||
list(AllFieldsModel._meta.many_to_many) +
|
||||
list(AllFieldsModel._meta.virtual_fields)
|
||||
)
|
||||
|
||||
cls.fields_and_reverse_objects = (
|
||||
cls.all_fields +
|
||||
list(AllFieldsModel._meta.related_objects)
|
||||
)
|
||||
|
||||
def test_each_field_should_have_a_concrete_attribute(self):
|
||||
self.assertTrue(all(f.concrete.__class__ == bool for f in self.fields))
|
||||
|
||||
def test_each_field_should_have_an_editable_attribute(self):
|
||||
self.assertTrue(all(f.editable.__class__ == bool for f in self.all_fields))
|
||||
|
||||
def test_each_field_should_have_a_has_rel_attribute(self):
|
||||
self.assertTrue(all(f.is_relation.__class__ == bool for f in self.all_fields))
|
||||
|
||||
def test_each_object_should_have_auto_created(self):
|
||||
self.assertTrue(
|
||||
all(f.auto_created.__class__ == bool
|
||||
for f in self.fields_and_reverse_objects)
|
||||
)
|
||||
|
||||
def test_non_concrete_fields(self):
|
||||
for field in self.fields:
|
||||
if type(field) in NON_CONCRETE_FIELDS:
|
||||
self.assertFalse(field.concrete)
|
||||
else:
|
||||
self.assertTrue(field.concrete)
|
||||
|
||||
def test_non_editable_fields(self):
|
||||
for field in self.all_fields:
|
||||
if type(field) in NON_EDITABLE_FIELDS:
|
||||
self.assertFalse(field.editable)
|
||||
else:
|
||||
self.assertTrue(field.editable)
|
||||
|
||||
def test_related_fields(self):
|
||||
for field in self.all_fields:
|
||||
if type(field) in RELATION_FIELDS:
|
||||
self.assertTrue(field.is_relation)
|
||||
else:
|
||||
self.assertFalse(field.is_relation)
|
||||
|
||||
def test_field_names_should_always_be_available(self):
|
||||
for field in self.fields_and_reverse_objects:
|
||||
self.assertTrue(field.name)
|
||||
|
||||
def test_all_field_types_should_have_flags(self):
|
||||
for field in self.fields_and_reverse_objects:
|
||||
for flag in FLAG_PROPERTIES:
|
||||
self.assertTrue(hasattr(field, flag), "Field %s does not have flag %s" % (field, flag))
|
||||
if field.is_relation:
|
||||
true_cardinality_flags = sum(
|
||||
getattr(field, flag) is True
|
||||
for flag in FLAG_PROPERTIES_FOR_RELATIONS
|
||||
)
|
||||
# If the field has a relation, there should be only one of the
|
||||
# 4 cardinality flags available.
|
||||
self.assertEqual(1, true_cardinality_flags)
|
||||
|
||||
def test_cardinality_m2m(self):
|
||||
m2m_type_fields = (
|
||||
f for f in self.all_fields
|
||||
if f.is_relation and f.many_to_many
|
||||
)
|
||||
# Test classes are what we expect
|
||||
self.assertEqual(MANY_TO_MANY_CLASSES, {f.__class__ for f in m2m_type_fields})
|
||||
|
||||
# Ensure all m2m reverses are m2m
|
||||
for field in m2m_type_fields:
|
||||
reverse_field = field.rel
|
||||
self.assertTrue(reverse_field.is_relation)
|
||||
self.assertTrue(reverse_field.many_to_many)
|
||||
self.assertTrue(reverse_field.related_model)
|
||||
|
||||
def test_cardinality_o2m(self):
|
||||
o2m_type_fields = [
|
||||
f for f in self.fields_and_reverse_objects
|
||||
if f.is_relation and f.one_to_many
|
||||
]
|
||||
# Test classes are what we expect
|
||||
self.assertEqual(ONE_TO_MANY_CLASSES, {f.__class__ for f in o2m_type_fields})
|
||||
|
||||
# Ensure all o2m reverses are m2o
|
||||
for field in o2m_type_fields:
|
||||
if field.concrete:
|
||||
reverse_field = field.rel
|
||||
self.assertTrue(reverse_field.is_relation and reverse_field.many_to_one)
|
||||
|
||||
def test_cardinality_m2o(self):
|
||||
m2o_type_fields = [
|
||||
f for f in self.fields_and_reverse_objects
|
||||
if f.is_relation and f.many_to_one
|
||||
]
|
||||
# Test classes are what we expect
|
||||
self.assertEqual(MANY_TO_ONE_CLASSES, {f.__class__ for f in m2o_type_fields})
|
||||
|
||||
# Ensure all m2o reverses are o2m
|
||||
for obj in m2o_type_fields:
|
||||
if hasattr(obj, 'field'):
|
||||
reverse_field = obj.field
|
||||
self.assertTrue(reverse_field.is_relation and reverse_field.one_to_many)
|
||||
|
||||
def test_cardinality_o2o(self):
|
||||
o2o_type_fields = [
|
||||
f for f in self.all_fields
|
||||
if f.is_relation and f.one_to_one
|
||||
]
|
||||
# Test classes are what we expect
|
||||
self.assertEqual(ONE_TO_ONE_CLASSES, {f.__class__ for f in o2o_type_fields})
|
||||
|
||||
# Ensure all o2o reverses are o2o
|
||||
for obj in o2o_type_fields:
|
||||
if hasattr(obj, 'field'):
|
||||
reverse_field = obj.field
|
||||
self.assertTrue(reverse_field.is_relation and reverse_field.one_to_one)
|
||||
|
||||
def test_hidden_flag(self):
|
||||
incl_hidden = set(AllFieldsModel._meta.get_fields(include_hidden=True))
|
||||
no_hidden = set(AllFieldsModel._meta.get_fields())
|
||||
fields_that_should_be_hidden = (incl_hidden - no_hidden)
|
||||
for f in incl_hidden:
|
||||
self.assertEqual(f in fields_that_should_be_hidden, f.hidden)
|
||||
|
||||
def test_model_and_reverse_model_should_equal_on_relations(self):
|
||||
for field in AllFieldsModel._meta.get_fields():
|
||||
is_concrete_forward_field = field.concrete and field.related_model
|
||||
if is_concrete_forward_field:
|
||||
reverse_field = field.rel
|
||||
self.assertEqual(field.model, reverse_field.related_model)
|
||||
self.assertEqual(field.related_model, reverse_field.model)
|
|
@ -198,7 +198,7 @@ class ForeignKeyTests(test.TestCase):
|
|||
self.assertEqual(warnings, expected_warnings)
|
||||
|
||||
def test_related_name_converted_to_text(self):
|
||||
rel_name = Bar._meta.get_field_by_name('a')[0].rel.related_name
|
||||
rel_name = Bar._meta.get_field('a').rel.related_name
|
||||
self.assertIsInstance(rel_name, six.text_type)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue