Проблема обнаружения языка вне views.py в Django
У меня есть приложение Django, которое загружает список стран и штатов из json-файла и переводит их с помощью пользовательского диктонария перевода, который я создал для этой цели. Я не использую gettext для этой конкретной задачи, потому что gettext не работает с данными, поступающими из баз данных или файлов.
Сначала файл, который обрабатывает json-файл
from django.utils.translation import gettext_lazy as _, get_language
import importlib
current_lng = get_language()
# importing the appropriate translation file based on current_lng
imp = importlib.import_module("countries.translations.countries_%s" % current_lng)
import json
def readJson(filename):
with open(filename, 'r', encoding="utf8") as fp:
return json.load(fp)
def get_country():
filepath = 'myproj/static/data/countries_states_cities.json'
all_data = readJson(filepath)
all_countries = [('----', _("--- Select a Country ---"))]
for x in all_data:
y = (x['name'], imp.t_countries(x['name']))
all_countries.append(y)
return all_countries
# the files continues with other function but they are not relevant
arr_country = get_country()
Я использую countries_hangler.py в forms.py
from django import forms
from .models import Address
from django.conf import settings
from .countries_handler import arr_country
from django.utils.translation import gettext_lazy as _
class AddressForm(forms.ModelForm):
# data = []
#def __init__(self, data):
# self.data = data
country = forms.ChoiceField(
choices = arr_country,
required = False,
label=_('Company Country Location'),
widget=forms.Select(attrs={'class':'form-control', 'id': 'id_country'}),
)
def get_state_by_country(self, country):
return return_state_by_country(country)
def get_city_by_state(self, state):
return return_city_by_state(state)
class Meta:
model = Address
fields = ['country']
В файле views.py у меня есть такие строки
from django.shortcuts import render
from django.http import HttpResponseRedirect, JsonResponse
from .forms import AddressForm
import json
from django.utils import translation
from django.utils.translation import gettext_lazy as _, get_language
def load_form(request):
form = AddressForm
# the lang variable is only passed for testing
return render(request, 'countries/country_form.html', {'form':form, 'lang':get_language()})
settings.py правильно настроен, вот соответствующие части
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# ...
LANGUAGE_CODE = 'en'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('en', 'English'),
('fr', 'French'),
]
Также мой urls.py определен правильно
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path('admin/', admin.site.urls),
path('countries/', include('countries.urls')),
prefix_default_language=False
)
Теперь, когда я меняю язык таким образом:
http://127.0.0.1:8000/fr/countries/
функция get_language в countries_handler.py по-прежнему возвращает 'en' вместо 'fr'. В результате файл перевода для французского языка никогда не загружается:
# countries/translations/countries_fr.py
# here's my little custom translation tool
def t_countries(c):
country_dict = {
"Albania" : "Albanie",
"Algeria" : "Algérie",
"Antigua and Barbuda" : "Antigua-et-Barbuda",
"Argentina" : "Argentine",
"Armenia" : "Arménie",
# ...
}
if c in country_dict:
return country_dict[c]
return c
Кстати, если я помещу следующие строки в forms.py или countries_handler.py:
from django.utils import translation
и тогда
translation.activate('fr')
Тогда изменение языка будет очень хорошо заметно.
Теперь я действительно знаю причину проблемы, единственная проблема, что я не могу получить решение. ПРИЧИНА ПРОБЛЕМЫ ЗАКЛЮЧАЕТСЯ В СЛЕДУЮЩЕМ:
В forms.py (через languages_handler.py) get_language() не определяет язык, измененный из url, потому что forms.py создает форму во время запуска сервера разработки, а не после запроса url.
В результате get_language возвращает правильный язык только в views.py, но не в forms.py. Конечно, я попытался загрузить данные формы в представление, а не в форму следующим образом
# views.py
from .countries_handler import arr_country
# .....
def load_form(request):
form = AddressForm(arr_country)
return render(request, 'countries/country_form.html', {'form':form, 'lang':get_language()})
и затем в форме:
class AddressForm(forms.ModelForm):
data = []
def __init__(self, data):
self.data = data
country = forms.ChoiceField(
choices = data,
required = False,
label=_('Company Country Location'),
widget=forms.Select(attrs={'class':'form-control', 'id': 'id_country'}),
)
Но это не работает. В этом случае форма даже не отображается!!!
Тогда, что я должен сделать, чтобы решить эту проблему? Есть ли способ определить текущий язык пользователя из url или сформировать cookie, а затем отправить данные на форму и построить ее динамически во время выполнения?
Мне нужно, чтобы список стран загружался с правильным переводом, когда я меняю язык в урле.
Вот как это сделать. Прежде всего, обратите внимание, что:
Forms.py и countries_handler.py запускаются при старте сервера, а не при возникновении http-запроса. Поэтому любое изменение, поступающее из браузера, например, изменение пользователем языка в url, должно быть обработано в views.py, в том самом месте, куда попадает маршрут в urls.py. Вот правильный views.py:
# ...
from .countries_handler import get_country, return_state_by_country
from django.utils.translation import gettext_lazy as _, get_language
# ...
def load_form(request):
# load_form is called by urls.py, therefore it respond to a change made in the url, at run time.
# get_language must be called here to get a correct result.
arr_country = get_country(get_language())
form = AddressForm(arr_country)
return render(request, 'countries/country_form.html', {'form':form, 'lang':get_language()})
Здесь вызывается get_country из countries_handler.py. get_language корректно передает ему текущий выбранный язык.
Вот forms.py:
class AddressForm(forms.ModelForm):
""" Using an empty list as a default argument is a common error.
It may lead to unwanted behavior. The correct way to do it is to
initialize the list to None """
def __init__(self, data=None, *args, **kwargs):
super(AddressForm, self).__init__(*args, **kwargs)
if data is not None:
self.fields['country'].choices = data
country = forms.ChoiceField(
choices = (),
required = False,
label=_('Company Country Location'),
widget=forms.Select(attrs={'class':'form-control', 'id': 'id_country'}),
)
def get_state_by_country(self, country):
return return_state_by_country(country)
def get_city_by_state(self, state):
return return_city_by_state(state)
class Meta:
model = Address
fields = ['country']
Обратите внимание, как я заполняю поля выбора из конструктора напрямую. данные объявлены как необязательные, потому что когда я использую тот же класс для вызова return_state_by_country(country), например, у меня нет никаких данных для передачи.
Так и есть. Я не выкладываю все файлы проекта, чтобы избежать многословия. Но основные находятся в этом ответе. В нем я узнал, как динамически передавать данные в конструктор формы во время выполнения. Остальное остается неизменным.