diff -r 783b8c4c26f8 django/contrib/admindocs/tests/__init__.py
--- a/django/contrib/admindocs/tests/__init__.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/contrib/admindocs/tests/__init__.py	Wed Dec 16 13:56:02 2009 -0300
@@ -1,36 +1,30 @@
 import unittest
+import fields
 from django.contrib.admindocs import views
-import fields
+from django.db.models import fields as builtin_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'
+            views.get_readable_field_data_type(fields.DescriptionLackingField()),
+            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'
-        )
diff -r 783b8c4c26f8 django/contrib/admindocs/tests/fields.py
--- a/django/contrib/admindocs/tests/fields.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/contrib/admindocs/tests/fields.py	Wed Dec 16 13:56:02 2009 -0300
@@ -1,13 +1,7 @@
 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."""
+    description = "A custom field type"
 
-class DocstringLackingField(models.Field):
+class DescriptionLackingField(models.Field):
     pass
diff -r 783b8c4c26f8 django/contrib/admindocs/views.py
--- a/django/contrib/admindocs/views.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/contrib/admindocs/views.py	Wed Dec 16 13:56:02 2009 -0300
@@ -327,19 +327,11 @@
     return ''
 
 def get_readable_field_data_type(field):
-    """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."""
+    """Returns the description for a given field type, if it exists,
+    Fields' descriptions can contain format strings, which will be interpolated
+    against the values of field.__dict__ before being output."""
 
-    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__
-        } 
+    return field.description % field.__dict__
 
 def extract_views_from_urlpatterns(urlpatterns, base=''):
     """
diff -r 783b8c4c26f8 django/contrib/gis/db/models/fields/__init__.py
--- a/django/contrib/gis/db/models/fields/__init__.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/contrib/gis/db/models/fields/__init__.py	Wed Dec 16 13:56:02 2009 -0300
@@ -1,3 +1,4 @@
+from django.utils.translation import ugettext_lazy as _
 from django.contrib.gis import forms
 # Getting the SpatialBackend container and the geographic quoting method.
 from django.contrib.gis.db.backend import SpatialBackend, gqn
@@ -30,7 +31,7 @@
     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'
@@ -38,6 +39,8 @@
     # Geodetic units.
     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):
         """
         The initialization function for geometry fields.  Takes the following
@@ -257,29 +260,29 @@
 
 # The OpenGIS Geometry Type Fields
 class PointField(GeometryField):
-    """Point"""
     geom_type = 'POINT'
+    description = _("Point")
 
 class LineStringField(GeometryField):
-    """Line string"""
     geom_type = 'LINESTRING'
+    description = _("Line string")
 
 class PolygonField(GeometryField):
-    """Polygon"""
     geom_type = 'POLYGON'
+    description = _("Polygon")
 
 class MultiPointField(GeometryField):
-    """Multi-point"""
     geom_type = 'MULTIPOINT'
+    description = _("Multi-point")
 
 class MultiLineStringField(GeometryField):
-    """Multi-line string"""
     geom_type = 'MULTILINESTRING'
+    description = _("Multi-line string")
 
 class MultiPolygonField(GeometryField):
-    """Multi polygon"""
     geom_type = 'MULTIPOLYGON'
+    description = _("Multi polygon")
 
 class GeometryCollectionField(GeometryField):
-    """Geometry collection"""
     geom_type = 'GEOMETRYCOLLECTION'
+    description = _("Geometry collection")
diff -r 783b8c4c26f8 django/contrib/localflavor/us/models.py
--- a/django/contrib/localflavor/us/models.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/contrib/localflavor/us/models.py	Wed Dec 16 13:56:02 2009 -0300
@@ -1,16 +1,21 @@
 from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
 from django.db.models.fields import Field, CharField
 from django.contrib.localflavor.us.us_states import STATE_CHOICES
-  
+
 class USStateField(CharField):
-    """U.S. state (two uppercase letters)"""
+
+    description = _("U.S. state (two uppercase letters)")
+
     def __init__(self, *args, **kwargs):
         kwargs['choices'] = STATE_CHOICES
         kwargs['max_length'] = 2
         super(USStateField, self).__init__(*args, **kwargs)
-  
+
 class PhoneNumberField(Field):
-    """Phone number"""
+
+    description = _("Phone number")
+
     def get_internal_type(self):
         return "PhoneNumberField"
 
diff -r 783b8c4c26f8 django/db/models/fields/__init__.py
--- a/django/db/models/fields/__init__.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/db/models/fields/__init__.py	Wed Dec 16 13:56:02 2009 -0300
@@ -49,8 +49,6 @@
 #     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
@@ -61,6 +59,13 @@
     creation_counter = 0
     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,
             max_length=None, unique=False, blank=False, null=False,
             db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
@@ -342,10 +347,8 @@
         return getattr(obj, self.attname)
 
 class AutoField(Field):
-    """Integer"""
-    
+    description = ugettext_lazy("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
@@ -375,10 +378,8 @@
         return None
 
 class BooleanField(Field):
-    """Boolean (Either True or False)"""
-
     empty_strings_allowed = False
-
+    description = ugettext_lazy("Boolean (Either True or False)")
     def __init__(self, *args, **kwargs):
         kwargs['blank'] = True
         if 'default' not in kwargs and not kwargs.get('null'):
@@ -421,8 +422,7 @@
         return super(BooleanField, self).formfield(**defaults)
 
 class CharField(Field):
-    """String (up to %(max_length)s)"""
-    
+    description = ugettext_lazy("String (up to %(max_length)s)")
     def get_internal_type(self):
         return "CharField"
 
@@ -444,8 +444,7 @@
 
 # TODO: Maybe move this into contrib, because it's specialized.
 class CommaSeparatedIntegerField(CharField):
-    """Comma-separated integers"""
-    
+    description = ugettext_lazy("Comma-separated integers")
     def formfield(self, **kwargs):
         defaults = {
             'form_class': forms.RegexField,
@@ -461,10 +460,8 @@
 ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
 
 class DateField(Field):
-    """Date (without time)"""
-    
+    description = ugettext_lazy("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.
@@ -539,8 +536,7 @@
         return super(DateField, self).formfield(**defaults)
 
 class DateTimeField(DateField):
-    """Date (with time)"""
-    
+    description = ugettext_lazy("Date (with time)")
     def get_internal_type(self):
         return "DateTimeField"
 
@@ -600,10 +596,8 @@
         return super(DateTimeField, self).formfield(**defaults)
 
 class DecimalField(Field):
-    """Decimal number"""
-    
     empty_strings_allowed = False
-
+    description = ugettext_lazy("Decimal number")
     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)
@@ -657,8 +651,7 @@
         return super(DecimalField, self).formfield(**defaults)
 
 class EmailField(CharField):
-    """E-mail address"""
-    
+    description = ugettext_lazy("E-mail address")
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = kwargs.get('max_length', 75)
         CharField.__init__(self, *args, **kwargs)
@@ -669,8 +662,7 @@
         return super(EmailField, self).formfield(**defaults)
 
 class FilePathField(Field):
-    """File path"""
-    
+    description = ugettext_lazy("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)
@@ -690,9 +682,8 @@
         return "FilePathField"
 
 class FloatField(Field):
-    """Floating point number"""
-    
     empty_strings_allowed = False
+    description = ugettext_lazy("Floating point number")
 
     def get_db_prep_value(self, value):
         if value is None:
@@ -717,10 +708,8 @@
         return super(FloatField, self).formfield(**defaults)
 
 class IntegerField(Field):
-    """Integer"""
-    
     empty_strings_allowed = False
-
+    description = ugettext_lazy("Integer")
     def get_db_prep_value(self, value):
         if value is None:
             return None
@@ -744,10 +733,8 @@
         return super(IntegerField, self).formfield(**defaults)
 
 class IPAddressField(Field):
-    """IP address"""
-    
     empty_strings_allowed = False
-
+    description = ugettext_lazy("IP address")
     def __init__(self, *args, **kwargs):
         kwargs['max_length'] = 15
         Field.__init__(self, *args, **kwargs)
@@ -761,10 +748,8 @@
         return super(IPAddressField, self).formfield(**defaults)
 
 class NullBooleanField(Field):
-    """Boolean (Either True, False or None)"""
-
     empty_strings_allowed = False
-
+    description = ugettext_lazy("Boolean (Either True, False or None)")
     def __init__(self, *args, **kwargs):
         kwargs['null'] = True
         Field.__init__(self, *args, **kwargs)
@@ -804,8 +789,7 @@
         return super(NullBooleanField, self).formfield(**defaults)
 
 class PositiveIntegerField(IntegerField):
-    """Integer"""
-    
+    description = ugettext_lazy("Integer")
     def get_internal_type(self):
         return "PositiveIntegerField"
 
@@ -815,8 +799,7 @@
         return super(PositiveIntegerField, self).formfield(**defaults)
 
 class PositiveSmallIntegerField(IntegerField):
-    """Integer"""
-
+    description = ugettext_lazy("Integer")
     def get_internal_type(self):
         return "PositiveSmallIntegerField"
 
@@ -826,8 +809,7 @@
         return super(PositiveSmallIntegerField, self).formfield(**defaults)
 
 class SlugField(CharField):
-    """String (up to %(max_length)s)"""
-
+    description = ugettext_lazy("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.
@@ -844,14 +826,12 @@
         return super(SlugField, self).formfield(**defaults)
 
 class SmallIntegerField(IntegerField):
-    """Integer"""
-    
+    description = ugettext_lazy("Integer")
     def get_internal_type(self):
         return "SmallIntegerField"
 
 class TextField(Field):
-    """Text"""
-    
+    description = ugettext_lazy("Text")
     def get_internal_type(self):
         return "TextField"
 
@@ -861,10 +841,8 @@
         return super(TextField, self).formfield(**defaults)
 
 class TimeField(Field):
-    """Time"""
-    
+    description = ugettext_lazy("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:
@@ -936,8 +914,7 @@
         return super(TimeField, self).formfield(**defaults)
 
 class URLField(CharField):
-    """URL"""
-    
+    description = ugettext_lazy("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
@@ -949,8 +926,7 @@
         return super(URLField, self).formfield(**defaults)
 
 class XMLField(TextField):
-    """XML text"""
-    
+    description = ugettext_lazy("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)
diff -r 783b8c4c26f8 django/db/models/fields/files.py
--- a/django/db/models/fields/files.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/db/models/fields/files.py	Wed Dec 16 13:56:02 2009 -0300
@@ -209,8 +209,6 @@
         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
@@ -218,6 +216,8 @@
     # The descriptor to use for accessing the attribute off of the class.
     descriptor_class = FileDescriptor
 
+    description = ugettext_lazy("File path")
+
     def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
         for arg in ('primary_key', 'unique'):
             if arg in kwargs:
@@ -325,10 +325,9 @@
         super(ImageFieldFile, self).delete(save)
 
 class ImageField(FileField):
-    """File path"""
-    
     attr_class = ImageFieldFile
     descriptor_class = ImageFileDescriptor
+    description = ugettext_lazy("File path")
 
     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
diff -r 783b8c4c26f8 django/db/models/fields/related.py
--- a/django/db/models/fields/related.py	Mon Dec 14 13:01:43 2009 -0300
+++ b/django/db/models/fields/related.py	Wed Dec 16 13:56:02 2009 -0300
@@ -691,9 +691,8 @@
         return self.to._meta.pk
 
 class ForeignKey(RelatedField, Field):
-    """Foreign Key (type determined by related field)"""
-    
     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):
         try:
             to_name = to._meta.object_name.lower()
@@ -790,13 +789,13 @@
         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.
+    """
+    description = ugettext_lazy("One-to-one relationship")
     def __init__(self, to, to_field=None, **kwargs):
         kwargs['unique'] = True
         super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
@@ -850,8 +849,7 @@
     })
 
 class ManyToManyField(RelatedField, Field):
-    """Many-to-many relationship"""
-    
+    description = ugettext_lazy("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)
diff -r 783b8c4c26f8 docs/howto/custom-model-fields.txt
--- a/docs/howto/custom-model-fields.txt	Mon Dec 14 13:01:43 2009 -0300
+++ b/docs/howto/custom-model-fields.txt	Wed Dec 16 13:56:02 2009 -0300
@@ -5,6 +5,7 @@
 ===========================
 
 .. versionadded:: 1.0
+.. currentmodule:: django.db.models
 
 Introduction
 ============
@@ -165,7 +166,8 @@
     from django.db import models
 
     class HandField(models.Field):
-        """A hand of cards (bridge style)"""
+
+        description = "A hand of cards (bridge style)"
 
         def __init__(self, *args, **kwargs):
             kwargs['max_length'] = 104
@@ -248,7 +250,8 @@
 For example::
 
     class HandField(models.Field):
-        """A hand of cards (bridge style)"""
+
+        description = "A hand of cards (bridge style)"
 
         __metaclass__ = models.SubfieldBase
 
@@ -262,16 +265,18 @@
 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.
-The best way to do this is to simply provide a docstring for it.  This will 
-automatically be picked up by ``django.contrib.admindocs``, if you have it
-installed, and the first line of it will show up as the field type in the 
-documentation for any model that uses your field.  In the above examples, it 
-will show up as 'A hand of cards (bridge style)'.  Note that if you provide a 
-more verbose docstring, only the first line will show up in 
-``django.contrib.admindocs``.  The full docstring will, of course, still be
-available through ``pydoc`` or the interactive interpreter's ``help()`` 
-function.
+In addition to providing a docstring for it, which is useful for developers,
+you can also allow users of the admin app to see a short description of the
+field type via the ``django.contrib.admindocs`` application if you have it
+installed, in a way similar to how the fields builtin to Django` are show. For
+this you need to provide such a descriptive text in a ``description`` class
+attribute of your custom field. In the above examples, it will show up as
+'A hand of cards (bridge style)'.
 
 Useful methods
 --------------
