4
4
from urllib .parse import parse_qsl , urlparse , urlunparse
5
5
6
6
from django .urls import path , re_path , include
7
+ from django .core .exceptions import ValidationError
7
8
from django .contrib .admin .options import IS_POPUP_VAR , TO_FIELD_VAR
8
9
from django .contrib .admin .utils import unquote , quote
9
10
from django .contrib import admin
14
15
from django .forms .models import _get_foreign_key
15
16
from django .http import HttpResponseRedirect
16
17
from django .shortcuts import get_object_or_404
17
- from django .template .response import SimpleTemplateResponse
18
+ from django .template .response import TemplateResponse
18
19
from django .utils .decorators import method_decorator
19
20
from django .utils .functional import cached_property
20
21
from django .utils .html import format_html
25
26
26
27
csrf_protect_m = method_decorator (csrf_protect )
27
28
28
- __all__ = ('SubAdmin' , 'RootSubAdmin' , 'SubAdminMixin' , 'RootSubAdminMixin' , 'SubAdminChangeList' , 'SubAdminHelper' )
29
+ __all__ = ('SubAdmin' , 'RootSubAdmin' , 'SubAdminMixin' , 'RootSubAdminMixin' , 'SubAdminChangeList' , 'SubAdminHelper' , 'SubAdminFormMixin' )
29
30
30
31
31
32
class SubAdminHelper (object ):
@@ -37,15 +38,14 @@ def __init__(self, sub_admin, view_args, object_id=None):
37
38
self .view_args = view_args
38
39
self .base_viewname = sub_admin .get_base_viewname ()
39
40
self .load_tree (sub_admin )
40
-
41
-
41
+
42
42
def load_tree (self , sub_admin ):
43
43
parent_admin = sub_admin .parent_admin
44
44
fk_lookup = sub_admin .fk_name
45
45
46
46
i = 2 if self .object_id else 1
47
47
while parent_admin :
48
- obj = sub_admin .get_parent_instance (self .view_args [i * - 1 ])
48
+ obj = sub_admin .get_parent_instance (self .view_args [- i ])
49
49
self .parents .append ({
50
50
'admin' : parent_admin ,
51
51
'object' : obj ,
@@ -57,13 +57,13 @@ def load_tree(self, sub_admin):
57
57
parent_admin = getattr (sub_admin , 'parent_admin' , None )
58
58
if parent_admin :
59
59
fk_lookup = '%s__%s' % (fk_lookup , sub_admin .fk_name )
60
-
60
+
61
61
i += 1
62
-
62
+
63
63
@cached_property
64
64
def parent (self ):
65
65
return self .parents [0 ]
66
-
66
+
67
67
@cached_property
68
68
def root (self ):
69
69
return self .parents [- 1 ]
@@ -87,6 +87,38 @@ def url_for_result(self, result):
87
87
return self .model_admin .reverse_url ('change' , * self .model_admin .get_base_url_args (self .request ) + [pk ])
88
88
89
89
90
+ class SubAdminFormMixin (object ):
91
+ def _post_clean (self ):
92
+ validate_unique = self ._validate_unique
93
+ self ._validate_unique = False
94
+ super ()._post_clean ()
95
+
96
+ for fk_field , fk_instance in self ._related_instances_fields .items ():
97
+ setattr (self .instance , fk_field , fk_instance )
98
+
99
+ self ._validate_unique = validate_unique
100
+ if self ._validate_unique :
101
+ self .validate_unique ()
102
+
103
+
104
+ def validate_unique (self ):
105
+ exclude = self ._get_subadmin_validation_exclusions ()
106
+
107
+ try :
108
+ self .instance .validate_unique (exclude = exclude )
109
+ except ValidationError as e :
110
+ self ._update_errors (e )
111
+
112
+ def _get_subadmin_validation_exclusions (self ):
113
+ return [f for f in self ._get_validation_exclusions () if f not in self ._related_instances_fields .keys ()]
114
+
115
+ @cached_property
116
+ def _related_instances_fields (self ):
117
+ return {
118
+ key : self ._related_instances [key ] for key in self ._related_instances .keys () if key in self ._meta .model ._meta ._forward_fields_map .keys ()
119
+ }
120
+
121
+
90
122
class SubAdminBase (object ):
91
123
subadmins = None
92
124
@@ -104,7 +136,7 @@ def get_subadmin_urls(self):
104
136
]
105
137
106
138
urlpatterns += urls
107
-
139
+
108
140
return urlpatterns
109
141
110
142
def render_change_form (self , request , context , add = False , change = False , form_url = '' , obj = None ):
@@ -117,7 +149,7 @@ def render_change_form(self, request, context, add=False, change=False, form_url
117
149
'name' : modeladmin .model ._meta .verbose_name_plural ,
118
150
'url' : modeladmin .reverse_url ('changelist' , * url_args ),
119
151
})
120
-
152
+
121
153
context .update ({'subadmin_links' : subadmin_links })
122
154
return super ().render_change_form (request , context , add = add , change = change ,
123
155
form_url = form_url , obj = obj )
@@ -142,7 +174,7 @@ def __init__(self, parent_model, parent_admin):
142
174
self .fk_name = _get_foreign_key (parent_model , self .model ).name
143
175
144
176
super ().__init__ (self .model , parent_admin .admin_site )
145
-
177
+
146
178
self .subadmin_instances = self .get_subadmin_instances ()
147
179
148
180
def get_subadmin_helper (self , view_args , object_id = None ):
@@ -198,11 +230,17 @@ def get_exclude(self, request, obj=None):
198
230
exclude .extend (request .subadmin .related_instances .keys ())
199
231
return list (set (exclude ))
200
232
201
- def save_model (self , request , obj , form , change ):
202
- for fk_field , instance in request .subadmin .related_instances .items ():
203
- if fk_field in self .model ._meta ._forward_fields_map .keys ():
204
- setattr (obj , fk_field , instance )
205
- super ().save_model (request , obj , form , change )
233
+ def prep_subadmin_form (self , request , form ):
234
+ attrs = {'_related_instances' : request .subadmin .related_instances }
235
+ return type (form )(form .__name__ , (SubAdminFormMixin , form ), attrs )
236
+
237
+ def get_form (self , request , obj = None , ** kwargs ):
238
+ form = super ().get_form (request , obj , ** kwargs )
239
+ return self .prep_subadmin_form (request , form )
240
+
241
+ def get_changelist_form (self , request , ** kwargs ):
242
+ form = super ().get_changelist_form (request , ** kwargs )
243
+ return self .prep_subadmin_form (request , form )
206
244
207
245
def get_base_viewname (self ):
208
246
if hasattr (self .parent_admin , 'get_base_viewname' ):
@@ -220,7 +258,7 @@ def get_base_url_args(self, request):
220
258
if hasattr (request , 'subadmin' ):
221
259
return request .subadmin .base_url_args
222
260
return []
223
-
261
+
224
262
def context_add_parent_data (self , request , context = None ):
225
263
context = context or {}
226
264
parent_instance = request .subadmin .parent_instance
@@ -229,7 +267,7 @@ def context_add_parent_data(self, request, context=None):
229
267
'parent_opts' : parent_instance ._meta ,
230
268
})
231
269
return context
232
-
270
+
233
271
def get_parent_instance (self , parent_id ):
234
272
return get_object_or_404 (self .parent_model , pk = unquote (parent_id ))
235
273
@@ -327,16 +365,16 @@ def response_add(self, request, obj, post_url_continue=None):
327
365
328
366
if "_saveasnew" in request .POST :
329
367
url_args = url_args [:- 1 ]
330
-
368
+
331
369
obj_url = self .reverse_url ('change' , * url_args + [quote (pk_value )])
332
-
370
+
333
371
if self .has_change_permission (request , obj ):
334
372
obj_repr = format_html ('<a href="{}">{}</a>' , urlquote (obj_url ), obj )
335
373
else :
336
374
obj_repr = str (obj )
337
-
375
+
338
376
msg_dict = {
339
- 'name' : str ( opts .verbose_name ) ,
377
+ 'name' : opts .verbose_name ,
340
378
'obj' : obj_repr ,
341
379
}
342
380
@@ -351,7 +389,11 @@ def response_add(self, request, obj, post_url_continue=None):
351
389
'value' : str (value ),
352
390
'obj' : str (obj ),
353
391
})
354
- return SimpleTemplateResponse ('admin/popup_response.html' , {
392
+ return TemplateResponse (request , self .popup_response_template or [
393
+ 'admin/%s/%s/popup_response.html' % (opts .app_label , opts .model_name ),
394
+ 'admin/%s/popup_response.html' % opts .app_label ,
395
+ 'admin/popup_response.html' ,
396
+ ], {
355
397
'popup_response_data' : popup_response_data ,
356
398
})
357
399
@@ -389,7 +431,7 @@ def response_add(self, request, obj, post_url_continue=None):
389
431
)
390
432
self .message_user (request , msg , messages .SUCCESS )
391
433
return self .response_post_save_add (request , obj )
392
-
434
+
393
435
def response_change (self , request , obj ):
394
436
if IS_POPUP_VAR in request .POST :
395
437
to_field = request .POST .get (TO_FIELD_VAR )
@@ -402,7 +444,11 @@ def response_change(self, request, obj):
402
444
'obj' : str (obj ),
403
445
'new_value' : str (new_value ),
404
446
})
405
- return SimpleTemplateResponse ('admin/popup_response.html' , {
447
+ return TemplateResponse (request , self .popup_response_template or [
448
+ 'admin/%s/%s/popup_response.html' % (opts .app_label , opts .model_name ),
449
+ 'admin/%s/popup_response.html' % opts .app_label ,
450
+ 'admin/popup_response.html' ,
451
+ ], {
406
452
'popup_response_data' : popup_response_data ,
407
453
})
408
454
@@ -472,7 +518,7 @@ def response_post_save_change(self, request, obj):
472
518
else :
473
519
post_url = reverse ('admin:index' , current_app = self .admin_site .name )
474
520
return HttpResponseRedirect (post_url )
475
-
521
+
476
522
def response_delete (self , request , obj_display , obj_id ):
477
523
opts = self .model ._meta
478
524
@@ -481,7 +527,11 @@ def response_delete(self, request, obj_display, obj_id):
481
527
'action' : 'delete' ,
482
528
'value' : str (obj_id ),
483
529
})
484
- return SimpleTemplateResponse ('admin/popup_response.html' , {
530
+ return TemplateResponse (request , self .popup_response_template or [
531
+ 'admin/%s/%s/popup_response.html' % (opts .app_label , opts .model_name ),
532
+ 'admin/%s/popup_response.html' % opts .app_label ,
533
+ 'admin/popup_response.html' ,
534
+ ], {
485
535
'popup_response_data' : popup_response_data ,
486
536
})
487
537
@@ -510,7 +560,7 @@ class RootSubAdminMixin(SubAdminBase):
510
560
511
561
def __init__ (self , * args , ** kwargs ):
512
562
super ().__init__ (* args , ** kwargs )
513
- self .subadmin_instances = self .get_subadmin_instances ()
563
+ self .subadmin_instances = self .get_subadmin_instances ()
514
564
515
565
def get_urls (self ):
516
566
return self .get_subadmin_urls () + super ().get_urls ()
0 commit comments