|
|
@ -57,22 +57,26 @@ class MultiLanguageJSONWidget(Widget): |
|
|
return child_media |
|
|
return child_media |
|
|
|
|
|
|
|
|
def _get_active_language_codes(self) -> list[str]: |
|
|
def _get_active_language_codes(self) -> list[str]: |
|
|
codes: list[str] = [] |
|
|
|
|
|
if Language is not None: |
|
|
|
|
|
try: |
|
|
|
|
|
codes = list( |
|
|
|
|
|
Language.objects.filter(status=True).values_list("code", flat=True) # type: ignore[attr-defined] |
|
|
|
|
|
) |
|
|
|
|
|
except Exception: |
|
|
|
|
|
try: |
|
|
|
|
|
codes = list(Language.objects.values_list("code", flat=True)) |
|
|
|
|
|
except Exception: |
|
|
|
|
|
codes = [] |
|
|
|
|
|
|
|
|
|
|
|
if not codes: |
|
|
|
|
|
codes = [code for code, _ in getattr(settings, "LANGUAGES", [("en", "English")])] |
|
|
|
|
|
|
|
|
|
|
|
return list(dict.fromkeys(codes)) |
|
|
|
|
|
|
|
|
return [ |
|
|
|
|
|
"ar", # Arabic |
|
|
|
|
|
"tr", # Turkish |
|
|
|
|
|
"fa", # Persian (Farsi) |
|
|
|
|
|
"ur", # Urdu |
|
|
|
|
|
"bn", # Bengali |
|
|
|
|
|
"id", # Indonesian |
|
|
|
|
|
"fr", # French |
|
|
|
|
|
"ru", # Russian |
|
|
|
|
|
"uz", # Uzbek |
|
|
|
|
|
"ky", # Kyrgyz |
|
|
|
|
|
"tg", # Tajik |
|
|
|
|
|
"az", # Azerbaijani |
|
|
|
|
|
"en", # English |
|
|
|
|
|
"de", # German |
|
|
|
|
|
"zh", # Mandarin Chinese |
|
|
|
|
|
"ha", # Hausa |
|
|
|
|
|
"sw", # Swahili |
|
|
|
|
|
"es", # Spanish |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
def _normalize_value(self, value: Any) -> dict[str, Any]: |
|
|
def _normalize_value(self, value: Any) -> dict[str, Any]: |
|
|
mapping: dict[str, Any] = {} |
|
|
mapping: dict[str, Any] = {} |
|
|
@ -185,6 +189,168 @@ class MultiLanguageJSONWidget(Widget): |
|
|
return template.render(context) |
|
|
return template.render(context) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MultiLanguageAddressWidget(MultiLanguageJSONWidget): |
|
|
|
|
|
""" |
|
|
|
|
|
Unfold-styled widget for JSONField storing localized address parts. |
|
|
|
|
|
Each language contains a list of address parts (e.g. book, chapter, verse). |
|
|
|
|
|
""" |
|
|
|
|
|
template_name = "utils/widgets/multilang_address_widget.html" |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None: |
|
|
|
|
|
super().__init__(input_widget_class=None, attrs=attrs) |
|
|
|
|
|
|
|
|
|
|
|
def _normalize_value(self, value: Any) -> dict[str, list[str]]: |
|
|
|
|
|
mapping: dict[str, list[str]] = {} |
|
|
|
|
|
if not value: |
|
|
|
|
|
return mapping |
|
|
|
|
|
|
|
|
|
|
|
if isinstance(value, str): |
|
|
|
|
|
try: |
|
|
|
|
|
value = json.loads(value) |
|
|
|
|
|
except Exception: |
|
|
|
|
|
return mapping |
|
|
|
|
|
|
|
|
|
|
|
if isinstance(value, list): |
|
|
|
|
|
for item in value: |
|
|
|
|
|
if not isinstance(item, dict): |
|
|
|
|
|
continue |
|
|
|
|
|
code = ( |
|
|
|
|
|
item.get("language_code") |
|
|
|
|
|
or item.get("code") |
|
|
|
|
|
or item.get("lang") |
|
|
|
|
|
or item.get("language") |
|
|
|
|
|
) |
|
|
|
|
|
title = item.get("title") |
|
|
|
|
|
if code and isinstance(title, list): |
|
|
|
|
|
mapping[str(code)] = [str(x) for x in title] |
|
|
|
|
|
elif isinstance(value, dict): |
|
|
|
|
|
for code, title in value.items(): |
|
|
|
|
|
if isinstance(title, list): |
|
|
|
|
|
mapping[str(code)] = [str(x) for x in title] |
|
|
|
|
|
else: |
|
|
|
|
|
mapping[str(code)] = [str(title)] |
|
|
|
|
|
|
|
|
|
|
|
return mapping |
|
|
|
|
|
|
|
|
|
|
|
def get_context(self, name: str, value: Any, attrs: Optional[dict[str, Any]]): |
|
|
|
|
|
context = Widget.get_context(self, name, value, attrs) |
|
|
|
|
|
|
|
|
|
|
|
languages = self._get_active_language_codes() |
|
|
|
|
|
values_map = self._normalize_value(value) |
|
|
|
|
|
|
|
|
|
|
|
# Ensure languages include any language codes present in value |
|
|
|
|
|
for code in values_map.keys(): |
|
|
|
|
|
if code not in languages: |
|
|
|
|
|
languages.append(code) |
|
|
|
|
|
|
|
|
|
|
|
# Reorder: languages with existing values first |
|
|
|
|
|
codes_with_values = [code for code in languages if values_map.get(code)] |
|
|
|
|
|
codes_without_values = [code for code in languages if code not in codes_with_values] |
|
|
|
|
|
languages = [*codes_with_values, *codes_without_values] |
|
|
|
|
|
|
|
|
|
|
|
rendered_languages: list[dict[str, Any]] = [] |
|
|
|
|
|
for code in languages: |
|
|
|
|
|
parts = values_map.get(code) or [] |
|
|
|
|
|
rendered_languages.append({ |
|
|
|
|
|
"code": code, |
|
|
|
|
|
"parts": parts, |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
# Prepare serialized hidden value (JSON string) |
|
|
|
|
|
serialized_list: list[dict[str, Any]] = [] |
|
|
|
|
|
for code in languages: |
|
|
|
|
|
parts = values_map.get(code) |
|
|
|
|
|
if parts: |
|
|
|
|
|
filtered_parts = [str(x).strip() for x in parts if str(x).strip()] |
|
|
|
|
|
if filtered_parts: |
|
|
|
|
|
serialized_list.append({"language_code": code, "title": filtered_parts}) |
|
|
|
|
|
|
|
|
|
|
|
context["widget"].update( |
|
|
|
|
|
{ |
|
|
|
|
|
"languages": languages, |
|
|
|
|
|
"rendered_languages": rendered_languages, |
|
|
|
|
|
"field_name": name, |
|
|
|
|
|
"serialized": json.dumps(serialized_list, ensure_ascii=False), |
|
|
|
|
|
"has_value_codes": codes_with_values, |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
return context |
|
|
|
|
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name): |
|
|
|
|
|
hidden_value = data.get(name) |
|
|
|
|
|
if hidden_value not in (None, ""): |
|
|
|
|
|
return hidden_value |
|
|
|
|
|
return "[]" |
|
|
|
|
|
|
|
|
|
|
|
def value_omitted_from_data(self, data, files, name): |
|
|
|
|
|
return name not in data |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LinksJSONWidget(Widget): |
|
|
|
|
|
""" |
|
|
|
|
|
Unfold-styled widget for JSONField storing a list of links (title, link). |
|
|
|
|
|
""" |
|
|
|
|
|
template_name = "utils/widgets/links_json_widget.html" |
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None: |
|
|
|
|
|
super().__init__(attrs) |
|
|
|
|
|
|
|
|
|
|
|
def _normalize_value(self, value: Any) -> list[dict[str, str]]: |
|
|
|
|
|
if not value: |
|
|
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
if isinstance(value, str): |
|
|
|
|
|
try: |
|
|
|
|
|
value = json.loads(value) |
|
|
|
|
|
except Exception: |
|
|
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
if isinstance(value, list): |
|
|
|
|
|
results = [] |
|
|
|
|
|
for item in value: |
|
|
|
|
|
if isinstance(item, dict): |
|
|
|
|
|
title = item.get("title", "") |
|
|
|
|
|
link = item.get("link", "") |
|
|
|
|
|
results.append({ |
|
|
|
|
|
"title": str(title), |
|
|
|
|
|
"link": str(link) |
|
|
|
|
|
}) |
|
|
|
|
|
return results |
|
|
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
def get_context(self, name: str, value: Any, attrs: Optional[dict[str, Any]]): |
|
|
|
|
|
context = super().get_context(name, value, attrs) |
|
|
|
|
|
links = self._normalize_value(value) |
|
|
|
|
|
|
|
|
|
|
|
# Prepare serialized hidden value (JSON string) |
|
|
|
|
|
serialized_list = [l for l in links if l.get("title") or l.get("link")] |
|
|
|
|
|
|
|
|
|
|
|
context["widget"].update({ |
|
|
|
|
|
"field_name": name, |
|
|
|
|
|
"links": links, |
|
|
|
|
|
"serialized": json.dumps(serialized_list, ensure_ascii=False), |
|
|
|
|
|
}) |
|
|
|
|
|
return context |
|
|
|
|
|
|
|
|
|
|
|
def value_from_datadict(self, data, files, name): |
|
|
|
|
|
hidden_value = data.get(name) |
|
|
|
|
|
if hidden_value not in (None, ""): |
|
|
|
|
|
return hidden_value |
|
|
|
|
|
return "[]" |
|
|
|
|
|
|
|
|
|
|
|
def value_omitted_from_data(self, data, files, name): |
|
|
|
|
|
return name not in data |
|
|
|
|
|
|
|
|
|
|
|
def render(self, name, value, attrs=None, renderer=None): |
|
|
|
|
|
if value is None: |
|
|
|
|
|
value = '' |
|
|
|
|
|
context = self.get_context(name, value, attrs) |
|
|
|
|
|
template = get_template(self.template_name) |
|
|
|
|
|
return template.render(context) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|