Coverage for templateselector/fields.py: 97%

# Coverage for templateselector/fields.py : 97%

## 120 statements 118 run 2 missing 0 excluded 2 partial

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | # -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import from operator import itemgetter from django.conf import settings from django.contrib import admin from django.utils import six from django.utils.module_loading import import_string from django.utils.text import capfirst from templateselector.handlers import get_results_from_registry from templateselector.widgets import TemplateSelector, AdminTemplateSelector import re from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.validators import MaxLengthValidator from django.db.models import CharField from django.forms import TypedChoiceField from django.template import TemplateDoesNotExist, engines from django.template.loader import get_template from django.utils.encoding import force_text from django.utils.functional import curry from django.utils.translation import ugettext_lazy as _ __all__ = ['TemplateField', 'TemplateChoiceField'] def get_templates_from_loaders(): for engine in engines.all(): # I only know how to search the DjangoTemplates yo... engine = getattr(engine, 'engine') loaders = engine.template_loaders for result in get_results_from_registry(loaders): yield result def nice_display_name(template_path): setting = getattr(settings, 'TEMPLATESELECTOR_DISPLAY_NAMES', {}) if template_path in setting.keys(): return setting[template_path] to_space_re = re.compile(r'[^a-zA-Z0-9\-]+') name = template_path.rpartition('/')[-1] basename = name.rpartition('.')[0] lastpart_spaces = to_space_re.sub(' ', basename) return capfirst(_(lastpart_spaces)) def template_exists_validator(value): try: get_template(value) except TemplateDoesNotExist: raise ValidationError( _('%(value)s is not a valid template'), params={'value': value}, ) class TemplateField(CharField): def __init__(self, match='^.*$', display_name='templateselector.fields.nice_display_name', *args, **kwargs): if 'max_length' in kwargs: raise ImproperlyConfigured(_("max_length is implicitly set to 191 internally")) kwargs['max_length'] = 191 # in case of using mysql+utf8mb4 & indexing super(TemplateField, self).__init__(*args, **kwargs) self.validators.append(template_exists_validator) 64 ↛ 65line 64 didn't jump to line 65, because the condition on line 64 was never true if not match.startswith('^'): raise ImproperlyConfigured("Missing required ^ at start") 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true if not match.endswith('$'): raise ImproperlyConfigured("Missing required $ at end") self.match = match if isinstance(display_name, six.text_type) and '.' in display_name: display_name = import_string(display_name) if not callable(display_name): raise ImproperlyConfigured(_("display_name= argument must be a callable which takes a single string")) self.display_name = display_name def deconstruct(self): name, path, args, kwargs = super(TemplateField, self).deconstruct() del kwargs["max_length"] kwargs['match'] = self.match kwargs['display_name'] = self.display_name return name, path, args, kwargs def formfield(self, **kwargs): defaults = { 'form_class': TemplateChoiceField, 'match': self.match, 'display_name': self.display_name, } defaults.update(kwargs) return super(TemplateField, self).formfield(**defaults) def contribute_to_class(self, cls, name, **kwargs): super(TemplateField, self).contribute_to_class(cls, name, **kwargs) display = curry(self.__get_FIELD_template_display, field=self) display.short_description = self.verbose_name display.admin_order_field = name setattr(cls, 'get_%s_display' % self.name, display) template_instance = curry(self.__get_FIELD_template_instance, field=self) setattr(cls, 'get_%s_instance' % self.name, template_instance) def __get_FIELD_template_display(self, cls, field): value = getattr(cls, field.attname) return self.display_name(value) def __get_FIELD_template_instance(self, cls, field): value = getattr(cls, field.attname) return get_template(value) class TemplateChoiceField(TypedChoiceField): widget = TemplateSelector def __init__(self, match='^.*$', display_name='templateselector.fields.nice_display_name', *args, **kwargs): if 'coerce' in kwargs: raise ImproperlyConfigured(_("Don't pass a coercion callable")) kwargs['coerce'] = force_text max_length = None if 'max_length' in kwargs: max_length = kwargs.pop('max_length') if isinstance(display_name, six.text_type) and '.' in display_name: display_name = import_string(display_name) if not callable(display_name): raise ImproperlyConfigured(_("display_name= argument must be a callable which takes a single string")) super(TemplateChoiceField, self).__init__(*args, **kwargs) if not match.startswith('^'): raise ImproperlyConfigured("Missing required ^ at start") if not match.endswith('$'): raise ImproperlyConfigured("Missing required $ at end") def lazysorted(): def filter_choices(regex_, namecaller_): match_re = re.compile(regex_) choices = get_templates_from_loaders() for choice in choices: if match_re.match(choice): name = namecaller_(choice) yield (choice, name) results = filter_choices(regex_=match, namecaller_=display_name) return sorted(set(results), key=itemgetter(1)) self.choices = lazysorted self.max_length = max_length if max_length is not None: self.validators.append(MaxLengthValidator(int(max_length))) def prepare_value(self, value): """ To avoid evaluating the lazysorted callable more than necessary to establish a potential initial value for the field, we do it here. If there's - only one template choice, and - the field is required, and - there's no prior initial set (either by being bound or by being set higher up the stack then forcibly select the only "good" value as the default. """ if value is None and self.required: choices =list(self.choices) if len(choices) == 1: value = choices[0][0] return super(TemplateChoiceField, self).prepare_value(value) # It doesn't matter wtf the formfield() method for our custom model field says # because the admin looks at the MRO for the model field and tries to render # it as a bloody text input. admin.options.FORMFIELD_FOR_DBFIELD_DEFAULTS[TemplateField] = {'widget': AdminTemplateSelector}