Fixed #12385: Made built-in field type descriptions in admindocs translatable again. Many thanks to Ramiro for the problem report and patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11878 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Karen Tracey 2009-12-16 18:13:34 +00:00
parent 4e81086021
commit 833df0afaa
9 changed files with 85 additions and 120 deletions

View File

@ -1,9 +1,9 @@
import unittest import unittest
from django.contrib.admindocs import views
import fields import fields
from django.contrib.admindocs import views
from django.db.models import fields as builtin_fields from django.db.models import fields as builtin_fields
class TestFieldType(unittest.TestCase): class TestFieldType(unittest.TestCase):
def setUp(self): def setUp(self):
pass pass
@ -25,12 +25,6 @@ class TestFieldType(unittest.TestCase):
u'A custom field type' u'A custom field type'
) )
self.assertEqual( self.assertEqual(
views.get_readable_field_data_type(fields.DocstringLackingField()), views.get_readable_field_data_type(fields.DescriptionLackingField()),
u'Field of type: DocstringLackingField' u'Field of type: DescriptionLackingField'
)
def test_multiline_custom_field_truncation(self):
self.assertEqual(
views.get_readable_field_data_type(fields.ManyLineDocstringField()),
u'Many-line custom field'
) )

View File

@ -1,13 +1,7 @@
from django.db import models from django.db import models
class CustomField(models.Field): class CustomField(models.Field):
"""A custom field type""" description = "A custom field type"
class ManyLineDocstringField(models.Field): class DescriptionLackingField(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 pass

View File

@ -327,19 +327,11 @@ def get_return_data_type(func_name):
return '' return ''
def get_readable_field_data_type(field): def get_readable_field_data_type(field):
"""Returns the first line of a doc string for a given field type, if it """Returns the description for a given field type, if it exists,
exists. Fields' docstrings can contain format strings, which will be Fields' descriptions can contain format strings, which will be interpolated
interpolated against the values of Field.__dict__ before being output. 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__: return field.description % field.__dict__
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

@ -1,3 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.contrib.gis import forms from django.contrib.gis import forms
# Getting the SpatialBackend container and the geographic quoting method. # Getting the SpatialBackend container and the geographic quoting method.
from django.contrib.gis.db.backend import SpatialBackend, gqn from django.contrib.gis.db.backend import SpatialBackend, gqn
@ -30,7 +31,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'
@ -38,6 +39,8 @@ class GeometryField(SpatialBackend.Field):
# Geodetic units. # Geodetic units.
geodetic_units = ('Decimal Degree', 'degree') geodetic_units = ('Decimal Degree', 'degree')
description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.")
def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kwargs): def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kwargs):
""" """
The initialization function for geometry fields. Takes the following The initialization function for geometry fields. Takes the following
@ -257,29 +260,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'
description = _("Point")
class LineStringField(GeometryField): class LineStringField(GeometryField):
"""Line string"""
geom_type = 'LINESTRING' geom_type = 'LINESTRING'
description = _("Line string")
class PolygonField(GeometryField): class PolygonField(GeometryField):
"""Polygon"""
geom_type = 'POLYGON' geom_type = 'POLYGON'
description = _("Polygon")
class MultiPointField(GeometryField): class MultiPointField(GeometryField):
"""Multi-point"""
geom_type = 'MULTIPOINT' geom_type = 'MULTIPOINT'
description = _("Multi-point")
class MultiLineStringField(GeometryField): class MultiLineStringField(GeometryField):
"""Multi-line string"""
geom_type = 'MULTILINESTRING' geom_type = 'MULTILINESTRING'
description = _("Multi-line string")
class MultiPolygonField(GeometryField): class MultiPolygonField(GeometryField):
"""Multi polygon"""
geom_type = 'MULTIPOLYGON' geom_type = 'MULTIPOLYGON'
description = _("Multi polygon")
class GeometryCollectionField(GeometryField): class GeometryCollectionField(GeometryField):
"""Geometry collection"""
geom_type = 'GEOMETRYCOLLECTION' geom_type = 'GEOMETRYCOLLECTION'
description = _("Geometry collection")

View File

@ -1,16 +1,21 @@
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.db.models.fields import Field, CharField from django.db.models.fields import Field, CharField
from django.contrib.localflavor.us.us_states import STATE_CHOICES from django.contrib.localflavor.us.us_states import STATE_CHOICES
class USStateField(CharField): class USStateField(CharField):
"""U.S. state (two uppercase letters)"""
description = _("U.S. state (two uppercase letters)")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['choices'] = STATE_CHOICES kwargs['choices'] = STATE_CHOICES
kwargs['max_length'] = 2 kwargs['max_length'] = 2
super(USStateField, self).__init__(*args, **kwargs) super(USStateField, self).__init__(*args, **kwargs)
class PhoneNumberField(Field): class PhoneNumberField(Field):
"""Phone number"""
description = _("Phone number")
def get_internal_type(self): def get_internal_type(self):
return "PhoneNumberField" return "PhoneNumberField"

View File

@ -49,8 +49,6 @@ 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
@ -61,6 +59,13 @@ class Field(object):
creation_counter = 0 creation_counter = 0
auto_creation_counter = -1 auto_creation_counter = -1
# Generic field type description, usually overriden by subclasses
def _description(self):
return _(u'Field of type: %(field_type)s') % {
'field_type': self.__class__.__name__
}
description = property(_description)
def __init__(self, verbose_name=None, name=None, primary_key=False, def __init__(self, verbose_name=None, name=None, primary_key=False,
max_length=None, unique=False, blank=False, null=False, max_length=None, unique=False, blank=False, null=False,
db_index=False, rel=None, default=NOT_PROVIDED, editable=True, db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
@ -342,10 +347,8 @@ class Field(object):
return getattr(obj, self.attname) return getattr(obj, self.attname)
class AutoField(Field): class AutoField(Field):
"""Integer""" description = ugettext_lazy("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
@ -375,10 +378,8 @@ 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
description = ugettext_lazy("Boolean (Either True or 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'):
@ -421,8 +422,7 @@ 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)""" description = ugettext_lazy("String (up to %(max_length)s)")
def get_internal_type(self): def get_internal_type(self):
return "CharField" return "CharField"
@ -444,8 +444,7 @@ 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""" description = ugettext_lazy("Comma-separated integers")
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = { defaults = {
'form_class': forms.RegexField, 'form_class': forms.RegexField,
@ -461,10 +460,8 @@ 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)""" description = ugettext_lazy("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.
@ -539,8 +536,7 @@ class DateField(Field):
return super(DateField, self).formfield(**defaults) return super(DateField, self).formfield(**defaults)
class DateTimeField(DateField): class DateTimeField(DateField):
"""Date (with time)""" description = ugettext_lazy("Date (with time)")
def get_internal_type(self): def get_internal_type(self):
return "DateTimeField" return "DateTimeField"
@ -600,10 +596,8 @@ 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
description = ugettext_lazy("Decimal number")
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)
@ -657,8 +651,7 @@ class DecimalField(Field):
return super(DecimalField, self).formfield(**defaults) return super(DecimalField, self).formfield(**defaults)
class EmailField(CharField): class EmailField(CharField):
"""E-mail address""" description = ugettext_lazy("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)
@ -669,8 +662,7 @@ class EmailField(CharField):
return super(EmailField, self).formfield(**defaults) return super(EmailField, self).formfield(**defaults)
class FilePathField(Field): class FilePathField(Field):
"""File path""" description = ugettext_lazy("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)
@ -690,9 +682,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
description = ugettext_lazy("Floating point number")
def get_db_prep_value(self, value): def get_db_prep_value(self, value):
if value is None: if value is None:
@ -717,10 +708,8 @@ 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
description = ugettext_lazy("Integer")
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
@ -744,10 +733,8 @@ 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
description = ugettext_lazy("IP address")
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)
@ -761,10 +748,8 @@ 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
description = ugettext_lazy("Boolean (Either True, False or None)")
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)
@ -804,8 +789,7 @@ class NullBooleanField(Field):
return super(NullBooleanField, self).formfield(**defaults) return super(NullBooleanField, self).formfield(**defaults)
class PositiveIntegerField(IntegerField): class PositiveIntegerField(IntegerField):
"""Integer""" description = ugettext_lazy("Integer")
def get_internal_type(self): def get_internal_type(self):
return "PositiveIntegerField" return "PositiveIntegerField"
@ -815,8 +799,7 @@ class PositiveIntegerField(IntegerField):
return super(PositiveIntegerField, self).formfield(**defaults) return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField): class PositiveSmallIntegerField(IntegerField):
"""Integer""" description = ugettext_lazy("Integer")
def get_internal_type(self): def get_internal_type(self):
return "PositiveSmallIntegerField" return "PositiveSmallIntegerField"
@ -826,8 +809,7 @@ 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)""" description = ugettext_lazy("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.
@ -844,14 +826,12 @@ class SlugField(CharField):
return super(SlugField, self).formfield(**defaults) return super(SlugField, self).formfield(**defaults)
class SmallIntegerField(IntegerField): class SmallIntegerField(IntegerField):
"""Integer""" description = ugettext_lazy("Integer")
def get_internal_type(self): def get_internal_type(self):
return "SmallIntegerField" return "SmallIntegerField"
class TextField(Field): class TextField(Field):
"""Text""" description = ugettext_lazy("Text")
def get_internal_type(self): def get_internal_type(self):
return "TextField" return "TextField"
@ -861,10 +841,8 @@ class TextField(Field):
return super(TextField, self).formfield(**defaults) return super(TextField, self).formfield(**defaults)
class TimeField(Field): class TimeField(Field):
"""Time""" description = ugettext_lazy("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:
@ -936,8 +914,7 @@ class TimeField(Field):
return super(TimeField, self).formfield(**defaults) return super(TimeField, self).formfield(**defaults)
class URLField(CharField): class URLField(CharField):
"""URL""" description = ugettext_lazy("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
@ -949,8 +926,7 @@ class URLField(CharField):
return super(URLField, self).formfield(**defaults) return super(URLField, self).formfield(**defaults)
class XMLField(TextField): class XMLField(TextField):
"""XML text""" description = ugettext_lazy("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,8 +209,6 @@ 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
@ -218,6 +216,8 @@ class FileField(Field):
# The descriptor to use for accessing the attribute off of the class. # The descriptor to use for accessing the attribute off of the class.
descriptor_class = FileDescriptor descriptor_class = FileDescriptor
description = ugettext_lazy("File path")
def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs): def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
for arg in ('primary_key', 'unique'): for arg in ('primary_key', 'unique'):
if arg in kwargs: if arg in kwargs:
@ -325,10 +325,9 @@ 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
description = ugettext_lazy("File path")
def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
self.width_field, self.height_field = width_field, height_field self.width_field, self.height_field = width_field, height_field

View File

@ -691,9 +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
description = ugettext_lazy("Foreign Key (type determined by related field)")
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
try: try:
to_name = to._meta.object_name.lower() to_name = to._meta.object_name.lower()
@ -790,13 +789,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.
"""
description = ugettext_lazy("One-to-one relationship")
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)
@ -850,8 +849,7 @@ def create_many_to_many_intermediary_model(field, klass):
}) })
class ManyToManyField(RelatedField, Field): class ManyToManyField(RelatedField, Field):
"""Many-to-many relationship""" description = ugettext_lazy("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)

View File

@ -5,6 +5,7 @@ Writing custom model fields
=========================== ===========================
.. versionadded:: 1.0 .. versionadded:: 1.0
.. currentmodule:: django.db.models
Introduction Introduction
============ ============
@ -165,7 +166,8 @@ behave like any existing field, so we'll subclass directly from
from django.db import models from django.db import models
class HandField(models.Field): class HandField(models.Field):
"""A hand of cards (bridge style)"""
description = "A hand of cards (bridge style)"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 104 kwargs['max_length'] = 104
@ -248,7 +250,8 @@ simple: make sure your field subclass uses a special metaclass:
For example:: For example::
class HandField(models.Field): class HandField(models.Field):
"""A hand of cards (bridge style)"""
description = "A hand of cards (bridge style)"
__metaclass__ = models.SubfieldBase __metaclass__ = models.SubfieldBase
@ -262,16 +265,17 @@ called when the attribute is initialized.
Documenting your Custom Field Documenting your Custom Field
----------------------------- -----------------------------
.. class:: django.db.models.Field
.. attribute:: description
As always, you should document your field type, so users will know what it is. As always, you should document your field type, so users will know what it is.
The best way to do this is to simply provide a docstring for it. This will In addition to providing a docstring for it, which is useful for developers,
automatically be picked up by ``django.contrib.admindocs``, if you have it you can also allow users of the admin app to see a short description of the
installed, and the first line of it will show up as the field type in the field type via the ``django.contrib.admindocs`` application. To do this simply
documentation for any model that uses your field. In the above examples, it provide descriptive text in a ``description`` class attribute of your custom field.
will show up as 'A hand of cards (bridge style)'. Note that if you provide a In the above example, the type description displayed by the ``admindocs`` application
more verbose docstring, only the first line will show up in for a ``HandField`` will be 'A hand of cards (bridge style)'.
``django.contrib.admindocs``. The full docstring will, of course, still be
available through ``pydoc`` or the interactive interpreter's ``help()``
function.
Useful methods Useful methods
-------------- --------------