Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[1.1.X] Fixed #7977: Fixed admindocs to use docstrings instead of a s…

…tatic array to locate type information. Thanks J. Clifford Dyer.

r11833 from trunk.


git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@11834 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit fa591297533b4949a2414fbb27a9ff4b48ea482c 1 parent 7f1c309
@kmtracey kmtracey authored
View
1  django/contrib/admindocs/models.py
@@ -0,0 +1 @@
+# Empty models.py to allow for specifying admindocs as a test label.
View
36 django/contrib/admindocs/tests/__init__.py
@@ -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
13 django/contrib/admindocs/tests/fields.py
@@ -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
49 django/contrib/admindocs/views.py
@@ -326,43 +326,20 @@ def get_return_data_type(func_name):
return 'Integer'
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):
- 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=''):
"""
View
9 django/contrib/gis/db/models/fields/__init__.py
@@ -30,7 +30,7 @@ def get_srid_info(srid):
return _srid_cache[srid]
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.
geom_type = 'GEOMETRY'
@@ -257,22 +257,29 @@ def get_db_prep_save(self, value):
# The OpenGIS Geometry Type Fields
class PointField(GeometryField):
+ """Point"""
geom_type = 'POINT'
class LineStringField(GeometryField):
+ """Line string"""
geom_type = 'LINESTRING'
class PolygonField(GeometryField):
+ """Polygon"""
geom_type = 'POLYGON'
class MultiPointField(GeometryField):
+ """Multi-point"""
geom_type = 'MULTIPOINT'
class MultiLineStringField(GeometryField):
+ """Multi-line string"""
geom_type = 'MULTILINESTRING'
class MultiPolygonField(GeometryField):
+ """Multi polygon"""
geom_type = 'MULTIPOLYGON'
class GeometryCollectionField(GeometryField):
+ """Geometry collection"""
geom_type = 'GEOMETRYCOLLECTION'
View
2  django/contrib/localflavor/us/models.py
@@ -2,6 +2,7 @@
from django.db.models.fields import Field
class USStateField(Field):
+ """U.S. state (two uppercase letters)"""
def get_internal_type(self):
return "USStateField"
@@ -18,6 +19,7 @@ def formfield(self, **kwargs):
return super(USStateField, self).formfield(**defaults)
class PhoneNumberField(Field):
+ """Phone number"""
def get_internal_type(self):
return "PhoneNumberField"
View
52 django/db/models/fields/__init__.py
@@ -49,6 +49,8 @@ class FieldDoesNotExist(Exception):
# getattr(obj, opts.pk.attname)
class Field(object):
+ """Base class for all field types"""
+
# Designates whether empty strings fundamentally are allowed at the
# database level.
empty_strings_allowed = True
@@ -340,7 +342,10 @@ def value_from_object(self, obj):
return getattr(obj, self.attname)
class AutoField(Field):
+ """Integer"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
kwargs['blank'] = True
@@ -370,7 +375,10 @@ def formfield(self, **kwargs):
return None
class BooleanField(Field):
+ """Boolean (Either True or False)"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
kwargs['blank'] = True
if 'default' not in kwargs and not kwargs.get('null'):
@@ -413,6 +421,8 @@ def formfield(self, **kwargs):
return super(BooleanField, self).formfield(**defaults)
class CharField(Field):
+ """String (up to %(max_length)s)"""
+
def get_internal_type(self):
return "CharField"
@@ -434,6 +444,8 @@ def formfield(self, **kwargs):
# TODO: Maybe move this into contrib, because it's specialized.
class CommaSeparatedIntegerField(CharField):
+ """Comma-separated integers"""
+
def formfield(self, **kwargs):
defaults = {
'form_class': forms.RegexField,
@@ -449,7 +461,10 @@ def formfield(self, **kwargs):
ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
class DateField(Field):
+ """Date (without time)"""
+
empty_strings_allowed = False
+
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
#HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@@ -524,6 +539,8 @@ def formfield(self, **kwargs):
return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField):
+ """Date (with time)"""
+
def get_internal_type(self):
return "DateTimeField"
@@ -583,7 +600,10 @@ def formfield(self, **kwargs):
return super(DateTimeField, self).formfield(**defaults)
class DecimalField(Field):
+ """Decimal number"""
+
empty_strings_allowed = False
+
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
Field.__init__(self, verbose_name, name, **kwargs)
@@ -637,6 +657,8 @@ def formfield(self, **kwargs):
return super(DecimalField, self).formfield(**defaults)
class EmailField(CharField):
+ """E-mail address"""
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs)
@@ -647,6 +669,8 @@ def formfield(self, **kwargs):
return super(EmailField, self).formfield(**defaults)
class FilePathField(Field):
+ """File path"""
+
def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive
kwargs['max_length'] = kwargs.get('max_length', 100)
@@ -666,6 +690,8 @@ def get_internal_type(self):
return "FilePathField"
class FloatField(Field):
+ """Floating point number"""
+
empty_strings_allowed = False
def get_db_prep_value(self, value):
@@ -691,7 +717,10 @@ def formfield(self, **kwargs):
return super(FloatField, self).formfield(**defaults)
class IntegerField(Field):
+ """Integer"""
+
empty_strings_allowed = False
+
def get_db_prep_value(self, value):
if value is None:
return None
@@ -715,7 +744,10 @@ def formfield(self, **kwargs):
return super(IntegerField, self).formfield(**defaults)
class IPAddressField(Field):
+ """IP address"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs)
@@ -729,7 +761,10 @@ def formfield(self, **kwargs):
return super(IPAddressField, self).formfield(**defaults)
class NullBooleanField(Field):
+ """Boolean (Either True, False or None)"""
+
empty_strings_allowed = False
+
def __init__(self, *args, **kwargs):
kwargs['null'] = True
Field.__init__(self, *args, **kwargs)
@@ -769,6 +804,8 @@ def formfield(self, **kwargs):
return super(NullBooleanField, self).formfield(**defaults)
class PositiveIntegerField(IntegerField):
+ """Integer"""
+
def get_internal_type(self):
return "PositiveIntegerField"
@@ -778,6 +815,8 @@ def formfield(self, **kwargs):
return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField):
+ """Integer"""
+
def get_internal_type(self):
return "PositiveSmallIntegerField"
@@ -787,6 +826,8 @@ def formfield(self, **kwargs):
return super(PositiveSmallIntegerField, self).formfield(**defaults)
class SlugField(CharField):
+ """String (up to %(max_length)s)"""
+
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 50)
# Set db_index=True unless it's been set manually.
@@ -803,10 +844,14 @@ def formfield(self, **kwargs):
return super(SlugField, self).formfield(**defaults)
class SmallIntegerField(IntegerField):
+ """Integer"""
+
def get_internal_type(self):
return "SmallIntegerField"
class TextField(Field):
+ """Text"""
+
def get_internal_type(self):
return "TextField"
@@ -816,7 +861,10 @@ def formfield(self, **kwargs):
return super(TextField, self).formfield(**defaults)
class TimeField(Field):
+ """Time"""
+
empty_strings_allowed = False
+
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
if auto_now or auto_now_add:
@@ -888,6 +936,8 @@ def formfield(self, **kwargs):
return super(TimeField, self).formfield(**defaults)
class URLField(CharField):
+ """URL"""
+
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 200)
self.verify_exists = verify_exists
@@ -899,6 +949,8 @@ def formfield(self, **kwargs):
return super(URLField, self).formfield(**defaults)
class XMLField(TextField):
+ """XML text"""
+
def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
self.schema_path = schema_path
Field.__init__(self, verbose_name, name, **kwargs)
View
4 django/db/models/fields/files.py
@@ -209,6 +209,8 @@ def __set__(self, instance, value):
instance.__dict__[self.field.name] = value
class FileField(Field):
+ """File path"""
+
# The class to wrap instance attributes in. Accessing the file object off
# the instance will always return an instance of attr_class.
attr_class = FieldFile
@@ -323,6 +325,8 @@ def delete(self, save=True):
super(ImageFieldFile, self).delete(save)
class ImageField(FileField):
+ """File path"""
+
attr_class = ImageFieldFile
descriptor_class = ImageFileDescriptor
View
11 django/db/models/fields/related.py
@@ -682,6 +682,8 @@ def get_related_field(self):
return self.to._meta.pk
class ForeignKey(RelatedField, Field):
+ """Foreign Key (type determined by related field)"""
+
empty_strings_allowed = False
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try:
@@ -771,12 +773,13 @@ def db_type(self):
return rel_field.db_type()
class OneToOneField(ForeignKey):
- """
+ """One-to-one relationship
+
A OneToOneField is essentially the same as a ForeignKey, with the exception
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),
- rather than returning a list.
- """
+ rather than returning a list."""
+
def __init__(self, to, to_field=None, **kwargs):
kwargs['unique'] = True
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@@ -791,6 +794,8 @@ def formfield(self, **kwargs):
return super(OneToOneField, self).formfield(**kwargs)
class ManyToManyField(RelatedField, Field):
+ """Many-to-many relationship"""
+
def __init__(self, to, **kwargs):
try:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
Please sign in to comment.
Something went wrong with that request. Please try again.