您的位置:首页 > 编程语言 > Go语言

初探django-写一个小游戏charade

2015-12-08 16:50 531 查看
初探django-写一个小游戏charade
2017/7/5
目的:通过“根据tutorial学习的同时,尝试写一个猜英文单词的游戏页面(charade)”这样一个行为,来记录 django 开发过程中的经验细节。
说明:后续开发其他项目时,也有补充部分经验到这里。

一、准备环境
1、建立项目
[root@tvm01 ~]# mkdir /opt/charade
[root@tvm01 ~]# cd /opt/charade
[root@tvm01 charade]# django-admin startproject www
[root@tvm01 charade]# cd www/

2、备注
1)此处为简单演示,未使用类似 virtualenv 这类配置,具体方法可以自行测试几次,很简单。
2)使用的系统是 centos6.5x64,并编译安装了python2.7,如果是 centos7, 则默认已经是 python2.7 的版本

二、操作概述
1、项目设置
[root@tvm01 www]# vim www/settings.py
设定时区,数据库等信息
建立数据库和表:
[root@tvm01 www]# python manage.py migrate

2、配置app
1)创建app
[root@tvm01 www]# django-admin startapp charade

2)设定urls:
[root@tvm01 www]# vim www/urls.py
[root@tvm01 www]# vim charade/urls.py

3)模型
[root@tvm01 www]# vim charade/models.py

通知django有数据变更
[root@tvm01 www]# python manage.py makemigrations charade
Migrations for 'charade':
0001_initial.py:
- Create model GameScoreBoard
- Create model GameTemporaryTable
- Create model Vocabulary

检查要变更的sql:
[root@tvm01 www]# python manage.py sqlmigrate charade 0001

执行变更:
[root@tvm01 www]# python manage.py migrate
这个是 migrate 所有的 apps
也可以指定某个 app 来执行 migrate 操作:
[root@tvm01 www]# python manage.py migrate charade 0001

查看执行过的变更:
[root@tvm01 www]# python manage.py showmigrations charade
charade
[X] 0001_initial

4)注册模型到后台
[root@tvm01 www]# vim charade/admin.py

5)视图
[root@tvm01 www]# vim charade/views.py

6)模版
模版和静态文件的目录
[root@tvm01 www]# mkdir charade/templates/charade -p
[root@tvm01 www]# mkdir charade/static/charade/images -p
[root@tvm01 www]# vim charade/templates/charade/index.html
[root@tvm01 www]# vim charade/templates/charade/detail.html

3、运行服务
[root@tvm01 www]# python manage.py runserver 0.0.0.0:80

4、创建管理员
[root@tvm01 www]# python manage.py createsuperuser

5、测试
[root@tvm01 www]# python manage.py test charade

6、shell
[root@tvm01 www]# python manage.py shell

7、流程
startproject(www) -> startapp(charade) -> settings/urls(www) -> urls/models(charade) -> views/admin(charade) -> templates/static(charade)

三、技巧
1、在模版的一个循环中,根据行数来使用不同的css class
注意 cycle 的用法
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}

2、调整后台的模版
1)找到django的源码文件路径
[root@tvm01 www]# python -c "
import sys
sys.path = sys.path[1:]
import django
print(django.__path__)"
['/usr/local/lib/python2.7/site-packages/django']

2)创建目录,拷贝并修改模版
[root@tvm01 mysite]# mkdir templates/admin -p
[root@tvm01 mysite]# cp /usr/local/lib/python2.7/site-packages/django/contrib/admin/templates/admin/base_site.html templates/admin/base_site.html

3)配置 www/settings.py
TEMPLATES = [
{
(略)
'DIRS': [os.path.join(BASE_DIR, 'templates'),],
(略)
},
]

3、支持中文
参考: https://docs.djangoproject.com/en/1.9/ref/unicode/#general-string-handling https://docs.djangoproject.com/en/1.9/ref/unicode/#models

1)针对 string
解决办法:
from __future__ import unicode_literals
原因:
a)Python 2 legacy:
my_string = "This is a bytestring"
my_unicode = u"This is an Unicode string"

b)Python 2 with unicode literals or Python 3:
from __future__ import unicode_literals

my_string = b"This is a bytestring"
my_unicode = "This is an Unicode string"

2)针对 model
解决办法:
from django.utils.encoding import python_2_unicode_compatible
原因:
选择 __str__() 还是 __unicode__()
如果使用的是 Python 3 的环境,则使用 __str__() 而不是 __unicode__()
如果要兼容 Python 2 的环境,请使用修饰符 python_2_unicode_compatible().

3)通常情况下,异常内容包括
UnicodeEncodeError
UnicodeDecodeError

4)使用示例(针对 model class 使用修饰符,直接返回中文字符,而不会报错)
# coding: utf-8
from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible

在每个 model class 前面,修饰一下:
@python_2_unicode_compatible
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField('选项', max_length=200)
votes = models.IntegerField('票数', default=0)

def __str__(self):              # __unicode__ on Python 2
return self.choice_text

4、使用用户认证系统组件
1)创建一个app
[root@tvm01 www]# django-admin startapp accounts

2)配置 www/settings.py
[root@tvm01 www]# vim www/settings.py
INSTALLED_APPS = [
(略)
'accounts',
(略)
]

3)配置 www/urls.py
[root@tvm01 www]# vim www/urls.py
urlpatterns = [
(略)
url(r'^accounts/', include('accounts.urls')),
(略)
]

4)配置 accounts/urls.py
[root@tvm01 www]# vim accounts/urls.py
from django.conf.urls import url
from django.contrib.auth import views as auth_views

app_name = 'accounts'
urlpatterns = [
#################################### accounts
#
url(r'^login/$', auth_views.login, name='login'),
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
]

5)配置模版
本次示例的模版是自己照着django官网auth文档中“All authentication views”这一块提到的模版写的,其实,也可以用django的form来生成模版内容,详情请参考github源码的示例

[root@tvm01 www]# mkdir accounts/templates/accounts -p
[root@tvm01 www]# cat accounts/templates/accounts/login.html
{% extends "charade/base.html" %}
{% load staticfiles %}
{% block titles %}Login{% endblock %}
{% block js4this %}
<script src="{% static 'charade/js/charade-login.js' %}"></script>
{% endblock %}
{% block content %}
<div class="row text-center">
{% if form.errors %}
<p class="text-danger">Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p class="text-warning">Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p class="text-info">Please login to see this page.</p>
{% endif %}
{% endif %}
</div>
<div class="row text-center">
<div class="col-md-4"></div>
<div class="col-md-4">
<form class="form-horizontal" method="post" action="{% url 'accounts:login' %}">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="id_username">username :</label>
{{ form.username }}
</div>
<div class="form-group">
<label class="control-label" for="id_password">password :</label>
{{ form.password }}
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</div>
</form>
</div>
<div class="col-md-4"></div>
</div>
{% endblock %}

6)使用修饰符
配置 charade/views.py
from django.contrib.auth.decorators import login_required

@login_required
def game_board(request):

在需要login的view前修饰一下,则render的页面会要求登录。

5、使用cache组件
1)配置 www/settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}

2)配置linux系统安装memcached并启动服务(略)
3)python安装memcache
[root@tvm001 ~]# pip install python-memcached
[root@tvm001 ~]# python -c "import memcache;print memcache.__version__"
1.57
4)使用修饰符
配置 charade/views.py
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def show_about(request):

被修饰的view将会被cache,抓包可以发现
Cache-Control:max-age=900

6、语法差异
从1.8到1.9,有几个地方要注意
1)Reversing by dotted path is deprecated
[root@tvm001 www]# python manage.py test
Creating test database for alias 'default'...
...../usr/lib/python2.7/site-packages/django/template/defaulttags.py:499: RemovedInDjango110Warning: Reversing by dotted path is deprecated (django.contrib.auth.views.login).
url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)

+.. deprecated:: 1.8
+
+    The dotted Python path syntax is deprecated and will be removed in
+    Django 2.0:::
+
+        {% url 'path.to.some_view' v1 v2 %}

全部调整为 name or namespance 的方式即可。

2)Namespacing URL names
1.9的版本是这样的:
【polls/urls.py】
app_name = 'polls'
【mysite/urls.py】
urlpatterns = [
url(r'^polls/', include('polls.urls')),

1.8的版本则是这样的:
【polls/urls.py】
没有定义 app_name
【mysite/urls.py】
url(r'^polls/', include('polls.urls', namespace="polls")),

可以打开2个版本的文档,搜索 namespacing 这一段来对比: https://docs.djangoproject.com/en/1.9/intro/tutorial03/ https://docs.djangoproject.com/en/1.8/intro/tutorial03/

3)在 startapp 时,将创建一个 app.py,可以为该 app 设置一些全局的变量。
例如:
[root@tvm01 www]# cat accounts/apps.py
from __future__ import unicode_literals

from django.apps import AppConfig

class AccountsConfig(AppConfig):
name = 'accounts'

7、国际化和本地化
1)在代码中
a)在项目的 settings.py 中增加中间件,目的是检测 LANGUAGE_CODE(下面的示例用到了,当然,不需要获取 LANGUAGE_CODE 时,不用即可):
[root@tvm01 www]# vim www/settings.py
MIDDLEWARE_CLASSES = [
(略)
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
(略)
]
b)在 apps 的 views.py 中显示国际化的文字
[root@tvm01 www]# vim charade/views.py
from django.utils.translation import ugettext_lazy as _
def show_lang(request):
"""test i18n only"""
msgs = _('language code: %s')
context = msgs % request.LANGUAGE_CODE
return HttpResponse(context)

c)在 apps 的 models.py 中显示国际化的文字
注意 verbose_name 的2种用法
from django.utils.translation import ugettext_lazy as _
class OSType(models.Model):
tag = models.CharField(_('OS Tag'), max_length=20)
desc = models.CharField(_('OS Description'), max_length=20, default='Extra info.')

def __str__(self):
return self.tag

class Meta:
verbose_name = _('OS Type')
verbose_name_plural = _('OS Type')

class Vm(models.Model):
hostname = models.CharField(_('hostname'), max_length=100, unique=True)
os_type = models.ForeignKey(OSType, default='1', verbose_name=_('OS Type'))

def __str__(self):
return self.hostname

class Meta:
verbose_name = _('VMs')
verbose_name_plural = _('VMs')

2)在模版中使用
模版的示例如下:
{% load i18n %}
{% block titles %}{% trans 'str to translate' %}{% endblock %}

首先,load i18n
其次,使用{% trans 'xxx' %} 来指定要翻译的语句

3)本地语言翻译文件
django查找翻译文件的顺序:
->在 settings 中定义了 LOCALE_PATHS 优先处理
->在 INSTALLED_APPS 中定义的 app 目录下的 locale 目录
->在 django/conf/locale 目录

创建翻译文件,以 app 目录下的 locale 目录为例说明:
a)建立目录
[root@tvm01 www]# cd charade
[root@tvm01 charade]# mkdir locale

b)创建或更新消息文件
[root@tvm01 charade]# django-admin makemessages -l zh
processing locale zh
c)更新这个文件的内容
[root@tvm01 charade]# ls locale/zh/LC_MESSAGES/
django.po
d)编译
[root@tvm01 charade]# django-admin compilemessages
f)重载服务后观察页面是否符合需求。

4)用户切换语言
a)设定可选语言
[root@tvm01 www]# vim www/settings.py
LANGUAGES = [
('en', 'English'),
('zh-cn', 'zh'),
]

b)启用 django 自带的语言偏好设置的视图
[root@tvm01 www]# vim www/urls.py
urlpatterns = [
(略)
url(r'^i18n/', include('django.conf.urls.i18n')),
]

c)在已有的页面模版中包括以下表单来提供语言切换这个小功能
[root@tvm01 www]# vim charade/templates/charade/ready.html
(略)
{% block content %}
(略)
<!-- Language Start -->
<div class="row text-right">
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select name="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>
</div>
<!-- End of Language -->
(略)
{% endblock %}

d)验证是否符合预期
再次访问页面,将出现一个下拉菜单,选择语言后,单击“Go”,此时,将在session中记录语言偏好。

4)异常处理 UnicodeEncodeError
在使用python2的环境中,遇到一个异常,当时的场景是
在使用 uwsgi 启动 django 工程后,访问某个 url 触发异常;
而直接使用 django 的 manage.py 来启动 web 服务后,访问上述 url 则无异常;
原因:
由于代码中使用 print 打印了中文到终端,注释掉 print 语句后异常消失,以下是参考文章。
 http://chase-seibert.github.io/blog/2014/01/12/python-unicode-console-output.html 大意是:在 uwsgi 的配置中,应当设置 "evn = PYTHONIOENCODING=UTF-8"
 https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/uwsgi/ 大意是:在文件上传中,要注意 unicode 字符,在 uwsgi 的配置中,应当设置 "env = LANG=en_US.UTF-8"
 https://www.itopen.it/django-deployment-with-nginx-and-uwsgi/ 大意是:在 uwsgi 的服务控制脚本中,应当设置提前环境变量,再启动服务

注:尝试过在uwsgi.ini的配置文件中写入 "evn = PYTHONIOENCODING=UTF-8" 这类配置,但并未生效。
综合并测试后的解决方案:
在 uwsgi 服务启动控制脚本中,增加环境变量的设置:
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export PYTHONIOENCODING=UTF-8

8、邮件
1)配置smtp帐号信息
[root@tvm01 www]# vim www/settings.py
# email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = False
EMAIL_HOST = 'smtp.xxx.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'test@xxx.com'
EMAIL_HOST_PASSWORD = 'TestEmail'
DEFAULT_FROM_EMAIL = 'TestEmail <test@xxx.com>'

2)在 views.py 中可以这样尝试发邮件:
from django.core.mail import EmailMultiAlternatives
from django.conf import settings

def validate_new_user(request, user):
tpl_email_subject = 'accounts/activation_email_subject.html'
tpl_email_body = 'accounts/activation_email_body.html'

website_domain = request.META['SERVER_NAME']

context_subject = {'website_domain': website_domain}
subject = render_to_string(tpl_email_subject, context_subject)
email_subject = ''.join(subject.splitlines())

context_body = {
'protocol': 'https' if request.is_secure() else 'http',
'website_domain': website_domain,
}
email_body = render_to_string(tpl_email_body, context_body)

email_from = settings.DEFAULT_FROM_EMAIL
email_to = [user.email]

msg = EmailMultiAlternatives(email_subject,
email_body,
email_from,
email_to)
msg.content_subtype = "html"
msg.send()

9、如何在 class-based views 中使用 decorator 来修饰呢?
例如,对 upload 这个 app 做登录限制。
如果按照之前的做法(第4节第6小结提到),则将出现如下错误:
AttributeError: 'function' object has no attribute 'as_view'

此时,有2种解决办法,具体请参考这里的介绍: https://docs.djangoproject.com/en/1.9/topics/class-based-views/intro/#decorating-class-based-views 此处示例第一种,在URLconf中配置:
[root@tvm01 www]# vim upload/urls.py
from django.contrib.auth.decorators import login_required

(略)
urlpatterns = [
(略)
url(r'^list/$', login_required(views.PictureListView.as_view()), name='pic-list'),
]

10、数据库
默认使用的是sqlite,这个在测试时用还不错,问题是,如果你想转移到其他的数据库时,应该怎么操作呢?
1)导出数据(如果你需要)
建议只导出 app 的数据,django自身的管理数据可以用之前提到的方法再创建,否则后续导入时可能会引起冲突。
[root@tvm01 www]# python manage.py dumpdata charade > ../dump.charade.json

2)调整配置(以mysql为例)
首先,安装 MySQL-python 这个接口模块:
[root@tvm01 www]# pip install MySQL-python

然后,调整 settings 关于 database 这一段:
[root@tvm01 www]# vim www/settings.py

# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases 
DATABASES = {
'default': {
#'ENGINE': 'django.db.backends.sqlite3',
#'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.mysql',
'NAME': 'charade',
'USER': 'charade_rw',
'PASSWORD': 'charadepass',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}

最后,重新加载服务,让新的配置生效。

3)导入数据
[root@tvm01 www]# python manage.py loaddata ../dump.charade.json
Installed 46 object(s) from 1 fixture(s)

11、关于 timezone 的问题
【Django 1.9】
参考:
timezone:https://docs.djangoproject.com/en/1.9/topics/i18n/timezones/
创建一个项目时,默认的值如下:
LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

而国际化过程中,我们通常会做如下配置:

# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/ 
#LANGUAGE_CODE = 'zh-hans'
LANGUAGE_CODE = 'en'

TIME_ZONE = 'Asia/Shanghai'(调整了这里)

USE_I18N = True

USE_L10N = True

USE_TZ = True

LANGUAGES = [
('en', 'English'),
('zh-cn', 'zh'),
]

【Django 1.11】
参考:
timezone:https://docs.djangoproject.com/en/1.11/topics/i18n/timezones/

Time zone 默认是禁用的,要启用,请设置 USE_TZ = True
Time zone 使用了 pytz 这个模块在安装 Django 时会附带安装。
旧的 django 版本不需要 pytz 或不会自动安装它。
使用 django-admin startproject 创建项目时,为了方便,默认将在 settings.py 中配置 USE_TZ = True

12、django自带的管理页面admin在哪里可以找到定义的urls
通常在项目中,默认是这样的:
url(r'^admin/', include(admin.site.urls)),

admin.site.urls 究竟定义了哪些呢?
首先,通过下述指令可以得到 django 的源码路径:
python -c 'import django;print django'
其次,查找 contrib 目录下对应的源码即可:
grep -A10 'urlpatterns' $(python -c 'import django;print django' |awk -F"'" '{print $(NF-1)}' |cut -d'_' -f1)contrib/admin/sites.py

部分结果如下所示:
urlpatterns = [
url(r'^$', wrap(self.index), name='index'),
url(r'^login/$', self.login, name='login'),
url(r'^logout/$', wrap(self.logout), name='logout'),
url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
name='password_change_done'),
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
name='view_on_site'),
]

很显然,在模版中如果要引用 admin 页面的地址,可以这样写:
<a href="{% url 'admin:index' %}" class="navbar-link">
{% trans 'Management' %}
</a>

13、如何在命令行下通过 curl 来访问 django 页面?
[root@tvm01 ~]# cat test_django_by_curl.sh
#!/bin/bash
#
#2017/2/4

domain_name='http://localhost'
login_url="${domain_name}/accounts/login/"
logout_url="${domain_name}/accounts/logout/"
target_url="${domain_name}/hosts/load/vms"
username='root'
password=''
f_cookies=cookies.txt
curl_opts="-c ${f_cookies} -b ${f_cookies}"

echo "[-] Step1: get csrftoken ..."
curl -s ${curl_opts} ${login_url} >/dev/null
django_token="$(grep csrftoken ${f_cookies} | awk '{print $NF}')"

echo "[-] Step2: perform login ..."
curl ${curl_opts} ${target_url} \
-H "X-CSRFToken: ${django_token}" \
-d "username=${username}&password=${password}"

echo -e "\n[-] Step3: perform logout ..."
curl -L -I ${logout_url} && rm -f ${f_cookies}

14、如何自定义 django 自带的 admin 管理后台
具体请参考官方文档。
例如,这里有个示范,讲述了如何自定义批量更新的操作: https://docs.djangoproject.com/en/1.11/ref/contrib/admin/actions/ 
四、示例
下面的示例列表是按照创建时间的先后顺序来的,理由是:
写每一个项目时,经验多半是基于上一个项目的积累。
 https://github.com/opera443399/charade.git https://github.com/opera443399/asset.git https://github.com/opera443399/navigation.git 
ZYXW、参考
1、Getting started https://docs.djangoproject.com/en/1.9/ https://docs.djangoproject.com/en/1.9/intro https://docs.djangoproject.com/en/1.9/topics/ 模型:https://docs.djangoproject.com/en/1.9/intro/tutorial01/
后台:https://docs.djangoproject.com/en/1.9/intro/tutorial02/
视图:https://docs.djangoproject.com/en/1.9/intro/tutorial03/
表单:https://docs.djangoproject.com/en/1.9/intro/tutorial04/
测试:https://docs.djangoproject.com/en/1.9/intro/tutorial05/
静态文件:https://docs.djangoproject.com/en/1.9/intro/tutorial06/
管理后台:https://docs.djangoproject.com/en/1.9/intro/tutorial07/
分页:https://docs.djangoproject.com/en/1.9/topics/pagination/
认证:https://docs.djangoproject.com/en/1.9/topics/auth/default/
模型表单:https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/
模版系统标签:https://docs.djangoproject.com/en/1.9/ref/templates/builtins/#ref-templates-builtins-tags
app:https://docs.djangoproject.com/en/1.9/ref/applications/
i18n: https://docs.djangoproject.com/es/1.9/topics/i18n/translation/ timezone:https://docs.djangoproject.com/en/1.9/topics/i18n/timezones/
timezone:https://docs.djangoproject.com/en/1.11/topics/i18n/timezones/
修饰符: https://docs.djangoproject.com/en/1.9/topics/class-based-views/intro/#decorating-class-based-views unicode:https://docs.djangoproject.com/en/1.9/ref/unicode/
actions:https://docs.djangoproject.com/en/1.11/ref/contrib/admin/actions/

2、Django 基础教程 http://www.ziqiangxuetang.com/django/django-tutorial.html 
3、Django 国际化实例及原理分析 http://www.ibm.com/developerworks/cn/web/1101_jinjh_djangoi18n/ 
4、define css class in django forms http://stackoverflow.com/questions/401025/define-css-class-in-django-forms 
5、让django模型中的字段和model名显示为中文 http://blog.csdn.net/a0100034930/article/details/42392095 
6、uwsgi https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/uwsgi/ http://chase-seibert.github.io/blog/2014/01/12/python-unicode-console-output.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python django pip i18n charade