Skip to content

Commit

Permalink
Add State.looks_like_models_file (#390)
Browse files Browse the repository at this point in the history
Co-authored-by: Thibaut Decombe <[email protected]>
  • Loading branch information
adamchainz and UnknownPlatypus authored Sep 24, 2023
1 parent 140ff2e commit 445c85b
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ Injects the now-required ``length`` argument, with its previous default ``12``.
~~~~~~~~~~~~~~~~~~~~

Transforms the ``NullBooleanField()`` model field to ``BooleanField(null=True)``.
Ignores usage in migration files, since Django kept the old class around to support old migrations.
Applied only in model files, not migration files, since Django kept the old class around to support old migrations.
You will need to make migrations after this fix makes changes to models.

.. code-block:: diff
Expand Down
5 changes: 5 additions & 0 deletions src/django_upgrade/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(self, target_version: tuple[int, int]) -> None:
migrations_re = re.compile(r"(^|[\\/])migrations([\\/])")
settings_re = re.compile(r"(\b|_)settings(\b|_)")
test_re = re.compile(r"(\b|_)tests?(\b|_)")
models_re = re.compile(r"(^|[\\/])models([\\/]|\.py)")


class State:
Expand Down Expand Up @@ -72,6 +73,10 @@ def looks_like_settings_file(self) -> bool:
def looks_like_test_file(self) -> bool:
return test_re.search(self.filename) is not None

@cached_property
def looks_like_models_file(self) -> bool:
return models_re.search(self.filename) is not None


AST_T = TypeVar("AST_T", bound=ast.AST)
TokenFunc = Callable[[List[Token], int], None]
Expand Down
4 changes: 2 additions & 2 deletions src/django_upgrade/fixers/null_boolean_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def visit_ImportFrom(
parents: list[ast.AST],
) -> Iterable[tuple[Offset, TokenFunc]]:
if (
not state.looks_like_migrations_file
state.looks_like_models_file
and is_rewritable_import_from(node)
and node.module == "django.db.models"
):
Expand All @@ -53,7 +53,7 @@ def visit_Call(
node: ast.Call,
parents: list[ast.AST],
) -> Iterable[tuple[Offset, TokenFunc]]:
if not state.looks_like_migrations_file and (
if state.looks_like_models_file and (
(
isinstance(node.func, ast.Name)
and "NullBooleanField" in state.from_imports["django.db.models"]
Expand Down
17 changes: 17 additions & 0 deletions tests/fixers/test_null_boolean_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def test_unmatched_import():
NullBooleanField()
""",
settings,
filename="models/blog.py",
)


Expand Down Expand Up @@ -43,6 +44,7 @@ class Book(Model):
valuable = BooleanField("Valuable", null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -57,6 +59,7 @@ def test_transform():
field = BooleanField(null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -71,6 +74,7 @@ def test_transform_import_exists():
field = BooleanField(null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -85,6 +89,7 @@ def test_transform_import_exists_second():
field = BooleanField(null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -99,6 +104,7 @@ def test_transform_module_import():
field = models.BooleanField(null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -113,6 +119,7 @@ def test_transform_with_pos_arg():
field = BooleanField("My Field", null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -127,6 +134,7 @@ def test_transform_with_kwarg():
field = BooleanField(verbose_name="My Field", null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -141,6 +149,7 @@ def test_transform_with_kwarg_ending_comma():
field = BooleanField(verbose_name="My Field", null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -155,6 +164,7 @@ def test_transform_with_kwargs():
field = BooleanField(verbose_name="My Field", validators=[], null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -173,6 +183,7 @@ def test_transform_with_kwargs_multiline():
null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -187,6 +198,7 @@ def test_transform_with_star_pos_arg():
field = BooleanField(*names, null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -201,6 +213,7 @@ def test_transform_with_star_kwargs():
field = BooleanField(**kwargs, null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -215,6 +228,7 @@ def test_transform_with_null_is_true_kwarg_relative_import():
models.BooleanField(null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -229,6 +243,7 @@ def test_transform_with_null_is_true_kwarg_absolute_import_renamed():
BooleanField(null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -243,6 +258,7 @@ def test_transform_with_null_is_true_kwarg_absolute_import_removed():
BooleanField(null=True)
""",
settings,
filename="models/blog.py",
)


Expand All @@ -257,4 +273,5 @@ def test_transform_with_null_is_function():
BooleanField(null=f())
""",
settings,
filename="models/blog.py",
)
41 changes: 41 additions & 0 deletions tests/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,47 @@ def test_looks_like_migrations_file_false(filename: str) -> None:
assert not state.looks_like_migrations_file


@pytest.mark.parametrize(
"filename",
(
"my_app/models/blog.py",
"my_app\\models\\blog.py",
"my_app/models/blogging/blog.py",
"my_app\\models\\blogging\\blog.py",
"my_other_app/models.py",
"my_other_app\\models.py",
"my_app/models/__init__.py",
),
)
def test_looks_like_models_file_true(filename: str) -> None:
state = State(
settings=settings,
filename=filename,
from_imports=defaultdict(set),
)
assert state.looks_like_models_file


@pytest.mark.parametrize(
"filename",
(
"my_app/model.py",
"my_app/model/blog.py",
"my_app/model\\blog.py",
"my_app/test_models/test_foo.py",
"my_app/tests/test_models.py",
"my_app/migrations/0020_delete_old_models.py",
),
)
def test_looks_like_models_file_false(filename: str) -> None:
state = State(
settings=settings,
filename=filename,
from_imports=defaultdict(set),
)
assert not state.looks_like_models_file


@pytest.mark.parametrize(
"filename",
(
Expand Down

0 comments on commit 445c85b

Please sign in to comment.