Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #7977: Fixed admindocs to use docstrings instead of a static ar…

…ray to locate type information.

Thanks J. Clifford Dyer.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@11833 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 0986a4d2e147926fc1ee62fb67406d0a39fbe776 1 parent 783884a
Karen Tracey authored December 12, 2009
1  django/contrib/admindocs/models.py
... ...
@@ -0,0 +1 @@
  1
+# Empty models.py to allow for specifying admindocs as a test label.
36  django/contrib/admindocs/tests/__init__.py
... ...
@@ -0,0 +1,36 @@
  1
+import unittest
  2
+from django.contrib.admindocs import views
  3
+import fields
  4
+
  5
+from django.db.models import fields as builtin_fields
  6
+
  7
+class TestFieldType(unittest.TestCase):
  8
+    def setUp(self):
  9
+        pass
  10
+        
  11
+    def test_field_name(self):
  12
+        self.assertRaises(AttributeError,
  13
+            views.get_readable_field_data_type, "NotAField"
  14
+        )
  15
+        
  16
+    def test_builtin_fields(self):
  17
+        self.assertEqual(
  18
+            views.get_readable_field_data_type(builtin_fields.BooleanField()),
  19
+            u'Boolean (Either True or False)'
  20
+        )
  21
+    
  22
+    def test_custom_fields(self):
  23
+        self.assertEqual(
  24
+            views.get_readable_field_data_type(fields.CustomField()),
  25
+            u'A custom field type'
  26
+        )
  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'
  36
+        )
13  django/contrib/admindocs/tests/fields.py
... ...
@@ -0,0 +1,13 @@
  1
+from django.db import models
  2
+
  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."""
  11
+
  12
+class DocstringLackingField(models.Field):
  13
+    pass
49  django/contrib/admindocs/views.py
@@ -326,43 +326,20 @@ def get_return_data_type(func_name):
326 326
             return 'Integer'
327 327
     return ''
328 328
 
329  
-# Maps Field objects to their human-readable data types, as strings.
330  
-# Column-type strings can contain format strings; they'll be interpolated
331  
-# against the values of Field.__dict__ before being output.
332  
-# If a column type is set to None, it won't be included in the output.
333  
-DATA_TYPE_MAPPING = {
334  
-    'AutoField'                 : _('Integer'),
335  
-    'BooleanField'              : _('Boolean (Either True or False)'),
336  
-    'CharField'                 : _('String (up to %(max_length)s)'),
337  
-    'CommaSeparatedIntegerField': _('Comma-separated integers'),
338  
-    'DateField'                 : _('Date (without time)'),
339  
-    'DateTimeField'             : _('Date (with time)'),
340  
-    'DecimalField'              : _('Decimal number'),
341  
-    'EmailField'                : _('E-mail address'),
342  
-    'FileField'                 : _('File path'),
343  
-    'FilePathField'             : _('File path'),
344  
-    'FloatField'                : _('Floating point number'),
345  
-    'ForeignKey'                : _('Integer'),
346  
-    'ImageField'                : _('File path'),
347  
-    'IntegerField'              : _('Integer'),
348  
-    'IPAddressField'            : _('IP address'),
349  
-    'ManyToManyField'           : '',
350  
-    'NullBooleanField'          : _('Boolean (Either True, False or None)'),
351  
-    'OneToOneField'             : _('Relation to parent model'),
352  
-    'PhoneNumberField'          : _('Phone number'),
353  
-    'PositiveIntegerField'      : _('Integer'),
354  
-    'PositiveSmallIntegerField' : _('Integer'),
355  
-    'SlugField'                 : _('String (up to %(max_length)s)'),
356  
-    'SmallIntegerField'         : _('Integer'),
357  
-    'TextField'                 : _('Text'),
358  
-    'TimeField'                 : _('Time'),
359  
-    'URLField'                  : _('URL'),
360  
-    'USStateField'              : _('U.S. state (two uppercase letters)'),
361  
-    'XMLField'                  : _('XML text'),
362  
-}
363  
-
364 329
 def get_readable_field_data_type(field):
365  
-    return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
  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
+        } 
366 343
 
367 344
 def extract_views_from_urlpatterns(urlpatterns, base=''):
368 345
     """
9  django/contrib/gis/db/models/fields/__init__.py
@@ -30,7 +30,7 @@ def get_srid_info(srid):
30 30
     return _srid_cache[srid]
31 31
 
32 32
 class GeometryField(SpatialBackend.Field):
33  
-    "The base GIS field -- maps to the OpenGIS Specification Geometry type."
  33
+    """The base GIS field -- maps to the OpenGIS Specification Geometry type."""
34 34
 
35 35
     # The OpenGIS Geometry name.
36 36
     geom_type = 'GEOMETRY'
@@ -257,22 +257,29 @@ def get_db_prep_save(self, value):
257 257
 
258 258
 # The OpenGIS Geometry Type Fields
259 259
 class PointField(GeometryField):
  260
+    """Point"""
260 261
     geom_type = 'POINT'
261 262
 
262 263
 class LineStringField(GeometryField):
  264
+    """Line string"""
263 265
     geom_type = 'LINESTRING'
264 266
 
265 267
 class PolygonField(GeometryField):
  268
+    """Polygon"""
266 269
     geom_type = 'POLYGON'
267 270
 
268 271
 class MultiPointField(GeometryField):
  272
+    """Multi-point"""
269 273
     geom_type = 'MULTIPOINT'
270 274
 
271 275
 class MultiLineStringField(GeometryField):
  276
+    """Multi-line string"""
272 277
     geom_type = 'MULTILINESTRING'
273 278
 
274 279
 class MultiPolygonField(GeometryField):
  280
+    """Multi polygon"""
275 281
     geom_type = 'MULTIPOLYGON'
276 282
 
277 283
 class GeometryCollectionField(GeometryField):
  284
+    """Geometry collection"""
278 285
     geom_type = 'GEOMETRYCOLLECTION'
2  django/contrib/localflavor/us/models.py
@@ -2,6 +2,7 @@
2 2
 from django.db.models.fields import Field
3 3
 
4 4
 class USStateField(Field): 
  5
+    """U.S. state (two uppercase letters)"""
5 6
     def get_internal_type(self): 
6 7
         return "USStateField" 
7 8
         
@@ -18,6 +19,7 @@ def formfield(self, **kwargs):
18 19
         return super(USStateField, self).formfield(**defaults)
19 20
 
20 21
 class PhoneNumberField(Field):
  22
+    """Phone number"""
21 23
     def get_internal_type(self):
22 24
         return "PhoneNumberField"
23 25
 
52  django/db/models/fields/__init__.py
@@ -49,6 +49,8 @@ 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
+    
52 54
     # Designates whether empty strings fundamentally are allowed at the
53 55
     # database level.
54 56
     empty_strings_allowed = True
@@ -340,7 +342,10 @@ def value_from_object(self, obj):
340 342
         return getattr(obj, self.attname)
341 343
 
342 344
 class AutoField(Field):
  345
+    """Integer"""
  346
+    
343 347
     empty_strings_allowed = False
  348
+
344 349
     def __init__(self, *args, **kwargs):
345 350
         assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__
346 351
         kwargs['blank'] = True
@@ -370,7 +375,10 @@ def formfield(self, **kwargs):
370 375
         return None
371 376
 
372 377
 class BooleanField(Field):
  378
+    """Boolean (Either True or False)"""
  379
+
373 380
     empty_strings_allowed = False
  381
+
374 382
     def __init__(self, *args, **kwargs):
375 383
         kwargs['blank'] = True
376 384
         if 'default' not in kwargs and not kwargs.get('null'):
@@ -413,6 +421,8 @@ def formfield(self, **kwargs):
413 421
         return super(BooleanField, self).formfield(**defaults)
414 422
 
415 423
 class CharField(Field):
  424
+    """String (up to %(max_length)s)"""
  425
+    
416 426
     def get_internal_type(self):
417 427
         return "CharField"
418 428
 
@@ -434,6 +444,8 @@ def formfield(self, **kwargs):
434 444
 
435 445
 # TODO: Maybe move this into contrib, because it's specialized.
436 446
 class CommaSeparatedIntegerField(CharField):
  447
+    """Comma-separated integers"""
  448
+    
437 449
     def formfield(self, **kwargs):
438 450
         defaults = {
439 451
             'form_class': forms.RegexField,
@@ -449,7 +461,10 @@ def formfield(self, **kwargs):
449 461
 ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$')
450 462
 
451 463
 class DateField(Field):
  464
+    """Date (without time)"""
  465
+    
452 466
     empty_strings_allowed = False
  467
+
453 468
     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
454 469
         self.auto_now, self.auto_now_add = auto_now, auto_now_add
455 470
         #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
@@ -524,6 +539,8 @@ def formfield(self, **kwargs):
524 539
         return super(DateField, self).formfield(**defaults)
525 540
 
526 541
 class DateTimeField(DateField):
  542
+    """Date (with time)"""
  543
+    
527 544
     def get_internal_type(self):
528 545
         return "DateTimeField"
529 546
 
@@ -583,7 +600,10 @@ def formfield(self, **kwargs):
583 600
         return super(DateTimeField, self).formfield(**defaults)
584 601
 
585 602
 class DecimalField(Field):
  603
+    """Decimal number"""
  604
+    
586 605
     empty_strings_allowed = False
  606
+
587 607
     def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs):
588 608
         self.max_digits, self.decimal_places = max_digits, decimal_places
589 609
         Field.__init__(self, verbose_name, name, **kwargs)
@@ -637,6 +657,8 @@ def formfield(self, **kwargs):
637 657
         return super(DecimalField, self).formfield(**defaults)
638 658
 
639 659
 class EmailField(CharField):
  660
+    """E-mail address"""
  661
+    
640 662
     def __init__(self, *args, **kwargs):
641 663
         kwargs['max_length'] = kwargs.get('max_length', 75)
642 664
         CharField.__init__(self, *args, **kwargs)
@@ -647,6 +669,8 @@ def formfield(self, **kwargs):
647 669
         return super(EmailField, self).formfield(**defaults)
648 670
 
649 671
 class FilePathField(Field):
  672
+    """File path"""
  673
+    
650 674
     def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
651 675
         self.path, self.match, self.recursive = path, match, recursive
652 676
         kwargs['max_length'] = kwargs.get('max_length', 100)
@@ -666,6 +690,8 @@ def get_internal_type(self):
666 690
         return "FilePathField"
667 691
 
668 692
 class FloatField(Field):
  693
+    """Floating point number"""
  694
+    
669 695
     empty_strings_allowed = False
670 696
 
671 697
     def get_db_prep_value(self, value):
@@ -691,7 +717,10 @@ def formfield(self, **kwargs):
691 717
         return super(FloatField, self).formfield(**defaults)
692 718
 
693 719
 class IntegerField(Field):
  720
+    """Integer"""
  721
+    
694 722
     empty_strings_allowed = False
  723
+
695 724
     def get_db_prep_value(self, value):
696 725
         if value is None:
697 726
             return None
@@ -715,7 +744,10 @@ def formfield(self, **kwargs):
715 744
         return super(IntegerField, self).formfield(**defaults)
716 745
 
717 746
 class IPAddressField(Field):
  747
+    """IP address"""
  748
+    
718 749
     empty_strings_allowed = False
  750
+
719 751
     def __init__(self, *args, **kwargs):
720 752
         kwargs['max_length'] = 15
721 753
         Field.__init__(self, *args, **kwargs)
@@ -729,7 +761,10 @@ def formfield(self, **kwargs):
729 761
         return super(IPAddressField, self).formfield(**defaults)
730 762
 
731 763
 class NullBooleanField(Field):
  764
+    """Boolean (Either True, False or None)"""
  765
+
732 766
     empty_strings_allowed = False
  767
+
733 768
     def __init__(self, *args, **kwargs):
734 769
         kwargs['null'] = True
735 770
         Field.__init__(self, *args, **kwargs)
@@ -769,6 +804,8 @@ def formfield(self, **kwargs):
769 804
         return super(NullBooleanField, self).formfield(**defaults)
770 805
 
771 806
 class PositiveIntegerField(IntegerField):
  807
+    """Integer"""
  808
+    
772 809
     def get_internal_type(self):
773 810
         return "PositiveIntegerField"
774 811
 
@@ -778,6 +815,8 @@ def formfield(self, **kwargs):
778 815
         return super(PositiveIntegerField, self).formfield(**defaults)
779 816
 
780 817
 class PositiveSmallIntegerField(IntegerField):
  818
+    """Integer"""
  819
+
781 820
     def get_internal_type(self):
782 821
         return "PositiveSmallIntegerField"
783 822
 
@@ -787,6 +826,8 @@ def formfield(self, **kwargs):
787 826
         return super(PositiveSmallIntegerField, self).formfield(**defaults)
788 827
 
789 828
 class SlugField(CharField):
  829
+    """String (up to %(max_length)s)"""
  830
+
790 831
     def __init__(self, *args, **kwargs):
791 832
         kwargs['max_length'] = kwargs.get('max_length', 50)
792 833
         # Set db_index=True unless it's been set manually.
@@ -803,10 +844,14 @@ def formfield(self, **kwargs):
803 844
         return super(SlugField, self).formfield(**defaults)
804 845
 
805 846
 class SmallIntegerField(IntegerField):
  847
+    """Integer"""
  848
+    
806 849
     def get_internal_type(self):
807 850
         return "SmallIntegerField"
808 851
 
809 852
 class TextField(Field):
  853
+    """Text"""
  854
+    
810 855
     def get_internal_type(self):
811 856
         return "TextField"
812 857
 
@@ -816,7 +861,10 @@ def formfield(self, **kwargs):
816 861
         return super(TextField, self).formfield(**defaults)
817 862
 
818 863
 class TimeField(Field):
  864
+    """Time"""
  865
+    
819 866
     empty_strings_allowed = False
  867
+
820 868
     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
821 869
         self.auto_now, self.auto_now_add = auto_now, auto_now_add
822 870
         if auto_now or auto_now_add:
@@ -888,6 +936,8 @@ def formfield(self, **kwargs):
888 936
         return super(TimeField, self).formfield(**defaults)
889 937
 
890 938
 class URLField(CharField):
  939
+    """URL"""
  940
+    
891 941
     def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs):
892 942
         kwargs['max_length'] = kwargs.get('max_length', 200)
893 943
         self.verify_exists = verify_exists
@@ -899,6 +949,8 @@ def formfield(self, **kwargs):
899 949
         return super(URLField, self).formfield(**defaults)
900 950
 
901 951
 class XMLField(TextField):
  952
+    """XML text"""
  953
+    
902 954
     def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs):
903 955
         self.schema_path = schema_path
904 956
         Field.__init__(self, verbose_name, name, **kwargs)
4  django/db/models/fields/files.py
@@ -209,6 +209,8 @@ def __set__(self, instance, value):
209 209
         instance.__dict__[self.field.name] = value
210 210
 
211 211
 class FileField(Field):
  212
+    """File path"""
  213
+    
212 214
     # The class to wrap instance attributes in. Accessing the file object off
213 215
     # the instance will always return an instance of attr_class.
214 216
     attr_class = FieldFile
@@ -323,6 +325,8 @@ def delete(self, save=True):
323 325
         super(ImageFieldFile, self).delete(save)
324 326
 
325 327
 class ImageField(FileField):
  328
+    """File path"""
  329
+    
326 330
     attr_class = ImageFieldFile
327 331
     descriptor_class = ImageFileDescriptor
328 332
 
11  django/db/models/fields/related.py
@@ -691,6 +691,8 @@ def get_related_field(self):
@@ -788,12 +790,13 @@ def db_type(self):
@@ -847,6 +850,8 @@ def set_managed(field, model, cls):

0 notes on commit 0986a4d

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