*说明:
仅用于记录Django学习期间的一些小案例
开发环境:
硬件:树莓派4B
操作系统:ubuntu18.04 server
Python:python3.6.9
Django版本:3.0.4
一、新建项目
#新建项目
django-admin startproject io_register
tree
└── io_register
├── io_register
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
2 directories, 6 files
#创建app
cd io_register/
./manage.py startapp io_demo
tree
├── io_demo
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── io_register
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ └── settings.cpython-36.pyc
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
二、项目实现-后台
最最简单的项目(仅登记出入人员的姓名、性别、手机、住址、身份证5项仅需一个model)
#models
from django.db import models
class io_register_model(models.Model):
sex_choices = [
(1 ,'男' ),
(2 ,'女'),
( 0 ,'未知')
]
name=models.CharField(max_length=32,verbose_name='姓名')
sex=models.IntegerField(choices=sex_choices,verbose_name='性别')
phone=models.CharField(max_length=16,verbose_name='电话')
cid=models.CharField(max_length=18,verbose_name='身份证号')
addr=models.CharField(max_length=128,verbose_name='住址')
add_time=models.DateTimeField(auto_now=True,verbose_name='登记时间')
#用于获取数据显示至前端页面
@classmethod
def get_all(cls):
return cls.objects.all()
def __str__(self):
return '出入人员<{}>'.format(self.name)
class Meta:
verbose_name=verbose_name_plural="出入登记"
##########################################################################
#admin.py
from django.contrib import admin
from .models import io_register_model
class io_register_Admin(admin.ModelAdmin):
list_display=('id','name','sex','phone','cid','addr','add_time')#admin后台显示的字段
list_filter=('sex','add_time')#筛选
search_fields=('name','phone','cid','addr')#搜索
fieldsets=(
('人员信息-姓名',{'fields':['name','sex','cid']}),
('人员信息-联络方式',{'fields':['phone','addr']}),
)
admin.site.register(io_register_model,io_register_Admin)
#注册APP,在settings文件的NSTALLED APPS中添加io_demo
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'io_demo',#将io_demo注册
]
#生成迁移文件
./manage.py makemigrations
#同步表
./manage.py migrate
#创建超级用户
./manage . py createsuperuser
#注意需要在settings中配置允许任意IP地址访问
ALLOWED_HOSTS = ['*']
#运行项目
./manage.py runserver 0.0.0.0:80
#出现如下提示则项目启动成功可输入http://ip/admin访问后台admin
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
March 14, 2020 - 14:34:09
Django version 3.0.4, using settings 'io_register.settings'
Starting development server at http://0.0.0.0:80/
Quit the server with CONTROL-C.
更改后台显示为中文
LANGUAGE_CODE = 'zh-Hans'#语言
TIME_ZONE = 'Asia/Shanghai'#时区
USE_I18N = True
USE_L10N = True
USE_TZ = True
这里发现菜单位置任然显示英文,我们在io_demo的app中添加verbose_name更改显示中文名《登记管理》
#apps.py
from django.apps import AppConfig
class IoDemoConfig(AppConfig):
name = 'io_demo'
verbose_name='登记管理'
#__init__.py
default_app_config="io_demo.apps.IoDemoConfig"
使用simpleui美化后台页面
#安装simpleui
pip install simpleui
#在settings中引入simpleui
INSTALLED_APPS = [
'simpleui',#引入simpleui,注意一定要放在第一行
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'io_demo',
]
刷新下页面再来看下后台显示(django会自动重启如果没有你需要重新启动):
三、项目实现-前端
创建index.html
mkdir templates
#index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>出入登记</title>
</head>
<body>
<table>
<thead>
<tr>
<th><input type="checkbox" id="action-toggle"></th>
<th>序号</th>
<th>姓名</th>
<th>性别</th>
<th>电话</th>
<th>身份证</th>
<th>住址</th>
<th>登记时间</th>
</tr>
</thead>
<tbody>
{% for context in contexts %}
<tr>
<td><input type="checkbox" id="action-toggle"></td>
<td>{{ context.id }}</td>
<td>{{ context.name }}</td>
<td>{{ context.get_sex_display }}</td>
<td>{{ context.phone }}</td>
<td>{{ context.cid }}</td>
<td>{{ context.addr }}</td>
<td>{{ context.add_time }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
#在settings中设置模版路径
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],#模版路径
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
#在views视图中编写响应
#views.py
from django.shortcuts import render
from django.views.generic import View
from .models import io_register_model
class index(View):
def post(self,request):
pass
def get(self,request):
contexts=io_register_model.get_all()
return render(request,'index.html',context={'contexts':contexts})
#在io_register的urls中设置路由
from django.contrib import admin
from django.urls import path
from io_demo.views import index
urlpatterns = [
path('',index.as_view(),name='index'),
path('admin/', admin.site.urls),
]
测试下(后台添加了一条测试数据)
实现前台提交数据到后台
#更新index.html页面如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>出入登记</title>
</head>
<body>
<table>
<thead>
<tr>
<th><input type="checkbox" id="action-toggle"></th>
<th>序号</th>
<th>姓名</th>
<th>性别</th>
<th>电话</th>
<th>身份证</th>
<th>住址</th>
<th>登记时间</th>
</tr>
</thead>
<tbody>
{% for context in contexts %}
<tr>
<td><input type="checkbox" id="action-toggle"></td>
<td>{{ context.id }}</td>
<td>{{ context.name }}</td>
<td>{{ context.get_sex_display }}</td>
<td>{{ context.phone }}</td>
<td>{{ context.cid }}</td>
<td>{{ context.addr }}</td>
<td>{{ context.add_time }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<hr>
{{ errors }}
<form action="\" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="submit">
</form>
</body>
</html>
#创建forms.py用于验证提交的字段
from django import forms
from .models import io_register_model
class io_register_Form(forms.ModelForm):
def clean_phone(self):
data=self.cleaned_data['phone']
if not data.isdigit() and len(data)!=11:
raise forms.ValidationError('电话必须是数字且长度为11位!')
return int(data)
def clean_cid(self):
data=self.cleaned_data['cid']
if not ((len(data)!=15) or (len(data)!=18)):
raise forms.ValidationError('身份证号码长度不正确,15位或18位!')
return data
class Meta:
model=io_register_model
fields=('name','sex','phone','cid','addr')
更新views.py将forms渲染至前端并接收前端的post
#views.py
from django.shortcuts import render,redirect
from django.views.generic import View
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import io_register_model
from .forms import io_register_Form
class index(View):
def post(self,request):
form=io_register_Form(request.POST)
if form.is_valid():
form.save()#保存数据
return HttpResponseRedirect(reverse('index'))
else:
errors = form.errors
return render(request,'index.html',context={'contexts':io_register_model.get_all(),'errors':errors})
def get(self,request):
contexts=io_register_model.get_all()
context={
'contexts':contexts,
'form':io_register_Form()
}
return render(request,'index.html',context=context)
最终效果如下:
通过前端提交一条数据
四、优化
1、重写views.py
#不要吐槽说是优化了代码量反而没有减少而是增多了,这么做的目的是让每一部分的功能明确起来并且具体化,在大型项目中便于后期维护。
#views.py
from django.shortcuts import render,redirect
from django.views.generic import View
from django.http import HttpResponseRedirect
from django.urls import reverse
from .models import io_register_model
from .forms import io_register_Form
class index(View):
template_name='index.html'#定义变量存储模版
def get_context(self):
context={
'contexts':io_register_model.get_all()
}
return context
def post(self,request):
form=io_register_Form(request.POST)
if form.is_valid():
form.save()#保存数据
return HttpResponseRedirect(reverse('index'))
else:
context=self.get_context()
context.update({'form':io_register_Form()})
errors = form.errors
return render(request,self.template_name,context=context)
def get(self,request):
context=self.get_context()
context.update({'form':io_register_Form()})
return render(request,self.template_name,context=context)
五、使用中间件middleware统计访问网页所需要的时间
在io_demo下创建middlewares.py文件
#middlewares.py
import time
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin
class TimeitMiddleware(MiddlewareMixin):
def process_request(self,request):
self.start_time=time.time()
return
def process_view(self,request,func,*args,**kwargs):
if request.path !=reverse('index'):
return None
start=time.time()
response=func(request)
costed=time.time()-start
print('process view:{:.2f}s'.format(costed))
return response
def process_exception(self,request,exception):
pass
def process_template_esponse(self,request,response):
return response
def process_response(self,request,response):
costed=time.time()-self.start_time
print('request to response cose:{:.2f}s'.format(costed))
return response
'''
*process_request:这是请求来到middleware中时进入的第一个方法 。
*process_view:这个方法是在process_request方法之后执行的,参数如上面代码
所示,其中func就是我们将要执行的view方法。因此,如果要统计一个view 的执行
时间,可以在这里做.它的返回值跟process_request一样,是HttpResponse或者
None,其逻辑也一样.如果返回None,那么Django会帮你执行vies函数,从而得到最终的response.
*process_template_response:执行完上面的方法,并且Django帮我们执行完View,
拿到最终的response后,如果使用了模板的response就会来到这个方法中.在这个方法中,我们
可以对response做一下操作,比如Content Type 设置,或者其他header的修改|增加.
*process_response:当所有流程都处理完毕后,就来到了这个方法.这个方法的逻辑跟process_template_response
是完全一样的,只是后者是针对带有模板的response的处理。
*process_exception:上面的处理方法是按顺序介绍的,而这个方法不太一样.只有在发生异常时,
才会进入这个方法,哪个阶段发生的异常呢?可以简单理解为在将要调用的View中出现异常
(就是在process_view的func函数中)或者返回的模板response在渲染时发生的异常.但是需要
注意的是,如果你在process view中手动调用了func,就像我们上面做的那样,就不会触发 process_exception了.
这个方法接收到异常之后,可以选择处理异常,然后返回一个含有异常信息的HttpResponse,或者直
接返回None不处理,这种情况下Django 会使用自己的异常模板.
'''
#在settings中引入中间件
MIDDLEWARE = [
'io_demo.middlewares.TimeitMiddleware',#引入中间件
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
访问主页观察下效果:
六、编写TestCase单元测试
单元测试的主要 目的是让你的代码更健壮,尤其是在进行重构或者业务增加的时候 。 跑通单
元测试,就意味着新加入的代码或者你修改的代码没有问题。
在 Django 中运行测试用例时,如果我们用的是 SQLite 数据库, Dj ango 会帮我们创建一个基
于内存的测试数据库,用于测试。 这意味着测试中所创建的数据对我们的开发环境或者线上环
境是没有影响的 。
但是对于 MySQL 数据库, 同ango 会直接用配置 的数据库用 户 和l 密码创建-个名 为
test student db 的数据库,用于测试。 因此,需要保证有建表和建库的权限 。
Django 提供了一个名为 TestCase 的基类,我们可以通过继承这个类来实现自己的测试逻
辑 。
def setup (self):用来初始化环境,包括创建初始化的数据,或者做一些其他准备工作 。
def test_xxx(self):方法后面的xxx可以是任意东西。以 test 开头的方法,会被认为是需要测试的方法,跑测试时会被执行。每个需要被测试的方法是相互独立的 。
def tearDown (self):跟 setUp 相对,用来清理测试环境和测试数据。
1、Model层的测试
更新models.py如下:
#models
from django.db import models
class io_register_model(models.Model):
sex_choices = [
(1 ,'男' ),
(2 ,'女'),
( 0 ,'未知')
]
name=models.CharField(max_length=32,verbose_name='姓名')
sex=models.IntegerField(choices=sex_choices,verbose_name='性别')
phone=models.CharField(max_length=16,verbose_name='电话')
cid=models.CharField(max_length=18,verbose_name='身份证号')
addr=models.CharField(max_length=128,verbose_name='住址')
add_time=models.DateTimeField(auto_now=True,verbose_name='登记时间')
@classmethod
def get_all(cls):
return cls.objects.all()
#用于测试sex字段
@property
def sex_show(self):
return dict(self.sex_choices)[self.sex]
def __str__(self):
return '出入人员<{}>'.format(self.name)
class Meta:
verbose_name=verbose_name_plural="出入登记"
#编写这个models的测试
#test.py
from django.test import TestCase,Client
from .models import io_register_model
class io_register_TestCase(TestCase):
def setUp(self):
io_register_model.objects.create(
name='小脑斧',
sex=1,
phone='11223344551',
cid='112233445566778899',
addr='大深林',
)
def test_create_and_sex_show(self):
io_register=io_register_model.objects.create(
name='小松许',
sex=1,
phone='11223344552',
cid='112233445566778811',
addr='大深林',
)
self.assertEqual(io_register.get_sex_display,'男','性别字段和内容不一致')
def test_filter(self):
io_register_model.objects.create(
name='小松许',
sex=1,
phone='11223344552',
cid='112233445566778811',
addr='大深林',
)
name='小脑斧'
io_registers=io_register_model.objects.filter(name=name)
self.assertEqual(io_registers.count(),1,'存在重复记录名字{}'.format(name))
#test_create_and_sex_show 用来测试数据创建以及sex字段的正确展示,test_filter测试查询是否可用。
#配置了choices参数的字段,Django提供了get_xxxx_display方法用于替换show_xxx
#运行测试
./manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_create_and_sex_show (io_demo.tests.io_register_TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/u01/project/django_project/io_register/io_demo/tests.py", line 22, in test_create_and_sex_show
self.assertEqual(io_register.get_sex_display,'男','性别字段和内容不一致')
AssertionError: functools.partial(<bound method Model._ge[97 chars]sex>) != '男' : 性别字段和内容不一致
----------------------------------------------------------------------
Ran 2 tests in 0.013s
FAILED (failures=1)
Destroying test database for alias 'default'...
2、view层测测试
更新test.py代码如下
#test.py
from django.test import TestCase,Client
from .models import io_register_model
class io_register_TestCase(TestCase):
def setUp(self):
io_register_model.objects.create(
name='小脑斧',
sex=1,
phone='11223344551',
cid='112233445566778899',
addr='大深林',
)
def test_create_and_sex_show(self):
io_register=io_register_model.objects.create(
name='小松许',
sex=1,
phone='11223344552',
cid='112233445566778811',
addr='大深林',
)
self.assertEqual(io_register.get_sex_display,'男','性别字段和内容不一致')
def test_filter(self):
io_register_model.objects.create(
name='小松许',
sex=1,
phone='11223344552',
cid='112233445566778811',
addr='大深林',
)
name='小脑斧'
io_registers=io_register_model.objects.filter(name=name)
self.assertEqual(io_registers.count(),1,'存在重复记录名字{}'.format(name))
#view层的测试
def test_get_index(self):
client=Client()
response=client.get('/')
self.assertEqual(response.status_code,200,'status code 200!')
def test_post_io_register(self):
client=Client()
data=dict(
name='test_post',
sex=1,
phone='11223344552',
cid='112233445566778811',
addr='大深林',
)
response=client.post('/',data)
self.assertEqual(response.status_code,302,'statuc code 302!')
response=client.get('/')
self.assertTrue(b'test_post' in response.content,'response content must contain "test_post')
#test_create_and_sex_show 用来测试数据创建以及sex字段的正确展示,test_filter测试查询是否可用。
#配置了choices参数的字段,Django提供了get_xxxx_display方法用于替换show_xxx
#################################################################################
已上传github:
https://github.com/lb0737/io_register