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:
parent
783884af56
commit
0986a4d2e1
|
@ -0,0 +1 @@
|
|||
# Empty models.py to allow for specifying admindocs as a test label.
|
|
@ -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'
|
||||
)
|
|
@ -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
|
|
@ -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=''):
|
||||
"""
|
||||
|
|
|
@ -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 @@ class GeometryField(SpatialBackend.Field):
|
|||
|
||||
# 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'
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.conf import settings
|
|||
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 @@ class USStateField(Field):
|
|||
return super(USStateField, self).formfield(**defaults)
|
||||
|
||||
class PhoneNumberField(Field):
|
||||
"""Phone number"""
|
||||
def get_internal_type(self):
|
||||
return "PhoneNumberField"
|
||||
|
||||
|
|
|
@ -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 @@ class Field(object):
|
|||
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 @@ class AutoField(Field):
|
|||
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 @@ class BooleanField(Field):
|
|||
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 @@ class CharField(Field):
|
|||
|
||||
# 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 @@ class CommaSeparatedIntegerField(CharField):
|
|||
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 @@ class DateField(Field):
|
|||
return super(DateField, self).formfield(**defaults)
|
||||
|
||||
class DateTimeField(DateField):
|
||||
"""Date (with time)"""
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
|
@ -583,7 +600,10 @@ class DateTimeField(DateField):
|
|||
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 @@ class DecimalField(Field):
|
|||
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 @@ class EmailField(CharField):
|
|||
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 @@ class FilePathField(Field):
|
|||
return "FilePathField"
|
||||
|
||||
class FloatField(Field):
|
||||
"""Floating point number"""
|
||||
|
||||
empty_strings_allowed = False
|
||||
|
||||
def get_db_prep_value(self, value):
|
||||
|
@ -691,7 +717,10 @@ class FloatField(Field):
|
|||
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 @@ class IntegerField(Field):
|
|||
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 @@ class IPAddressField(Field):
|
|||
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 @@ class NullBooleanField(Field):
|
|||
return super(NullBooleanField, self).formfield(**defaults)
|
||||
|
||||
class PositiveIntegerField(IntegerField):
|
||||
"""Integer"""
|
||||
|
||||
def get_internal_type(self):
|
||||
return "PositiveIntegerField"
|
||||
|
||||
|
@ -778,6 +815,8 @@ class PositiveIntegerField(IntegerField):
|
|||
return super(PositiveIntegerField, self).formfield(**defaults)
|
||||
|
||||
class PositiveSmallIntegerField(IntegerField):
|
||||
"""Integer"""
|
||||
|
||||
def get_internal_type(self):
|
||||
return "PositiveSmallIntegerField"
|
||||
|
||||
|
@ -787,6 +826,8 @@ class PositiveSmallIntegerField(IntegerField):
|
|||
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 @@ class SlugField(CharField):
|
|||
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 @@ class TextField(Field):
|
|||
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 @@ class TimeField(Field):
|
|||
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 @@ class URLField(CharField):
|
|||
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)
|
||||
|
|
|
@ -209,6 +209,8 @@ class FileDescriptor(object):
|
|||
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 @@ class ImageFieldFile(ImageFile, FieldFile):
|
|||
super(ImageFieldFile, self).delete(save)
|
||||
|
||||
class ImageField(FileField):
|
||||
"""File path"""
|
||||
|
||||
attr_class = ImageFieldFile
|
||||
descriptor_class = ImageFileDescriptor
|
||||
|
||||
|
|
|
@ -691,6 +691,8 @@ class ManyToManyRel(object):
|
|||
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:
|
||||
|
@ -788,12 +790,13 @@ class ForeignKey(RelatedField, Field):
|
|||
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)
|
||||
|
@ -847,6 +850,8 @@ def create_many_to_many_intermediary_model(field, klass):
|
|||
})
|
||||
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue