初探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 migrate2、配置app1)创建app[root@tvm01 www]# django-admin startapp charade2)设定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 charadeMigrations 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 charadecharade [X] 0001_initial4)注册模型到后台[root@tvm01 www]# vim charade/admin.py5)视图[root@tvm01 www]# vim charade/views.py 6)模版模版和静态文件的目录[root@tvm01 www]# mkdir charade/templates/charade -p[root@tvm01 www]# mkdir charade/static/charade/p_w_picpaths -p[root@tvm01 www]# vim charade/templates/charade/index.html[root@tvm01 www]# vim charade/templates/charade/detail.html3、运行服务[root@tvm01 www]# python manage.py runserver 0.0.0.0:80  4、创建管理员[root@tvm01 www]# python manage.py createsuperuser5、测试[root@tvm01 www]# python manage.py test charade6、shell[root@tvm01 www]# python manage.py shell7、流程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 %}    
        ...    {% endfor %}2、调整后台的模版1)找到django的源码文件路径[root@tvm01 www]# python -c "import syssys.path = sys.path[1:]import djangoprint(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.html3)配置 www/settings.pyTEMPLATES = [    {    (略)        'DIRS': [os.path.join(BASE_DIR, 'templates'),],    (略)    },]3、支持中文参考:https://docs.djangoproject.com/en/1.9/ref/unicode/#general-string-handlinghttps://docs.djangoproject.com/en/1.9/ref/unicode/#models1)针对 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_literalsmy_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)通常情况下,异常内容包括UnicodeEncodeErrorUnicodeDecodeError 4)使用示例(针对 model class 使用修饰符,直接返回中文字符,而不会报错)# coding: utf-8from __future__ import unicode_literalsfrom django.utils.encoding import python_2_unicode_compatible在每个 model class 前面,修饰一下:@python_2_unicode_compatibleclass 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 accounts2)配置 www/settings.py[root@tvm01 www]# vim www/settings.pyINSTALLED_APPS = [    (略)    'accounts',    (略)]3)配置 www/urls.py[root@tvm01 www]# vim www/urls.pyurlpatterns = [    (略)    url(r'^accounts/', include('accounts.urls')),    (略)]4)配置 accounts/urls.py[root@tvm01 www]# vim accounts/urls.pyfrom django.conf.urls import urlfrom django.contrib.auth import views as auth_viewsapp_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 %}    
{% endblock %}{% block content %}
    {% if form.errors %}        
Your username and password didn't match. Please try again.

    {% endif %}    {% if next %}        {% if user.is_authenticated %}        
Your account doesn't have access to this page. To proceed,        please login with an account that has access.

        {% else %}        
Please login to see this page.

        {% endif %}    {% endif %}
    
    {% csrf_token %}    
        
username :        { { form.username }}        
        
password :        { { form.password }}        
        
        
        
{% endblock %}6)使用修饰符配置 charade/views.pyfrom django.contrib.auth.decorators import login_required@login_requireddef game_board(request):在需要login的view前修饰一下,则render的页面会要求登录。5、使用cache组件1)配置 www/settings.pyCACHES = {    '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.574)使用修饰符配置 charade/views.pyfrom django.views.decorators.cache import cache_page@cache_page(60 * 15)def show_about(request):被修饰的view将会被cache,抓包可以发现Cache-Control:max-age=9006、语法差异从1.8到1.9,有几个地方要注意1)Reversing by dotted path is deprecated[root@tvm001 www]# python manage.py testCreating 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 names1.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.pyfrom __future__ import unicode_literalsfrom django.apps import AppConfigclass AccountsConfig(AppConfig):    name = 'accounts'7、国际化和本地化1)在代码中a)在项目的 settings.py 中增加中间件,目的是检测 LANGUAGE_CODE(下面的示例用到了,当然,不需要获取 LANGUAGE_CODE 时,不用即可):[root@tvm01 www]# vim www/settings.pyMIDDLEWARE_CLASSES = [    (略)    'django.contrib.sessions.middleware.SessionMiddleware',    'django.middleware.locale.LocaleMiddleware',    'django.middleware.common.CommonMiddleware',    (略)]b)在 apps 的 views.py 中显示国际化的文字[root@tvm01 www]# vim charade/views.pyfrom 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 localeb)创建或更新消息文件[root@tvm01 charade]# django-admin makemessages -l zhprocessing locale zhc)更新这个文件的内容[root@tvm01 charade]# ls locale/zh/LC_MESSAGES/django.pod)编译[root@tvm01 charade]# django-admin compilemessagesf)重载服务后观察页面是否符合需求。4)用户切换语言a)设定可选语言[root@tvm01 www]# vim www/settings.pyLANGUAGES = [    ('en', 'English'),    ('zh-cn', 'zh'),]b)启用 django 自带的语言偏好设置的视图[root@tvm01 www]# vim www/urls.pyurlpatterns = [    (略)    url(r'^i18n/', include('django.conf.urls.i18n')),]c)在已有的页面模版中包括以下表单来提供语言切换这个小功能[root@tvm01 www]# vim charade/templates/charade/ready.html(略){% block content %}(略)    
    
        
{% csrf_token %}            
            
                {% get_current_language as LANGUAGE_CODE %}                {% get_available_languages as LANGUAGES %}                {% get_language_info_list for LANGUAGES as languages %}                {% for language in languages %}                    
                        { { language.name_local }} ({ { language.code }})                                    {% endfor %}                        
                
(略){% 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-8export LC_ALL=en_US.UTF-8export PYTHONIOENCODING=UTF-88、邮件1)配置smtp帐号信息[root@tvm01 www]# vim www/settings.py# emailEMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'EMAIL_USE_TLS = FalseEMAIL_HOST = 'smtp.xxx.com'EMAIL_PORT = 25EMAIL_HOST_USER = 'test@xxx.com'EMAIL_HOST_PASSWORD = 'TestEmail'DEFAULT_FROM_EMAIL = 'TestEmail 
'2)在 views.py 中可以这样尝试发邮件:from django.core.mail import EmailMultiAlternativesfrom django.conf import settingsdef 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.pyfrom 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/#databasesDATABASES = {    '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 = TrueUSE_L10N = TrueUSE_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 = TrueUSE_L10N = TrueUSE_TZ = TrueLANGUAGES = [    ('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
\d+)/(?P
.+)/$', wrap(contenttype_views.shortcut),                name='view_on_site'),        ]        很显然,在模版中如果要引用 admin 页面的地址,可以这样写:                            
                            {% trans 'Management' %}                                                        13、如何在命令行下通过 curl 来访问 django 页面?[root@tvm01 ~]# cat test_django_by_curl.sh #!/bin/bash##2017/2/4domain_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.txtcurl_opts="-c ${f_cookies} -b ${f_cookies}"echo "[-] Step1: get csrftoken ..."curl -s ${curl_opts} ${login_url} >/dev/nulldjango_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.githttps://github.com/opera443399/asset.githttps://github.com/opera443399/navigation.gitZYXW、参考1、Getting startedhttps://docs.djangoproject.com/en/1.9/https://docs.djangoproject.com/en/1.9/introhttps://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-tagsapp: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-viewsunicode: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.html3、Django 国际化实例及原理分析http://www.ibm.com/developerworks/cn/web/1101_jinjh_djangoi18n/4、define css class in django formshttp://stackoverflow.com/questions/401025/define-css-class-in-django-forms5、让django模型中的字段和model名显示为中文http://blog.csdn.net/a0100034930/article/details/423920956、uwsgihttps://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/uwsgi/http://chase-seibert.github.io/blog/2014/01/12/python-unicode-console-output.html