Skip to content

Commit 4642ee2

Browse files
authored
Merge pull request #103 from bckohan/v2.x.x
V2.2.0
2 parents 40b7d48 + 28cb58e commit 4642ee2

File tree

8 files changed

+200
-73
lines changed

8 files changed

+200
-73
lines changed

django_typer/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
model_parser_completer, # noqa: F401
4848
)
4949

50-
VERSION = (2, 1, 3)
50+
VERSION = (2, 2, 0)
5151

5252
__title__ = "Django Typer"
5353
__version__ = ".".join(str(i) for i in VERSION)

django_typer/completers.py

+29-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
# pylint: disable=line-too-long
1818

19+
import inspect
1920
import os
2021
import pkgutil
2122
import sys
@@ -38,12 +39,15 @@
3839
FloatField,
3940
GenericIPAddressField,
4041
IntegerField,
42+
Manager,
4143
Max,
4244
Model,
4345
Q,
4446
TextField,
4547
UUIDField,
4648
)
49+
from django.db.models.query import QuerySet
50+
from django.utils.translation import gettext as _
4751

4852
Completer = t.Callable[[Context, Parameter, str], t.List[CompletionItem]]
4953
Strings = t.Union[t.Sequence[str], t.KeysView[str], t.Generator[str, None, None]]
@@ -107,7 +111,7 @@ def handle(
107111
function that returns a configured parser and completer for a model object
108112
and helps reduce boilerplate.
109113
110-
:param model_cls: The Django model class to query.
114+
:param model_or_qry: The Django model class or a queryset to filter against.
111115
:param lookup_field: The name of the model field to use for lookup.
112116
:param help_field: The name of the model field to use for help text or None if
113117
no help text should be provided.
@@ -130,6 +134,7 @@ def handle(
130134
QueryBuilder = t.Callable[["ModelObjectCompleter", Context, Parameter, str], Q]
131135

132136
model_cls: t.Type[Model]
137+
_queryset: t.Optional[QuerySet] = None
133138
lookup_field: str
134139
help_field: t.Optional[str] = None
135140
query: t.Callable[[Context, Parameter, str], Q]
@@ -144,6 +149,10 @@ def handle(
144149

145150
_field: Field
146151

152+
@property
153+
def queryset(self) -> t.Union[QuerySet, Manager[Model]]:
154+
return self._queryset or self.model_cls.objects
155+
147156
def to_str(self, obj: t.Any) -> str:
148157
return str(obj)
149158

@@ -253,7 +262,11 @@ def uuid_query(self, context: Context, parameter: Parameter, incomplete: str) ->
253262
self._offset += 1
254263

255264
if len(uuid) > 32:
256-
raise ValueError(f"Too many UUID characters: {incomplete}")
265+
raise ValueError(
266+
_("Too many UUID characters: {incomplete}").format(
267+
incomplete=incomplete
268+
)
269+
)
257270
min_uuid = UUID(uuid + "0" * (32 - len(uuid)))
258271
max_uuid = UUID(uuid + "f" * (32 - len(uuid)))
259272
return Q(**{f"{self.lookup_field}__gte": min_uuid}) & Q(
@@ -262,15 +275,23 @@ def uuid_query(self, context: Context, parameter: Parameter, incomplete: str) ->
262275

263276
def __init__(
264277
self,
265-
model_cls: t.Type[Model],
278+
model_or_qry: t.Union[t.Type[Model], QuerySet],
266279
lookup_field: t.Optional[str] = None,
267280
help_field: t.Optional[str] = help_field,
268281
query: t.Optional[QueryBuilder] = None,
269282
limit: t.Optional[int] = limit,
270283
case_insensitive: bool = case_insensitive,
271284
distinct: bool = distinct,
272285
):
273-
self.model_cls = model_cls
286+
if inspect.isclass(model_or_qry) and issubclass(model_or_qry, Model):
287+
self.model_cls = model_or_qry
288+
elif isinstance(model_or_qry, QuerySet): # type: ignore
289+
self.model_cls = model_or_qry.model
290+
self._queryset = model_or_qry
291+
else:
292+
raise ValueError(
293+
_("ModelObjectCompleter requires a Django model class or queryset.")
294+
)
274295
self.lookup_field = str(
275296
lookup_field or getattr(self.model_cls._meta.pk, "name", "id")
276297
)
@@ -295,7 +316,9 @@ def __init__(
295316
self.query = self.float_query
296317
else:
297318
raise ValueError(
298-
f"Unsupported lookup field class: {self._field.__class__.__name__}"
319+
_("Unsupported lookup field class: {cls}").format(
320+
cls=self._field.__class__.__name__
321+
)
299322
)
300323

301324
def __call__(
@@ -343,9 +366,7 @@ def __call__(
343366
],
344367
help=getattr(obj, self.help_field, None) if self.help_field else "",
345368
)
346-
for obj in getattr(self.model_cls, "objects")
347-
.filter(completion_qry)
348-
.distinct()[0 : self.limit]
369+
for obj in self.queryset.filter(completion_qry).distinct()[0 : self.limit]
349370
if (
350371
getattr(obj, self.lookup_field) is not None
351372
and self.to_str(getattr(obj, self.lookup_field))

django_typer/management/__init__.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from django.core.management.base import OutputWrapper as BaseOutputWrapper
1616
from django.core.management.color import Style as ColorStyle
1717
from django.db.models import Model
18+
from django.db.models.query import QuerySet
1819
from django.utils.functional import Promise, classproperty
1920
from django.utils.translation import gettext as _
2021

@@ -109,7 +110,7 @@ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
109110

110111

111112
def model_parser_completer(
112-
model_cls: t.Type[Model],
113+
model_or_qry: t.Union[t.Type[Model], QuerySet],
113114
lookup_field: t.Optional[str] = None,
114115
case_insensitive: bool = False,
115116
help_field: t.Optional[str] = ModelObjectCompleter.help_field,
@@ -139,7 +140,7 @@ def handle(
139140
...
140141
141142
142-
:param model_cls: the model class to use for lookup
143+
:param model_or_qry: the model class or QuerySet to use for lookup
143144
:param lookup_field: the field to use for lookup, by default the primary key
144145
:param case_insensitive: whether to perform case insensitive lookups and
145146
completions, default: False
@@ -155,13 +156,13 @@ def handle(
155156
"""
156157
return {
157158
"parser": ModelObjectParser(
158-
model_cls,
159+
model_or_qry if inspect.isclass(model_or_qry) else model_or_qry.model, # type: ignore
159160
lookup_field,
160161
case_insensitive=case_insensitive,
161162
on_error=on_error,
162163
),
163164
"shell_complete": ModelObjectCompleter(
164-
model_cls,
165+
model_or_qry,
165166
lookup_field,
166167
case_insensitive=case_insensitive,
167168
help_field=help_field,

doc/source/changelog.rst

+57-52
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,29 @@
22
Change Log
33
==========
44

5-
v2.1.3
6-
======
5+
v2.2.0 (26-JUL-2024)
6+
====================
7+
8+
* Implemented `ModelObjectCompleter should optionally accept a QuerySet in place of a Model class. <https://github.com/bckohan/django-typer/issues/96>`_
9+
10+
v2.1.3 (15-JUL-2024)
11+
====================
712

813
* Fixed `Move from django_typer to django_typer.management broke doc reference links. <https://github.com/bckohan/django-typer/issues/98>`_
914
* Implemented `Support Django 5.1 <https://github.com/bckohan/django-typer/issues/97>`_
1015

11-
v2.1.2
12-
======
16+
v2.1.2 (07-JUN-2024)
17+
====================
1318

1419
* Fixed `Type hint kwargs to silence pylance warnings about partially unknown types <https://github.com/bckohan/django-typer/issues/93>`_
1520

16-
v2.1.1
17-
======
21+
v2.1.1 (06-JUN-2024)
22+
====================
1823

1924
* Fixed `handle = None does not work for mypy to silence type checkers <https://github.com/bckohan/django-typer/issues/90>`_
2025

21-
v2.1.0
22-
======
26+
v2.1.0 (05-JUN-2024)
27+
====================
2328

2429
.. warning::
2530

@@ -39,19 +44,19 @@ v2.1.0
3944
* Implemented `Move core code out of __init__.py into management/__init__.py <https://github.com/bckohan/django-typer/issues/81>`_
4045
* Fixed `Typer(help="") doesnt work. <https://github.com/bckohan/django-typer/issues/78>`_
4146

42-
v2.0.2
43-
======
47+
v2.0.2 (03-JUN-2024)
48+
====================
4449

4550
* Fixed `class help attribute should be type hinted to allow a lazy translation string. <https://github.com/bckohan/django-typer/issues/85>`_
4651

4752

48-
v2.0.1
49-
======
53+
v2.0.1 (31-MAY-2024)
54+
====================
5055

5156
* Fixed `Readme images are broken. <https://github.com/bckohan/django-typer/issues/77>`_
5257

53-
v2.0.0
54-
======
58+
v2.0.0 (31-MAY-2024)
59+
====================
5560

5661
This major version release, includes an extensive internal refactor, numerous bug fixes and the
5762
addition of a plugin-based extension pattern.
@@ -75,19 +80,19 @@ addition of a plugin-based extension pattern.
7580
* Implemented `Add completer/parser for GenericIPAddressField. <https://github.com/bckohan/django-typer/issues/12>`_
7681

7782

78-
v1.1.2
79-
======
83+
v1.1.2 (22-APR-2024)
84+
====================
8085

8186
* Fixed `Overridden common Django arguments fail to pass through when passed through call_command <https://github.com/bckohan/django-typer/issues/54>`_
8287

83-
v1.1.1
84-
======
88+
v1.1.1 (11-APR-2024)
89+
====================
8590

8691
* Implemented `Fix pyright type checking and add to CI <https://github.com/bckohan/django-typer/issues/51>`_
8792
* Implemented `Convert CONTRIBUTING.rst to markdown <https://github.com/bckohan/django-typer/issues/50>`_
8893

89-
v1.1.0
90-
======
94+
v1.1.0 (03-APR-2024)
95+
====================
9196

9297
* Implemented `Convert readme to markdown. <https://github.com/bckohan/django-typer/issues/48>`_
9398
* Fixed `typer 0.12.0 breaks django_typer 1.0.9 <https://github.com/bckohan/django-typer/issues/47>`_
@@ -98,41 +103,41 @@ v1.0.9 (yanked)
98103

99104
* Fixed `Support typer 0.12.0 <https://github.com/bckohan/django-typer/issues/46>`_
100105

101-
v1.0.8
102-
======
106+
v1.0.8 (26-MAR-2024)
107+
====================
103108

104109
* Fixed `Support typer 0.10 and 0.11 <https://github.com/bckohan/django-typer/issues/45>`_
105110

106-
v1.0.7
107-
======
111+
v1.0.7 (17-MAR-2024)
112+
====================
108113

109114
* Fixed `Helps throw an exception when invoked from an absolute path that is not relative to the getcwd() <https://github.com/bckohan/django-typer/issues/44>`_
110115

111-
v1.0.6
112-
======
116+
v1.0.6 (14-MAR-2024)
117+
====================
113118

114119
* Fixed `prompt options on groups still prompt when given as named parameters on call_command <https://github.com/bckohan/django-typer/issues/43>`_
115120

116121

117-
v1.0.5
118-
======
122+
v1.0.5 (14-MAR-2024)
123+
====================
119124

120125
* Fixed `Options with prompt=True are prompted twice <https://github.com/bckohan/django-typer/issues/42>`_
121126

122127

123-
v1.0.4
124-
======
128+
v1.0.4 (13-MAR-2024)
129+
====================
125130

126131
* Fixed `Help sometimes shows full script path in Usage: when it shouldnt. <https://github.com/bckohan/django-typer/issues/40>`_
127132
* Fixed `METAVAR when ModelObjectParser supplied should default to model name <https://github.com/bckohan/django-typer/issues/39>`_
128133

129-
v1.0.3
130-
======
134+
v1.0.3 (08-MAR-2024)
135+
====================
131136

132137
* Fixed `Incomplete typing info for @command decorator <https://github.com/bckohan/django-typer/issues/33>`_
133138

134-
v1.0.2
135-
======
139+
v1.0.2 (05-MAR-2024)
140+
====================
136141

137142
* Fixed `name property on TyperCommand is too generic and should be private. <https://github.com/bckohan/django-typer/issues/37>`_
138143
* Fixed `When usage errors are thrown the help output should be that of the subcommand invoked not the parent group. <https://github.com/bckohan/django-typer/issues/36>`_
@@ -143,50 +148,50 @@ v1.0.2
143148
* Fixed `Missing subcommand produces stack trace without --traceback. <https://github.com/bckohan/django-typer/issues/27>`_
144149
* Fixed `Allow handle() to be an initializer. <https://github.com/bckohan/django-typer/issues/24>`_
145150

146-
v1.0.1
147-
======
151+
v1.0.1 (29-FEB-2024)
152+
====================
148153

149154
* Fixed `shell_completion broken for click < 8.1 <https://github.com/bckohan/django-typer/issues/21>`_
150155

151-
v1.0.0
152-
======
156+
v1.0.0 (26-FEB-2024)
157+
====================
153158

154159
* Initial production/stable release.
155160

156-
v0.6.1b
157-
=======
161+
v0.6.1b (24-FEB-2024)
162+
=====================
158163

159164
* Incremental beta release - this is also the second release candidate for version 1.
160165
* Peg typer version to 0.9.x
161166

162-
v0.6.0b
163-
=======
167+
v0.6.0b (23-FEB-2024)
168+
=====================
164169

165170
* Incremental beta release - this is also the first release candidate for version 1.
166171

167172

168-
v0.5.0b
169-
=======
173+
v0.5.0b (31-JAN-2024)
174+
=====================
170175

171176
* Incremental Beta Release
172177

173-
v0.4.0b
174-
=======
178+
v0.4.0b (08-JAN-2024)
179+
=====================
175180

176181
* Incremental Beta Release
177182

178-
v0.3.0b
179-
=======
183+
v0.3.0b (06-JAN-2024)
184+
=====================
180185

181186
* Incremental Beta Release
182187

183-
v0.2.0b
184-
=======
188+
v0.2.0b (04-JAN-2024)
189+
=====================
185190

186191
* Incremental Beta Release
187192

188193

189-
v0.1.0b
190-
=======
194+
v0.1.0b (05-DEC-2023)
195+
=====================
191196

192197
* Initial Release (Beta)

doc/source/shell_completion.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ Model Objects
392392

393393
* completer: :class:`~django_typer.completers.ModelObjectCompleter`
394394
* parser: :class:`~django_typer.parsers.ModelObjectParser`
395-
* convenience: :func:`~django_typer.model_parser_completer`
395+
* convenience: :func:`~django_typer.management.model_parser_completer`
396396

397397
This completer/parser pairing provides the ability to fetch a model object from one of its fields.
398398
Most field types are supported. Additionally any other field can be set as the help text that some
@@ -419,7 +419,7 @@ shells support. Refer to the reference documentation and the
419419
ModelClass,
420420
typer.Argument(
421421
**model_parser_completer(
422-
ModelClass,
422+
ModelClass, # may also accept a QuerySet for pre-filtering
423423
'field_name', # the field that should be matched (defaults to id)
424424
help_field='other_field' # optionally provide some additional help text
425425
),

0 commit comments

Comments
 (0)