Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 833df0afaa953070bbb8b4df62b0d78aeedaf314 1 parent 4e81086
Karen Tracey authored December 16, 2009
20  django/contrib/admindocs/tests/__init__.py
... ...
@@ -1,36 +1,30 @@
1 1
 import unittest
2  
-from django.contrib.admindocs import views
3 2
 import fields
4  
-
  3
+from django.contrib.admindocs import views
5 4
 from django.db.models import fields as builtin_fields
6 5
 
  6
+
7 7
 class TestFieldType(unittest.TestCase):
8 8
     def setUp(self):
9 9
         pass
10  
-        
  10
+
11 11
     def test_field_name(self):
12 12
         self.assertRaises(AttributeError,
13 13
             views.get_readable_field_data_type, "NotAField"
14 14
         )
15  
-        
  15
+
16 16
     def test_builtin_fields(self):
17 17
         self.assertEqual(
18 18
             views.get_readable_field_data_type(builtin_fields.BooleanField()),
19 19
             u'Boolean (Either True or False)'
20 20
         )
21  
-    
  21
+
22 22
     def test_custom_fields(self):
23 23
         self.assertEqual(
24 24
             views.get_readable_field_data_type(fields.CustomField()),
25 25
             u'A custom field type'
26 26
         )
27 27
         self.assertEqual(
28  
-            views.get_readable_field_data_type(fields.DocstringLackingField()),
29  
-            u'Field of type: DocstringLackingField'
30  
-        )
31  
-    
32  
-    def test_multiline_custom_field_truncation(self):
33  
-        self.assertEqual(
34  
-            views.get_readable_field_data_type(fields.ManyLineDocstringField()),
35  
-            u'Many-line custom field'
  28
+            views.get_readable_field_data_type(fields.DescriptionLackingField()),
  29
+            u'Field of type: DescriptionLackingField'
36 30
         )
10  django/contrib/admindocs/tests/fields.py
... ...
@@ -1,13 +1,7 @@
1 1
 from django.db import models
2 2
 
3 3
 class CustomField(models.Field):
4  
-    """A custom field type"""
5  
-    
6  
-class ManyLineDocstringField(models.Field):
7  
-    """Many-line custom field
8  
-    
9  
-    This docstring has many lines.  Lorum ipsem etc. etc.  Four score 
10  
-    and seven years ago, and so on and so forth."""
  4
+    description = "A custom field type"
11 5
 
12  
-class DocstringLackingField(models.Field):
  6
+class DescriptionLackingField(models.Field):
13 7
     pass
18  django/contrib/admindocs/views.py
@@ -327,19 +327,11 @@ def get_return_data_type(func_name):
327 327
     return ''
328 328
 
329 329
 def get_readable_field_data_type(field):
330  
-    """Returns the first line of a doc string for a given field type, if it 
331  
-    exists.  Fields' docstrings can contain format strings, which will be 
332  
-    interpolated against the values of Field.__dict__ before being output.  
333  
-    If no docstring is given, a sensible value will be auto-generated from 
334  
-    the field's class name."""
335  
-
336  
-    if field.__doc__:
337  
-        doc = field.__doc__.split('\n')[0]
338  
-        return _(doc) % field.__dict__
339  
-    else:
340  
-        return _(u'Field of type: %(field_type)s') % {
341  
-            'field_type': field.__class__.__name__
342  
-        } 
  330
+    """Returns the description for a given field type, if it exists,
  331
+    Fields' descriptions can contain format strings, which will be interpolated
  332
+    against the values of field.__dict__ before being output."""
  333
+
  334
+    return field.description % field.__dict__
343 335
 
344 336
 def extract_views_from_urlpatterns(urlpatterns, base=''):
345 337
     """
19  django/contrib/gis/db/models/fields/__init__.py
... ...
@@ -1,3 +1,4 @@
  1
+from django.utils.translation import ugettext_lazy as _
1 2
 from django.contrib.gis import forms
2 3
 # Getting the SpatialBackend container and the geographic quoting method.
3 4
 from django.contrib.gis.db.backend import SpatialBackend, gqn
@@ -30,7 +31,7 @@ def get_srid_info(srid):
30 31
     return _srid_cache[srid]
31 32
 
32 33
 class GeometryField(SpatialBackend.Field):
33  
-    """The base GIS field -- maps to the OpenGIS Specification Geometry type."""
  34
+    "The base GIS field -- maps to the OpenGIS Specification Geometry type."
34 35
 
35 36
     # The OpenGIS Geometry name.
36 37
     geom_type = 'GEOMETRY'
@@ -38,6 +39,8 @@ class GeometryField(SpatialBackend.Field):
38 39
     # Geodetic units.
39 40
     geodetic_units = ('Decimal Degree', 'degree')
40 41
 
  42
+    description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.")
  43
+
41 44
     def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kwargs):
42 45
         """
43 46
         The initialization function for geometry fields.  Takes the following
@@ -257,29 +260,29 @@ def get_db_prep_save(self, value):
257 260
 
258 261
 # The OpenGIS Geometry Type Fields
259 262
 class PointField(GeometryField):
260  
-    """Point"""
261 263
     geom_type = 'POINT'
  264
+    description = _("Point")
262 265
 
263 266
 class LineStringField(GeometryField):
264  
-    """Line string"""
265 267
     geom_type = 'LINESTRING'
  268
+    description = _("Line string")
266 269
 
267 270
 class PolygonField(GeometryField):
268  
-    """Polygon"""
269 271
     geom_type = 'POLYGON'
  272
+    description = _("Polygon")
270 273
 
271 274
 class MultiPointField(GeometryField):
272  
-    """Multi-point"""
273 275
     geom_type = 'MULTIPOINT'
  276
+    description = _("Multi-point")
274 277
 
275 278
 class MultiLineStringField(GeometryField):
276  
-    """Multi-line string"""
277 279
     geom_type = 'MULTILINESTRING'
  280
+    description = _("Multi-line string")
278 281
 
279 282
 class MultiPolygonField(GeometryField):
280  
-    """Multi polygon"""
281 283
     geom_type = 'MULTIPOLYGON'
  284
+    description = _("Multi polygon")
282 285
 
283 286
 class GeometryCollectionField(GeometryField):
284  
-    """Geometry collection"""
285 287
     geom_type = 'GEOMETRYCOLLECTION'
  288
+    description = _("Geometry collection")
13  django/contrib/localflavor/us/models.py
... ...
@@ -1,16 +1,21 @@
1 1
 from django.conf import settings
  2
+from django.utils.translation import ugettext_lazy as _
2 3
 from django.db.models.fields import Field, CharField
3 4
 from django.contrib.localflavor.us.us_states import STATE_CHOICES
4  
-  
  5
+
5 6
 class USStateField(CharField):
6  
-    """U.S. state (two uppercase letters)"""
  7
+
  8
+    description = _("U.S. state (two uppercase letters)")
  9
+
7 10
     def __init__(self, *args, **kwargs):
8 11
         kwargs['choices'] = STATE_CHOICES
9 12
         kwargs['max_length'] = 2
10 13
         super(USStateField, self).__init__(*args, **kwargs)
11  
-  
  14
+
12 15
 class PhoneNumberField(Field):
13  
-    """Phone number"""
  16
+
  17
+    description = _("Phone number")
  18
+
14 19
     def get_internal_type(self):
15 20
         return "PhoneNumberField"
16 21
 
80  django/db/models/fields/__init__.py
@@ -49,8 +49,6 @@ class FieldDoesNotExist(Exception):
49 49
 #     getattr(obj, opts.pk.attname)
50 50
 
51 51
 class Field(object):
52  
-    """Base class for all field types"""
53  
-    
54 52
     # Designates whether empty strings fundamentally are allowed at the
55 53
     # database level.
56 54
     empty_strings_allowed = True
@@ -61,6 +59,13 @@ class Field(object):
61 59
     creation_counter = 0
62 60
     auto_creation_counter = -1
63 61
 
  62
+    # Generic field type description, usually overriden by subclasses
  63
+    def _description(self):
  64
+        return _(u'Field of type: %(field_type)s') % {
  65
+            'field_type': self.__class__.__name__
  66
+        }
  67
+    description = property(_description)
  68
+
64 69
     def __init__(self, verbose_name=None, name=None, primary_key=False,
65 70
             max_length=None, unique=False, blank=False, null=False,
66 71
             db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
@@ -342,10 +347,8 @@ def value_from_object(self, obj):
342 347
         return getattr(obj, self.attname)
343 348
 
344 349
 class AutoField(Field):
345  
-    """Integer"""
346  
-    
  350
+    description = ugettext_lazy("Integer")
347 351
     empty_strings_allowed = False
348  
-
349 352
     def __init__(self, *args, **kwargs):
350 353
         assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
351 354
         kwargs['blank'] = True
@@ -375,10 +378,8 @@ def formfield(self, **kwargs):
375 378
         return None
376 379
 
377 380
 class BooleanField(Field):
378  
-    """Boolean (Either True or False)"""
379  
-
380 381
     empty_strings_allowed = False
381  
-
  382
+    description = ugettext_lazy("Boolean (Either True or False)")
382 383
     def __init__(self, *args, **kwargs):
383 384
         kwargs['blank'] = True
384 385
         if 'default' not in kwargs and not kwargs.get('null'):
@@ -421,8 +422,7 @@ def formfield(self, **kwargs):
421 422
         return super(BooleanField, self).formfield(**defaults)
422 423
 
423 424
 class CharField(Field):
424  
-    """String (up to %(max_length)s)"""
425  
-    
  425
+    description = ugettext_lazy("String (up to %(max_length)s)")
426 426
     def get_internal_type(self):
427 427
         return "CharField"
428 428
 
@@ -444,8 +444,7 @@ def formfield(self, **kwargs):
444 444
 
445 445
 # TODO: Maybe move this into contrib, because it's specialized.
446 446
 class CommaSeparatedIntegerField(CharField):
447  
-    """Comma-separated integers"""
448  
-    
  447
+    description = ugettext_lazy("Comma-separated integers")
449 448
     def formfield(self, **kwargs):
450 449
         defaults = {
451 450
             'form_class': forms.RegexField,
@@ -461,10 +460,8 @@ def formfield(self, **kwargs):
461 460
 ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
462 461
 
463 462
 class DateField(Field):
464  
-    """Date (without time)"""
465  
-    
  463
+    description = ugettext_lazy("Date (without time)")
466 464
     empty_strings_allowed = False
467  
-
468 465
     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
469 466
         self.auto_now, self.auto_now_add = auto_now, auto_now_add
470 467
         #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@@ -539,8 +536,7 @@ def formfield(self, **kwargs):
539 536
         return super(DateField, self).formfield(**defaults)
540 537
 
541 538
 class DateTimeField(DateField):
542  
-    """Date (with time)"""
543  
-    
  539
+    description = ugettext_lazy("Date (with time)")
544 540
     def get_internal_type(self):
545 541
         return "DateTimeField"
546 542
 
@@ -600,10 +596,8 @@ def formfield(self, **kwargs):
600 596
         return super(DateTimeField, self).formfield(**defaults)
601 597
 
602 598
 class DecimalField(Field):
603  
-    """Decimal number"""
604  
-    
605 599
     empty_strings_allowed = False
606  
-
  600
+    description = ugettext_lazy("Decimal number")
607 601
     def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
608 602
         self.max_digits, self.decimal_places = max_digits, decimal_places
609 603
         Field.__init__(self, verbose_name, name, **kwargs)
@@ -657,8 +651,7 @@ def formfield(self, **kwargs):
657 651
         return super(DecimalField, self).formfield(**defaults)
658 652
 
659 653
 class EmailField(CharField):
660  
-    """E-mail address"""
661  
-    
  654
+    description = ugettext_lazy("E-mail address")
662 655
     def __init__(self, *args, **kwargs):
663 656
         kwargs['max_length'] = kwargs.get('max_length', 75)
664 657
         CharField.__init__(self, *args, **kwargs)
@@ -669,8 +662,7 @@ def formfield(self, **kwargs):
669 662
         return super(EmailField, self).formfield(**defaults)
670 663
 
671 664
 class FilePathField(Field):
672  
-    """File path"""
673  
-    
  665
+    description = ugettext_lazy("File path")
674 666
     def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
675 667
         self.path, self.match, self.recursive = path, match, recursive
676 668
         kwargs['max_length'] = kwargs.get('max_length', 100)
@@ -690,9 +682,8 @@ def get_internal_type(self):
690 682
         return "FilePathField"
691 683
 
692 684
 class FloatField(Field):
693  
-    """Floating point number"""
694  
-    
695 685
     empty_strings_allowed = False
  686
+    description = ugettext_lazy("Floating point number")
696 687
 
697 688
     def get_db_prep_value(self, value):
698 689
         if value is None:
@@ -717,10 +708,8 @@ def formfield(self, **kwargs):
717 708
         return super(FloatField, self).formfield(**defaults)
718 709
 
719 710
 class IntegerField(Field):
720  
-    """Integer"""
721  
-    
722 711
     empty_strings_allowed = False
723  
-
  712
+    description = ugettext_lazy("Integer")
724 713
     def get_db_prep_value(self, value):
725 714
         if value is None:
726 715
             return None
@@ -744,10 +733,8 @@ def formfield(self, **kwargs):
744 733
         return super(IntegerField, self).formfield(**defaults)
745 734
 
746 735
 class IPAddressField(Field):
747  
-    """IP address"""
748  
-    
749 736
     empty_strings_allowed = False
750  
-
  737
+    description = ugettext_lazy("IP address")
751 738
     def __init__(self, *args, **kwargs):
752 739
         kwargs['max_length'] = 15
753 740
         Field.__init__(self, *args, **kwargs)
@@ -761,10 +748,8 @@ def formfield(self, **kwargs):
761 748
         return super(IPAddressField, self).formfield(**defaults)
762 749
 
763 750
 class NullBooleanField(Field):
764  
-    """Boolean (Either True, False or None)"""
765  
-
766 751
     empty_strings_allowed = False
767  
-
  752
+    description = ugettext_lazy("Boolean (Either True, False or None)")
768 753
     def __init__(self, *args, **kwargs):
769 754
         kwargs['null'] = True
770 755
         Field.__init__(self, *args, **kwargs)
@@ -804,8 +789,7 @@ def formfield(self, **kwargs):
804 789
         return super(NullBooleanField, self).formfield(**defaults)
805 790
 
806 791
 class PositiveIntegerField(IntegerField):
807  
-    """Integer"""
808  
-    
  792
+    description = ugettext_lazy("Integer")
809 793
     def get_internal_type(self):
810 794
         return "PositiveIntegerField"
811 795
 
@@ -815,8 +799,7 @@ def formfield(self, **kwargs):
815 799
         return super(PositiveIntegerField, self).formfield(**defaults)
816 800
 
817 801
 class PositiveSmallIntegerField(IntegerField):
818  
-    """Integer"""
819  
-
  802
+    description = ugettext_lazy("Integer")
820 803
     def get_internal_type(self):
821 804
         return "PositiveSmallIntegerField"
822 805
 
@@ -826,8 +809,7 @@ def formfield(self, **kwargs):
826 809
         return super(PositiveSmallIntegerField, self).formfield(**defaults)
827 810
 
828 811
 class SlugField(CharField):
829  
-    """String (up to %(max_length)s)"""
830  
-
  812
+    description = ugettext_lazy("String (up to %(max_length)s)")
831 813
     def __init__(self, *args, **kwargs):
832 814
         kwargs['max_length'] = kwargs.get('max_length', 50)
833 815
         # Set db_index=True unless it's been set manually.
@@ -844,14 +826,12 @@ def formfield(self, **kwargs):
844 826
         return super(SlugField, self).formfield(**defaults)
845 827
 
846 828
 class SmallIntegerField(IntegerField):
847  
-    """Integer"""
848  
-    
  829
+    description = ugettext_lazy("Integer")
849 830
     def get_internal_type(self):
850 831
         return "SmallIntegerField"
851 832
 
852 833
 class TextField(Field):
853  
-    """Text"""
854  
-    
  834
+    description = ugettext_lazy("Text")
855 835
     def get_internal_type(self):
856 836
         return "TextField"
857 837
 
@@ -861,10 +841,8 @@ def formfield(self, **kwargs):
861 841
         return super(TextField, self).formfield(**defaults)
862 842
 
863 843
 class TimeField(Field):
864  
-    """Time"""
865  
-    
  844
+    description = ugettext_lazy("Time")
866 845
     empty_strings_allowed = False
867  
-
868 846
     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
869 847
         self.auto_now, self.auto_now_add = auto_now, auto_now_add
870 848
         if auto_now or auto_now_add:
@@ -936,8 +914,7 @@ def formfield(self, **kwargs):
936 914
         return super(TimeField, self).formfield(**defaults)
937 915
 
938 916
 class URLField(CharField):
939  
-    """URL"""
940  
-    
  917
+    description = ugettext_lazy("URL")
941 918
     def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
942 919
         kwargs['max_length'] = kwargs.get('max_length', 200)
943 920
         self.verify_exists = verify_exists
@@ -949,8 +926,7 @@ def formfield(self, **kwargs):
949 926
         return super(URLField, self).formfield(**defaults)
950 927
 
951 928
 class XMLField(TextField):
952  
-    """XML text"""
953  
-    
  929
+    description = ugettext_lazy("XML text")
954 930
     def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
955 931
         self.schema_path = schema_path
956 932
         Field.__init__(self, verbose_name, name, **kwargs)
7  django/db/models/fields/files.py
@@ -209,8 +209,6 @@ def __set__(self, instance, value):
209 209
         instance.__dict__[self.field.name] = value
210 210
 
211 211
 class FileField(Field):
212  
-    """File path"""
213  
-    
214 212
     # The class to wrap instance attributes in. Accessing the file object off
215 213
     # the instance will always return an instance of attr_class.
216 214
     attr_class = FieldFile
@@ -218,6 +216,8 @@ class FileField(Field):
218 216
     # The descriptor to use for accessing the attribute off of the class.
219 217
     descriptor_class = FileDescriptor
220 218
 
  219
+    description = ugettext_lazy("File path")
  220
+
221 221
     def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs):
222 222
         for arg in ('primary_key', 'unique'):
223 223
             if arg in kwargs:
@@ -325,10 +325,9 @@ def delete(self, save=True):
325 325
         super(ImageFieldFile, self).delete(save)
326 326
 
327 327
 class ImageField(FileField):
328  
-    """File path"""
329  
-    
330 328
     attr_class = ImageFieldFile
331 329
     descriptor_class = ImageFileDescriptor
  330
+    description = ugettext_lazy("File path")
332 331
 
333 332
     def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
334 333
         self.width_field, self.height_field = width_field, height_field
14  django/db/models/fields/related.py
@@ -691,9 +691,8 @@ def get_related_field(self):
@@ -790,13 +789,13 @@ def db_type(self):
@@ -850,8 +849,7 @@ def set_managed(field, model, cls):
26  docs/howto/custom-model-fields.txt
@@ -5,6 +5,7 @@ Writing custom model fields
5 5
 ===========================
6 6
 
7 7
 .. versionadded:: 1.0
  8
+.. currentmodule:: django.db.models
8 9
 
9 10
 Introduction
10 11
 ============
@@ -165,7 +166,8 @@ behave like any existing field, so we'll subclass directly from
165 166
     from django.db import models
166 167
 
167 168
     class HandField(models.Field):
168  
-        """A hand of cards (bridge style)"""
  169
+
  170
+        description = "A hand of cards (bridge style)"
169 171
 
170 172
         def __init__(self, *args, **kwargs):
171 173
             kwargs['max_length'] = 104
@@ -248,7 +250,8 @@ simple: make sure your field subclass uses a special metaclass:
248 250
 For example::
249 251
 
250 252
     class HandField(models.Field):
251  
-        """A hand of cards (bridge style)"""
  253
+
  254
+        description = "A hand of cards (bridge style)"
252 255
 
253 256
         __metaclass__ = models.SubfieldBase
254 257
 
@@ -262,16 +265,17 @@ called when the attribute is initialized.
262 265
 Documenting your Custom Field
263 266
 -----------------------------
264 267
 
  268
+.. class:: django.db.models.Field
  269
+
  270
+.. attribute:: description
  271
+
265 272
 As always, you should document your field type, so users will know what it is.
266  
-The best way to do this is to simply provide a docstring for it.  This will 
267  
-automatically be picked up by ``django.contrib.admindocs``, if you have it
268  
-installed, and the first line of it will show up as the field type in the 
269  
-documentation for any model that uses your field.  In the above examples, it 
270  
-will show up as 'A hand of cards (bridge style)'.  Note that if you provide a 
271  
-more verbose docstring, only the first line will show up in 
272  
-``django.contrib.admindocs``.  The full docstring will, of course, still be
273  
-available through ``pydoc`` or the interactive interpreter's ``help()`` 
274  
-function.
  273
+In addition to providing a docstring for it, which is useful for developers,
  274
+you can also allow users of the admin app to see a short description of the
  275
+field type via the ``django.contrib.admindocs`` application. To do this simply 
  276
+provide descriptive text in a ``description`` class attribute of your custom field. 
  277
+In the above example, the type description displayed by the ``admindocs`` application 
  278
+for a ``HandField`` will be 'A hand of cards (bridge style)'.
275 279
 
276 280
 Useful methods
277 281
 --------------

0 notes on commit 833df0a

Please sign in to comment.
Something went wrong with that request. Please try again.