-
Notifications
You must be signed in to change notification settings - Fork 28
/
admin.py
326 lines (253 loc) · 12.5 KB
/
admin.py
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# -*- coding: utf-8 -*-
from django import forms
from django.contrib import admin
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
from django.contrib.contenttypes.admin import GenericStackedInline
from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import smart_str
from django.forms.models import fields_for_model
from django.utils.translation import ugettext_lazy as _
from django.utils.text import capfirst
from djangoseo.utils import get_seo_content_types
from djangoseo.systemviews import get_seo_views
# TODO Use groups as fieldsets
def get_path_admin(use_site=False, use_subdomains=False):
list_display = ['_path']
search_fields = ['_path']
list_filter = []
if use_site:
list_display.append('_site')
list_filter.append('_site')
if use_subdomains:
list_display.append('_subdomain')
return type('PathMetadataAdmin', (admin.ModelAdmin, ), {
'list_display': tuple(list_display),
'list_filter': tuple(list_filter),
'search_fields': tuple(search_fields)
})
def get_model_instance_admin(use_site=False, use_subdomains=False):
list_display = ['_content_type', '_object_id', '_path']
search_fields = ['_path', '_content_type__name']
list_filter = []
if use_site:
list_display.append('_site')
list_filter.append('_site')
if use_subdomains:
list_display.append('_subdomain')
return type('ModelInstanceMetadataAdmin', (admin.ModelAdmin,), {
'list_display': tuple(list_display),
'list_filter': tuple(list_filter),
'search_fields': tuple(search_fields)
})
def get_model_admin(use_site=False, use_subdomains=False):
list_display = ['_content_type']
search_fields = ['_content_type__name']
list_filter = []
if use_site:
list_display.append('_site')
list_filter.append('_site')
if use_subdomains:
list_display.append('_subdomain')
return type('ModelMetadataAdmin', (admin.ModelAdmin,), {
'list_display': tuple(list_display),
'list_filter': tuple(list_filter),
'search_fields': tuple(search_fields)
})
def get_view_admin(use_site=False, use_subdomains=False):
list_display = ['_view']
search_fields = ['_view']
list_filter = []
if use_site:
list_display.append('_site')
list_filter.append('_site')
if use_subdomains:
list_display.append('_subdomain')
return type('ViewMetadataAdmin', (admin.ModelAdmin,), {
'list_display': tuple(list_display),
'list_filter': tuple(list_filter),
'search_fields': tuple(search_fields)
})
def register_seo_admin(admin_site, metadata_class, filter_list=None):
""" Register the backends specified in Meta.backends with the admin """
use_sites = metadata_class._meta.use_sites
use_subdomains = metadata_class._meta.use_subdomains
path_admin = get_path_admin(use_sites, use_subdomains)
model_instance_admin = get_model_instance_admin(use_sites, use_subdomains)
model_admin = get_model_admin(use_sites, use_subdomains)
view_admin = get_view_admin(use_sites, use_subdomains)
def get_list_display():
return tuple(name for name, obj in metadata_class._meta.elements.items()
if obj.editable)
backends = metadata_class._meta.backends
if 'model' in backends:
class ModelAdmin(model_admin):
form = get_model_form(metadata_class)
list_display = model_admin.list_display + get_list_display()
if filter_list: # Cannot give the filter_list variable name 'list_filter', because there is a name conflict
list_filter = filter_list
_register_admin(admin_site, metadata_class._meta.get_model('model'), ModelAdmin)
if 'view' in backends:
class ViewAdmin(view_admin):
form = get_view_form(metadata_class)
list_display = view_admin.list_display + get_list_display()
if filter_list: # Cannot give the filter_list variable name 'list_filter', because there is a name conflict
list_filter = filter_list
_register_admin(admin_site, metadata_class._meta.get_model('view'), ViewAdmin)
if 'path' in backends:
class PathAdmin(path_admin):
form = get_path_form(metadata_class)
list_display = path_admin.list_display + get_list_display()
if filter_list: # Cannot give the filter_list variable name 'list_filter', because there is a name conflict
list_filter = filter_list
_register_admin(admin_site, metadata_class._meta.get_model('path'), PathAdmin)
if 'modelinstance' in backends:
class ModelInstanceAdmin(model_instance_admin):
form = get_modelinstance_form(metadata_class)
list_display = model_instance_admin.list_display + get_list_display()
if filter_list: # Cannot give the filter_list variable name 'list_filter', because there is a name conflict
list_filter = filter_list
_register_admin(admin_site, metadata_class._meta.get_model('modelinstance'), ModelInstanceAdmin)
def _register_admin(admin_site, model, admin_class):
""" Register model in the admin, ignoring any previously registered models.
Alternatively it could be used in the future to replace a previously
registered model.
"""
try:
admin_site.register(model, admin_class)
except admin.sites.AlreadyRegistered:
pass
class MetadataFormset(BaseGenericInlineFormSet):
def _construct_form(self, i, **kwargs):
""" Override the method to change the form attribute empty_permitted """
form = super(MetadataFormset, self)._construct_form(i, **kwargs)
# Monkey patch the form to always force a save.
# It's unfortunate, but necessary because we always want an instance
# Affect on performance shouldn't be too great, because ther is only
# ever one metadata attached
form.empty_permitted = False
form.has_changed = lambda: True
# Set a marker on this object to prevent automatic metadata creation
# This is seen by the post_save handler, which then skips this instance.
if self.instance:
self.instance.__seo_metadata_handled = True
return form
def get_inline(metadata_class):
attrs = {
'max_num': 1,
'extra': 1,
'model': metadata_class._meta.get_model('modelinstance'),
'ct_field': "_content_type",
'ct_fk_field': "_object_id",
'formset': MetadataFormset,
}
return type('MetadataInline', (GenericStackedInline,), attrs)
def get_model_form(metadata_class):
model_class = metadata_class._meta.get_model('model')
# Restrict content type choices to the models set in seo_models
content_types = get_seo_content_types(metadata_class._meta.seo_models)
content_type_choices = [(x._get_pk_val(), smart_str(x)) for x in
ContentType.objects.filter(id__in=content_types)]
# Get a list of fields, with _content_type at the start
important_fields = ['_content_type'] + core_choice_fields(metadata_class)
_fields = important_fields + list(fields_for_model(model_class,
exclude=important_fields).keys())
class ModelMetadataForm(forms.ModelForm):
_content_type = forms.ChoiceField(label=capfirst(_("model")),
choices=content_type_choices)
class Meta:
model = model_class
fields = _fields
def clean__content_type(self):
value = self.cleaned_data['_content_type']
try:
return ContentType.objects.get(pk=int(value))
except (ContentType.DoesNotExist, ValueError):
raise forms.ValidationError("Invalid ContentType")
return ModelMetadataForm
def get_modelinstance_form(metadata_class):
model_class = metadata_class._meta.get_model('modelinstance')
# Restrict content type choices to the models set in seo_models
content_types = get_seo_content_types(metadata_class._meta.seo_models)
# Get a list of fields, with _content_type at the start
important_fields = ['_content_type'] + ['_object_id'] + core_choice_fields(metadata_class)
_fields = important_fields + list(fields_for_model(model_class,
exclude=important_fields).keys())
class ModelMetadataForm(forms.ModelForm):
_content_type = forms.ModelChoiceField(
queryset=ContentType.objects.filter(id__in=content_types),
empty_label=None,
label=capfirst(_("model")),
)
_object_id = forms.IntegerField(label=capfirst(_("ID")))
class Meta:
model = model_class
fields = _fields
return ModelMetadataForm
def get_path_form(metadata_class):
model_class = metadata_class._meta.get_model('path')
# Get a list of fields, with _view at the start
important_fields = ['_path'] + core_choice_fields(metadata_class)
_fields = important_fields + list(fields_for_model(model_class,
exclude=important_fields).keys())
class ModelMetadataForm(forms.ModelForm):
class Meta:
model = model_class
fields = _fields
return ModelMetadataForm
def get_view_form(metadata_class):
model_class = metadata_class._meta.get_model('view')
# Restrict content type choices to the models set in seo_models
view_choices = [(key, " ".join(key.split("_"))) for key in get_seo_views(metadata_class)]
view_choices.insert(0, ("", "---------"))
# Get a list of fields, with _view at the start
important_fields = ['_view'] + core_choice_fields(metadata_class)
_fields = important_fields + list(fields_for_model(model_class,
exclude=important_fields).keys())
class ModelMetadataForm(forms.ModelForm):
_view = forms.ChoiceField(label=capfirst(_("view")),
choices=view_choices, required=False)
class Meta:
model = model_class
fields = _fields
return ModelMetadataForm
def core_choice_fields(metadata_class):
""" If the 'optional' core fields (_site and _language) are required,
list them here.
"""
fields = []
if metadata_class._meta.use_sites:
fields.append('_site')
if metadata_class._meta.use_i18n:
fields.append('_language')
return fields
def _monkey_inline(model, admin_class_instance, metadata_class, inline_class, admin_site):
""" Monkey patch the inline onto the given admin_class instance. """
if model in metadata_class._meta.seo_models:
# *Not* adding to the class attribute "inlines", as this will affect
# all instances from this class. Explicitly adding to instance attribute.
admin_class_instance.__dict__['inlines'] = admin_class_instance.inlines + [inline_class]
# Because we've missed the registration, we need to perform actions
# that were done then (on admin class instantiation)
inline_instance = inline_class(admin_class_instance.model, admin_site)
if hasattr(admin_class_instance, 'inline_instances'):
admin_class_instance.inline_instances.append(inline_instance)
def _with_inline(func, admin_site, metadata_class, inline_class):
""" Decorator for register function that adds an appropriate inline."""
def register(model_or_iterable, admin_class=None, **options):
# Call the (bound) function we were given.
# We have to assume it will be bound to admin_site
func(model_or_iterable, admin_class, **options)
_monkey_inline(model_or_iterable, admin_site._registry[model_or_iterable],
metadata_class, inline_class, admin_site)
return register
def auto_register_inlines(admin_site, metadata_class):
""" This is a questionable function that automatically adds our metadata
inline to all relevant models in the site.
"""
inline_class = get_inline(metadata_class)
for model, admin_class_instance in admin_site._registry.items():
_monkey_inline(model, admin_class_instance, metadata_class, inline_class, admin_site)
# Monkey patch the register method to automatically add an inline for this site.
# _with_inline() is a decorator that wraps the register function with the same injection code
# used above (_monkey_inline).
admin_site.register = _with_inline(admin_site.register, admin_site, metadata_class, inline_class)