Django小记之简单实现小区进出登记(包含simpleui,middleware中间件,TestCase单元测试)

*说明:
仅用于记录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

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值