Features: 1) Add detailed translation logic in deepl_translate command with placeholder mapping and DeepL API integration; 2) Enhance deepl_translate with better PO handling and dynamic saving.

Fixes: 1) Correct incorrect type annotations and imports for `readline` in `deepl_translate` command; 2) Remove unused variables `billing_address` and `shipping_address` in address application logic.

Extra: Add `# type: ignore` comments to suppress type-checking errors across admin classes.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-07-14 17:12:26 +03:00
parent e49c942a1b
commit c149adb0a8
4 changed files with 93 additions and 27 deletions

View file

@ -170,7 +170,7 @@ class CategoryChildrenInline(TabularInline):
@register(AttributeGroup) @register(AttributeGroup)
class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = AttributeGroup # type: ignore [misc] model = AttributeGroup # type: ignore [misc]
list_display = ("name", "modified") list_display = ("name", "modified")
@ -182,7 +182,7 @@ class AttributeGroupAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Attribute) @register(Attribute)
class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Attribute # type: ignore [misc] model = Attribute # type: ignore [misc]
list_display = ("name", "group", "value_type", "modified") list_display = ("name", "group", "value_type", "modified")
@ -196,7 +196,7 @@ class AttributeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(AttributeValue) @register(AttributeValue)
class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = AttributeValue # type: ignore [misc] model = AttributeValue # type: ignore [misc]
list_display = ("attribute", "value", "modified") list_display = ("attribute", "value", "modified")
@ -226,7 +226,7 @@ class CategoryAdmin(FieldsetsMixin, ActivationActionsMixin, DraggableMPTTAdmin):
@register(Brand) @register(Brand)
class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Brand # type: ignore [misc] model = Brand # type: ignore [misc]
list_display = ("name",) list_display = ("name",)
@ -239,7 +239,7 @@ class BrandAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Product) @register(Product)
class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Product # type: ignore [misc] model = Product # type: ignore [misc]
list_display = ( list_display = (
@ -279,7 +279,7 @@ class ProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(ProductTag) @register(ProductTag)
class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = ProductTag # type: ignore [misc] model = ProductTag # type: ignore [misc]
list_display = ("tag_name",) list_display = ("tag_name",)
@ -291,7 +291,7 @@ class ProductTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(CategoryTag) @register(CategoryTag)
class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = CategoryTag # type: ignore [misc] model = CategoryTag # type: ignore [misc]
list_display = ("tag_name",) list_display = ("tag_name",)
@ -303,7 +303,7 @@ class CategoryTagAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Vendor) @register(Vendor)
class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Vendor # type: ignore [misc] model = Vendor # type: ignore [misc]
list_display = ("name", "markup_percent", "modified") list_display = ("name", "markup_percent", "modified")
@ -317,7 +317,7 @@ class VendorAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Feedback) @register(Feedback)
class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Feedback # type: ignore [misc] model = Feedback # type: ignore [misc]
list_display = ("order_product", "rating", "comment", "modified") list_display = ("order_product", "rating", "comment", "modified")
@ -330,7 +330,7 @@ class FeedbackAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Order) @register(Order)
class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Order # type: ignore [misc] model = Order # type: ignore [misc]
list_display = ( list_display = (
@ -359,7 +359,7 @@ class OrderAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(OrderProduct) @register(OrderProduct)
class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = OrderProduct # type: ignore [misc] model = OrderProduct # type: ignore [misc]
list_display = ("order", "product", "quantity", "buy_price", "status", "modified") list_display = ("order", "product", "quantity", "buy_price", "status", "modified")
@ -373,7 +373,7 @@ class OrderProductAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(PromoCode) @register(PromoCode)
class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = PromoCode # type: ignore [misc] model = PromoCode # type: ignore [misc]
list_display = ( list_display = (
@ -402,7 +402,7 @@ class PromoCodeAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Promotion) @register(Promotion)
class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Promotion # type: ignore [misc] model = Promotion # type: ignore [misc]
list_display = ("name", "discount_percent", "modified") list_display = ("name", "discount_percent", "modified")
@ -415,7 +415,7 @@ class PromotionAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Stock) @register(Stock)
class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Stock # type: ignore [misc] model = Stock # type: ignore [misc]
list_display = ("product", "vendor", "sku", "quantity", "price", "modified") list_display = ("product", "vendor", "sku", "quantity", "price", "modified")
@ -436,7 +436,7 @@ class StockAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(Wishlist) @register(Wishlist)
class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = Wishlist # type: ignore [misc] model = Wishlist # type: ignore [misc]
list_display = ("user", "modified") list_display = ("user", "modified")
@ -448,7 +448,7 @@ class WishlistAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
@register(ProductImage) @register(ProductImage)
class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): class ProductImageAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc]
# noinspection PyClassVar # noinspection PyClassVar
model = ProductImage # type: ignore [misc] model = ProductImage # type: ignore [misc]
list_display = ("alt", "product", "priority", "modified") list_display = ("alt", "product", "priority", "modified")

View file

@ -126,11 +126,11 @@ class Command(BaseCommand):
if not auth_key: if not auth_key:
raise CommandError("DEEPL_AUTH_KEY not set") raise CommandError("DEEPL_AUTH_KEY not set")
# attempt to import readline for interactive prefill # attempt to import readline for interactive fill
try: try:
import readline import readline
except ImportError: except ImportError:
readline = None # fallback readline = None # type: ignore [assignment]
for target_lang in target_langs: for target_lang in target_langs:
api_code = DEEPL_TARGET_LANGUAGES_MAPPING.get(target_lang) api_code = DEEPL_TARGET_LANGUAGES_MAPPING.get(target_lang)
@ -159,7 +159,6 @@ class Command(BaseCommand):
if not en_po: if not en_po:
raise CommandError(f"Failed to load en_GB PO for {app_conf.label}") raise CommandError(f"Failed to load en_GB PO for {app_conf.label}")
# gather entries with missing translations
missing = [e for e in en_po if e.msgid and not e.msgstr and not e.obsolete] missing = [e for e in en_po if e.msgid and not e.msgstr and not e.obsolete]
if missing: if missing:
self.stdout.write(self.style.NOTICE(f"⚠️ {len(missing)} missing in en_GB")) self.stdout.write(self.style.NOTICE(f"⚠️ {len(missing)} missing in en_GB"))
@ -168,16 +167,16 @@ class Command(BaseCommand):
if readline: if readline:
def hook(): def hook():
readline.insert_text(default) readline.insert_text(default) # noqa: B023
readline.redisplay() readline.redisplay()
readline.set_pre_input_hook(hook) readline.set_pre_input_hook(hook) # type: ignore [attr-defined]
prompt = f"Enter translation for '{e.msgid}': " prompt = f"Enter translation for '{e.msgid}': "
user_in = input(prompt).strip() user_in = input(prompt).strip()
if readline: if readline:
readline.set_pre_input_hook(None) readline.set_pre_input_hook(None) # type: ignore [attr-defined]
if user_in: if user_in:
e.msgstr = user_in e.msgstr = user_in
@ -187,7 +186,76 @@ class Command(BaseCommand):
en_po.save(en_path) en_po.save(en_path)
self.stdout.write(self.style.SUCCESS("Updated en_GB PO")) self.stdout.write(self.style.SUCCESS("Updated en_GB PO"))
# … rest of your DeepL logic unchanged … entries = [e for e in en_po if e.msgid and not e.obsolete]
# build new_po, translate missing entries, save target PO, etc. source_map = {e.msgid: e.msgstr for e in entries}
tgt_dir = os.path.join(
app_conf.path,
"locale",
target_lang.replace("-", "_"),
"LC_MESSAGES",
)
os.makedirs(tgt_dir, exist_ok=True)
tgt_path = os.path.join(str(tgt_dir), "django.po")
old_tgt = None
if os.path.exists(tgt_path):
self.stdout.write(f" loading existing {target_lang} PO…")
try:
old_tgt = load_po_sanitized(str(tgt_path))
except Exception as e:
self.stdout.write(self.style.WARNING(f"Existing PO parse error({e!s}), starting fresh"))
new_po = polib.POFile()
new_po.metadata = en_po.metadata.copy()
new_po.metadata["Language"] = target_lang
for entry in entries:
prev = old_tgt.find(entry.msgid) if old_tgt else None
new_po.append(
polib.POEntry(
msgid=entry.msgid,
msgstr=prev.msgstr if prev and prev.msgstr else "",
msgctxt=entry.msgctxt,
comment=entry.comment,
tcomment=entry.tcomment,
occurrences=entry.occurrences,
flags=entry.flags,
)
)
to_trans = [e for e in new_po if not e.msgstr]
if not to_trans:
self.stdout.write(self.style.WARNING(f"All done for {app_conf.label}"))
continue
protected = []
maps: list[list[str]] = []
for entry in to_trans:
txt = source_map[entry.msgid]
p_txt, p_map = placeholderize(txt)
protected.append(p_txt)
maps.append(p_map)
data = [
("auth_key", auth_key),
("target_lang", api_code),
] + [("text", t) for t in protected]
resp = requests.post("https://api.deepl.com/v2/translate", data=data)
try:
resp.raise_for_status()
result = resp.json()
except Exception as exc:
raise CommandError(f"DeepL error: {exc} {resp.text}") from exc
trans = result.get("translations", [])
if len(trans) != len(to_trans):
raise CommandError(f"Got {len(trans)} translations, expected {len(to_trans)}")
for entry, obj, pmap in zip(to_trans, trans, maps, strict=True):
entry.msgstr = deplaceholderize(obj["text"], pmap)
new_po.save(str(tgt_path))
self.stdout.write(self.style.SUCCESS(f"Saved {tgt_path}"))
self.stdout.write(self.style.SUCCESS("Done.")) self.stdout.write(self.style.SUCCESS("Done."))

View file

@ -1543,8 +1543,6 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi
def apply_addresses(self, billing_address_uuid: str | None = None, shipping_address_uuid: str | None = None): def apply_addresses(self, billing_address_uuid: str | None = None, shipping_address_uuid: str | None = None):
try: try:
billing_address = Address.objects.none()
shipping_address = Address.objects.none()
if not any([shipping_address_uuid, billing_address_uuid]) and not self.is_whole_digital: if not any([shipping_address_uuid, billing_address_uuid]) and not self.is_whole_digital:
raise ValueError(_("you can only buy physical products with shipping address specified")) raise ValueError(_("you can only buy physical products with shipping address specified"))

View file

@ -48,7 +48,7 @@ class OrderInline(admin.TabularInline):
icon = "fa-solid fa-cart-shopping" icon = "fa-solid fa-cart-shopping"
class UserAdmin(ActivationActionsMixin, BaseUserAdmin): class UserAdmin(ActivationActionsMixin, BaseUserAdmin): # type: ignore [misc]
inlines = (BalanceInline, OrderInline) inlines = (BalanceInline, OrderInline)
fieldsets = ( fieldsets = (
(None, {"fields": ("email", "password")}), (None, {"fields": ("email", "password")}),