Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add State.looks_like_models_file #390

Merged
merged 1 commit into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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