首页 > 解决方案 > 在Django中检测views.py之外的语言的问题

问题描述

我有一个 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()

我在 forms.py 中使用 countries_hangler.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/

country_handler.py 中的 get_language 函数仍返回“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,然后将数据发送到表单并在运行时动态构建它?

当我更改 url 中的语言时,我需要正确加载国家列表。

标签: pythondjangodjango-modelsdjango-viewsdjango-forms

解决方案


我自己终于找到了解决方案,我会分享它。首先要注意:

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()})

这里调用了 countries_handler.py 中的 get_country。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']

请注意我是如何直接从构造函数中填充选择字段的。data 被声明为可选,因为例如当我使用同一个类调用 return_state_by_country(country) 时,我没有任何数据要传递。

那是。我不会发布项目的所有文件以避免冗长。但最重要的是在这个答案中。我在那里学习了如何在运行时动态地将数据传递给表单构造函数。其余的保持不变。


推荐阅读