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,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 File

@ -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 File

@ -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."""
"""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=''):
"""

View File

@ -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 @@ class GeometryField(SpatialBackend.Field):
# 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 File

@ -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 File

@ -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 @@ class Field(object):
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 @@ class AutoField(Field):
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 @@ class BooleanField(Field):
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 @@ class CharField(Field):
# 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 @@ class CommaSeparatedIntegerField(CharField):
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 @@ class DateField(Field):
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 @@ class DateTimeField(DateField):
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 @@ class DecimalField(Field):
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 @@ class EmailField(CharField):
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 @@ class FilePathField(Field):
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 @@ class FloatField(Field):
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 @@ class IntegerField(Field):
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 @@ class IPAddressField(Field):
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 @@ class NullBooleanField(Field):
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 @@ class PositiveIntegerField(IntegerField):
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 @@ class PositiveSmallIntegerField(IntegerField):
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 @@ class SlugField(CharField):
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 @@ class TextField(Field):
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 @@ class TimeField(Field):
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 @@ class URLField(CharField):
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 File

@ -209,8 +209,6 @@ 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
@ -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 @@ class ImageFieldFile(ImageFile, FieldFile):
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 File

@ -691,9 +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
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 @@ 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.
"""
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 create_many_to_many_intermediary_model(field, klass):
})
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 File

@ -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
--------------