Friday, August 2, 2013

Normalize '__unicode__' accross django models

Don't repeat your self, use monkeypatching - a better default '__unicode__'

This latest top-secret project for sure is keeping me busy and time wise leaves little to keep blogging, however it being Friday and all and because you're worth it here is a Django expert tip.

Django's default '__unicode__' representation leaves a bit to be desired to say the least. In a nut shell it returns the class name + 'object' for all instances and so requires that all your models have a '__unicode__' defined to have a useful string represenation. Well with a bit of friendly monkeys we can patch it right up as per the code below:

from django.db.models.base import Model
import logging
import hashlib
import re
from django.conf import settings

logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------------------------------------------------- 
logger.info("Patching 'django.db.models.base.Model': adding get_pk_display() method that return pk like string "\
  " that is based on class name and pk of the model, ex. #NG000-341-000 or #NGNone, or #NGXYA") 

# confirm signature of code we are patching and warn if it has changed
#raise Exception(hashlib.md5(str(Model)).hexdigest())
if not '300fd49fc5099da23c0929fb1b7b48cd' == \
  hashlib.md5(str(Model)).hexdigest():
 logger.warn("Hexdigest md5 signature of 'django.db.models.base.Model' does not match expected value."
     " There might be a chance patch is broken so please review code and update as needed.")

def get_pk_display(self):
 try:
  return u'#{0}{1:09}'.format(''.join(re.findall('[A-Z]', self.__class__.__name__)), self.pk)
 except: # most likely non integer based pk or not yet saved i.e. pk == None
  return u'#{0}{1}'.format(''.join(re.findall('[A-Z]', self.__class__.__name__)), self.pk)

# do the actual patch
Model.get_pk_display = get_pk_display
del get_pk_display  # clean up namespace after patching 
#----------------------------------------------------------------------------------------------------------------------


#---------------------------------------------------------------------------------------------------------------------- 
logger.info("Patching 'django.db.models.base.Model': adding default __unicode__() method that return self.name" \
  " prepended with self.get_pk_display if settings.DEBUG = True. If self.name does not exist then only" \
  " self.get_pk_display is returned") 

# confirm signature of code we are patching and warn if it has changed
#raise Exception(hashlib.md5(str(Model)).hexdigest())
if not '300fd49fc5099da23c0929fb1b7b48cd' == \
  hashlib.md5(str(Model)).hexdigest():
 logger.warn("Hexdigest md5 signature of 'django.db.models.base.Model' does not match expected value."
     " There might be a chance patch is broken so please review code and update as needed.")
def __unicode__(self):
 try:
  return u'{0}{1}'.format(self.get_pk_display() + ': ' if settings.DEBUG else '', self.name)
 except:
  return self.get_pk_display()
Model.__unicode__ = __unicode__ 

I store the above file in the project root with an app labled '_monkeypatches' in a file named 'patch_django_db_models_base_model.py' - nomenclature and organization is an art in itself but I prefer verbose for my own sake and the team - and below is what it does in plain English:

  • Every model in your django project gets a new method 'get_pk_display' – it takes the capital letters of the class name and the instances default pk and renders a front end friendly id of object. For example UserProfile with pk 7 would be rendered as '#UP000000007'. This is a handy method in itself as for example I use it heavily on the current project that requires information is hidden until certian conditions are met.
  • Every model get's a new shiny __unicode__ that returns the objects 'self.name' (from 4+ years of Django it's uncommon for my objects not to need a 'name' field) and prepends it with the output 'get_pk_display' IF settings.DEBUG is True – if object does not have 'self.name' defined then 'get_pk_display' is just returned, ex '#C000000312 Danols Web Engineering'.

I tell you – the above is rather handy and now I am in the process of wiping out uneeded __unicode__ and cleaning up templates.

Thoughts, comments and feel free to share your nuggets of Django wisdom.

Be great – @danielsokolow

No comments:

Post a Comment