diff --git a/utils/multilang_json_widget.py b/utils/multilang_json_widget.py index c774174..8ec55b1 100644 --- a/utils/multilang_json_widget.py +++ b/utils/multilang_json_widget.py @@ -57,22 +57,26 @@ class MultiLanguageJSONWidget(Widget): return child_media 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]: mapping: dict[str, Any] = {} @@ -185,6 +189,168 @@ class MultiLanguageJSONWidget(Widget): 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) + + +