Hide keyboard shortcuts

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}