Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #12385: Made built-in field type descriptions in admindocs tran…

…slatable 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
commit 833df0afaa953070bbb8b4df62b0d78aeedaf314 1 parent 4e81086
@kmtracey kmtracey authored
View
20 django/contrib/admindocs/tests/__init__.py
@@ -1,36 +1,30 @@
import unittest
-from django.contrib.admindocs import views
import fields
-
+from django.contrib.admindocs import views
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'
+ views.get_readable_field_data_type(fields.DescriptionLackingField()),
+ u'Field of type: DescriptionLackingField'
)
View
10 django/contrib/admindocs/tests/fields.py
@@ -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
View
18 django/contrib/admindocs/views.py
@@ -327,19 +327,11 @@ def get_return_data_type(func_name):
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."""
-
- 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__
- }
+ """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."""
+
+ return field.description % field.__dict__
def extract_views_from_urlpatterns(urlpatterns, base=''):
"""
View
19 django/contrib/gis/db/models/fields/__init__.py
@@ -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 @@ 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'
@@ -38,6 +39,8 @@ class GeometryField(SpatialBackend.Field):
# 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 @@ def get_db_prep_save(self, value):
# 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")
View
13 django/contrib/localflavor/us/models.py
@@ -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"
View
80 django/db/models/fields/__init__.py
@@ -49,8 +49,6 @@ 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
@@ -61,6 +59,13 @@ class Field(object):
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 @@ def value_from_object(self, obj):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
# 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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def get_internal_type(self):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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 @@ def formfield(self, **kwargs):
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)
View
7 django/db/models/fields/files.py
@@ -209,8 +209,6 @@ 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
@@ -218,6 +216,8 @@ class FileField(Field):
# 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 @@ def delete(self, save=True):
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
View
14 django/db/models/fields/related.py
@@ -691,9 +691,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
+ 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 @@ 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.
+ """
+ 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 @@ def set_managed(field, model, cls):
})
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)
View
26 docs/howto/custom-model-fields.txt
@@ -5,6 +5,7 @@ Writing custom model fields
===========================
.. versionadded:: 1.0
+.. currentmodule:: django.db.models
Introduction
============
@@ -165,7 +166,8 @@ behave like any existing field, so we'll subclass directly from
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 @@ simple: make sure your field subclass uses a special metaclass:
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,17 @@ called when the attribute is initialized.
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. To do this simply
+provide descriptive text in a ``description`` class attribute of your custom field.
+In the above example, the type description displayed by the ``admindocs`` application
+for a ``HandField`` will be 'A hand of cards (bridge style)'.
Useful methods
--------------
Please sign in to comment.
Something went wrong with that request. Please try again.