Ticket #11688: 11688-verbose_name_plural_evolution-2.diff

File 11688-verbose_name_plural_evolution-2.diff, 85.1 KB (added by ramiro, 3 years ago)

Moved get_verbose_name() method to ._meta Options class, updated and enhanced documentation

  • 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(opts.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._meta.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._meta.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._meta.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._meta.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(opts.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(opts.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 = opts.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_.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(opts.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(opts.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(opts.get_verbose_name()),
    10901090            'adminform': adminForm,
    10911091            'object_id': object_id,
    10921092            'original': obj,
     
    11941194                        changecount += 1
    11951195
    11961196                if changecount:
    1197                     if changecount == 1:
    1198                         name = force_unicode(opts.verbose_name)
    1199                     else:
    1200                         name = force_unicode(opts.verbose_name_plural)
     1197                    name = force_unicode(opts.get_verbose_name(changecount))
    12011198                    msg = ungettext("%(count)s %(name)s was changed successfully.",
    12021199                                    "%(count)s %(name)s were changed successfully.",
    12031200                                    changecount) % {'count': changecount,
    1204                                                     'name': name,
    1205                                                     'obj': force_unicode(obj)}
     1201                                                    'name': name}
    12061202                    self.message_user(request, msg)
    12071203
    12081204                return HttpResponseRedirect(request.get_full_path())
     
    12291225            'All %(total_count)s selected', cl.result_count)
    12301226
    12311227        context = {
    1232             'module_name': force_unicode(opts.verbose_name_plural),
     1228            'module_name': force_unicode(opts.get_verbose_name(0)),
    12331229            'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
    12341230            'selection_note_all': selection_note_all % {'total_count': cl.result_count},
    12351231            'title': cl.title,
     
    12551251    @transaction.commit_on_success
    12561252    def delete_view(self, request, object_id, extra_context=None):
    12571253        "The 'delete' admin view for this model."
    1258         opts = self.model._meta
     1254        model = self.model
     1255        opts = model._meta
    12591256        app_label = opts.app_label
    12601257
    12611258        obj = self.get_object(request, unquote(object_id))
     
    12641261            raise PermissionDenied
    12651262
    12661263        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)})
     1264            raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.get_verbose_name()), 'key': escape(object_id)})
    12681265
    1269         using = router.db_for_write(self.model)
     1266        using = router.db_for_write(model)
    12701267
    12711268        # Populate deleted_objects, a data structure of all related objects that
    12721269        # will also be deleted.
     
    12801277            self.log_deletion(request, obj, obj_display)
    12811278            self.delete_model(request, obj)
    12821279
    1283             self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
     1280            self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.get_verbose_name()), 'obj': force_unicode(obj_display)})
    12841281
    12851282            if not self.has_change_permission(request, None):
    12861283                return HttpResponseRedirect(reverse('admin:index',
     
    12891286                                        (opts.app_label, opts.module_name),
    12901287                                        current_app=self.admin_site.name))
    12911288
    1292         object_name = force_unicode(opts.verbose_name)
     1289        object_name = force_unicode(opts.get_verbose_name())
    12931290
    12941291        if perms_needed or protected:
    12951292            title = _("Cannot delete %(name)s") % {"name": object_name}
     
    13291326        context = {
    13301327            'title': _('Change history: %s') % force_unicode(obj),
    13311328            'action_list': action_list,
    1332             'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
     1329            'module_name': capfirst(force_unicode(opts.get_verbose_name(0))),
    13331330            'object': obj,
    13341331            'app_label': app_label,
    13351332            'opts': opts,
     
    13651362        self.opts = self.model._meta
    13661363        super(InlineModelAdmin, self).__init__()
    13671364        if self.verbose_name is None:
    1368             self.verbose_name = self.model._meta.verbose_name
     1365            self.verbose_name = self.model._meta.get_verbose_name()
    13691366        if self.verbose_name_plural is None:
    1370             self.verbose_name_plural = self.model._meta.verbose_name_plural
     1367            self.verbose_name_plural = self.model._meta.get_verbose_name(0)
    13711368
    13721369    @property
    13731370    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._meta.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._meta.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(opts.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(opts.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(opts.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, a `QuerySet` instance, or
     169    a `django.db.models.Options` instance.
    169170
    170171    """
    171172    if isinstance(obj, (models.Model, models.base.ModelBase)):
     
    175176    else:
    176177        opts = obj
    177178    return {
    178         'verbose_name': force_unicode(opts.verbose_name),
    179         'verbose_name_plural': force_unicode(opts.verbose_name_plural)
     179        'verbose_name': force_unicode(opts.get_verbose_name()),
     180        'verbose_name_plural': force_unicode(opts.get_verbose_name(0))
    180181    }
    181182
    182183
     
    194195        if n is None:
    195196            n = obj.count()
    196197        obj = obj.model
    197     d = model_format_dict(obj)
    198     singular, plural = d["verbose_name"], d["verbose_name_plural"]
    199     return ungettext(singular, plural, n or 0)
     198    else:
     199        if n is None:
     200            n = 0
     201    singular = obj._meta.get_verbose_name(1)
     202    plural = obj._meta.get_verbose_name(n)
     203    return ungettext(singular, plural, n)
    200204
    201205
    202206def lookup_field(name, obj, model_admin=None):
     
    229233def label_for_field(name, model, model_admin=None, return_attr=False):
    230234    """
    231235    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
     236    name of an object attribute, as well as a genuine field. If return_attr is
    233237    True, the resolved attribute (which could be a callable) is also returned.
    234238    This will be None if (and only if) the name refers to a field.
    235239    """
     
    237241    try:
    238242        field = model._meta.get_field_by_name(name)[0]
    239243        if isinstance(field, RelatedObject):
    240             label = field.opts.verbose_name
     244            label = field.opts.get_verbose_name()
    241245        else:
    242246            label = field.verbose_name
    243247    except models.FieldDoesNotExist:
    244248        if name == "__unicode__":
    245             label = force_unicode(model._meta.verbose_name)
     249            label = force_unicode(model._meta.get_verbose_name())
    246250            attr = unicode
    247251        elif name == "__str__":
    248             label = smart_str(model._meta.verbose_name)
     252            label = smart_str(model._meta.get_verbose_name())
    249253            attr = str
    250254        else:
    251255            if callable(name):
     
    311315
    312316
    313317def get_model_from_relation(field):
    314     if isinstance(field, models.related.RelatedObject):
     318    if isinstance(field, RelatedObject):
    315319        return field.model
    316320    elif getattr(field, 'rel'): # or isinstance?
    317321        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.opts.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._meta.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._meta.get_verbose_name()
     22        self.verbose_name_plural = model._meta.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  
    9898        for obj_name, obj in attrs.items():
    9999            new_class.add_to_class(obj_name, obj)
    100100
     101        if hasattr(new_class, 'verbose_names'):
     102            new_class._meta._verbose_name = new_class._meta.get_verbose_name()
     103            new_class._meta._verbose_name_plural = new_class._meta.get_verbose_name(0)
     104
    101105        # All the fields of any type declared on this model
    102106        new_fields = new_class._meta.local_fields + \
    103107                     new_class._meta.local_many_to_many + \
     
    770774
    771775    def unique_error_message(self, model_class, unique_check):
    772776        opts = model_class._meta
    773         model_name = capfirst(opts.verbose_name)
     777        model_name = capfirst(opts.get_verbose_name())
    774778
    775779        # A unique field
    776780        if len(unique_check) == 1:
  • 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._meta.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._meta.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
    5 from django.db.models.related import RelatedObject
    66from django.db.models.fields.related import ManyToManyRel
    77from django.db.models.fields import AutoField, FieldDoesNotExist
    88from django.db.models.fields.proxy import OrderWrt
    99from django.db.models.loading import get_models, app_cache_ready
     10from django.db.models.related import RelatedObject
     11from django.utils.datastructures import SortedDict
     12from django.utils.encoding import force_unicode, smart_str
     13from django.utils.functional import cached_property
    1014from django.utils.translation import activate, deactivate_all, get_language, string_concat
    11 from django.utils.encoding import force_unicode, smart_str
    12 from django.utils.datastructures import SortedDict
    1315
    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',
     16DEFAULT_NAMES = ('db_table', 'ordering',
    1817                 'unique_together', 'permissions', 'get_latest_by',
    1918                 'order_with_respect_to', 'app_label', 'db_tablespace',
    2019                 'abstract', 'managed', 'proxy', 'auto_created')
    2120
     21DEPRECATED_NAMES = ('verbose_name', 'verbose_name_plural')
     22
     23CAMEL_CASE_RE = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
     24
    2225class Options(object):
    2326    def __init__(self, meta, app_label=None):
    2427        self.local_fields, self.local_many_to_many = [], []
    2528        self.virtual_fields = []
    26         self.module_name, self.verbose_name = None, None
    27         self.verbose_name_plural = None
     29        self.cls = None
     30        self.module_name, self._verbose_name = None, None
     31        self._verbose_name_plural = None
    2832        self.db_table = ''
    2933        self.ordering = []
    3034        self.unique_together =  []
     
    5458        # from *other* models. Needed for some admin checks. Internal use only.
    5559        self.related_fkey_lookups = []
    5660
     61    def _get_verbose_name(self):
     62        warnings.warn("Meta.verbose_name is deprecated. Use a verbose_names()"
     63            " classmethod in the model instead.", PendingDeprecationWarning)
     64        return self._verbose_name
     65    def _set_verbose_name(self, value):
     66        self._verbose_name = value
     67    verbose_name = property(_get_verbose_name, _set_verbose_name)
     68
     69    def _get_verbose_name_plural(self):
     70        warnings.warn("Meta.verbose_name_plural is deprecated. Use a "
     71            "verbose_names() classmethod in the model instead.",
     72            PendingDeprecationWarning)
     73        return self._verbose_name_plural
     74    def _set_verbose_name_plural(self, value):
     75        self._verbose_name_plural = value
     76    verbose_name_plural = property(_get_verbose_name_plural, _set_verbose_name_plural)
     77
    5778    def contribute_to_class(self, cls, name):
    5879        from django.db import connection
    5980        from django.db.backends.util import truncate_name
    6081
     82        self.cls = cls
    6183        cls._meta = self
    6284        self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
    6385        # First, construct the default values for these options.
    6486        self.object_name = cls.__name__
    6587        self.module_name = self.object_name.lower()
    66         self.verbose_name = get_verbose_name(self.object_name)
     88        self._verbose_name = CAMEL_CASE_RE.sub(' \\1', self.object_name).lower().strip()
    6789
    6890        # Next, apply any overridden values from 'class Meta'.
    6991        if self.meta:
     
    80102                elif hasattr(self.meta, attr_name):
    81103                    setattr(self, attr_name, getattr(self.meta, attr_name))
    82104
     105            for attr_name in DEPRECATED_NAMES:
     106                if attr_name in meta_attrs:
     107                    warnings.warn("%(cls)s: Meta.%(attr_name)s is deprecated. Use a "
     108                        "verbose_names() classmethod in the model "
     109                        "instead." % {'cls': cls, 'attr_name': attr_name},
     110                        PendingDeprecationWarning)
     111                    setattr(self, '_%s' % attr_name, meta_attrs.pop(attr_name))
     112
    83113            # unique_together can be either a tuple of tuples, or a single
    84114            # tuple of two strings. Normalize it to a tuple of tuples, so that
    85115            # calling code can uniformly expect that.
     
    90120
    91121            # verbose_name_plural is a special case because it uses a 's'
    92122            # by default.
    93             if self.verbose_name_plural is None:
    94                 self.verbose_name_plural = string_concat(self.verbose_name, 's')
     123            if self._verbose_name_plural is None:
     124                self._verbose_name_plural = string_concat(self._verbose_name, 's')
    95125
    96126            # Any leftover attributes must be invalid.
    97127            if meta_attrs != {}:
    98128                raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys()))
    99129        else:
    100             self.verbose_name_plural = string_concat(self.verbose_name, 's')
     130            self._verbose_name_plural = string_concat(self._verbose_name, 's')
    101131        del self.meta
    102132
    103133        # If the db_table wasn't provided, use the app_label + module_name.
     
    199229        """
    200230        lang = get_language()
    201231        deactivate_all()
    202         raw = force_unicode(self.verbose_name)
     232        raw = force_unicode(self._verbose_name)
    203233        activate(lang)
    204234        return raw
    205     verbose_name_raw = property(verbose_name_raw)
     235    verbose_name_raw = cached_property(verbose_name_raw)
    206236
    207237    def _fields(self):
    208238        """
     
    495525        Returns the index of the primary key field in the self.fields list.
    496526        """
    497527        return self.fields.index(self.pk)
     528
     529    def get_verbose_name(self, count=1):
     530        if hasattr(self.cls, 'verbose_names'):
     531            retv = self.cls.verbose_names(count)
     532            if retv is not None:
     533                return retv
     534        if count == 1:
     535            return CAMEL_CASE_RE.sub(' \\1', self.object_name).lower().strip()
     536        return string_concat(self.get_verbose_name(1), 's')
  • 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._meta.get_verbose_name(count)
  • django/utils/functional.py

    diff --git a/django/utils/functional.py b/django/utils/functional.py
    a b  
    3131
    3232class cached_property(object):
    3333    """
    34     Decorator that creates converts a method with a single
    35     self argument into a property cached on the instance.
     34    Decorator that converts a method with a single self argument into a
     35    property cached on the instance.
    3636    """
    3737    def __init__(self, func):
    3838        self.func = func
  • 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._meta.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._meta.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._meta.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._meta.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._meta.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._meta.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._meta.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._meta.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._meta.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._meta.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
    543634Extra instance methods
    544635======================
    545636
    546637In addition to :meth:`~Model.save()`, :meth:`~Model.delete()`, a model object
    547638might have some of the following methods:
    548639
     640``get_*_display``
     641-----------------
     642
    549643.. method:: Model.get_FOO_display()
    550644
    551645For every field that has :attr:`~django.db.models.Field.choices` set, the
     
    570664    >>> p.get_gender_display()
    571665    'Male'
    572666
     667``get_next_by_*`` and ``get_prev_by_*``
     668---------------------------------------
     669
    573670.. method:: Model.get_next_by_FOO(\**kwargs)
    574671.. method:: Model.get_previous_by_FOO(\**kwargs)
    575672
     
    586683Note that in the case of identical date values, these methods will use the
    587684primary key as a tie-breaker. This guarantees that no records are skipped or
    588685duplicated. That also means you cannot use those methods on unsaved objects.
    589 
  • 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"
    271281
    272282    If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``.
     283
     284``Meta`` methods
     285================
     286
     287``get_verbose_name``
     288--------------------
     289
     290.. method:: Options.get_verbose_name(count=1)
     291
     292.. versionadded:: 1.4
     293
     294It provides an API to access translated and correctly pluralized verbose names
     295of models (something that previously involved accessing the
     296``Model._meta.verbose_name`` and ``Model._meta.verbose_name_plural``
     297attributes.)
     298
     299.. seealso::
     300
     301    The :meth:`~Model.verbose_names` user-provided classmethod that works
     302    together with this method.
     303
     304This method will always return a value independently of whether the model
     305implements the :meth:`~Model.verbose_names` classmethod or not. Django provides
     306fallback return values compatible with the default values of the deprecated
     307:attr:`~Options.verbose_name` and :attr:`~Options.verbose_name_plural` options.
     308
     309For example, given this model::
     310
     311    class Door(models.Model):
     312        height = models.PositiveIntegerField()
     313
     314then these are the return values of this method::
     315
     316    >>> Door._meta.get_verbose_name(1) # One door
     317    >>> 'door' # Automatically provided singular verbose name
     318    >>> Door._meta.get_verbose_name(3) # More than one door
     319    >>> 'doors'
     320    # Note how it returns an automatically provided simple naive pluralization
     321    # appending a 's' to the singular value
     322
     323Or, for the examples in the :meth:`~Model.verbose_names` documentation::
     324
     325    >>> SSN._meta.get_verbose_name() # One SSN, count default value
     326    >>> 'social security number'
     327    # Note how it returns the value returned by SSN.verbose_names(count) for a
     328    # value of count=1
     329    >>> SSN._meta.get_verbose_name(0) # Zero SSN
     330    >>> 'social security numbers'
     331    # Note how it returns an automatically provided simple naive pluralization
     332    # appending a 's' to the singular value
     333
     334    >>> SecurityPolicy._meta.get_verbose_name() # One policy
     335    >>> 'security policy'
     336    # Note how it returns a value automatically provided by Django by processing
     337    # the model class name
     338    >>> SecurityPolicy._meta.get_verbose_name(10) # Ten policies
     339    >>> 'security policies'
     340    # Note how it returns the value returned by
     341    # SecurityPolicy.verbose_names(count) for a count value different from 1
  • 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._meta.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.Options.get_verbose_name` method) for other parts of the
     210sentence so all of it is consistently based on the cardinality of the elements
     211at play.
     212
     213.. _plural forms: http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms
    216214
    217215.. _pluralization-var-notes:
    218216
     
    294292Lazy translation
    295293----------------
    296294
    297 Use the function :func:`django.utils.translation.ugettext_lazy()` to translate
    298 strings lazily -- when the value is accessed rather than when the
    299 ``ugettext_lazy()`` function is called.
     295Use the lazy versions of translation functions in
     296:mod:`django.utils.translation` (easily recognizable by the ``lazy`` suffix in
     297their names) to translate strings lazily -- when the value is accessed rather
     298than when they are called.
    300299
    301 For example, to translate a model's ``help_text``, do the following::
     300This is an essential need when we need to mark for translation text that is
     301intermixed with code that is executed at module load time.
     302
     303As this is something that can easily happen when defining Django models (the
     304declarative notation is implemented in a way such that model fields are actually
     305class level attributes) this means you need to make sure to use lazy
     306translations in the following cases:
     307
     308Model fields and relationship ``verbose_name`` and ``help_text`` option values
     309~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     310
     311For example, to translate the help text of the *name* field in the following
     312model, do the following::
    302313
    303314    from django.utils.translation import ugettext_lazy
    304315
     
    310321is used in a string context, such as template rendering on the Django admin
    311322site.
    312323
     324In addition to normal fields your models might also contain relationships:
     325``ForeignKey``, ``ManyTomanyField`` or ``OneToOneField`` fields. You can mark
     326the name of a relationship itself as translatable by using its ``verbose_name``
     327option::
     328
     329    from django.utils.translation import ugettext_lazy as _
     330
     331    class MyThing(models.Model):
     332        kind = models.ForeignKey(ThingKind, related_name='kinds',
     333                                 verbose_name=_('kind'))
     334
     335Just like you would do in the :meth:`~django.db.models.Model.verbose_names`
     336model classmethod, you should provide a lowercase verbose name text for the
     337relation as Django will automatically titlecase it when required.
     338
     339Model verbose names values
     340~~~~~~~~~~~~~~~~~~~~~~~~~~
     341
     342It is recommended to always provide a
     343:meth:`~django.db.models.Model.verbose_names` method rather than relying on
     344Django's default English-centric and somewhat naïve determination of verbose
     345names::
     346
     347    from django.utils.translation import ugettext_lazy
     348
     349    class MyThing(models.Model):
     350        name = models.CharField(_('name'), help_text=ugettext_lazy('This is the help text'))
     351
     352        @classmethod
     353        def verbose_names(cls, count=1):
     354            if count == 1:
     355                return ugettext_lazy('my thing')
     356            else:
     357                return ugettext_lazy('my things')
     358
     359In this particular case it is almost always a better idea to actually use
     360:func:`~django.utils.translation.ungettext_lazy` instead of ``ugettext_lazy``.
     361Refer to the ``verbose_names()`` documentation linked above for details on how
     362this can make the lifes of your translators and users easier.
     363
     364Model methods ``short_description`` attribute values
     365~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     366
     367For model methods, you can provide translations to Django and the admin site
     368with the ``short_description`` parameter set on the corresponding method::
     369
     370    from django.utils.translation import ugettext_lazy as _
     371
     372    class MyThing(models.Model):
     373        kind = models.ForeignKey(ThingKind, related_name='kinds',
     374                                 verbose_name=_('kind'))
     375
     376        def is_mouse(self):
     377            return self.kind.type == MOUSE_TYPE
     378        is_mouse.short_description = _('Is it a mouse?')
     379
     380Notes on translation in models
     381~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     382
    313383The result of a ``ugettext_lazy()`` call can be used wherever you would use a
    314384unicode string (an object with type ``unicode``) in Python. If you try to use
    315385it where a bytestring (a ``str`` object) is expected, things will not work as
     
    328398<django.utils.functional...>"``, you have tried to insert the result of
    329399``ugettext_lazy()`` into a bytestring. That's a bug in your code.
    330400
    331 If you don't like the verbose name ``ugettext_lazy``, you can just alias it as
     401If you don't like the long ``ugettext_lazy`` name, you can just alias it as
    332402``_`` (underscore), like so::
    333403
    334404    from django.utils.translation import ugettext_lazy as _
     
    336406    class MyThing(models.Model):
    337407        name = models.CharField(help_text=_('This is the help text'))
    338408
    339 Always use lazy translations in :doc:`Django models </topics/db/models>`.
    340 Field names and table names should be marked for translation (otherwise, they
    341 won't be translated in the admin interface). This means writing explicit
    342 ``verbose_name`` and ``verbose_name_plural`` options in the ``Meta`` class,
    343 though, rather than relying on Django's default determination of
    344 ``verbose_name`` and ``verbose_name_plural`` by looking at the model's class
    345 name::
    346 
    347     from django.utils.translation import ugettext_lazy as _
    348 
    349     class MyThing(models.Model):
    350         name = models.CharField(_('name'), help_text=_('This is the help text'))
    351 
    352         class Meta:
    353             verbose_name = _('my thing')
    354             verbose_name_plural = _('my things')
    355 
    356 Notes on model classes translation
    357 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    358 
    359 Your model classes may not only contain normal fields: you may have relations
    360 (with a ``ForeignKey`` field) or additional model methods you may use for
    361 columns in the Django admin site.
    362 
    363 If you have models with foreign keys and you use the Django admin site, you can
    364 provide translations for the relation itself by using the ``verbose_name``
    365 parameter on the ``ForeignKey`` object::
    366 
    367     class MyThing(models.Model):
    368         kind = models.ForeignKey(ThingKind, related_name='kinds',
    369                                  verbose_name=_('kind'))
    370 
    371 As you would do for the ``verbose_name`` and ``verbose_name_plural`` settings of
    372 a model Meta class, you should provide a lowercase verbose name text for the
    373 relation as Django will automatically titlecase it when required.
    374 
    375 For model methods, you can provide translations to Django and the admin site
    376 with the ``short_description`` parameter set on the corresponding method::
    377 
    378     class MyThing(models.Model):
    379         kind = models.ForeignKey(ThingKind, related_name='kinds',
    380                                  verbose_name=_('kind'))
    381 
    382         def is_mouse(self):
    383             return self.kind.type == MOUSE_TYPE
    384         is_mouse.short_description = _('Is it a mouse?')
    385 
    386 As always with model classes translations, don't forget to use the lazy
    387 translation method!
    388 
    389409Working with lazy translation objects
    390410-------------------------------------
    391411
  • 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._meta.get_verbose_name())
     153        self.assertEqual('New-style model #1', a._meta.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._meta.get_verbose_name(0)))
     157        self.assertEqual('New-style model #1s', force_unicode(a._meta.get_verbose_name(0)))
     158
     159        b = NewstyleBoth.objects.create(name=u'Name')
     160        self.assertEqual('New-style model #2', NewstyleBoth._meta.get_verbose_name())
     161        self.assertEqual('New-style model #2', b._meta.get_verbose_name())
     162
     163        self.assertEqual('New-style models #2', NewstyleBoth._meta.get_verbose_name(0))
     164        self.assertEqual('New-style models #2', b._meta.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._meta.get_verbose_name())
     170        self.assertEqual('newstyle plural', c._meta.get_verbose_name())
     171
     172        self.assertEqual('New-style models #3', NewstylePlural._meta.get_verbose_name(0))
     173        self.assertEqual('New-style models #3', c._meta.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._meta.get_verbose_name())
     226        self.assertEqual('Modelo moderno traducible #1', a._meta.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._meta.get_verbose_name(0)))
     230        self.assertEqual('Modelo moderno traducible #1s', force_unicode(a._meta.get_verbose_name(0)))
     231
     232        b = NewstyleBothIntl.objects.create(name=u'Name')
     233        self.assertEqual('Modelo moderno traducible #2', NewstyleBothIntl._meta.get_verbose_name())
     234        self.assertEqual('Modelo moderno traducible #2', b._meta.get_verbose_name())
     235
     236        self.assertEqual('Modelos modernos traducibles #2', NewstyleBothIntl._meta.get_verbose_name(0))
     237        self.assertEqual('Modelos modernos traducibles #2', b._meta.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._meta.get_verbose_name())
     243        self.assertEqual('newstyle plural intl', c._meta.get_verbose_name())
     244
     245        self.assertEqual('Modelos modernos traducibles #3', NewstylePluralIntl._meta.get_verbose_name(0))
     246        self.assertEqual('Modelos modernos traducibles #3', c._meta.get_verbose_name(0))
Back to Top