Skip to content
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 .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Set up Python
uses: astral-sh/setup-uv@v5
with:
python-version: "3.10"
python-version: "3.14"
- name: Build artifacts
run: |
uvx --from build python -m build --installer uv
Expand Down
14 changes: 5 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
matrix:
python: ["3.13"]
python: ["3.10"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
Expand All @@ -42,16 +42,12 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
django: ["4.2", "5.0", "5.1", "5.2"]
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
django: ["5.1", "5.2"]
database: ["sqlite", "postgres", "mysql"]
exclude:
- python: 3.9
django: 5.0
- python: 3.9
django: 5.1
- python: 3.9
django: 5.2
- python: "3.14"
django: "5.1"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DJANGO: ${{ matrix.django }}
Expand Down
10 changes: 5 additions & 5 deletions modeltranslation/_compat.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any
from collections.abc import Callable

import django
from typing import Iterable
from typing import Optional
from collections.abc import Iterable

if TYPE_CHECKING:
from django.db.models import QuerySet
Expand Down Expand Up @@ -32,7 +32,7 @@ def clear_ForeignObjectRel_caches(field: ForeignObjectRel):

def build_refresh_from_db(
old_refresh_from_db: Callable[
[Any, Optional[str], Optional[Iterable[str]], QuerySet[Any] | None], None
[Any, str | None, Iterable[str] | None, QuerySet[Any] | None], None
],
):
from modeltranslation.manager import append_translated
Expand All @@ -57,7 +57,7 @@ def is_hidden(field: ForeignObjectRel) -> bool:

# Django versions below 5.1 do not have `from_queryset` argument.
def build_refresh_from_db( # type: ignore[misc]
old_refresh_from_db: Callable[[Any, Optional[str], Optional[Iterable[str]]], None],
old_refresh_from_db: Callable[[Any, str | None, Iterable[str] | None], None],
):
from modeltranslation.manager import append_translated

Expand Down
7 changes: 2 additions & 5 deletions modeltranslation/_typing.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from __future__ import annotations

import sys
from typing import Literal, TypeVar, Union

from django.contrib import admin
from django.contrib.admin.options import BaseModelAdmin

if sys.version_info >= (3, 11):
from typing import Self, TypeAlias # noqa: F401
else:
from typing_extensions import Self, TypeAlias # noqa: F401
from typing_extensions import Self # noqa: F401
from typing import TypeAlias

AutoPopulate: TypeAlias = "bool | Literal['all', 'default', 'required']"

Expand Down
10 changes: 5 additions & 5 deletions modeltranslation/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _get_declared_fieldsets(
) -> _FieldsetSpec | None:
# Take custom modelform fields option into account
if not self.fields and hasattr(self.form, "_meta") and self.form._meta.fields:
self.fields = self.form._meta.fields # type: ignore[misc]
self.fields = self.form._meta.fields # type: ignore[assignment,misc]
# takes into account non-standard add_fieldsets attribute used by UserAdmin
fieldsets = (
self.add_fieldsets
Expand All @@ -57,14 +57,14 @@ def _get_declared_fieldsets(
if fieldsets:
return self._patch_fieldsets(fieldsets)
elif self.fields:
return [(None, {"fields": self.replace_orig_field(self.get_fields(request, obj))})]
return [(None, {"fields": self.replace_orig_field(self.get_fields(request, obj))})] # type: ignore[typeddict-item]
return None

def _patch_fieldsets(self, fieldsets: _FieldsetSpec) -> _FieldsetSpec:
fieldsets_new = list(fieldsets)
for name, dct in fieldsets:
if "fields" in dct:
dct["fields"] = self.replace_orig_field(dct["fields"])
dct["fields"] = self.replace_orig_field(dct["fields"]) # type: ignore[typeddict-item]
return fieldsets_new

def formfield_for_dbfield(
Expand All @@ -81,7 +81,7 @@ def patch_translation_field(
if field.required:
field.required = False
field.blank = True
self._orig_was_required["%s.%s" % (db_field.model._meta, db_field.name)] = True
self._orig_was_required["{}.{}".format(db_field.model._meta, db_field.name)] = True

# For every localized field copy the widget from the original field
# and add a css class to identify a modeltranslation widget.
Expand Down Expand Up @@ -131,7 +131,7 @@ def patch_translation_field(
# Add another css class to identify a default modeltranslation widget
css_classes.append("mt-default")
if orig_formfield.required or self._orig_was_required.get(
"%s.%s" % (orig_field.model._meta, orig_field.name)
"{}.{}".format(orig_field.model._meta, orig_field.name)
):
# In case the original form field was required, make the
# default translation field required instead.
Expand Down
3 changes: 2 additions & 1 deletion modeltranslation/decorators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, TypeVar
from typing import TYPE_CHECKING, Any, TypeVar
from collections.abc import Callable
from collections.abc import Iterable

from django.db.models import Model
Expand Down
6 changes: 3 additions & 3 deletions modeltranslation/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def create_translation_field(model: type[Model], field_name: str, lang: str, emp


def field_factory(baseclass: type[fields.Field]) -> type[TranslationField]:
class TranslationFieldSpecific(TranslationField, baseclass): # type: ignore[valid-type, misc]
class TranslationFieldSpecific(TranslationField, baseclass): # type: ignore[valid-type]
pass

# Reflect baseclass name of returned subclass
Expand Down Expand Up @@ -198,7 +198,7 @@ def __init__(
self.remote_field.related_name = "%s_rel_+" % self.name
elif is_hidden(self.remote_field):
# Even if the backwards relation is disabled, django internally uses it, need to use a language scoped related_name
self.remote_field.related_name = "_%s_%s_+" % (
self.remote_field.related_name = "_{}_{}_+".format(
self.model.__name__.lower(),
self.name,
)
Expand All @@ -210,7 +210,7 @@ def __init__(
self.related_query_name(), self.language
)
self.related_query_name = lambda: loc_related_query_name
self.remote_field.related_name = "%s_set" % (
self.remote_field.related_name = "{}_set".format(
build_localized_fieldname(self.model.__name__.lower(), language),
)
else:
Expand Down
2 changes: 1 addition & 1 deletion modeltranslation/management/commands/loaddata.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def check_mode(
if value == "0" or value.lower() == "false":
value = False # type: ignore[assignment]
if value not in ALLOWED:
raise ValueError("%s option can be only one of: %s" % (opt_str, ALLOWED_FOR_PRINT))
raise ValueError("{} option can be only one of: {}".format(opt_str, ALLOWED_FOR_PRINT))
setattr(namespace or parser.values, option.dest, value) # type: ignore[attr-defined]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def handle(self, *args: Any, **options: Any) -> None:
for model in models:
db_table = model._meta.db_table
model_name = model._meta.model_name
model_full_name = "%s.%s" % (model._meta.app_label, model_name)
model_full_name = "{}.{}".format(model._meta.app_label, model_name)
opts = translator.get_options_for_model(model)
for field_name, fields in opts.local_fields.items():
# Take `db_column` attribute into account
Expand Down Expand Up @@ -144,7 +144,7 @@ def get_sync_sql(
col_type = f.db_type(connection=connection)
field_sql = [style.SQL_FIELD(qn(f.column)), style.SQL_COLTYPE(col_type)] # type: ignore[arg-type]
# column creation
stmt = "ALTER TABLE %s ADD COLUMN %s" % (qn(db_table), " ".join(field_sql))
stmt = "ALTER TABLE {} ADD COLUMN {}".format(qn(db_table), " ".join(field_sql))
if not f.null:
stmt += " " + style.SQL_KEYWORD("NOT NULL")
sql_output.append(stmt + ";")
Expand Down
15 changes: 8 additions & 7 deletions modeltranslation/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def append_lookup_key(model: type[Model], lookup_key: str) -> set[str]:
rest = append_lookup_key(transmodel, pieces[1])
fields = {"__".join(pr) for pr in itertools.product(fields, rest)}
else:
fields = {"%s__%s" % (f, pieces[1]) for f in fields}
fields = {"{}__{}".format(f, pieces[1]) for f in fields}
return fields


Expand Down Expand Up @@ -169,7 +169,7 @@ def get_field_by_colum_name(model: type[Model], col: str) -> Field:
return field
except FieldDoesNotExist:
pass
field = _C2F_CACHE.get((model, col), None) # type: ignore[arg-type]
field = _C2F_CACHE.get((model, col), None) # type: ignore[assignment]
if field:
return field
# D'oh, need to search through all of them.
Expand Down Expand Up @@ -252,7 +252,7 @@ def select_related(self, *fields: Any, **kwargs: Any) -> Self:
new_args.append(None)
else:
new_args.append(rewrite_lookup_key(self.model, key))
return super().select_related(*new_args, **kwargs)
return super().select_related(*new_args, **kwargs) # type: ignore[arg-type]

# This method was not present in django-linguo
def _rewrite_col(self, col: Col) -> None:
Expand All @@ -277,11 +277,12 @@ def _rewrite_where(self, q: Lookup | Node) -> None:
self._rewrite_col(q.lhs)
if isinstance(q, Node):
for child in q.children:
self._rewrite_where(child)
self._rewrite_where(child) # type: ignore[arg-type]

def _rewrite_order(self) -> None:
self.query.order_by = [
rewrite_order_lookup_key(self.model, field_name) for field_name in self.query.order_by
rewrite_order_lookup_key(self.model, field_name) # type: ignore[arg-type]
for field_name in self.query.order_by
]

def _rewrite_select_related(self) -> None:
Expand All @@ -297,7 +298,7 @@ def _rewrite_q(self, q: Node | tuple[str, Any]) -> Any:
if isinstance(q, tuple) and len(q) == 2:
return rewrite_lookup_key(self.model, q[0]), q[1]
if isinstance(q, Node):
q.children = list(map(self._rewrite_q, q.children))
q.children = list(map(self._rewrite_q, q.children)) # type: ignore[arg-type]
return q

# This method was not present in django-linguo
Expand All @@ -309,7 +310,7 @@ def _rewrite_f(self, q: models.F | Node) -> models.F | Node:
q.name = rewrite_lookup_key(self.model, q.name)
return q
if isinstance(q, Node):
q.children = list(map(self._rewrite_f, q.children))
q.children = list(map(self._rewrite_f, q.children)) # type: ignore[arg-type]
# Django >= 1.8
if hasattr(q, "lhs"):
q.lhs = self._rewrite_f(q.lhs)
Expand Down
2 changes: 1 addition & 1 deletion modeltranslation/settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from typing import Callable
from collections.abc import Callable

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
Expand Down
2 changes: 1 addition & 1 deletion modeltranslation/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class CustomThroughModel(models.Model):

@property
def test_property(self):
return "%s_%s" % (self.__class__.__name__, self.rel_1_id)
return "{}_{}".format(self.__class__.__name__, self.rel_1_id)

def test_method(self):
return self.rel_1_id + 1
Expand Down
5 changes: 0 additions & 5 deletions modeltranslation/tests/test_runtime_typing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sys
from typing import get_type_hints

import pytest
Expand All @@ -17,10 +16,6 @@ class TestInlineModelAdmin(
pass


@pytest.mark.skipif(
sys.version_info < (3, 10),
reason="get_type_hints fails on Python 3.9 despite future annotations",
)
@pytest.mark.parametrize(
"cls",
[
Expand Down
9 changes: 5 additions & 4 deletions modeltranslation/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from textwrap import dedent
from copy import deepcopy
from functools import partial
from typing import Any, Callable, ClassVar, cast
from typing import Any, ClassVar, cast
from collections.abc import Callable
from collections.abc import Collection, Iterable, Sequence

from django.core.exceptions import ImproperlyConfigured
Expand Down Expand Up @@ -187,7 +188,7 @@ def get_field_names(self) -> list[str]:
def __str__(self) -> str:
local = tuple(self.local_fields.keys())
inherited = tuple(set(self.all_fields.keys()) - set(local))
return "%s: %s + %s" % (self.__class__.__name__, local, inherited)
return "{}: {} + {}".format(self.__class__.__name__, local, inherited)


class MultilingualOptions(options.Options):
Expand Down Expand Up @@ -256,7 +257,7 @@ class NewMultilingualManager(
def deconstruct(self):
return (
False, # as_manager
"%s.%s" % (self._old_module, self._old_class), # manager_class
"{}.{}".format(self._old_module, self._old_class), # manager_class
None, # qs_class
self._constructor_args[0], # args
self._constructor_args[1], # kwargs
Expand Down Expand Up @@ -356,7 +357,7 @@ def add_constraints():
yield new_constraint

model._meta.unique_together += tuple(add_unique_together()) # type: ignore[operator]
model._meta.constraints += tuple(add_constraints())
model._meta.constraints += tuple(add_constraints()) # type: ignore[operator]
# `unique_together` needs `original_attrs` to be set, for this changes to appear in migrations.
for attr_name in ("unique_together",):
if value := getattr(model._meta, attr_name):
Expand Down
8 changes: 4 additions & 4 deletions modeltranslation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def build_lang(lang: str) -> str:


def build_localized_fieldname(field_name: str, lang: str) -> str:
return str("%s_%s" % (field_name, build_lang(lang)))
return str("{}_{}".format(field_name, build_lang(lang)))


def _build_localized_verbose_name(verbose_name: Any, lang: str) -> str:
Expand All @@ -79,7 +79,7 @@ def _build_localized_verbose_name(verbose_name: Any, lang: str) -> str:

def _join_css_class(bits: list[str], offset: int) -> str:
if "-".join(bits[-offset:]) in settings.AVAILABLE_LANGUAGES + ["en-us", "ind"]:
return "%s-%s" % ("_".join(bits[: len(bits) - offset]), "_".join(bits[-offset:]))
return "{}-{}".format("_".join(bits[: len(bits) - offset]), "_".join(bits[-offset:]))
return ""


Expand Down Expand Up @@ -112,10 +112,10 @@ def build_css_class(localized_fieldname: str, prefix: str = ""):
# 'foo_bar_de' --> 'foo_bar-de',
# 'foo_bar_baz_de' --> 'foo_bar_baz-de'
css_class = _join_css_class(bits, 1)
return "%s-%s" % (prefix, css_class) if prefix else css_class
return "{}-{}".format(prefix, css_class) if prefix else css_class


def unique(seq: Iterable[_T]) -> Generator[_T, None, None]:
def unique(seq: Iterable[_T]) -> Generator[_T]:
"""
Returns a generator yielding unique sequence members in order

Expand Down
Loading