Skip to content

Commit a1f986e

Browse files
committed
feat(serializer): support dynamicly set fields on serializer
1 parent 4e9688d commit a1f986e

6 files changed

Lines changed: 163 additions & 46 deletions

File tree

.pre-commit-config.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
exclude: "^docs/|/migrations/"
2+
default_stages: [commit]
3+
4+
repos:
5+
- repo: https://github.com/pre-commit/pre-commit-hooks
6+
rev: v4.3.0
7+
hooks:
8+
- id: check-ast
9+
- id: trailing-whitespace
10+
- id: end-of-file-fixer
11+
- id: check-case-conflict
12+
- id: mixed-line-ending
13+
- id: detect-private-key
14+
- id: debug-statements
15+
- id: check-yaml
16+
- id: check-toml
17+
18+
- repo: https://github.com/commitizen-tools/commitizen
19+
rev: v2.28.0
20+
hooks:
21+
- id: commitizen
22+
stages: [commit-msg]
23+
24+
- repo: https://github.com/asottile/pyupgrade
25+
rev: v2.34.0
26+
hooks:
27+
- id: pyupgrade
28+
args: [--py39-plus]
29+
30+
- repo: https://github.com/psf/black
31+
rev: 22.3.0
32+
hooks:
33+
- id: black
34+
args: [--config=pyproject.toml]
35+
36+
- repo: https://github.com/PyCQA/isort
37+
rev: 5.10.1
38+
hooks:
39+
- id: isort
40+
args: [--settings-path=pyproject.toml]
41+
42+
- repo: https://github.com/PyCQA/flake8
43+
rev: 4.0.1
44+
hooks:
45+
- id: flake8
46+
args: ["--config=setup.cfg"]
47+
additional_dependencies: [flake8-isort]
48+
49+
# sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date
50+
ci:
51+
autoupdate_schedule: weekly
52+
skip: []
53+
submodules: false

drfexts/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.1.0'
1+
__version__ = '0.13.0'

drfexts/filtersets/backends.py

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@ def get_filterset_class(self, view, queryset=None):
107107
filterset_class = getattr(view, 'filterset_class', None)
108108
filterset_fields_overwrite = getattr(view, 'filterset_fields_overwrite', {})
109109

110-
# full text search
111-
112110
if filterset_class:
113111
filterset_model = filterset_class._meta.model # noqa
114112

@@ -131,42 +129,52 @@ def get_filterset_class(self, view, queryset=None):
131129
overwrite_fields = {k: v for k, v in filterset_fields_overwrite.items() if isinstance(v, Filter)}
132130
overwrite_kwargs = {k: v for k, v in filterset_fields_overwrite.items() if isinstance(v, dict)}
133131

134-
for filter_name, field in serializer.fields.items():
135-
if (
136-
getattr(field, "write_only", False)
137-
or field.source == "*"
138-
or isinstance(field, serializers.BaseSerializer)
139-
):
140-
continue
132+
def filters_from_serializer(_serializer, field_name_prefix='', filter_name_prefix=''):
133+
if isinstance(_serializer, serializers.ListSerializer):
134+
_serializer = _serializer.child
141135

142-
field_name = field.source.replace(".", "__") or filter_name
143-
if get_model_field(filterset_model, field_name) is None and (
144-
queryset is not None and filter_name not in queryset.query.annotations
145-
):
146-
continue
136+
for filter_name, field in _serializer.fields.items():
137+
if getattr(field, "write_only", False) or field.source == "*":
138+
continue
147139

148-
try:
149-
filter_spec = FILTER_FOR_SERIALIZER_FIELD_DEFAULTS[field]
150-
except KeyError:
151-
warnings.warn(f"{filter_name} 字段未找到过滤器, 跳过自动成filter!")
152-
continue
140+
field_name = field.source.replace(".", "__") or filter_name
141+
if field_name_prefix:
142+
field_name = field_name_prefix + "__" + field_name
143+
144+
if filter_name_prefix:
145+
filter_name = filter_name_prefix + "." + filter_name
146+
147+
if get_model_field(filterset_model, field_name) is None and (
148+
queryset is not None and filter_name not in queryset.query.annotations
149+
):
150+
continue
153151

154-
extra = filter_spec.get("extra")
155-
kwargs = {"field_name": field_name, "label": field.label, "help_text": field.help_text}
156-
if callable(extra):
157-
kwargs.update(extra(field))
152+
if isinstance(field, serializers.BaseSerializer):
153+
filters_from_serializer(field, field_name_prefix=field_name, filter_name_prefix=filter_name)
158154

159-
if "queryset" in kwargs and kwargs["queryset"] is None:
160-
warnings.warn(f"{filter_name} 字段未提供queryset, 跳过自动成filter!")
161-
continue
155+
try:
156+
filter_spec = FILTER_FOR_SERIALIZER_FIELD_DEFAULTS[field]
157+
except KeyError:
158+
warnings.warn(f"{filter_name} 字段未找到过滤器, 跳过自动成filter!")
159+
continue
162160

163-
overwrite_value = overwrite_kwargs.get(filter_name)
164-
if overwrite_value:
165-
kwargs.update(overwrite_value)
161+
extra = filter_spec.get("extra")
162+
kwargs = {"field_name": field_name, "label": field.label, "help_text": field.help_text}
163+
if callable(extra):
164+
kwargs.update(extra(field))
166165

167-
filterset_field = filter_spec["filter_class"](**kwargs)
168-
filterset_fields[filter_name] = filterset_field
166+
if "queryset" in kwargs and kwargs["queryset"] is None:
167+
warnings.warn(f"{filter_name} 字段未提供queryset, 跳过自动成filter!")
168+
continue
169169

170+
overwrite_value = overwrite_kwargs.get(filter_name)
171+
if overwrite_value:
172+
kwargs.update(overwrite_value)
173+
174+
filterset_field = filter_spec["filter_class"](**kwargs)
175+
filterset_fields[filter_name] = filterset_field
176+
177+
filters_from_serializer(serializer)
170178
filterset_fields.update(overwrite_fields)
171179

172180
AutoFilterSet = type("AutoFilterSet", (self.filterset_base,), filterset_fields) # noqa
@@ -252,6 +260,7 @@ class FullTextSearchFilter(SearchFilter):
252260
"""
253261
Search filter that supports fulltext search
254262
"""
263+
255264
search_vector = None
256265
search_query = None
257266

drfexts/models.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class NotNull(Func):
3030
arity = 1
3131

3232

33-
class StatusQueryset(models.QuerySet):
33+
class StatusQuerySet(models.QuerySet):
3434

3535
def editable(self):
3636
return self.exclude(status__in=[CommonStatus.DELETED, CommonStatus.INVALID])
@@ -39,7 +39,7 @@ def active(self):
3939
return self.filter(status__in=[CommonStatus.VALID, CommonStatus.PAUSED, CommonStatus.TO_INVALID])
4040

4141
def valid(self):
42-
return self.filter(status__in=CommonStatus.VALID)
42+
return self.filter(status=CommonStatus.VALID)
4343

4444

4545
class BaseModel(models.Model):
@@ -51,7 +51,7 @@ class BaseModel(models.Model):
5151
updated_at = UpdatedAtField() # 修改时间
5252
created_at = CreatedAtField() # 创建时间
5353

54-
objects = StatusQueryset.as_manager()
54+
objects = StatusQuerySet.as_manager()
5555

5656
class Meta:
5757
abstract = True
@@ -68,7 +68,7 @@ class BaseCodeModel(models.Model):
6868
updated_at = UpdatedAtField() # 修改时间
6969
created_at = CreatedAtField() # 创建时间
7070

71-
objects = StatusQueryset.as_manager()
71+
objects = StatusQuerySet.as_manager()
7272

7373
class Meta:
7474
abstract = True
@@ -87,7 +87,7 @@ class BaseCreatorModel(models.Model):
8787
updated_at = UpdatedAtField() # 修改时间
8888
created_at = CreatedAtField() # 创建时间
8989

90-
objects = StatusQueryset.as_manager()
90+
objects = StatusQuerySet.as_manager()
9191

9292
class Meta:
9393
abstract = True
@@ -120,7 +120,7 @@ class AuditModel(models.Model):
120120
updated_at = UpdatedAtField() # 修改时间
121121
created_at = CreatedAtField() # 创建时间
122122

123-
objects = StatusQueryset.as_manager()
123+
objects = StatusQuerySet.as_manager()
124124

125125
class Meta:
126126
abstract = True

drfexts/viewsets.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,7 @@ class ExtGenericViewSet(GenericViewSet):
121121

122122
def get_serializer_class(self):
123123
"""
124-
Return the class to use for the serializer.
125-
Defaults to using `self.serializer_class`.
126-
127-
You may want to override this if you need to provide different
128-
serializations depending on the incoming request.
129-
130-
(Eg. admins get full serialization, others get basic serialization)
124+
支持针对不同action指定不同的序列化器
131125
"""
132126
assert self.serializer_class is not None, (
133127
"'%s' should either include a `serializer_class` attribute, "
@@ -144,6 +138,19 @@ def get_serializer_class(self):
144138

145139
return self.serializer_class
146140

141+
def get_serializer(self, *args, **kwargs):
142+
"""
143+
支持动态设置序列化器字段
144+
"""
145+
serializer_class = self.get_serializer_class()
146+
if hasattr(serializer_class, "get_dynamic_fields") and callable(serializer_class.get_dynamic_fields):
147+
dynamic_fields = serializer_class.get_dynamic_fields(self.request)
148+
if dynamic_fields:
149+
kwargs["fields"] = dynamic_fields
150+
151+
kwargs.setdefault('context', self.get_serializer_context())
152+
return serializer_class(*args, **kwargs)
153+
147154
def data_permissions(self, request, view, queryset):
148155
"""
149156
检查数据权限

pyproject.toml

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,47 @@
1+
[tool.black]
2+
line-length = 89
3+
target-version = ['py38', 'py39']
4+
include = '\.pyi?$'
5+
exclude = '''
6+
/(
7+
\.eggs
8+
| \.git
9+
| \.hg
10+
| \.mypy_cache
11+
| \.tox
12+
| \.venv
13+
| _build
14+
| buck-out
15+
| build
16+
| dist
17+
18+
# The following are specific to Black, you probably don't want those.
19+
| blib2to3
20+
| tests/data
21+
| profiling
22+
)/
23+
'''
24+
25+
[tool.isort]
26+
profile = "black"
27+
known_first_party = ["scf", "config"]
28+
default_section = "THIRDPARTY"
29+
skip = ["venv/", ".venv/"]
30+
include_trailing_comma = true
31+
force_grid_wrap = 0
32+
use_parentheses = true
33+
34+
[tool.commitizen]
35+
version = "0.13.0"
36+
tag_format = "v$major.$minor.$patch$prerelease"
37+
version_files = [
38+
"pyproject.toml:version",
39+
"__init__.py"
40+
]
41+
142
[tool.poetry]
243
name = "drfexts"
3-
version = "0.10.7"
44+
version = "0.13.0"
445
readme = "README.md"
546
description = "Django Restframework Utils"
647
authors = ["aiden <allaher@icloud.com>"]
@@ -29,6 +70,13 @@ conditions = "^0.2.0"
2970

3071
[tool.poetry.dev-dependencies]
3172
#pytest = "^4.0"
73+
flake8 = "^4.0.1"
74+
flake8-isort = "^4.1.1"
75+
coverage = "^6.2"
76+
black = "^22.3.0"
77+
pylint-django = "^2.5.0"
78+
pre-commit = "^2.16.0"
79+
commitizen = "^2.28.0"
3280

3381
[build-system]
3482
requires = ["poetry-core>=1.0.0"]

0 commit comments

Comments
 (0)