Web Server 简单的Web Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import socketEOL1 = '\n\n' EOL2 = '\n\r\n' body = '''Hello, world! <h1> from the5fire 《Django企业开发实战》</h1>''' response_params = [ 'HTTP/1.0 200 OK' , 'Date: Sat, 10 jun 2017 01:01:01 GMT' , 'Content-Type: text/plain; charset=utf-8' , 'Content-Length: {}\r\n' .format (len (body)), body, ] response = b'\r\n' .join(response_params) def handle_connection (conn, addr ): request = "" while EOL1 not in request and EOL2 not in request: request += conn.recv(1024 ) print (request) conn.send(response) conn.close() def main (): serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) serversocket.bind(('127.0.0.1' , 8080 )) serversocket.listen(1 ) print ('http://127.0.0.1:8080' ) try : while True : conn, address = serversocket.accept() handle_connection(conn, address) finally : serversocket.close() if __name__ == '__main__' : main()
WSGI,全称是Web Server Gateway Interface(Web服务网关接口)。规定了Web Server如何跟应用程序进行交互。
什么是表单?何时使用表单? 在web开发里表单的使用必不可少。表单用于让用户提交数据或上传文件,表单也用于让用户编辑已有数据。Django的表单Forms类的作用是把用户输入的数据转化成Python对象格式,便于后续操作(比如存储,修改) 。
自定义表单: 类似模型,Django表单也由各种字段组成。表单可以自定义,也可以由模型Models创建。值得注意的是模型里用的是verbose_name来描述一个字段, 而表单用的是label 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django import formsfrom .models import Contactclass ContactForm1 (forms.Form ): name = forms.CharField(label="Your Name" , max_length=255 ) email = forms.EmailField(label="Email address" ) class ContactForm2 (forms.ModelForm ): class Meta : model = Contact fields = ('name' , 'email' ,)
用户提交的数据可以通过以下方法与表单结合,生成与数据结合过的表单(Bound forms)。Django只能对Bound forms进行验证。
1 form = ContactForm(data=request.POST, files=request.FILES)
表单比较完整的运用:
1 2 3 4 5 6 7 8 9 from django import formsfrom django.contrib.auth.models import Userclass RegistrationForm (forms.Form ): username = forms.CharField(label='Username' , max_length=50 ) email = forms.EmailField(label='Email' ,) password1 = forms.CharField(label='Password' , widget=forms.PasswordInput) password2 = forms.CharField(label='Password Confirmation' , widget=forms.PasswordInput)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from django.shortcuts import render, get_object_or_404from django.contrib.auth.models import Userfrom .forms import RegistrationFormfrom django.http import HttpResponseRedirectdef register (request ): if request.method == 'POST' : form = RegistrationForm(request.POST) if form.is_valid(): username = form.cleaned_data['username' ] email = form.cleaned_data['email' ] password = form.cleaned_data['password2' ] user = User.objects.create_user(username=username, password=password, email=email) return HttpResponseRedirect("/accounts/login/" ) else : form = RegistrationForm() return render(request, 'users/registration.html' , {'form' : form})
1 2 3 4 <form action =”.” method =”POST” > {{ form.as_p }} </form >
工作:
当用户通过POST方法提交表单,我们将提交的数据与RegistrationForm结合,然后验证表单RegistrationForm的数据是否有效。
如果表单数据有效,我们先用Django User模型自带的create_user方法创建user对象,再创建user_profile。用户通过一张表单提交数据,我们实际上分别存储在两张表里。
如果用户注册成功,我们通过HttpResponseRedirect方法转到登陆页面。
如果用户没有提交表单或不是通过POST方法提交表单,我们转到注册页面,生成一张空的RegistrationForm。
表单的验证 每个forms类可以通过clean方法自定义表单验证。如果你只想对某些字段进行验证,你可以通过clean_字段名 方式自定义表单验证。如果用户提交的数据未通过验证,会返回ValidationError,并呈现给用户。如果用户提交的数据有效form.is_valid(),则会将数据存储在cleaned_data里。
通用视图里使用表单: 在Django基于类的视图(Class Based View)里使用表单也非常容易,只需定义form_class
就好了。下面是一个使用CreateView
创建一篇新文章的例子。
1 2 3 4 5 6 7 8 9 from django.views.generic.edit import CreateViewfrom .models import Articlefrom .forms import ArticleFormclass ArticleCreateView (CreateView ): model = Article form_class = ArticleForm template_name = 'blog/article_create_form.html'
完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 url(r'^addtest$' ,views.addtest,name="addtest" ) class AddForm (forms.Form ): a = forms.IntegerField() b = forms.IntegerField() from .forms import AddFormdef addtest (request ): if request.method == 'POST' : form = AddForm(request.POST) if form.is_valid(): a = form.cleaned_data['a' ] b = form.cleaned_data['b' ] return HttpResponse(str (int (a) + int (b))) else : form = AddForm() return render(request, 'addtest.html' , {'form' : form})
1 2 3 4 5 6 <form method ='post' > {% csrf_token %} {{ form }} <input type ="submit" value ="提交" > </form >
如果打印一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def addtest (request ): if request.method == 'POST' : form = AddForm(request.POST) if form.is_valid(): a = form.cleaned_data['a' ] b = form.cleaned_data['b' ] print (form) return HttpResponse(str (int (a) + int (b))) else : form = AddForm() print (form) return render(request, 'addtest.html' , {'form' : form})
在第一次访问的时候得到:
1 2 <tr > <th > <label for ="id_a" > A:</label > </th > <td > <input id ="id_a" name ="a" type ="number" required /> </td > </tr > <tr > <th > <label for ="id_b" > B:</label > </th > <td > <input id ="id_b" name ="b" type ="number" required /> </td > </tr >
在输入a=1.b=2提交得到:
1 2 <tr > <th > <label for ="id_a" > A:</label > </th > <td > <input id ="id_a" name ="a" type ="number" value ="1" required /> </td > </tr > <tr > <th > <label for ="id_b" > B:</label > </th > <td > <input id ="id_b" name ="b" type ="number" value ="2" required /> </td > </tr >
所以总结一下,Django 的 forms 提供了:
模板中表单的渲染 (自动生成HTML表单元素)
数据的验证工作,某一些输入不合法也不会丢失已经输入的数据。
还可以定制更复杂的验证工作,如果提供了10个输入框,必须必须要输入其中两个以上,在 forms.py 中都很容易实现.
Form相关的对象包括:
Widget:用来渲染成HTML元素的工具,如:forms.Textarea对应HTML中的标签
Field:Form对象中的一个字段,如:EmailField表示email字段,如果这个字段不是有效的email格式,就会产生错误。
Form:一系列Field对象的集合,负责验证和显示HTML元素
Form Media:用来渲染表单的CSS和JavaScript资源。
如果我们要设置admin的字段名称,我们可以直接使用verbose_name:
1 name = models.CharField(max_length=128 ,verbose_name='姓名' )
在Meta里也可以使用verbose_name:
1 2 class Meta : verbose_name = verbose_name_plural = '学员信息'
以前我们要修改字段名的话,需要设置一个函数,现在我们只需要使用chioce参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Student (models.Model ): SEX_TIMES = [ (0 ,'未知' ), (1 ,'男' ), (2 ,'女' ), ] STATUS_TIMES = [ (0 ,'申请' ), (1 ,'通过' ), (2 ,'拒绝' ), ] status = models.IntegerField(choices=STATUS_TIMES,verbose_name='审核状态' ) sex = models.IntegerField(choices=SEX_TIMES,verbose_name='性别' )
1 2 3 4 5 6 7 8 9 10 <table border ="1" cellspacing ="0" cellpadding ="2" > {% for student in students_list %} <tr > <td > {{ student.get_sex_display }} </td > <td > {{ student.get_status_display }} </td > </tr > {% endfor %} </table >
现在字段名显示的就不是0,1,2,而是男,女,未知,申请,通过,拒绝
网页的值传到服务器是通过<input>
或 <textarea>
标签中的 name 属性来传递的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 url(r'^$' ,views.index,name='index' ) def index (reqeust ): students = Student.objects.all () if reqeust.method == 'POST' : form = StudentForm(reqeust.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('index' )) else : form = StudentForm() context = {'students' : students, 'form' : form} return render(reqeust, 'student/index.html' , context=context) class StudentForm (forms.ModelForm ): def clean_qq (self ): qq = self.cleaned_data['qq' ] if not qq.isdigit(): raise forms.ValidationError('必须是数字!' ) return int (qq) class Meta : model = Student fields = ('name' ,'sex' ,'profession' ,'email' ,'qq' ,'phone' ,'status' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!DOCTYPE html > <html > <head > <title > 学员管理系统</title > </head > <body > <h3 > <a href ="/admin/" > Admin</a > </h3 > <ul > {% for student in students %} <li > {{ student.name }} - {{ student.get_status_display }} </li > {% endfor %} </ul > <hr /> <form action ="/" method ="post" > {% csrf_token %} {{ form }} <input type ="submit" value ="Submit" /> </form > </body > </html >
cleaned_data对象是Django的form对用户提交的数据根据字段类型做完转换之后的结果。
CBV 前面的views.py有一个对于request.method的判断,我们可以使用一个类来封装:
1 2 3 4 5 6 7 8 9 10 11 12 def index (reqeust ): students = Student.objects.all () if reqeust.method == 'POST' : form = StudentForm(reqeust.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('index' )) else : form = StudentForm() context = {'students' : students, 'form' : form} return render(reqeust, 'student/index.html' , context=context)
改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from django.shortcuts import render, HttpResponse, HttpResponseRedirectfrom django.urls import reversefrom .models import Studentfrom .forms import StudentForm,AddFormfrom django.views import Viewclass IndexView (View ): template_name = 'index.html' def get_context (self ): return {'students' :Student.get_all()} def get (self,reqeust ): context = self.get_context() form = StudentForm() context.update({'form' :form}) return render(reqeust,self.template_name,context=context) def post (self,reqeust ): form = StudentForm(reqeust.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('index' )) context = self.get_context() context.update({'form' :form}) return render(reqeust.self.template_name,context=context)
因为这是一个类,不再是一个方法.这时urls.py就需要改为: 这个 as_view 其实是对 get 和post 方法的包装。
1 2 3 4 from .views import IndexViewurl(r'^$' ,IndexView.as_view(),name='index' ),
简单来说: 一般请求的判断方法:
1 2 3 4 5 def view (request, *args, **kwargs ): if request.method.lower() == 'get' : do_something() if request.method.lower() == 'post' : do_something()
现在使用使用View.as_view()代替判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ClassName (View ): ''' 继承View自动判断请求方法 ''' def post (): pass def get (): pass def other (): pass url(url, ClassName.as_view(), name)
设计思想: 把视图函数的逻辑定义到类的方法里面去,然后在函数中实例化这个类,通过调用类的方法实现函数逻辑,而把逻辑定义在类中的一个好处就是可以通过继承复用这些方法。
使用分页功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Student (models.Model ): ... @classmethod def get_all (cls ): return cls.objects.all () url(r'^students/(\d+)/$' ,views.students,name='student_page' ) def students (reqeust, student_page ): paginator = Paginator(Student.get_all(),5 ) students_list = paginator.page(student_page) content = {'students_list' : students_list} return render(reqeust, 'student/students.html' ,context=content)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" /> <title > students</title > <style type ="text/css" > li { display : inline; } </style > </head > <body > <h1 > students</h1 > <table border ="1" cellspacing ="0" cellpadding ="2" > <tr > <th > ID</th > <th > 姓名</th > <th > 性别</th > <th > 职业</th > <th > EMAIL</th > <th > QQ</th > <th > 电话</th > <th > 审核状态</th > <th > 创建时间</th > </tr > {% for student in students_list %} <tr > <td > {{student.pk}} </td > <td > {{student.name}} </td > <td > {{student.get_sex_display}} </td > <td > {{student.profession}} </td > <td > {{student.email}} </td > <td > {{student.qq}} </td > <td > {{student.phone}} </td > <td > {{student.get_status_display}} </td > <td > {{student.created_time}} </td > </tr > {% endfor %} </table > {% for idx in students_list.paginator.page_range %} {% if idx == students_list.number %} <li > {{idx}} </li > {% else %} <li > <a href = {% url 'student_sys:student_page' idx %} > {{idx}} </a > </li > {% endif %} {% endfor %} </body > </html >
导入静态文件:
在manage.py目录下创建static目录,该目录下创建css,js,img,json,other等等目录.不同目录存放不同的文件
setting.py文件添加STATICFILES_DIRS = [os.path.join(BASE_DIR,'static')]
注意这里的'static'
就是对应文件夹的名称.
在 模板文件导入:<link rel="stylesheet" type="text/css" href="/static/student/css/student.css"/>
静态文件的反向解析:
{% load static from staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'student/css/student.css' %}"/>
url的反向解析:
1 2 3 4 5 6 url(r'^' ,include('student.urls' ,namespace='student_sys' )) url(r'^students/(\d+)/$' ,views.students,name='student_page' ),
1 2 3 4 5 <a href =" {% url 'student_sys:student_page' 3 %} " > 3</a > <a href = {% url 'student_sys:student_page' 3 %} > 3</a >
使用中间件:
方法:
__init__.py: 不需要传参数,服务器响应第一个请求的时候自动调用.用于确定是否启用该中间件 .
process_request(self,request): 分配url匹配视图之前调用.一般情况下,我们可以在这里做一些校验,比如用户登录或者HTTP中是否有认证头之类的验证。返回None或者HttpResponse对象.
==如果返回 HttpResponse,那么接下来的处理方法只会执行 process_response==,其他方法将不会被执行。这里需要注意的是,如果你的 middleware是 settings配置的 MIDDLEWARE的第一个,那么剩下的middleware也不会被执行;==如果返回None,那么 Django会继续执行其他方法。==
process_view(self,request,view_func,view_args,view_kwargs):调用视图之前执行 ,返回None或者HttpResponse对象.
view_func就是我们将要执行的view方法。它的返回值跟 process_request一样,是 Httpresponse或者None,其逻辑也一样。如果返回None,那么 Django会帮你执行view函数,从而得到最终的 response
process_template_response(self,request,response):在视图刚好执行完后调用 ,返回None或者HttpResponse对象.在这里可以使用render
执行完上面的方法,并且 Django帮我们执行完view,拿到最终的 response后,如果使用了模板的 response(这是指通过 return render方式返回的 response),就会来到这个方法中。在这个方法中,我们可以对 response做一下操作,比如 Content-Type设置,或者其他 header的修改/增加。
process_response(self,request,response):响应返回浏览器之前调用 ,返回HttpResponse对象.
所有流程都处理完毕后,就来到了这个方法。这个方法的逻辑跟 process_template_response是完全一样的,只是后者是针对带有模板的 response的处理。
process_exception(self,request,exception):当视图发生异常时调用 ,返回HttpResponse对象.
在发生异常时,才会进入这个方法。哪个阶段发生的异常呢?可以简单理解为在将要调用的ⅵew中出现异常(就是==在 process_view的func函数中==)或者==返回的模板 response在渲染时发生的异常==。但是需要注意的是,如果你在 process_View中手动调用了func,就不会触发 process_exception了 。这个方法接收到异常之后,可以选择处理异常,然后返回一个含有异常信息的 Httpresponse,或者直接返回None不处理,这种情况下 Django会使用自己的异常模板
多个中间件的调用顺序:
Django框架的执行过程:(上面黄色箭头就是执行过程)
request请求经过WSGI后,先进入中间件,开始先走process_request函数,然后走到由关系映射后,这里注意并没有直接进入视图函数,而是从头开始执行process_view()函数;然后再去执行与urls.py匹配的视图函数;
如果视图函数没有报错,那就直接挨个反过来从最后一个中间件开始,依次将返回的实例对象(也就是我们在视图函数中写的 return HttpResponse()等等)传递给每个中间件的process_response函数;最后再交给客户端浏览器;
如果执行视图函数出错,那就反过来从最后一个中间件开始,将错误信息传递给每个中间件的process_exception()函数,走完所有后,然后最终再走procss_response后,最终再交给客户端浏览器注意:视图函数的错误是由process_exception()函数处理的,从最后一个中间件开始,依次逐级提交捕捉到的异常然后最终交给procss_response()函数,将最终的错误信息交给客户端浏览器。
简单来说:
process_request,process_view,process_response都是返回None则继续运行,返回HttpResponse则开始执行process_response
process_template_response只有在返回值中有render才会被调用,必须返回一个继承有 render 方法的 response 对象
process_exception在视图函数发生错误时调用.process_View中手动调用了func,就不会触发 process_exception了. 返回None则是不处理错误,错误将传递到下一个中间件的process_exception.返回HttpResponse则开始执行process_response
多个中间件的执行顺序: 处理视图前:正序 处理视图后:倒序
也就是说: process_request,process_view为正序执行, process_exception,process_response为倒序执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import timefrom django.urls import reversefrom django.utils.deprecation import MiddlewareMixinclass TimeItMiddleware (MiddlewareMixin ): def process_request (self,request ): self.start_time = time.time() 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 (f'process view: {costed:.2 f} ' ) return response def process_response (self,request,response ): costed = time.time() - self.start_time return response MIDDLEWARE = [ 'middleware.student.middlewares.TimeItMiddleware' , ]
单元测试 单元测试写在app里面的test.py.(与views.py同级)
如果使用MySQL数据库存储,那么Django就会自动生成一个测试用的数据库. 当然我们可以修改这个数据库的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'NAME' : 'djangobook' , 'USER' :'root' , 'PASSWORD' :'5KVp2y7,k96o' , 'HOST' :'localhost' , 'POST' :'3306' , 'TEST' :{ 'NAME' :'mytestdatabase' , } } }
Django提供了一个名为 Test case的基类,我们可以通过继承这个类来实现自己的测试逻辑。在此之前,我们需要了解 Test case给我们提供了哪些方法。
def setUp(self):用来初始化环境,包括创建初始化的数据,或者做一些其他准备工作
def test_xxx(self):方法后面的_xxx可以是任意东西.以test开头的方法认为是需要测试的方法,跑测试时会被执行.每个需要被测试的方法是相互独立的 .
def tearDown(self):跟 setUp相对,用来清理测试环境和测试数据.
Model 层测试: 这一层的测试主要保证数据的写入和查询是可用的,同时也需要保证我们在 Model层所提供的方法是符合预期的。
比如,在 Model中增加了 sex_show这样的属性,用来展示sex这个字段的中文显示,而不是数字1或者2.当然,这个功能是 Django已经提供给我们的。
1 2 3 4 5 6 7 8 9 10 11 12 13 from django.db import modelsclass Student (models.Model ): objects = StudentManager() SEX_TIMES = [ (0 ,'未知' ), (1 ,'男' ), (2 ,'女' ), ] ... @property def sex_show (self ): return dict (self.SEX_ITEMS)[self.sex]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 from django.test import TestCasefrom .models import Studentclass StudentTestCase (TestCase ): def setUp (self ): Student.objects.create( name='hyl' , sex=1 , email='xxxx.com' , profession='程序员' , qq=333 phone='22222' ) def test_create_and_sex_show (self ): student = Student.objects.create( name='YYYY' , sex=1 , email='zzzz.com' , profession='程序员' , qq=333 phone='22222' ) self.assertEqual(Student.sex_show,'男' ,'性别字段内容跟展示不一致' ) def test_filter (self ): Student.objects.create( name='hyl' , sex=1 , email='xxxx.com' , profession='程序员' , qq=333 phone='22222' ) name = 'hyl' students = Student.objects.filter (name=name) self.assertEqual(students.count(),1 ,f'应该只存在一个名称为{name} 的记录' )
View 层测试 这一层更多的是功能上的测试,也是我们重点关注的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from django.test import TestCase,Clientfrom .models import Studentclass StudentTestCase (TestCase ): def setUp (self ): Student.objects.create( name='hyl' , sex=1 , email='xxxx.com' , profession='程序员' , qq=333 phone='22222' ) def test_get_index (self ): client = Client() response = client.get('/' ) self.assertEqual(response.status_code,200 ,'status code must be 200!' ) def test_post_student (self ): client = Client() data = dict ( name = 'test_for_post' , sex = 1 , email = '333@d.com' , profession = '程序员' , qq = '333' , phone = '3333' ) response = client.post('/' ,data) self.assertEqual(response.status_code,302 ,'status code must be 302' ) response = client.get('/' ) self.assertTrue(b'test_for_post' in response.content, 'response content must contain "test_for_post"' )
执行单元测试: