When verbose_name changes, how do I auto-update a model's ContentType?
Asked Answered
C

2

7

Given is a Django model called BlogPost. At first, it's coded without a Meta.verbose_name. At ./manage.py syncdb time, a ContentType with a name "blog post" is created automatically. At some later point of time, Meta.verbose_name of "Blog post" is added.

Now there is a discrepancy: ContentType is called "blog post", while the model goes by verbose name of "Blog post", this difference is shown in any framework using generic relationships, e.g. in comments' admin. I would like to correct this situation by changing the name of the ContentType, however, I wouldn't want to do that either by hand (for obvious reasons) or via a migration (since I don't migrate anything else, Meta.verbose_name is just a code change).

How would you update the ContentType's name upon Meta.verbose_name change?

Cleopatra answered 27/7, 2011 at 15:29 Comment(0)
C
10

Answering own question: I've managed to do this with a small post_migrate signal. If you are not using South, it's probably perfectly possible to use the post_syncdb signal the same way. Any comments on this code are appreciated.

from django.contrib.contenttypes.models import ContentType
from django.utils.functional import Promise

from south.signals import post_migrate 
# or if using django >=1.7 migrations:
# from django.db.models.signals import post_migrate

def update_contenttypes_names(**kwargs):
    for c in ContentType.objects.all():
        cl = c.model_class()
        # Promises classes are from translated, mostly django-internal models. ignore them.
        if cl and not isinstance(cl._meta.verbose_name, Promise):
            new_name = cl._meta.verbose_name
            if c.name != new_name:
                print u"Updating ContentType's name: '%s' -> '%s'" % (c.name, new_name)
                c.name = new_name
                c.save()

post_migrate.connect(update_contenttypes_names, dispatch_uid="update_contenttypes")
Cleopatra answered 27/7, 2011 at 16:8 Comment(3)
Works great for me, thx! BTW, you should probably mark this as an answer :)Islet
If you use special chars in your verbose name, you want to use new_name = cl._meta.verbose_name.decode('utf-8')Circassia
If you're wondering like me where to put this code, app-level __init__.py is the place.Circassia
S
1

Another approach is to override ContentType.__str__ method, as it looks like this:

def __str__(self):
    # self.name is deprecated in favor of using model's verbose_name, which
    # can be translated. Formal deprecation is delayed until we have DB
    # migration to be able to remove the field from the database along with
    # the attribute.
    #
    # We return self.name only when users have changed its value from the
    # initial verbose_name_raw and might rely on it.
    model = self.model_class()
    if not model or self.name != model._meta.verbose_name_raw:
        return self.name
    else:
        return force_unicode(model._meta.verbose_name)

So, you can rewrite it, if you don't need some kind of backwards compatibility:

from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import force_unicode

def contenttype_as_str(self):
    return force_unicode(self.model_class()._meta.verbose_name)

ContentType.__str__ = contenttype_as_str

It's a bit tricky, but I believe it's more straightforward. Note, that since Django 1.4.1 force_text is used instead of force_unicode.

Swearingen answered 7/10, 2012 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.