Skip to content

Commit 8b6fc22

Browse files
committed
Thread safety for Django < 1.11
1 parent e7147e1 commit 8b6fc22

File tree

5 files changed

+65
-21
lines changed

5 files changed

+65
-21
lines changed

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
## [1.9.1] - 2017-11-18
5+
### Changed
6+
- Replaced thread unsafe workaround for Django < 1.11 with backported `get_form` implementation from Django 1.11. ([Issue #2])
7+
- `get_exclude` now takes into accounts fields set by `exclude` on Django < 1.11 ([Issue #3])
8+
9+
## 1.9.0 - 2017-11-13
10+
- Initial release
11+
12+
[1.9.1]: https://github.com/inueni/django-subadmin/compare/v1.9.0...v1.9.1
13+
[Issue #2]: https://github.com/inueni/django-subadmin/issues/2
14+
[Issue #3]: https://github.com/inueni/django-subadmin/issues/3

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
include README.md
2+
include CHANGELOG.md
23
include LICENSE
34
recursive-include subadmin/templates *
45
global-exclude __pycache__

README.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,7 @@ When adding or editing objects with `SubAdmin`, `ForeignKey` fields to parent in
9393

9494
## Supported Django versions
9595

96-
Current release of `django-subadmin` is **1.9.0** and is compatible with Django 1.9, 1.10 and 1.11.
97-
98-
Since Django versions before 1.11 don't support `get_exclude` on `ModelAdmin` instances, a workaround that temporarily stores excluded fields on `ModelAdmin` instance, is used. This should not cause any issues under normal circumstances.
96+
Current release of `django-subadmin` is **1.9.1** and is compatible with Django 1.9, 1.10 and 1.11.
9997

10098
#### Verison numbering
10199

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
import sys
88

9-
version = '1.9.0'
9+
version = '1.9.1'
1010

1111
try:
1212
from pypandoc import convert

subadmin/__init__.py

+48-17
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
from __future__ import unicode_literals
22

33
from collections import OrderedDict
4+
from functools import partial, update_wrapper
45

5-
from django.contrib.admin.options import IS_POPUP_VAR, TO_FIELD_VAR
6-
from django.contrib.admin.utils import unquote, quote
7-
from django.forms.models import _get_foreign_key
8-
from django.db import transaction
96
from django.conf.urls import url, include
7+
from django.contrib.admin.options import IS_POPUP_VAR, TO_FIELD_VAR
8+
from django.contrib.admin.utils import unquote, quote, flatten_fieldsets
109
from django.contrib import admin
1110
from django.contrib import messages
1211
from django.contrib.admin.views.main import ChangeList
1312
from django.contrib.admin.actions import delete_selected
13+
from django.core.exceptions import FieldError
14+
from django.db import transaction
15+
from django.forms.models import _get_foreign_key, modelform_defines_fields, modelform_factory, ALL_FIELDS
1416
from django.http import HttpResponseRedirect
1517
from django.shortcuts import get_object_or_404
1618
from django.template.response import SimpleTemplateResponse
@@ -22,7 +24,6 @@
2224
from django.utils.six.moves.urllib.parse import parse_qsl, urlparse, urlunparse
2325
from django.utils.translation import ugettext as _
2426
from django.views.decorators.csrf import csrf_protect
25-
from functools import update_wrapper
2627

2728
try:
2829
from django.urls import Resolver404, get_script_prefix, resolve, reverse
@@ -201,22 +202,52 @@ def get_queryset(self, request):
201202
return super(SubAdminMixin, self).get_queryset(request).filter(**lookup_kwargs)
202203

203204
def get_form(self, request, obj=None, **kwargs):
204-
if not MODELADMIN_GET_EXCLUDE_SUPPORT:
205-
# Workaround for Django < 1.11 not supporting get_exclude in ModelAdmin
206-
exclude = self.exclude
207-
self.exclude = list(exclude or []) + self.get_exclude(request, obj)
208-
form = super(SubAdminMixin, self).get_form(request, obj, **kwargs)
209-
self.exclude = exclude
210-
return form
205+
if MODELADMIN_GET_EXCLUDE_SUPPORT:
206+
return super(SubAdminMixin, self).get_form(request, obj, **kwargs)
207+
208+
if 'fields' in kwargs:
209+
fields = kwargs.pop('fields')
210+
else:
211+
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
212+
excluded = self.get_exclude(request, obj)
213+
exclude = [] if excluded is None else list(excluded)
214+
readonly_fields = self.get_readonly_fields(request, obj)
215+
exclude.extend(readonly_fields)
216+
217+
if excluded is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
218+
exclude.extend(self.form._meta.exclude)
219+
220+
exclude = exclude or None
221+
222+
new_attrs = OrderedDict(
223+
(f, None) for f in readonly_fields
224+
if f in self.form.declared_fields
225+
)
211226

212-
return super(SubAdminMixin, self).get_form(request, obj, **kwargs)
227+
form = type(self.form.__name__, (self.form,), new_attrs)
228+
229+
defaults = {
230+
"form": form,
231+
"fields": fields,
232+
"exclude": exclude,
233+
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
234+
}
235+
defaults.update(kwargs)
236+
237+
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
238+
defaults['fields'] = ALL_FIELDS
213239

214-
def get_exclude(self, request, obj=None):
215240
try:
216-
exclude = super(SubAdminMixin, self).get_exclude(request, obj) or []
217-
except AttributeError:
218-
exclude = []
241+
return modelform_factory(self.model, **defaults)
242+
except FieldError as e:
243+
raise FieldError('%s. Check fields/fieldsets/exclude attributes of class %s.' % (e, self.__class__.__name__))
219244

245+
def get_exclude(self, request, obj=None):
246+
if MODELADMIN_GET_EXCLUDE_SUPPORT:
247+
excluded = super(SubAdminMixin, self).get_exclude(request, obj)
248+
else:
249+
excluded = self.exclude
250+
exclude = [] if excluded is None else list(excluded)
220251
exclude.extend(request.subadmin.related_instances.keys())
221252
return list(set(exclude))
222253

0 commit comments

Comments
 (0)