Ticket #11688: 11688-verbose_name_plural_evolution-1.diff

File 11688-verbose_name_plural_evolution-1.diff, 77.9 KB (added by Ramiro Morales, 13 years ago)

First iteration of a proposed implementation of this feature

  • django/contrib/admin/actions.py

    diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py
    a b  
    2020
    2121    Next, it delets all selected objects and redirects back to the change list.
    2222    """
    23     opts = modeladmin.model._meta
     23    model = modeladmin.model
     24    opts = model._meta
    2425    app_label = opts.app_label
    2526
    2627    # Check that the user has delete permission for the actual model
    2728    if not modeladmin.has_delete_permission(request):
    2829        raise PermissionDenied
    2930
    30     using = router.db_for_write(modeladmin.model)
     31    using = router.db_for_write(model)
    3132
    3233    # Populate deletable_objects, a data structure of all related objects that
    3334    # will also be deleted.
     
    4647                modeladmin.log_deletion(request, obj, obj_display)
    4748            queryset.delete()
    4849            modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % {
    49                 "count": n, "items": model_ngettext(modeladmin.opts, n)
     50                "count": n, "items": model_ngettext(model, n)
    5051            })
    5152        # Return None to display the change list page again.
    5253        return None
    5354
    54     if len(queryset) == 1:
    55         objects_name = force_unicode(opts.verbose_name)
    56     else:
    57         objects_name = force_unicode(opts.verbose_name_plural)
     55    objects_name = force_unicode(model.get_verbose_name(len(queryset)))
    5856
    5957    if perms_needed or protected:
    6058        title = _("Cannot delete %(name)s") % {"name": objects_name}
  • django/contrib/admin/filters.py

    diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py
    a b  
    150150        if hasattr(field, 'verbose_name'):
    151151            self.lookup_title = field.verbose_name
    152152        else:
    153             self.lookup_title = other_model._meta.verbose_name
     153            self.lookup_title = other_model.get_verbose_name()
    154154        rel_name = other_model._meta.pk.name
    155155        self.lookup_kwarg = '%s__%s__exact' % (self.field_path, rel_name)
    156156        self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)
  • django/contrib/admin/models.py

    diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
    a b  
    22from django.contrib.contenttypes.models import ContentType
    33from django.contrib.auth.models import User
    44from django.contrib.admin.util import quote
    5 from django.utils.translation import ugettext_lazy as _
     5from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    66from django.utils.encoding import smart_unicode
    77from django.utils.safestring import mark_safe
    88
     
    2727    objects = LogEntryManager()
    2828
    2929    class Meta:
    30         verbose_name = _('log entry')
    31         verbose_name_plural = _('log entries')
    3230        db_table = 'django_admin_log'
    3331        ordering = ('-action_time',)
    3432
     
    6664        if self.content_type and self.object_id:
    6765            return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id)))
    6866        return None
     67
     68    @classmethod
     69    def verbose_names(cls, count=1):
     70        return ungettext_lazy('log entry', 'log entries', count)
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    a b  
    609609        """
    610610        choices = [] + default_choices
    611611        for func, name, description in self.get_actions(request).itervalues():
    612             choice = (name, description % model_format_dict(self.opts))
     612            choice = (name, description % model_format_dict(self.model))
    613613            choices.append(choice)
    614614        return choices
    615615
     
    674674            for formset in formsets:
    675675                for added_object in formset.new_objects:
    676676                    change_message.append(_('Added %(name)s "%(object)s".')
    677                                           % {'name': force_unicode(added_object._meta.verbose_name),
     677                                          % {'name': force_unicode(added_object.get_verbose_name()),
    678678                                             'object': force_unicode(added_object)})
    679679                for changed_object, changed_fields in formset.changed_objects:
    680680                    change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
    681681                                          % {'list': get_text_list(changed_fields, _('and')),
    682                                              'name': force_unicode(changed_object._meta.verbose_name),
     682                                             'name': force_unicode(changed_object.get_verbose_name()),
    683683                                             'object': force_unicode(changed_object)})
    684684                for deleted_object in formset.deleted_objects:
    685685                    change_message.append(_('Deleted %(name)s "%(object)s".')
    686                                           % {'name': force_unicode(deleted_object._meta.verbose_name),
     686                                          % {'name': force_unicode(deleted_object.get_verbose_name()),
    687687                                             'object': force_unicode(deleted_object)})
    688688        change_message = ' '.join(change_message)
    689689        return change_message or _('No fields changed.')
     
    769769        opts = obj._meta
    770770        pk_value = obj._get_pk_val()
    771771
    772         msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
     772        msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(obj.get_verbose_name()), 'obj': force_unicode(obj)}
    773773        # Here, we distinguish between different save types by checking for
    774774        # the presence of keys in request.POST.
    775775        if "_continue" in request.POST:
     
    785785                # escape() calls force_unicode.
    786786                (escape(pk_value), escapejs(obj)))
    787787        elif "_addanother" in request.POST:
    788             self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
     788            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(obj.get_verbose_name())))
    789789            return HttpResponseRedirect(request.path)
    790790        else:
    791791            self.message_user(request, msg)
     
    810810
    811811        # Handle proxy models automatically created by .only() or .defer().
    812812        # Refs #14529
    813         verbose_name = opts.verbose_name
     813        verbose_name = obj.get_verbose_name()
    814814        module_name = opts.module_name
    815815        if obj._deferred:
    816816            opts_ = opts.proxy_for_model._meta
    817             verbose_name = opts_.verbose_name
     817            verbose_name = opts.proxy_for_model.get_verbose_name()
    818818            module_name = opts_.module_name
    819819
    820820        pk_value = obj._get_pk_val()
     
    995995            media = media + inline_admin_formset.media
    996996
    997997        context = {
    998             'title': _('Add %s') % force_unicode(opts.verbose_name),
     998            'title': _('Add %s') % force_unicode(model.get_verbose_name()),
    999999            'adminform': adminForm,
    10001000            'is_popup': "_popup" in request.REQUEST,
    10011001            'show_delete': False,
     
    10201020            raise PermissionDenied
    10211021
    10221022        if obj is None:
    1023             raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
     1023            raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(model.get_verbose_name()), 'key': escape(object_id)})
    10241024
    10251025        if request.method == 'POST' and "_saveasnew" in request.POST:
    10261026            return self.add_view(request, form_url=reverse('admin:%s_%s_add' %
     
    10861086            media = media + inline_admin_formset.media
    10871087
    10881088        context = {
    1089             'title': _('Change %s') % force_unicode(opts.verbose_name),
     1089            'title': _('Change %s') % force_unicode(model.get_verbose_name()),
    10901090            'adminform': adminForm,
    10911091            'object_id': object_id,
    10921092            'original': obj,
     
    11051105        The 'change list' admin view for this model.
    11061106        """
    11071107        from django.contrib.admin.views.main import ERROR_FLAG
    1108         opts = self.model._meta
     1108        model = self.model
     1109        opts = model._meta
    11091110        app_label = opts.app_label
    11101111        if not self.has_change_permission(request, None):
    11111112            raise PermissionDenied
     
    11941195                        changecount += 1
    11951196
    11961197                if changecount:
    1197                     if changecount == 1:
    1198                         name = force_unicode(opts.verbose_name)
    1199                     else:
    1200                         name = force_unicode(opts.verbose_name_plural)
     1198                    name = force_unicode(model.get_verbose_name(changecount))
    12011199                    msg = ungettext("%(count)s %(name)s was changed successfully.",
    12021200                                    "%(count)s %(name)s were changed successfully.",
    12031201                                    changecount) % {'count': changecount,
    1204                                                     'name': name,
    1205                                                     'obj': force_unicode(obj)}
     1202                                                    'name': name}
    12061203                    self.message_user(request, msg)
    12071204
    12081205                return HttpResponseRedirect(request.get_full_path())
     
    12291226            'All %(total_count)s selected', cl.result_count)
    12301227
    12311228        context = {
    1232             'module_name': force_unicode(opts.verbose_name_plural),
     1229            'module_name': force_unicode(model.get_verbose_name(0)),
    12331230            'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
    12341231            'selection_note_all': selection_note_all % {'total_count': cl.result_count},
    12351232            'title': cl.title,
     
    12551252    @transaction.commit_on_success
    12561253    def delete_view(self, request, object_id, extra_context=None):
    12571254        "The 'delete' admin view for this model."
    1258         opts = self.model._meta
     1255        model = self.model
     1256        opts = model._meta
    12591257        app_label = opts.app_label
    12601258
    12611259        obj = self.get_object(request, unquote(object_id))
     
    12641262            raise PermissionDenied
    12651263
    12661264        if obj is None:
    1267             raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
     1265            raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(model.get_verbose_name()), 'key': escape(object_id)})
    12681266
    1269         using = router.db_for_write(self.model)
     1267        using = router.db_for_write(model)
    12701268
    12711269        # Populate deleted_objects, a data structure of all related objects that
    12721270        # will also be deleted.
     
    12801278            self.log_deletion(request, obj, obj_display)
    12811279            self.delete_model(request, obj)
    12821280
    1283             self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
     1281            self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(model.get_verbose_name()), 'obj': force_unicode(obj_display)})
    12841282
    12851283            if not self.has_change_permission(request, None):
    12861284                return HttpResponseRedirect(reverse('admin:index',
     
    12891287                                        (opts.app_label, opts.module_name),
    12901288                                        current_app=self.admin_site.name))
    12911289
    1292         object_name = force_unicode(opts.verbose_name)
     1290        object_name = force_unicode(model.get_verbose_name())
    12931291
    12941292        if perms_needed or protected:
    12951293            title = _("Cannot delete %(name)s") % {"name": object_name}
     
    13291327        context = {
    13301328            'title': _('Change history: %s') % force_unicode(obj),
    13311329            'action_list': action_list,
    1332             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
     1330            'module_name': capfirst(force_unicode(model.get_verbose_name(0))),
    13331331            'object': obj,
    13341332            'app_label': app_label,
    13351333            'opts': opts,
     
    13651363        self.opts = self.model._meta
    13661364        super(InlineModelAdmin, self).__init__()
    13671365        if self.verbose_name is None:
    1368             self.verbose_name = self.model._meta.verbose_name
     1366            self.verbose_name = self.model.get_verbose_name()
    13691367        if self.verbose_name_plural is None:
    1370             self.verbose_name_plural = self.model._meta.verbose_name_plural
     1368            self.verbose_name_plural = self.model.get_verbose_name(0)
    13711369
    13721370    @property
    13731371    def media(self):
  • django/contrib/admin/sites.py

    diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
    a b  
    341341                if True in perms.values():
    342342                    info = (app_label, model._meta.module_name)
    343343                    model_dict = {
    344                         'name': capfirst(model._meta.verbose_name_plural),
     344                        'name': capfirst(model.get_verbose_name(0)),
    345345                        'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
    346346                        'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
    347347                        'perms': perms,
     
    387387                    if True in perms.values():
    388388                        info = (app_label, model._meta.module_name)
    389389                        model_dict = {
    390                             'name': capfirst(model._meta.verbose_name_plural),
     390                            'name': capfirst(model.get_verbose_name(0)),
    391391                            'admin_url': reverse('admin:%s_%s_changelist' % info, current_app=self.name),
    392392                            'add_url': reverse('admin:%s_%s_add' % info, current_app=self.name),
    393393                            'perms': perms,
  • django/contrib/admin/util.py

    diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
    a b  
    9090            p = '%s.%s' % (opts.app_label,
    9191                           opts.get_delete_permission())
    9292            if not user.has_perm(p):
    93                 perms_needed.add(opts.verbose_name)
     93                perms_needed.add(obj.get_verbose_name())
    9494            # Display a link to the admin page.
    9595            return mark_safe(u'%s: <a href="%s">%s</a>' %
    96                              (escape(capfirst(opts.verbose_name)),
     96                             (escape(capfirst(obj.get_verbose_name())),
    9797                              admin_url,
    9898                              escape(obj)))
    9999        else:
    100100            # Don't display link to edit, because it either has no
    101101            # admin or is edited inline.
    102             return u'%s: %s' % (capfirst(opts.verbose_name),
     102            return u'%s: %s' % (capfirst(obj.get_verbose_name()),
    103103                                force_unicode(obj))
    104104
    105105    to_delete = collector.nested(format_callback)
     
    165165    Return a `dict` with keys 'verbose_name' and 'verbose_name_plural',
    166166    typically for use with string formatting.
    167167
    168     `obj` may be a `Model` instance, `Model` subclass, or `QuerySet` instance.
     168    `obj` may be a `Model` instance, `Model` subclass, or a `QuerySet` instance.
    169169
    170170    """
    171171    if isinstance(obj, (models.Model, models.base.ModelBase)):
    172         opts = obj._meta
     172        model = obj
    173173    elif isinstance(obj, models.query.QuerySet):
    174         opts = obj.model._meta
    175     else:
    176         opts = obj
     174        model = obj.model
    177175    return {
    178         'verbose_name': force_unicode(opts.verbose_name),
    179         'verbose_name_plural': force_unicode(opts.verbose_name_plural)
     176        'verbose_name': force_unicode(model.get_verbose_name()),
     177        'verbose_name_plural': force_unicode(model.get_verbose_name(0))
    180178    }
    181179
    182180
     
    229227def label_for_field(name, model, model_admin=None, return_attr=False):
    230228    """
    231229    Returns a sensible label for a field name. The name can be a callable or the
    232     name of an object attributes, as well as a genuine fields. If return_attr is
     230    name of an object attribute, as well as a genuine field. If return_attr is
    233231    True, the resolved attribute (which could be a callable) is also returned.
    234232    This will be None if (and only if) the name refers to a field.
    235233    """
     
    237235    try:
    238236        field = model._meta.get_field_by_name(name)[0]
    239237        if isinstance(field, RelatedObject):
    240             label = field.opts.verbose_name
     238            label = field.get_verbose_name()
    241239        else:
    242240            label = field.verbose_name
    243241    except models.FieldDoesNotExist:
    244242        if name == "__unicode__":
    245             label = force_unicode(model._meta.verbose_name)
     243            label = force_unicode(model.get_verbose_name())
    246244            attr = unicode
    247245        elif name == "__str__":
    248             label = smart_str(model._meta.verbose_name)
     246            label = smart_str(model.get_verbose_name())
    249247            attr = str
    250248        else:
    251249            if callable(name):
     
    311309
    312310
    313311def get_model_from_relation(field):
    314     if isinstance(field, models.related.RelatedObject):
     312    if isinstance(field, RelatedObject):
    315313        return field.model
    316314    elif getattr(field, 'rel'): # or isinstance?
    317315        return field.rel.to
  • django/contrib/admin/views/main.py

    diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
    a b  
    8181            title = ugettext('Select %s')
    8282        else:
    8383            title = ugettext('Select %s to change')
    84         self.title = title % force_unicode(self.opts.verbose_name)
     84        self.title = title % force_unicode(self.model.get_verbose_name())
    8585        self.pk_attname = self.lookup_opts.pk.attname
    8686
    8787    def get_filters(self, request, use_distinct=False):
  • django/contrib/auth/models.py

    diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
    a b  
    55from django.db import models
    66from django.db.models.manager import EmptyManager
    77from django.utils.encoding import smart_str
    8 from django.utils.translation import ugettext_lazy as _
    98from django.utils import timezone
     9from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    1010
    1111from django.contrib import auth
    1212from django.contrib.auth.signals import user_logged_in
     
    5454    objects = PermissionManager()
    5555
    5656    class Meta:
    57         verbose_name = _('permission')
    58         verbose_name_plural = _('permissions')
    5957        unique_together = (('content_type', 'codename'),)
    6058        ordering = ('content_type__app_label', 'content_type__model', 'codename')
    6159
     
    6967        return (self.codename,) + self.content_type.natural_key()
    7068    natural_key.dependencies = ['contenttypes.contenttype']
    7169
     70    @classmethod
     71    def verbose_names(cls, count=1):
     72        return ungettext_lazy('permission', 'permissions', count)
     73
    7274class Group(models.Model):
    7375    """Groups are a generic way of categorizing users to apply permissions, or some other label, to those users. A user can belong to any number of groups.
    7476
     
    7981    name = models.CharField(_('name'), max_length=80, unique=True)
    8082    permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True)
    8183
    82     class Meta:
    83         verbose_name = _('group')
    84         verbose_name_plural = _('groups')
    85 
    8684    def __unicode__(self):
    8785        return self.name
    8886
     87    @classmethod
     88    def verbose_names(cls, count=1):
     89        return ungettext_lazy('group', 'groups', count)
     90
    8991class UserManager(models.Manager):
    9092    def create_user(self, username, email=None, password=None):
    9193        """
     
    188190    user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True)
    189191    objects = UserManager()
    190192
    191     class Meta:
    192         verbose_name = _('user')
    193         verbose_name_plural = _('users')
    194 
    195193    def __unicode__(self):
    196194        return self.username
    197195
     
    337335                raise SiteProfileNotAvailable
    338336        return self._profile_cache
    339337
     338    @classmethod
     339    def verbose_names(cls, count=1):
     340        return ungettext_lazy('user', 'users', count)
     341
    340342
    341343class AnonymousUser(object):
    342344    id = None
  • django/contrib/comments/models.py

    diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py
    a b  
    55from django.contrib.sites.models import Site
    66from django.db import models
    77from django.core import urlresolvers
    8 from django.utils.translation import ugettext_lazy as _
    98from django.utils import timezone
     9from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    1010from django.conf import settings
    1111
    1212COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
     
    7373        db_table = "django_comments"
    7474        ordering = ('submit_date',)
    7575        permissions = [("can_moderate", "Can moderate comments")]
    76         verbose_name = _('comment')
    77         verbose_name_plural = _('comments')
    7876
    7977    def __unicode__(self):
    8078        return "%s: %s..." % (self.name, self.comment[:50])
     
    152150        }
    153151        return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
    154152
     153    @classmethod
     154    def verbose_names(cls, count=1):
     155        return ungettext_lazy('comment', 'comments', count)
     156
    155157class CommentFlag(models.Model):
    156158    """
    157159    Records a flag on a comment. This is intentionally flexible; right now, a
     
    178180    class Meta:
    179181        db_table = 'django_comment_flags'
    180182        unique_together = [('user', 'comment', 'flag')]
    181         verbose_name = _('comment flag')
    182         verbose_name_plural = _('comment flags')
    183183
    184184    def __unicode__(self):
    185185        return "%s flag of comment ID %s by %s" % \
     
    189189        if self.flag_date is None:
    190190            self.flag_date = timezone.now()
    191191        super(CommentFlag, self).save(*args, **kwargs)
     192
     193    @classmethod
     194    def verbose_names(cls, count=1):
     195        return ungettext_lazy('comment flag', 'comment flags', count)
  • django/contrib/contenttypes/models.py

    diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
    a b  
    11from django.db import models
    2 from django.utils.translation import ugettext_lazy as _
     2from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    33from django.utils.encoding import smart_unicode, force_unicode
    44
    55class ContentTypeManager(models.Manager):
     
    127127    objects = ContentTypeManager()
    128128
    129129    class Meta:
    130         verbose_name = _('content type')
    131         verbose_name_plural = _('content types')
    132130        db_table = 'django_content_type'
    133131        ordering = ('name',)
    134132        unique_together = (('app_label', 'model'),)
     
    145143        if not model or self.name != model._meta.verbose_name_raw:
    146144            return self.name
    147145        else:
    148             return force_unicode(model._meta.verbose_name)
     146            return force_unicode(model.get_verbose_name())
    149147
    150148    def model_class(self):
    151149        "Returns the Python model class for this type of content."
     
    170168
    171169    def natural_key(self):
    172170        return (self.app_label, self.model)
     171
     172    @classmethod
     173    def verbose_names(cls, count=1):
     174        return ungettext_lazy('content type', 'content types', count)
  • django/contrib/databrowse/datastructures.py

    diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py
    a b  
    1818        self.site = site
    1919        self.model = model
    2020        self.model_list = site.registry.keys()
    21         self.verbose_name = model._meta.verbose_name
    22         self.verbose_name_plural = model._meta.verbose_name_plural
     21        self.verbose_name = model.get_verbose_name()
     22        self.verbose_name_plural = model.get_verbose_name(0)
    2323
    2424    def __repr__(self):
    2525        return '<EasyModel for %s>' % smart_str(self.model._meta.object_name)
  • django/contrib/flatpages/models.py

    diff --git a/django/contrib/flatpages/models.py b/django/contrib/flatpages/models.py
    a b  
    11from django.db import models
    22from django.contrib.sites.models import Site
    3 from django.utils.translation import ugettext_lazy as _
     3from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    44
    55
    66class FlatPage(models.Model):
     
    1515
    1616    class Meta:
    1717        db_table = 'django_flatpage'
    18         verbose_name = _('flat page')
    19         verbose_name_plural = _('flat pages')
    2018        ordering = ('url',)
    2119
    2220    def __unicode__(self):
     
    2422
    2523    def get_absolute_url(self):
    2624        return self.url
     25
     26    @classmethod
     27    def verbose_names(cls, count=1):
     28        return ungettext_lazy('flat page', 'flat pages', count)
  • django/contrib/redirects/models.py

    diff --git a/django/contrib/redirects/models.py b/django/contrib/redirects/models.py
    a b  
    11from django.db import models
    22from django.contrib.sites.models import Site
    3 from django.utils.translation import ugettext_lazy as _
     3from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    44
    55class Redirect(models.Model):
    66    site = models.ForeignKey(Site)
     
    1010        help_text=_("This can be either an absolute path (as above) or a full URL starting with 'http://'."))
    1111
    1212    class Meta:
    13         verbose_name = _('redirect')
    14         verbose_name_plural = _('redirects')
    1513        db_table = 'django_redirect'
    1614        unique_together=(('site', 'old_path'),)
    1715        ordering = ('old_path',)
    18    
     16
    1917    def __unicode__(self):
    2018        return "%s ---> %s" % (self.old_path, self.new_path)
     19
     20    @classmethod
     21    def verbose_names(cls, count=1):
     22        return ungettext_lazy('redirect', 'redirects', count)
  • django/contrib/sessions/models.py

    diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py
    a b  
    11from django.db import models
    2 from django.utils.translation import ugettext_lazy as _
     2from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    33
    44
    55class SessionManager(models.Manager):
     
    4343
    4444    class Meta:
    4545        db_table = 'django_session'
    46         verbose_name = _('session')
    47         verbose_name_plural = _('sessions')
    4846
    4947    def get_decoded(self):
    5048        return SessionStore().decode(self.session_data)
    5149
     50    @classmethod
     51    def verbose_names(cls, count=1):
     52        return ungettext_lazy('session', 'sessions', count)
     53
    5254
    5355# At bottom to avoid circular import
    5456from django.contrib.sessions.backends.db import SessionStore
  • django/contrib/sites/models.py

    diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py
    a b  
    11from django.db import models
    2 from django.utils.translation import ugettext_lazy as _
     2from django.utils.translation import ugettext_lazy as _, ungettext_lazy
    33
    44
    55SITE_CACHE = {}
     
    4040
    4141    class Meta:
    4242        db_table = 'django_site'
    43         verbose_name = _('site')
    44         verbose_name_plural = _('sites')
    4543        ordering = ('domain',)
    4644
    4745    def __unicode__(self):
     
    6159        except KeyError:
    6260            pass
    6361
     62    @classmethod
     63    def verbose_names(cls, count=1):
     64        return ungettext_lazy('site', 'sites', count)
     65
    6466
    6567class RequestSite(object):
    6668    """
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    a b  
    22import sys
    33from functools import update_wrapper
    44from itertools import izip
     5import re
    56
    67import django.db.models.manager     # Imported to register signal handler.
    78from django.conf import settings
     
    1617from django.db.models.query import Q
    1718from django.db.models.query_utils import DeferredAttribute
    1819from django.db.models.deletion import Collector
    19 from django.db.models.options import Options
     20from django.db.models.options import Options, CAMEL_CASE_RE
    2021from django.db.models import signals
    2122from django.db.models.loading import register_models, get_model
    22 from django.utils.translation import ugettext_lazy as _
     23from django.utils.translation import ugettext_lazy as _, string_concat
    2324from django.utils.functional import curry
    2425from django.utils.encoding import smart_str, force_unicode
    2526from django.utils.text import get_text_list, capfirst
     
    9899        for obj_name, obj in attrs.items():
    99100            new_class.add_to_class(obj_name, obj)
    100101
     102        if hasattr(new_class, 'verbose_names'):
     103            new_class._meta._verbose_name = new_class.get_verbose_name()
     104            new_class._meta._verbose_name_plural = new_class.get_verbose_name(0)
     105
    101106        # All the fields of any type declared on this model
    102107        new_fields = new_class._meta.local_fields + \
    103108                     new_class._meta.local_many_to_many + \
     
    770775
    771776    def unique_error_message(self, model_class, unique_check):
    772777        opts = model_class._meta
    773         model_name = capfirst(opts.verbose_name)
     778        model_name = capfirst(self.get_verbose_name())
    774779
    775780        # A unique field
    776781        if len(unique_check) == 1:
     
    849854        if errors:
    850855            raise ValidationError(errors)
    851856
     857    @classmethod
     858    def get_verbose_name(cls, count=1):
     859        if hasattr(cls, 'verbose_names'):
     860            retv = cls.verbose_names(count)
     861            if retv is not None:
     862                return retv
     863        if count == 1:
     864            return CAMEL_CASE_RE.sub(' \\1', cls.__name__).lower().strip()
     865        return string_concat(cls.get_verbose_name(1), 's')
     866
    852867
    853868############################################
    854869# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    a b  
    115115    def set_attributes_from_rel(self):
    116116        self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
    117117        if self.verbose_name is None:
    118             self.verbose_name = self.rel.to._meta.verbose_name
     118            self.verbose_name = self.rel.to.get_verbose_name()
    119119        self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name
    120120
    121121    def do_related_class(self, other, cls):
     
    940940        qs = qs.complex_filter(self.rel.limit_choices_to)
    941941        if not qs.exists():
    942942            raise exceptions.ValidationError(self.error_messages['invalid'] % {
    943                 'model': self.rel.to._meta.verbose_name, 'pk': value})
     943                'model': self.rel.to.get_verbose_name(), 'pk': value})
    944944
    945945    def get_attname(self):
    946946        return '%s_id' % self.name
     
    10521052def create_many_to_many_intermediary_model(field, klass):
    10531053    from django.db import models
    10541054    managed = True
     1055
     1056    def verbose_name_method(f, t):
     1057        def verbose_names(cls, count=1):
     1058            return '%(from)s-%(to)s relationship%(extra)s' % {'from': f, 'to': t, 'extra': 's' if count != 1 else ''}
     1059        return verbose_names
     1060
    10551061    if isinstance(field.rel.to, basestring) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
    10561062        to_model = field.rel.to
    10571063        to = to_model.split('.')[-1]
     
    10801086        'app_label': klass._meta.app_label,
    10811087        'db_tablespace': klass._meta.db_tablespace,
    10821088        'unique_together': (from_, to),
    1083         'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to},
    1084         'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to},
    10851089    })
    10861090    # Construct and return the new class.
    10871091    return type(name, (models.Model,), {
    10881092        'Meta': meta,
    10891093        '__module__': klass.__module__,
    10901094        from_: models.ForeignKey(klass, related_name='%s+' % name, db_tablespace=field.db_tablespace),
    1091         to: models.ForeignKey(to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace)
     1095        to: models.ForeignKey(to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace),
     1096        'verbose_names': classmethod(verbose_name_method(from_, to)),
    10921097    })
    10931098
    10941099class ManyToManyField(RelatedField, Field):
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    a b  
     1from bisect import bisect
    12import re
    2 from bisect import bisect
     3import warnings
    34
    45from django.conf import settings
    56from django.db.models.related import RelatedObject
     
    1112from django.utils.encoding import force_unicode, smart_str
    1213from django.utils.datastructures import SortedDict
    1314
    14 # Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces".
    15 get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', ' \\1', class_name).lower().strip()
    16 
    17 DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
     15DEFAULT_NAMES = ('db_table', 'ordering',
    1816                 'unique_together', 'permissions', 'get_latest_by',
    1917                 'order_with_respect_to', 'app_label', 'db_tablespace',
    2018                 'abstract', 'managed', 'proxy', 'auto_created')
    2119
     20DEPRECATED_NAMES = ('verbose_name', 'verbose_name_plural')
     21
     22CAMEL_CASE_RE = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
     23
    2224class Options(object):
    2325    def __init__(self, meta, app_label=None):
    2426        self.local_fields, self.local_many_to_many = [], []
    2527        self.virtual_fields = []
    26         self.module_name, self.verbose_name = None, None
    27         self.verbose_name_plural = None
     28        self.module_name, self._verbose_name = None, None
     29        self._verbose_name_plural = None
    2830        self.db_table = ''
    2931        self.ordering = []
    3032        self.unique_together =  []
     
    5456        # from *other* models. Needed for some admin checks. Internal use only.
    5557        self.related_fkey_lookups = []
    5658
     59    def _get_verbose_name(self):
     60        warnings.warn("Meta.verbose_name is deprecated. Use a verbose_names()"
     61            " classmethod in the model instead.", PendingDeprecationWarning)
     62        return self._verbose_name
     63    def _set_verbose_name(self, value):
     64        self._verbose_name = value
     65    verbose_name = property(_get_verbose_name, _set_verbose_name)
     66
     67    def _get_verbose_name_plural(self):
     68        warnings.warn("Meta.verbose_name_plural is deprecated. Use a "
     69            "verbose_names() classmethod in the model instead.",
     70            PendingDeprecationWarning)
     71        return self._verbose_name_plural
     72    def _set_verbose_name_plural(self, value):
     73        self._verbose_name_plural = value
     74    verbose_name_plural = property(_get_verbose_name_plural, _set_verbose_name_plural)
     75
    5776    def contribute_to_class(self, cls, name):
    5877        from django.db import connection
    5978        from django.db.backends.util import truncate_name
     
    6382        # First, construct the default values for these options.
    6483        self.object_name = cls.__name__
    6584        self.module_name = self.object_name.lower()
    66         self.verbose_name = get_verbose_name(self.object_name)
     85        self._verbose_name = CAMEL_CASE_RE.sub(' \\1', self.object_name).lower().strip()
    6786
    6887        # Next, apply any overridden values from 'class Meta'.
    6988        if self.meta:
     
    8099                elif hasattr(self.meta, attr_name):
    81100                    setattr(self, attr_name, getattr(self.meta, attr_name))
    82101
     102            for attr_name in DEPRECATED_NAMES:
     103                if attr_name in meta_attrs:
     104                    warnings.warn("%(cls)s: Meta.%(attr_name)s is deprecated. Use a "
     105                        "verbose_names() classmethod in the model "
     106                        "instead." % {'cls': cls, 'attr_name': attr_name},
     107                        PendingDeprecationWarning)
     108                    setattr(self, '_%s' % attr_name, meta_attrs.pop(attr_name))
     109
    83110            # unique_together can be either a tuple of tuples, or a single
    84111            # tuple of two strings. Normalize it to a tuple of tuples, so that
    85112            # calling code can uniformly expect that.
     
    90117
    91118            # verbose_name_plural is a special case because it uses a 's'
    92119            # by default.
    93             if self.verbose_name_plural is None:
    94                 self.verbose_name_plural = string_concat(self.verbose_name, 's')
     120            if self._verbose_name_plural is None:
     121                self._verbose_name_plural = string_concat(self._verbose_name, 's')
    95122
    96123            # Any leftover attributes must be invalid.
    97124            if meta_attrs != {}:
    98125                raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
    99126        else:
    100             self.verbose_name_plural = string_concat(self.verbose_name, 's')
     127            self._verbose_name_plural = string_concat(self._verbose_name, 's')
    101128        del self.meta
    102129
    103130        # If the db_table wasn't provided, use the app_label + module_name.
     
    199226        """
    200227        lang = get_language()
    201228        deactivate_all()
    202         raw = force_unicode(self.verbose_name)
     229        raw = force_unicode(self._verbose_name)
    203230        activate(lang)
    204231        return raw
    205232    verbose_name_raw = property(verbose_name_raw)
  • django/db/models/related.py

    diff --git a/django/db/models/related.py b/django/db/models/related.py
    a b  
    3636                {'%s__isnull' % self.parent_model._meta.module_name: False})
    3737        lst = [(x._get_pk_val(), smart_unicode(x)) for x in queryset]
    3838        return first_choice + lst
    39        
     39
    4040    def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
    4141        # Defer to the actual field definition for db prep
    4242        return self.field.get_db_prep_lookup(lookup_type, value,
     
    6767
    6868    def get_cache_name(self):
    6969        return "_%s_cache" % self.get_accessor_name()
     70
     71    def get_verbose_name(self, count=1):
     72        return self.model.get_verbose_name(count)
  • django/views/generic/create_update.py

    diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
    a b  
    9595        return model.objects.get(**lookup_kwargs)
    9696    except ObjectDoesNotExist:
    9797        raise Http404("No %s found for %s"
    98                       % (model._meta.verbose_name, lookup_kwargs))
     98                      % (model.get_verbose_name(), lookup_kwargs))
    9999
    100100def create_object(request, model=None, template_name=None,
    101101        template_loader=loader, extra_context=None, post_save_redirect=None,
     
    119119            new_object = form.save()
    120120
    121121            msg = ugettext("The %(verbose_name)s was created successfully.") %\
    122                                     {"verbose_name": model._meta.verbose_name}
     122                                    {"verbose_name": model.get_verbose_name()}
    123123            messages.success(request, msg, fail_silently=True)
    124124            return redirect(post_save_redirect, new_object)
    125125    else:
     
    162162        if form.is_valid():
    163163            obj = form.save()
    164164            msg = ugettext("The %(verbose_name)s was updated successfully.") %\
    165                                     {"verbose_name": model._meta.verbose_name}
     165                                    {"verbose_name": model.get_verbose_name()}
    166166            messages.success(request, msg, fail_silently=True)
    167167            return redirect(post_save_redirect, obj)
    168168    else:
     
    205205    if request.method == 'POST':
    206206        obj.delete()
    207207        msg = ugettext("The %(verbose_name)s was deleted.") %\
    208                                     {"verbose_name": model._meta.verbose_name}
     208                                    {"verbose_name": model.get_verbose_name()}
    209209        messages.success(request, msg, fail_silently=True)
    210210        return HttpResponseRedirect(post_delete_redirect)
    211211    else:
  • django/views/generic/date_based.py

    diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
    a b  
    3434        queryset = queryset.filter(**{'%s__lte' % date_field: datetime.datetime.now()})
    3535    date_list = queryset.dates(date_field, 'year')[::-1]
    3636    if not date_list and not allow_empty:
    37         raise Http404("No %s available" % model._meta.verbose_name)
     37        raise Http404("No %s available" % model.get_verbose_name())
    3838
    3939    if date_list and num_latest:
    4040        latest = queryset.order_by('-'+date_field)[:num_latest]
     
    354354    try:
    355355        obj = queryset.get(**lookup_kwargs)
    356356    except ObjectDoesNotExist:
    357         raise Http404("No %s found for" % model._meta.verbose_name)
     357        raise Http404("No %s found for" % model.get_verbose_name())
    358358    if not template_name:
    359359        template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
    360360    if template_name_field:
  • django/views/generic/dates.py

    diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py
    a b  
    195195
    196196        if not allow_empty and not qs:
    197197            raise Http404(_(u"No %(verbose_name_plural)s available") % {
    198                     'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural)
     198                    'verbose_name_plural': force_unicode(qs.model.get_verbose_name(0))
    199199            })
    200200
    201201        return qs
     
    464464
    465465        if not self.get_allow_future() and date > datetime.date.today():
    466466            raise Http404(_(u"Future %(verbose_name_plural)s not available because %(class_name)s.allow_future is False.") % {
    467                 'verbose_name_plural': qs.model._meta.verbose_name_plural,
     467                'verbose_name_plural': qs.model.get_verbose_name(0),
    468468                'class_name': self.__class__.__name__,
    469469            })
    470470
  • django/views/generic/detail.py

    diff --git a/django/views/generic/detail.py b/django/views/generic/detail.py
    a b  
    4949            obj = queryset.get()
    5050        except ObjectDoesNotExist:
    5151            raise Http404(_(u"No %(verbose_name)s found matching the query") %
    52                           {'verbose_name': queryset.model._meta.verbose_name})
     52                          {'verbose_name': queryset.model.get_verbose_name()})
    5353        return obj
    5454
    5555    def get_queryset(self):
  • django/views/generic/list_detail.py

    diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
    a b  
    131131    try:
    132132        obj = queryset.get()
    133133    except ObjectDoesNotExist:
    134         raise Http404("No %s found matching the query" % (model._meta.verbose_name))
     134        raise Http404("No %s found matching the query" % (model.get_verbose_name()))
    135135    if not template_name:
    136136        template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
    137137    if template_name_field:
  • docs/ref/models/instances.txt

    diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
    a b  
    540540More details on named URL patterns are in the :doc:`URL dispatch documentation
    541541</topics/http/urls>`.
    542542
     543``verbose_names``
     544-----------------
     545
     546.. classmethod:: Model.verbose_names(count=1)
     547
     548.. versionadded:: 1.4
     549
     550A Python classmethod.
     551
     552This method has no default implementation and you might provide one depending
     553on the the human readable name you need for your model.
     554
     555The *count* argument is the quantity of model instances the verbose name is
     556being requested for.
     557
     558It provides a straight migration path from the :attr:`~Options.verbose_name` and
     559:attr:`~Options.verbose_name_plural` options that entered a deprecation cycle
     560starting with Django 1.4.  For example this model declaration::
     561
     562    class SSN(models.Model):
     563        value = models.CharField(max_length=11)
     564
     565        class Meta:
     566            verbose_name = 'social security number'
     567
     568needs to be changed to::
     569
     570    class SSN(models.Model):
     571        value = models.CharField(max_length=11)
     572
     573        @classmethod
     574        def verbose_names(cls, count=1):
     575            return 'social security number'
     576
     577and this one::
     578
     579    class SecurityPolicy(models.Model):
     580        title = models.CharField(max_length=30)
     581
     582        class Meta:
     583            verbose_name_plural = 'security policies'
     584
     585should be changed to::
     586
     587    class SecurityPolicy(models.Model):
     588        title = models.CharField(max_length=30)
     589
     590        @classmethod
     591        def verbose_names(cls, count=1):
     592            if count != 1:
     593                return 'security policies'
     594
     595This new syntax can take in account the number of model instances at play to
     596decide the exact verbose name to show in user interaction contexts. It provides
     597for better internationalization of your application because the name of your
     598model is now translatable in a more correct way to many more locales::
     599
     600    from django.utils.translation import ugettext_lazy
     601
     602    class Man(models.Model):
     603        first_name = models.CharField(max_length=30)
     604
     605        @classmethod
     606        def verbose_names(cls, count=1):
     607            if count == 1:
     608                return ugettext_lazy('man')
     609            else:
     610                return ugettext_lazy('men')
     611
     612Although you usually will use the
     613:func:`~django.utils.translation.ungettext_lazy` function::
     614
     615    from django.utils.translation import ungettext_lazy
     616
     617    class Library(models.Model):
     618        city_name = models.CharField(max_length=30)
     619
     620        @classmethod
     621        def verbose_names(cls, count=1):
     622            return ungetttext_lazy('llbrary', 'libraries', count)
     623
     624.. note::
     625    Remember to declare this method as a classmethod::
     626
     627        class MyModel(models.Model):
     628            ...
     629
     630            @classmethod
     631            def verbose_names(cls, count=1):
     632                ...
     633
     634.. seealso::
     635
     636    The :meth:`~Model.get_verbose_name` method that works together with this
     637    method.
     638
    543639Extra instance methods
    544640======================
    545641
    546642In addition to :meth:`~Model.save()`, :meth:`~Model.delete()`, a model object
    547643might have some of the following methods:
    548644
     645``get_*_display``
     646-----------------
     647
    549648.. method:: Model.get_FOO_display()
    550649
    551650For every field that has :attr:`~django.db.models.Field.choices` set, the
     
    570669    >>> p.get_gender_display()
    571670    'Male'
    572671
     672``get_next_by_*`` and ``get_prev_by_*``
     673---------------------------------------
     674
    573675.. method:: Model.get_next_by_FOO(\**kwargs)
    574676.. method:: Model.get_previous_by_FOO(\**kwargs)
    575677
     
    587689primary key as a tie-breaker. This guarantees that no records are skipped or
    588690duplicated. That also means you cannot use those methods on unsaved objects.
    589691
     692``get_verbose_name``
     693--------------------
     694
     695.. classmethod:: Model.get_verbose_name(count=1)
     696
     697.. versionadded:: 1.4
     698
     699A Python classmethod.
     700
     701Django automatically gives all models an implementation of this method so you
     702usually don't need to override it.
     703
     704It provides an official API to access translated and correctly pluralized
     705verbose names of models (something that previously involved using non public
     706APIs like accessing the ``Model._meta.verbose_name`` and
     707``Model._meta.verbose_name_plural`` attributes.)
     708
     709.. seealso::
     710
     711    The :meth:`~Model.verbose_names` user-provided classmethod that works
     712    together with this method.
     713
     714This method will always return a value independently of whether the model
     715implements the :meth:`~Model.verbose_names` classmethod or not. Django provides
     716fallback return values compatible with the default values of the deprecated
     717:attr:`~Options.verbose_name` and :attr:`~Options.verbose_name_plural` options.
     718
     719For example, given this model::
     720
     721    class Door(models.Model):
     722        height = models.PositiveIntegerField()
     723
     724then these are the return values of this method::
     725
     726    >>> Door.get_verbose_name(1) # One door
     727    >>> 'door' # Automatically provided singular verbose name
     728    >>> Door.get_verbose_name(3) # More than one door
     729    >>> 'doors'
     730    # Note how it returns an automatically provided simple naive pluralization
     731    # appending a 's' to the singular value
     732
     733Or, for the examples in the :meth:`~Model.verbose_names` section above::
     734
     735    >>> SSN.get_verbose_name() # One SSN, count default value
     736    >>> 'social security number'
     737    # Note how it returns the value returned by SSN.verbose_names(count) for a
     738    # value of count=1
     739    >>> SSN.get_verbose_name(0) # Zero SSN
     740    >>> 'social security numbers'
     741    # Note how it returns an automatically provided simple naive pluralization
     742    # appending a 's' to the singular value
     743
     744    >>> SecurityPolicy.get_verbose_name() # One policy
     745    >>> 'security policy'
     746    # Note how it returns a value automatically provided by Django by processing
     747    # the model class name
     748    >>> SecurityPolicy.get_verbose_name(10) # Ten policies
     749    >>> 'security policies'
     750    # Note how it returns the value returned by
     751    # SecurityPolicy.verbose_names(count) for a count value different from 1
  • docs/ref/models/options.txt

    diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
    a b  
    253253
    254254.. attribute:: Options.verbose_name
    255255
     256    .. deprecated:: 1.4
     257        This option has been replaced by the :meth:`~Model.verbose_names` model
     258        classmethod. Implement such method in your model to make Django aware
     259        of its human readable name(s).
     260
    256261    A human-readable name for the object, singular::
    257262
    258263        verbose_name = "pizza"
     
    265270
    266271.. attribute:: Options.verbose_name_plural
    267272
     273    .. deprecated:: 1.4
     274        This option has been replaced by the :meth:`~Model.verbose_names` model
     275        classmethod. Implement such method in your model to make Django aware
     276        of its human readable name(s).
     277
    268278    The plural name for the object::
    269279
    270280        verbose_name_plural = "stories"
  • docs/topics/db/models.txt

    diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
    a b  
    641641
    642642        class Meta:
    643643            ordering = ["horn_length"]
    644             verbose_name_plural = "oxen"
    645644
    646645Model metadata is "anything that's not a field", such as ordering options
    647 (:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`), or
    648 human-readable singular and plural names (:attr:`~Options.verbose_name` and
    649 :attr:`~Options.verbose_name_plural`). None are required, and adding ``class
    650 Meta`` to a model is completely optional.
     646(:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`).
     647None are required, and adding ``class Meta`` to a model is completely optional.
    651648
    652649A complete list of all possible ``Meta`` options can be found in the :doc:`model
    653650option reference </ref/models/options>`.
  • docs/topics/i18n/translation.txt

    diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
    a b  
    168168translation string and the number of objects.
    169169
    170170This function is useful when you need your Django application to be localizable
    171 to languages where the number and complexity of `plural forms
    172 <http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms>`_ is
    173 greater than the two forms used in English ('object' for the singular and
    174 'objects' for all the cases where ``count`` is different from one, irrespective
    175 of its value.)
     171to languages where the number and complexity of `plural forms`_ is greater than
     172the two forms as used in the English language (e.g. 'object' for the singular
     173and 'objects' for all the cases where ``count`` is different from one,
     174irrespective of its value.)
    176175
    177176For example::
    178177
     
    187186        }
    188187        return HttpResponse(page)
    189188
    190 In this example the number of objects is passed to the translation
    191 languages as the ``count`` variable.
     189In this example the number of objects is passed to the translation functions as
     190the ``count`` variable.
    192191
    193 Lets see a slightly more complex usage example::
     192Lets see a slightly more complex example::
    194193
    195194    from django.utils.translation import ungettext
    196195
    197196    count = Report.objects.count()
    198     if count == 1:
    199         name = Report._meta.verbose_name
    200     else:
    201         name = Report._meta.verbose_name_plural
     197    name = Report.get_verbose_name(count)
    202198
    203199    text = ungettext(
    204200            'There is %(count)d %(name)s available.',
     
    209205        'name': name
    210206    }
    211207
    212 Here we reuse localizable, hopefully already translated literals (contained in
    213 the ``verbose_name`` and ``verbose_name_plural`` model ``Meta`` options) for
    214 other parts of the sentence so all of it is consistently based on the
    215 cardinality of the elements at play.
     208Here we reuse localizable, potentially translated literals (as returned by the
     209:meth:`~django.db.models.Model.get_verbose_name` model classmethod) for other
     210parts of the sentence so all of it is consistently based on the cardinality of
     211the elements at play.
     212
     213.. _plural forms: http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms
    216214
    217215.. _pluralization-var-notes:
    218216
  • tests/modeltests/custom_pk/models.py

    diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
    a b  
    2626class Business(models.Model):
    2727    name = models.CharField(max_length=20, primary_key=True)
    2828    employees = models.ManyToManyField(Employee)
    29     class Meta:
    30         verbose_name_plural = 'businesses'
    3129
    3230    def __unicode__(self):
    3331        return self.name
    3432
     33    @classmethod
     34    def verbose_names(cls, count=1):
     35        return 'businesses'
     36
    3537class Bar(models.Model):
    3638    id = MyAutoField(primary_key=True, db_index=True)
    3739
  • tests/regressiontests/admin_util/models.py

    diff --git a/tests/regressiontests/admin_util/models.py b/tests/regressiontests/admin_util/models.py
    a b  
    3434    event = models.OneToOneField(Event)
    3535    name = models.CharField(max_length=255)
    3636
    37     class Meta:
    38         verbose_name = "awesome guest"
     37    @classmethod
     38    def verbose_names(cls, count=1):
     39        return "awesome guest"
  • tests/regressiontests/admin_views/models.py

    diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
    a b  
    6363    def __unicode__(self):
    6464        return self.title
    6565
    66     class Meta:
     66    @classmethod
     67    def verbose_names(cls, count=1):
    6768        # Use a utf-8 bytestring to ensure it works (see #11710)
    68         verbose_name = '¿Chapter?'
     69        return '¿Chapter?'
    6970
    7071
    7172class ChapterXtra1(models.Model):
     
    538539    age = models.PositiveIntegerField()
    539540    is_employee = models.NullBooleanField()
    540541
    541 class PrePopulatedPostLargeSlug(models.Model): 
    542     """ 
    543     Regression test for #15938: a large max_length for the slugfield must not 
    544     be localized in prepopulated_fields_js.html or it might end up breaking 
    545     the javascript (ie, using THOUSAND_SEPARATOR ends up with maxLength=1,000) 
    546     """ 
    547     title = models.CharField(max_length=100) 
    548     published = models.BooleanField() 
     542class PrePopulatedPostLargeSlug(models.Model):
     543    """
     544    Regression test for #15938: a large max_length for the slugfield must not
     545    be localized in prepopulated_fields_js.html or it might end up breaking
     546    the javascript (ie, using THOUSAND_SEPARATOR ends up with maxLength=1,000)
     547    """
     548    title = models.CharField(max_length=100)
     549    published = models.BooleanField()
    549550    slug = models.SlugField(max_length=1000)
    550    
     551
  • tests/regressiontests/backends/models.py

    diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py
    a b  
    2828# Until #13711 is fixed, this test can't be run under MySQL.
    2929if connection.features.supports_long_model_names:
    3030    class VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ(models.Model):
    31         class Meta:
    32             # We need to use a short actual table name or
    33             # we hit issue #8548 which we're not testing!
    34             verbose_name = 'model_with_long_table_name'
    3531        primary_key_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.AutoField(primary_key=True)
    3632        charfield_is_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.CharField(max_length=100)
    3733        m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz = models.ManyToManyField(Person,blank=True)
    3834
     35        # We need to use a short actual verbose name or
     36        # we hit issue #8548 which we're not testing!
     37        @classmethod
     38        def verbose_names(cls, count=1):
     39            return 'model_with_long_table_name'
     40
    3941
    4042class Tag(models.Model):
    4143    name = models.CharField(max_length=30)
  • tests/regressiontests/generic_views/models.py

    diff --git a/tests/regressiontests/generic_views/models.py b/tests/regressiontests/generic_views/models.py
    a b  
    11from django.db import models
    2 
     2from django.utils.translation import ungettext_lazy
    33
    44class Artist(models.Model):
    55    name = models.CharField(max_length=100)
    66
    77    class Meta:
    88        ordering = ['name']
    9         verbose_name = 'professional artist'
    10         verbose_name_plural = 'professional artists'
    119
    1210    def __unicode__(self):
    1311        return self.name
     
    1614    def get_absolute_url(self):
    1715        return ('artist_detail', (), {'pk': self.id})
    1816
     17    @classmethod
     18    def verbose_names(cls, count=1):
     19        return ungettext_lazy('professional artist', 'professional artists', count)
     20
    1921class Author(models.Model):
    2022    name = models.CharField(max_length=100)
    2123    slug = models.SlugField()
  • tests/regressiontests/i18n/models.py

    diff --git a/tests/regressiontests/i18n/models.py b/tests/regressiontests/i18n/models.py
    a b  
    1313    cents_payed = models.DecimalField(max_digits=4, decimal_places=2)
    1414    products_delivered = models.IntegerField()
    1515
    16     class Meta:
    17         verbose_name = _('Company')
    18  No newline at end of file
     16    @classmethod
     17    def verbose_names(cls, count=1):
     18        return _('Company')
  • tests/regressiontests/model_inheritance_regress/models.py

    diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py
    a b  
    108108
    109109    class Meta:
    110110        abstract = True
    111         verbose_name_plural = u'Audits'
     111
     112    @classmethod
     113    def verbose_names(cls, count=1):
     114        return u'Audits'
    112115
    113116class CertificationAudit(AuditBase):
    114117    class Meta(AuditBase.Meta):
  • tests/regressiontests/model_regress/models.py

    diff --git a/tests/regressiontests/model_regress/models.py b/tests/regressiontests/model_regress/models.py
    a b  
    1616
    1717    class Meta:
    1818        ordering = ('pub_date','headline')
    19         # A utf-8 verbose name (Ångström's Articles) to test they are valid.
    20         verbose_name = "\xc3\x85ngstr\xc3\xb6m's Articles"
    2119
    2220    def __unicode__(self):
    2321        return self.headline
    2422
     23    @classmethod
     24    def verbose_names(cls, count=1):
     25        # An utf-8 verbose name (Ångström's Articles) to test they are valid.
     26        return "\xc3\x85ngstr\xc3\xb6m's Articles"
     27
    2528class Movie(models.Model):
    2629    #5218: Test models with non-default primary keys / AutoFields
    2730    movie_id = models.AutoField(primary_key=True)
  • new file tests/regressiontests/verbose_names/__init__.py

    diff --git a/tests/regressiontests/verbose_names/__init__.py b/tests/regressiontests/verbose_names/__init__.py
    new file mode 100644
    diff --git a/tests/regressiontests/verbose_names/locale/es_AR/LC_MESSAGES/django.mo b/tests/regressiontests/verbose_names/locale/es_AR/LC_MESSAGES/django.mo
    new file mode 100644
    index 0000000000000000000000000000000000000000..5a0d607200b2b05b66bf95085b76d736061c03bf
    GIT binary patch
    literal 1052
    zc$|%q-EPw`7=|-CeiY6)<<E!{KzQUwz~r^4p-rb~NmsS$jyUlWlVwWn%65TX0Nep*
    zoFQ=^a0EB8b1njjFH5R2Xg939d2Ky!{C%-cZft%uU|a*;0=)uV2YoWC@f~y*^aIoe
    z{RAP<qm3%RSNjq8ZSeQCe+EAWeXaT9hG8_pgGROA2k@KVUuypbeh2)d_Fv!|bzkMU
    z1ZvdttDsHLm3n=-e8rM-RZvY23x@jaEmnFCvx!Jqf$rNEaOYgE{v+U?4HPV+$$U-u
    zv(Q-&|1JC%b&Y`($1Kq}Nbv!aDi@rff!Epdc71f<4@bd%-yE{3kQzrS%TxSvmMI(y
    zg3Q5wj60H14Ikky)r=s=vTY2H^Bma@fr<6ZvLJ|u>`ks#=bjSF!j=oUkSdKA?Fvay
    zZybiG??pap`jHoeZLixM`lHbci1KVkGlpYECj^CFe|KEA+zi8Zk#7l^Ei=$+6h!{=
    z|LiTx><F$IhjQ~N1T=f6pG*sy^Bt5NQK^`2jpH8PUgpZSUNeb(o`^K(8A02Jxi$yI
    zOwt1Pgq$dX`1EwCTFwrtrmY7Y-D@HHp=m_rp^MYAms}t%X*x^t@>bx2&w)-2)q<)&
    bu)3JLi_Oc+PuCP)!It}0aBbO@*fo9wrIRVG
  • new file tests/regressiontests/verbose_names/locale/es_AR/LC_MESSAGES/django.po

    diff --git a/tests/regressiontests/verbose_names/locale/es_AR/LC_MESSAGES/django.po b/tests/regressiontests/verbose_names/locale/es_AR/LC_MESSAGES/django.po
    new file mode 100644
    - +  
     1# SOME DESCRIPTIVE TITLE.
     2# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
     3# This file is distributed under the same license as the PACKAGE package.
     4# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
     5#
     6msgid ""
     7msgstr ""
     8"Project-Id-Version: PACKAGE VERSION\n"
     9"Report-Msgid-Bugs-To: \n"
     10"POT-Creation-Date: 2011-11-27 12:11-0600\n"
     11"PO-Revision-Date: 2011-11-27 15:00-0300\n"
     12"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     13"Language-Team: LANGUAGE <LL@li.org>\n"
     14"Language: \n"
     15"MIME-Version: 1.0\n"
     16"Content-Type: text/plain; charset=UTF-8\n"
     17"Content-Transfer-Encoding: 8bit\n"
     18"Plural-Forms: nplurals=2; plural=(n != 1)\n"
     19
     20msgid "Translatable legacy model #1"
     21msgstr "Modelo legado traducible #1"
     22
     23msgid "Translatable legacy model #2"
     24msgstr "Modelo legado traducible #2"
     25
     26msgid "Translatable legacy models #2"
     27msgstr "Modelos legados traducibles #2"
     28
     29msgid "Translatable legacy models #3"
     30msgstr "Modelos legados traducibles #3"
     31
     32msgid "Translatable New-style model #1"
     33msgstr "Modelo moderno traducible #1"
     34
     35msgid "Translatable New-style model #2"
     36msgid_plural "Translatable New-style models #2"
     37msgstr[0] "Modelo moderno traducible #2"
     38msgstr[1] "Modelos modernos traducibles #2"
     39
     40msgid "Translatable New-style models #3"
     41msgstr "Modelos modernos traducibles #3"
  • new file tests/regressiontests/verbose_names/models.py

    diff --git a/tests/regressiontests/verbose_names/models.py b/tests/regressiontests/verbose_names/models.py
    new file mode 100644
    - +  
     1from django.db import models
     2from django.utils.translation import ugettext_lazy as _, ungettext_lazy
     3
     4
     5class NoMeta(models.Model):
     6    name = models.CharField(max_length=20)
     7
     8class LegacyOnlyVerboseName(models.Model):
     9    name = models.CharField(max_length=20)
     10
     11    class Meta:
     12        verbose_name = 'Legacy model #1'
     13
     14class LegacyBothVNames(models.Model):
     15    name = models.CharField(max_length=20)
     16
     17    class Meta:
     18        verbose_name = 'Legacy model #2'
     19        verbose_name_plural = 'Legacy models #2'
     20
     21class LegacyOnlyVerboseNamePlural(models.Model):
     22    name = models.CharField(max_length=20)
     23
     24    class Meta:
     25        verbose_name_plural = 'Legacy models #3'
     26
     27class LegacyOnlyVerbosenameIntl(models.Model):
     28    name = models.CharField(max_length=20)
     29
     30    class Meta:
     31        verbose_name = _('Translatable legacy model #1')
     32
     33class LegacyBothIntl(models.Model):
     34    name = models.CharField(max_length=20)
     35
     36    class Meta:
     37        verbose_name = _('Translatable legacy model #2')
     38        verbose_name_plural = _('Translatable legacy models #2')
     39
     40class LegacyPluralIntl(models.Model):
     41    name = models.CharField(max_length=20)
     42
     43    class Meta:
     44        verbose_name_plural = _('Translatable legacy models #3')
     45
     46# === Models using new classmethod syntax for verbose names ==========
     47
     48class NewstyleSingular(models.Model):
     49    name = models.CharField(max_length=20)
     50
     51    @classmethod
     52    def verbose_names(cls, count=1):
     53        if count == 1:
     54            return 'New-style model #1'
     55
     56class NewstyleBoth(models.Model):
     57    name = models.CharField(max_length=20)
     58
     59    @classmethod
     60    def verbose_names(cls, count=1):
     61        if count == 1:
     62            return 'New-style model #2'
     63        else:
     64            return 'New-style models #2'
     65
     66class NewstylePlural(models.Model):
     67    name = models.CharField(max_length=20)
     68
     69    @classmethod
     70    def verbose_names(cls, count=1):
     71        if count != 1:
     72            return 'New-style models #3'
     73
     74class NewstyleSingularIntl(models.Model):
     75    name = models.CharField(max_length=20)
     76
     77    @classmethod
     78    def verbose_names(cls, count=1):
     79        if count == 1:
     80            return _('Translatable New-style model #1')
     81
     82class NewstyleBothIntl(models.Model):
     83    name = models.CharField(max_length=20)
     84
     85    @classmethod
     86    def verbose_names(cls, count=1):
     87        return ungettext_lazy('Translatable New-style model #2', 'Translatable New-style models #2', count)
     88
     89class NewstylePluralIntl(models.Model):
     90    name = models.CharField(max_length=20)
     91
     92    @classmethod
     93    def verbose_names(cls, count=1):
     94        if count != 1:
     95            return _('Translatable New-style models #3')
  • new file tests/regressiontests/verbose_names/tests.py

    diff --git a/tests/regressiontests/verbose_names/tests.py b/tests/regressiontests/verbose_names/tests.py
    new file mode 100644
    - +  
     1from __future__ import absolute_import
     2
     3from django.utils.encoding import force_unicode
     4from django.utils import translation
     5from django.utils.unittest import TestCase
     6
     7from .models import (NoMeta, LegacyOnlyVerboseName, LegacyBothVNames,
     8        LegacyOnlyVerboseNamePlural, LegacyOnlyVerbosenameIntl, LegacyBothIntl,
     9        LegacyPluralIntl, NewstyleSingular, NewstyleBoth, NewstylePlural,
     10        NewstyleSingularIntl, NewstyleBothIntl, NewstylePluralIntl)
     11
     12
     13class LegacyVerboseNameNoI18NTests(TestCase):
     14    """
     15    Test we don't disrupt behavior associated with legacy
     16    Meta.verbose_name{,_plural} attributes when translation isn't used.
     17    """
     18
     19    def test_noi18n_no_meta_inner_class(self):
     20        # A model without an inner Meta class
     21        a = NoMeta.objects.create(name=u'Name')
     22        self.assertEqual('no meta', NoMeta._meta.verbose_name)
     23        self.assertEqual('no meta', a._meta.verbose_name)
     24        # Automatically generated plural form, can be bogus (note the arbitrary
     25        # 's' tucked at the end)
     26        self.assertEqual('no metas', force_unicode(NoMeta._meta.verbose_name_plural))
     27        self.assertEqual('no metas', force_unicode(a._meta.verbose_name_plural))
     28
     29    def test_noi18n_only_verbose_name_option(self):
     30        a = LegacyOnlyVerboseName.objects.create(name=u'Name')
     31        # The verbose_name we specified
     32        self.assertEqual('Legacy model #1', LegacyOnlyVerboseName._meta.verbose_name)
     33        self.assertEqual('Legacy model #1', a._meta.verbose_name)
     34        # Automatically generated plural form, can be bogus (note the arbitrary
     35        # 's' tucked at the end)
     36        self.assertEqual('Legacy model #1s', force_unicode(LegacyOnlyVerboseName._meta.verbose_name_plural))
     37        self.assertEqual('Legacy model #1s', force_unicode(a._meta.verbose_name_plural))
     38
     39    def test_noi18n_both_verbose_name_options(self):
     40        b = LegacyBothVNames.objects.create(name=u'Name')
     41        # The verbose_name we specified
     42        self.assertEqual('Legacy model #2', LegacyBothVNames._meta.verbose_name)
     43        self.assertEqual('Legacy model #2', b._meta.verbose_name)
     44        # The verbose_name_plural we specified
     45        self.assertEqual('Legacy models #2', LegacyBothVNames._meta.verbose_name_plural)
     46        self.assertEqual('Legacy models #2', b._meta.verbose_name_plural)
     47
     48    def test_noi18n_only_verbose_name_plural_option(self):
     49        c = LegacyOnlyVerboseNamePlural.objects.create(name=u'Name')
     50        # Verbose name automatically generated from the class name
     51        self.assertEqual('legacy only verbose name plural', LegacyOnlyVerboseNamePlural._meta.verbose_name)
     52        self.assertEqual('legacy only verbose name plural', c._meta.verbose_name)
     53        # The verbose_name_plural we specified
     54        self.assertEqual('Legacy models #3', LegacyOnlyVerboseNamePlural._meta.verbose_name_plural)
     55        self.assertEqual('Legacy models #3', c._meta.verbose_name_plural)
     56
     57
     58class LegacyVerboseNameI18NTests(TestCase):
     59    """
     60    Test we don't disrupt behavior associated with legacy
     61    Meta.verbose_name{,_plural} attributes when translation is used.
     62    """
     63
     64    def setUp(self):
     65        translation.activate('es-ar')
     66
     67    def tearDown(self):
     68        translation.deactivate()
     69
     70    def test_i18n_no_meta_inner_class(self):
     71        # A model without an inner Meta class
     72        a = NoMeta.objects.create(name=u'Name')
     73        self.assertEqual('no meta', NoMeta._meta.verbose_name)
     74        self.assertEqual('no meta', a._meta.verbose_name)
     75        # Automatically generated plural form, can be bogus (note the arbitrary
     76        # 's' tucked at the end)
     77        self.assertEqual('no metas', force_unicode(NoMeta._meta.verbose_name_plural))
     78        self.assertEqual('no metas', force_unicode(a._meta.verbose_name_plural))
     79
     80    def test_i18n_only_verbose_name_option(self):
     81        a = LegacyOnlyVerbosenameIntl.objects.create(name=u'Name')
     82        # The verbose_name we specified
     83        self.assertEqual('Modelo legado traducible #1', force_unicode(LegacyOnlyVerbosenameIntl._meta.verbose_name))
     84        self.assertEqual('Modelo legado traducible #1', a._meta.verbose_name)
     85        # Automatically generated plural form, can be bogus (note the arbitrary
     86        # 's' tucked at the end)
     87        self.assertEqual('Modelo legado traducible #1s', force_unicode(LegacyOnlyVerbosenameIntl._meta.verbose_name_plural))
     88        self.assertEqual('Modelo legado traducible #1s', force_unicode(a._meta.verbose_name_plural))
     89
     90    def test_i18n_both_verbose_name_options(self):
     91        a = LegacyBothIntl.objects.create(name=u'Name')
     92        # The verbose_name we specified
     93        self.assertEqual('Modelo legado traducible #2', LegacyBothIntl._meta.verbose_name)
     94        self.assertEqual('Modelo legado traducible #2', a._meta.verbose_name)
     95        # The verbose_name_plural we specified
     96        self.assertEqual('Modelos legados traducibles #2', LegacyBothIntl._meta.verbose_name_plural)
     97        self.assertEqual('Modelos legados traducibles #2', a._meta.verbose_name_plural)
     98
     99    def test_i18n_only_verbose_name_plural_option(self):
     100        a = LegacyPluralIntl.objects.create(name=u'Name')
     101        # Verbose name automatically generated from the class name
     102        self.assertEqual('legacy plural intl', LegacyPluralIntl._meta.verbose_name)
     103        self.assertEqual('legacy plural intl', a._meta.verbose_name)
     104        # The verbose_name_plural we specified
     105        self.assertEqual('Modelos legados traducibles #3', LegacyPluralIntl._meta.verbose_name_plural)
     106        self.assertEqual('Modelos legados traducibles #3', a._meta.verbose_name_plural)
     107
     108
     109class VerboseNameNoI18NTests(TestCase):
     110    """
     111    Test new verbose_names() model classmethod behavior when translation isn't
     112    used.
     113    """
     114
     115    def test_backward_compatibility(self):
     116        """
     117        Test backward compatibility with legacy Meta.verbose_name{,_plural}
     118        attributes.
     119        """
     120        a = NewstyleSingular.objects.create(name=u'Name')
     121        # The verbose_name derived from the verbose_names() method we specified
     122        self.assertEqual('New-style model #1', NewstyleSingular._meta.verbose_name)
     123        self.assertEqual('New-style model #1', a._meta.verbose_name)
     124        # Automatically generated plural form, can be bogus (note the arbitrary
     125        # 's' tucked at the end)
     126        self.assertEqual('New-style model #1s', force_unicode(NewstyleSingular._meta.verbose_name_plural))
     127        self.assertEqual('New-style model #1s', force_unicode(a._meta.verbose_name_plural))
     128
     129        b = NewstyleBoth.objects.create(name=u'Name')
     130        # The verbose_name derived from the verbose_names() we specified
     131        self.assertEqual('New-style model #2', NewstyleBoth._meta.verbose_name)
     132        self.assertEqual('New-style model #2', b._meta.verbose_name)
     133        # The verbose_name_plural derived from the verbose_names() method we
     134        # specified
     135        self.assertEqual('New-style models #2', NewstyleBoth._meta.verbose_name_plural)
     136        self.assertEqual('New-style models #2', b._meta.verbose_name_plural)
     137
     138        c = NewstylePlural.objects.create(name=u'Name')
     139        # Verbose name automatically generated from the class name
     140        self.assertEqual('newstyle plural', NewstylePlural._meta.verbose_name)
     141        self.assertEqual('newstyle plural', c._meta.verbose_name)
     142        # The verbose_name_plural derived from the verbose_names() method we
     143        # specified
     144        self.assertEqual('New-style models #3', NewstylePlural._meta.verbose_name_plural)
     145        self.assertEqual('New-style models #3', c._meta.verbose_name_plural)
     146
     147    def test_new_behavior(self):
     148        """
     149        Test sanity of new verbose_names() model classmethod.
     150        """
     151        a = NewstyleSingular.objects.create(name=u'Name')
     152        self.assertEqual('New-style model #1', NewstyleSingular.get_verbose_name())
     153        self.assertEqual('New-style model #1', a.get_verbose_name())
     154        # Fallback get_verbose_name() implementation, its return value
     155        # can be bogus (note the arbitrary 's' tucked at the end)
     156        self.assertEqual('New-style model #1s', force_unicode(NewstyleSingular.get_verbose_name(0)))
     157        self.assertEqual('New-style model #1s', force_unicode(a.get_verbose_name(0)))
     158
     159        b = NewstyleBoth.objects.create(name=u'Name')
     160        self.assertEqual('New-style model #2', NewstyleBoth.get_verbose_name())
     161        self.assertEqual('New-style model #2', b.get_verbose_name())
     162
     163        self.assertEqual('New-style models #2', NewstyleBoth.get_verbose_name(0))
     164        self.assertEqual('New-style models #2', b.get_verbose_name(0))
     165
     166        c = NewstylePlural.objects.create(name=u'Name')
     167        # Fallback get_verbose_name() implementation: Returns a value
     168        # automatically generated from the class name
     169        self.assertEqual('newstyle plural', NewstylePlural.get_verbose_name())
     170        self.assertEqual('newstyle plural', c.get_verbose_name())
     171
     172        self.assertEqual('New-style models #3', NewstylePlural.get_verbose_name(0))
     173        self.assertEqual('New-style models #3', c.get_verbose_name(0))
     174
     175
     176class VerboseNameI18NTests(TestCase):
     177    """
     178    Test new verbose_names() model classmethod behavior when translation is
     179    used.
     180    """
     181
     182    def setUp(self):
     183        translation.activate('es-ar')
     184
     185    def tearDown(self):
     186        translation.deactivate()
     187
     188    def test_backward_compatibility(self):
     189        """
     190        Test backward compatibility with legacy Meta.verbose_name{,_plural}
     191        attributes.
     192        """
     193        a = NewstyleSingularIntl.objects.create(name=u'Name')
     194        # The verbose_name derived from the verbose_names() method we specified
     195        self.assertEqual('Modelo moderno traducible #1', NewstyleSingularIntl._meta.verbose_name)
     196        self.assertEqual('Modelo moderno traducible #1', a._meta.verbose_name)
     197        # Automatically generated plural form, can be bogus (note the arbitrary
     198        # 's' tucked at the end)
     199        self.assertEqual('Modelo moderno traducible #1s', force_unicode(NewstyleSingularIntl._meta.verbose_name_plural))
     200        self.assertEqual('Modelo moderno traducible #1s', force_unicode(a._meta.verbose_name_plural))
     201
     202        b = NewstyleBothIntl.objects.create(name=u'Name')
     203        # The verbose_name derived from the verbose_names() we specified
     204        self.assertEqual('Modelo moderno traducible #2', force_unicode(NewstyleBothIntl._meta.verbose_name))
     205        self.assertEqual('Modelo moderno traducible #2', b._meta.verbose_name)
     206        # The verbose_name_plural derived from the verbose_names() method we
     207        # specified
     208        self.assertEqual('Modelos modernos traducibles #2', NewstyleBothIntl._meta.verbose_name_plural)
     209        self.assertEqual('Modelos modernos traducibles #2', b._meta.verbose_name_plural)
     210
     211        c = NewstylePluralIntl.objects.create(name=u'Name')
     212        # Verbose name automatically generated from the class name -- untranslatable
     213        self.assertEqual('newstyle plural intl', NewstylePluralIntl._meta.verbose_name)
     214        self.assertEqual('newstyle plural intl', c._meta.verbose_name)
     215        # The verbose_name_plural derived from the verbose_names() method we
     216        # specified
     217        self.assertEqual('Modelos modernos traducibles #3', NewstylePluralIntl._meta.verbose_name_plural)
     218        self.assertEqual('Modelos modernos traducibles #3', c._meta.verbose_name_plural)
     219
     220    def test_new_behavior(self):
     221        """
     222        Test sanity of new verbose_names() model classmethod.
     223        """
     224        a = NewstyleSingularIntl.objects.create(name=u'Name')
     225        self.assertEqual('Modelo moderno traducible #1', NewstyleSingularIntl.get_verbose_name())
     226        self.assertEqual('Modelo moderno traducible #1', a.get_verbose_name())
     227        # Fallback get_verbose_name() implementation, its return value
     228        # can be bogus (note the arbitrary 's' tucked at the end)
     229        self.assertEqual('Modelo moderno traducible #1s', force_unicode(NewstyleSingularIntl.get_verbose_name(0)))
     230        self.assertEqual('Modelo moderno traducible #1s', force_unicode(a.get_verbose_name(0)))
     231
     232        b = NewstyleBothIntl.objects.create(name=u'Name')
     233        self.assertEqual('Modelo moderno traducible #2', NewstyleBothIntl.get_verbose_name())
     234        self.assertEqual('Modelo moderno traducible #2', b.get_verbose_name())
     235
     236        self.assertEqual('Modelos modernos traducibles #2', NewstyleBothIntl.get_verbose_name(0))
     237        self.assertEqual('Modelos modernos traducibles #2', b.get_verbose_name(0))
     238
     239        c = NewstylePluralIntl.objects.create(name=u'Name')
     240        # Fallback get_verbose_name() implementation: Returns a value
     241        # automatically generated from the class name -- untranslatable
     242        self.assertEqual('newstyle plural intl', NewstylePluralIntl.get_verbose_name())
     243        self.assertEqual('newstyle plural intl', c.get_verbose_name())
     244
     245        self.assertEqual('Modelos modernos traducibles #3', NewstylePluralIntl.get_verbose_name(0))
     246        self.assertEqual('Modelos modernos traducibles #3', c.get_verbose_name(0))
Back to Top