Fixed #7977: Fixed admindocs to use docstrings instead of a static array to locate type information.

Thanks J. Clifford Dyer.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@11833 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Karen Tracey 2009-12-12 20:25:41 +00:00
parent 783884af56
commit 0986a4d2e1
9 changed files with 137 additions and 40 deletions

View File

@ -0,0 +1 @@
# Empty models.py to allow for specifying admindocs as a test label.

View File

@ -0,0 +1,36 @@
import unittest
from django.contrib.admindocs import views
import fields
from django.db.models import fields as builtin_fields
class TestFieldType(unittest.TestCase):
def setUp(self):
pass
def test_field_name(self):
self.assertRaises(AttributeError,
views.get_readable_field_data_type, "NotAField"
)
def test_builtin_fields(self):
self.assertEqual(
views.get_readable_field_data_type(builtin_fields.BooleanField()),
u'Boolean (Either True or False)'
)
def test_custom_fields(self):
self.assertEqual(
views.get_readable_field_data_type(fields.CustomField()),
u'A custom field type'
)
self.assertEqual(
views.get_readable_field_data_type(fields.DocstringLackingField()),
u'Field of type: DocstringLackingField'
)
def test_multiline_custom_field_truncation(self):
self.assertEqual(
views.get_readable_field_data_type(fields.ManyLineDocstringField()),
u'Many-line custom field'
)

View File

@ -0,0 +1,13 @@
from django.db import models
class CustomField(models.Field):
"""A custom field type"""
class ManyLineDocstringField(models.Field):
"""Many-line custom field
This docstring has many lines. Lorum ipsem etc. etc. Four score
and seven years ago, and so on and so forth."""
class DocstringLackingField(models.Field):
pass

View File

@ -326,43 +326,20 @@ def get_return_data_type(func_name):
return 'Integer' return 'Integer'
return '' return ''
# Maps Field objects to their human-readable data types, as strings.
# Column-type strings can contain format strings; they'll be interpolated
# against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
DATA_TYPE_MAPPING = {
'AutoField' : _('Integer'),
'BooleanField' : _('Boolean (Either True or False)'),
'CharField' : _('String (up to %(max_length)s)'),
'CommaSeparatedIntegerField': _('Comma-separated integers'),
'DateField' : _('Date (without time)'),
'DateTimeField' : _('Date (with time)'),
'DecimalField' : _('Decimal number'),
'EmailField' : _('E-mail address'),
'FileField' : _('File path'),
'FilePathField' : _('File path'),
'FloatField' : _('Floating point number'),
'ForeignKey' : _('Integer'),
'ImageField' : _('File path'),
'IntegerField' : _('Integer'),
'IPAddressField' : _('IP address'),
'ManyToManyField' : '',
'NullBooleanField' : _('Boolean (Either True, False or None)'),
'OneToOneField' : _('Relation to parent model'),
'PhoneNumberField' : _('Phone number'),
'PositiveIntegerField' : _('Integer'),
'PositiveSmallIntegerField' : _('Integer'),
'SlugField' : _('String (up to %(max_length)s)'),
'SmallIntegerField' : _('Integer'),
'TextField' : _('Text'),
'TimeField' : _('Time'),
'URLField' : _('URL'),
'USStateField' : _('U.S. state (two uppercase letters)'),
'XMLField' : _('XML text'),
}
def get_readable_field_data_type(field): def get_readable_field_data_type(field):
return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__ """Returns the first line of a doc string for a given field type, if it
exists. Fields' docstrings can contain format strings, which will be
interpolated against the values of Field.__dict__ before being output.
If no docstring is given, a sensible value will be auto-generated from
the field's class name."""
if field.__doc__:
doc = field.__doc__.split('\n')[0]
return _(doc) % field.__dict__
else:
return _(u'Field of type: %(field_type)s') % {
'field_type': field.__class__.__name__
}
def extract_views_from_urlpatterns(urlpatterns, base=''): def extract_views_from_urlpatterns(urlpatterns, base=''):
""" """

View File

@ -30,7 +30,7 @@ def get_srid_info(srid):
return _srid_cache[srid] return _srid_cache[srid]
class GeometryField(SpatialBackend.Field): class GeometryField(SpatialBackend.Field):
"The base GIS field -- maps to the OpenGIS Specification Geometry type." """The base GIS field -- maps to the OpenGIS Specification Geometry type."""
# The OpenGIS Geometry name. # The OpenGIS Geometry name.
geom_type = 'GEOMETRY' geom_type = 'GEOMETRY'
@ -257,22 +257,29 @@ class GeometryField(SpatialBackend.Field):
# The OpenGIS Geometry Type Fields # The OpenGIS Geometry Type Fields
class PointField(GeometryField): class PointField(GeometryField):
"""Point"""
geom_type = 'POINT' geom_type = 'POINT'
class LineStringField(GeometryField): class LineStringField(GeometryField):
"""Line string"""
geom_type = 'LINESTRING' geom_type = 'LINESTRING'
class PolygonField(GeometryField): class PolygonField(GeometryField):
"""Polygon"""
geom_type = 'POLYGON' geom_type = 'POLYGON'
class MultiPointField(GeometryField): class MultiPointField(GeometryField):
"""Multi-point"""
geom_type = 'MULTIPOINT' geom_type = 'MULTIPOINT'
class MultiLineStringField(GeometryField): class MultiLineStringField(GeometryField):
"""Multi-line string"""
geom_type = 'MULTILINESTRING' geom_type = 'MULTILINESTRING'
class MultiPolygonField(GeometryField): class MultiPolygonField(GeometryField):
"""Multi polygon"""
geom_type = 'MULTIPOLYGON' geom_type = 'MULTIPOLYGON'
class GeometryCollectionField(GeometryField): class GeometryCollectionField(GeometryField):
"""Geometry collection"""
geom_type = 'GEOMETRYCOLLECTION' geom_type = 'GEOMETRYCOLLECTION'

View File

@ -2,6 +2,7 @@ from django.conf import settings
from django.db.models.fields import Field from django.db.models.fields import Field
class USStateField(Field): class USStateField(Field):
"""U.S. state (two uppercase letters)"""
def get_internal_type(self): def get_internal_type(self):
return "USStateField" return "USStateField"
@ -18,6 +19,7 @@ class USStateField(Field):
return super(USStateField, self).formfield(**defaults) return super(USStateField, self).formfield(**defaults)
class PhoneNumberField(Field): class PhoneNumberField(Field):
"""Phone number"""
def get_internal_type(self): def get_internal_type(self):
return "PhoneNumberField" return "PhoneNumberField"

View File

@ -49,6 +49,8 @@ class FieldDoesNotExist(Exception):
# getattr(obj, opts.pk.attname) # getattr(obj, opts.pk.attname)
class Field(object): class Field(object):
"""Base class for all field types"""
# Designates whether empty strings fundamentally are allowed at the # Designates whether empty strings fundamentally are allowed at the
# database level. # database level.
empty_strings_allowed = True empty_strings_allowed = True
@ -340,7 +342,10 @@ class Field(object):
return getattr(obj, self.attname) return getattr(obj, self.attname)
class AutoField(Field): class AutoField(Field):
"""Integer"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__ assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
kwargs['blank'] = True kwargs['blank'] = True
@ -370,7 +375,10 @@ class AutoField(Field):
return None return None
class BooleanField(Field): class BooleanField(Field):
"""Boolean (Either True or False)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['blank'] = True kwargs['blank'] = True
if 'default' not in kwargs and not kwargs.get('null'): if 'default' not in kwargs and not kwargs.get('null'):
@ -413,6 +421,8 @@ class BooleanField(Field):
return super(BooleanField, self).formfield(**defaults) return super(BooleanField, self).formfield(**defaults)
class CharField(Field): class CharField(Field):
"""String (up to %(max_length)s)"""
def get_internal_type(self): def get_internal_type(self):
return "CharField" return "CharField"
@ -434,6 +444,8 @@ class CharField(Field):
# TODO: Maybe move this into contrib, because it's specialized. # TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField): class CommaSeparatedIntegerField(CharField):
"""Comma-separated integers"""
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = { defaults = {
'form_class': forms.RegexField, 'form_class': forms.RegexField,
@ -449,7 +461,10 @@ class CommaSeparatedIntegerField(CharField):
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$') ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
class DateField(Field): class DateField(Field):
"""Date (without time)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add self.auto_now, self.auto_now_add = auto_now, auto_now_add
#HACKs : auto_now_add/auto_now should be done as a default or a pre_save. #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@ -524,6 +539,8 @@ class DateField(Field):
return super(DateField, self).formfield(**defaults) return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField): class DateTimeField(DateField):
"""Date (with time)"""
def get_internal_type(self): def get_internal_type(self):
return "DateTimeField" return "DateTimeField"
@ -583,7 +600,10 @@ class DateTimeField(DateField):
return super(DateTimeField, self).formfield(**defaults) return super(DateTimeField, self).formfield(**defaults)
class DecimalField(Field): class DecimalField(Field):
"""Decimal number"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
self.max_digits, self.decimal_places = max_digits, decimal_places self.max_digits, self.decimal_places = max_digits, decimal_places
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
@ -637,6 +657,8 @@ class DecimalField(Field):
return super(DecimalField, self).formfield(**defaults) return super(DecimalField, self).formfield(**defaults)
class EmailField(CharField): class EmailField(CharField):
"""E-mail address"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75) kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs) CharField.__init__(self, *args, **kwargs)
@ -647,6 +669,8 @@ class EmailField(CharField):
return super(EmailField, self).formfield(**defaults) return super(EmailField, self).formfield(**defaults)
class FilePathField(Field): class FilePathField(Field):
"""File path"""
def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive self.path, self.match, self.recursive = path, match, recursive
kwargs['max_length'] = kwargs.get('max_length', 100) kwargs['max_length'] = kwargs.get('max_length', 100)
@ -666,6 +690,8 @@ class FilePathField(Field):
return "FilePathField" return "FilePathField"
class FloatField(Field): class FloatField(Field):
"""Floating point number"""
empty_strings_allowed = False empty_strings_allowed = False
def get_db_prep_value(self, value): def get_db_prep_value(self, value):
@ -691,7 +717,10 @@ class FloatField(Field):
return super(FloatField, self).formfield(**defaults) return super(FloatField, self).formfield(**defaults)
class IntegerField(Field): class IntegerField(Field):
"""Integer"""
empty_strings_allowed = False empty_strings_allowed = False
def get_db_prep_value(self, value): def get_db_prep_value(self, value):
if value is None: if value is None:
return None return None
@ -715,7 +744,10 @@ class IntegerField(Field):
return super(IntegerField, self).formfield(**defaults) return super(IntegerField, self).formfield(**defaults)
class IPAddressField(Field): class IPAddressField(Field):
"""IP address"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 15 kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
@ -729,7 +761,10 @@ class IPAddressField(Field):
return super(IPAddressField, self).formfield(**defaults) return super(IPAddressField, self).formfield(**defaults)
class NullBooleanField(Field): class NullBooleanField(Field):
"""Boolean (Either True, False or None)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['null'] = True kwargs['null'] = True
Field.__init__(self, *args, **kwargs) Field.__init__(self, *args, **kwargs)
@ -769,6 +804,8 @@ class NullBooleanField(Field):
return super(NullBooleanField, self).formfield(**defaults) return super(NullBooleanField, self).formfield(**defaults)
class PositiveIntegerField(IntegerField): class PositiveIntegerField(IntegerField):
"""Integer"""
def get_internal_type(self): def get_internal_type(self):
return "PositiveIntegerField" return "PositiveIntegerField"
@ -778,6 +815,8 @@ class PositiveIntegerField(IntegerField):
return super(PositiveIntegerField, self).formfield(**defaults) return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField): class PositiveSmallIntegerField(IntegerField):
"""Integer"""
def get_internal_type(self): def get_internal_type(self):
return "PositiveSmallIntegerField" return "PositiveSmallIntegerField"
@ -787,6 +826,8 @@ class PositiveSmallIntegerField(IntegerField):
return super(PositiveSmallIntegerField, self).formfield(**defaults) return super(PositiveSmallIntegerField, self).formfield(**defaults)
class SlugField(CharField): class SlugField(CharField):
"""String (up to %(max_length)s)"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 50) kwargs['max_length'] = kwargs.get('max_length', 50)
# Set db_index=True unless it's been set manually. # Set db_index=True unless it's been set manually.
@ -803,10 +844,14 @@ class SlugField(CharField):
return super(SlugField, self).formfield(**defaults) return super(SlugField, self).formfield(**defaults)
class SmallIntegerField(IntegerField): class SmallIntegerField(IntegerField):
"""Integer"""
def get_internal_type(self): def get_internal_type(self):
return "SmallIntegerField" return "SmallIntegerField"
class TextField(Field): class TextField(Field):
"""Text"""
def get_internal_type(self): def get_internal_type(self):
return "TextField" return "TextField"
@ -816,7 +861,10 @@ class TextField(Field):
return super(TextField, self).formfield(**defaults) return super(TextField, self).formfield(**defaults)
class TimeField(Field): class TimeField(Field):
"""Time"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
self.auto_now, self.auto_now_add = auto_now, auto_now_add self.auto_now, self.auto_now_add = auto_now, auto_now_add
if auto_now or auto_now_add: if auto_now or auto_now_add:
@ -888,6 +936,8 @@ class TimeField(Field):
return super(TimeField, self).formfield(**defaults) return super(TimeField, self).formfield(**defaults)
class URLField(CharField): class URLField(CharField):
"""URL"""
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 200) kwargs['max_length'] = kwargs.get('max_length', 200)
self.verify_exists = verify_exists self.verify_exists = verify_exists
@ -899,6 +949,8 @@ class URLField(CharField):
return super(URLField, self).formfield(**defaults) return super(URLField, self).formfield(**defaults)
class XMLField(TextField): class XMLField(TextField):
"""XML text"""
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
self.schema_path = schema_path self.schema_path = schema_path
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)

View File

@ -209,6 +209,8 @@ class FileDescriptor(object):
instance.__dict__[self.field.name] = value instance.__dict__[self.field.name] = value
class FileField(Field): class FileField(Field):
"""File path"""
# The class to wrap instance attributes in. Accessing the file object off # The class to wrap instance attributes in. Accessing the file object off
# the instance will always return an instance of attr_class. # the instance will always return an instance of attr_class.
attr_class = FieldFile attr_class = FieldFile
@ -323,6 +325,8 @@ class ImageFieldFile(ImageFile, FieldFile):
super(ImageFieldFile, self).delete(save) super(ImageFieldFile, self).delete(save)
class ImageField(FileField): class ImageField(FileField):
"""File path"""
attr_class = ImageFieldFile attr_class = ImageFieldFile
descriptor_class = ImageFileDescriptor descriptor_class = ImageFileDescriptor

View File

@ -691,6 +691,8 @@ class ManyToManyRel(object):
return self.to._meta.pk return self.to._meta.pk
class ForeignKey(RelatedField, Field): class ForeignKey(RelatedField, Field):
"""Foreign Key (type determined by related field)"""
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try: try:
@ -788,12 +790,13 @@ class ForeignKey(RelatedField, Field):
return rel_field.db_type() return rel_field.db_type()
class OneToOneField(ForeignKey): class OneToOneField(ForeignKey):
""" """One-to-one relationship
A OneToOneField is essentially the same as a ForeignKey, with the exception A OneToOneField is essentially the same as a ForeignKey, with the exception
that always carries a "unique" constraint with it and the reverse relation that always carries a "unique" constraint with it and the reverse relation
always returns the object pointed to (since there will only ever be one), always returns the object pointed to (since there will only ever be one),
rather than returning a list. rather than returning a list."""
"""
def __init__(self, to, to_field=None, **kwargs): def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True kwargs['unique'] = True
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs) super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@ -847,6 +850,8 @@ def create_many_to_many_intermediary_model(field, klass):
}) })
class ManyToManyField(RelatedField, Field): class ManyToManyField(RelatedField, Field):
"""Many-to-many relationship"""
def __init__(self, to, **kwargs): def __init__(self, to, **kwargs):
try: try:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)