django-helptext / helptext / models.py

from itertools import groupby

from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils.importlib import import_module


def register_app(app_label):
    model_list = models.get_models(app_label)
    register_model(*model_list)


def _is_synced(*mods):
    from django.db import connection
    all_tables = connection.introspection.table_names()
    converter = connection.introspection.table_name_converter
    return set(converter(x._meta.db_table) for x in mods).issubset(all_tables)


def register_model(*model_list):
    if not ContentType._meta.installed:
        raise ImproperlyConfigured(
            "Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting "
            "in order to use the helptext application.")

    if not _is_synced(ContentType, FieldHelp):
        # syncdb not run yet -- return silently
        return

    for model in model_list:
        for mod, fields in groupby(model._meta.get_fields_with_model(),
                                   lambda x: x[1]):
            if mod is None:
                mod = model
            if not _is_synced(mod):
                # probably not synced yet
                continue
            fields = [x[0] for x in fields]

            content_type = ContentType.objects.get_for_model(mod)
            can_help_text = lambda f: all((
                f.editable,
                not f.auto_created,
                not isinstance(f.help_text, FieldHelpProxy),))
            for field in (f for f in fields if can_help_text(f)):
                fh = FieldHelp.objects.filter(content_type=content_type,
                                              field_name=field.name)
                if fh:
                    assert len(fh) == 1
                    fh = fh[0]
                    if fh.original_help_text != field.help_text:
                        fh.original_help_text = field.help_text
                        fh.save()
                else:
                    fh = FieldHelp(content_type=content_type,
                                   field_name=field.name,
                                   help_text='',
                                   original_help_text=field.help_text)
                    fh.save()
                field.help_text = FieldHelpProxy(fh)


class FieldHelpProxy(object):

    def __init__(self, fh):
        self.content_type = fh.content_type
        self.field_name = fh.field_name

    def __unicode__(self):
        try:
            fh = FieldHelp.objects.get(content_type=self.content_type,
                                       field_name=self.field_name)
            return fh.get_help_text()
        except FieldHelp.DoesNotExist:
            # shouldn't happen
            return ''


_configuration = None


def _get_configuration():
    global _configuration
    if _configuration is None:
        if hasattr(settings, 'HELPTEXT_CONFIGURATION'):
            path = settings.HELPTEXT_CONFIGURATION
            try:
                mod = import_module(path)
            except ImportError, e:
                str = 'Error importing helptext configuration %s: "%s"'
                raise ImproperlyConfigured(str % (path, e))
            try:
                helptext_configuration = mod.helptext_configuration
            except AttributeError:
                str = ("%s should contain a dictionary called "
                       "helptext_configuration.")
                raise ImproperlyConfigured(str % path)
            if not isinstance(helptext_configuration, dict):
                str = "%s.helptext_configuration should be a dictionary."
                raise ImproperlyConfigured(str % path)
            _configuration = helptext_configuration
        else:
            _configuration = False
    return _configuration


class FieldHelp(models.Model):
    content_type = models.ForeignKey(ContentType)
    field_name = models.CharField(max_length=120)
    help_text = models.TextField(blank=True)
    original_help_text = models.TextField(blank=True)

    def get_help_text(self):
        configured = self.get_configured_help_text()
        return self.help_text or configured or self.original_help_text

    def get_configured_help_text(self):
        helptext_configuration = _get_configuration()
        if _configuration:
            return helptext_configuration.get(self.lookup_key(), "")
        return ""

    @property
    def model(self):
        return self.content_type.model_class()

    @property
    def field(self):
        return self.model._meta.get_field(self.field_name)

    def app_title(self):
        return self.model._meta.app_label.title()
    app_title.short_description = 'Application'

    def model_title(self):
        return self.model._meta.verbose_name.title()
    model_title.short_description = 'Model'

    def field_title(self):
        return self.field.verbose_name.title()
    field_title.short_description = 'Field'

    def lookup_key(self):
        args = (self.app_title(), self.model_title(), self.field_title(),)
        return "%s.%s.%s" % args

    class Meta:
        ordering = ('content_type', 'field_name')
        unique_together = (('content_type', 'field_name'),)
        verbose_name_plural = "Field Help"

    def __repr__(self):
        return '<FieldHelp for %r: %r>' % \
               (self.content_type.name,
                self.help_text)

    def __unicode__(self):
        return u"%s - %s: %s" % (self.model._meta.app_label.title(),
                                 self.model._meta.verbose_name.title(),
                                 self.field.verbose_name.title())

    def delete(self):
        self.field.help_text = self.original_help_text
        super(FieldHelp, self).delete()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.