浏览代码

更新了Django部分的文档

jackfrued 6 年之前
父节点
当前提交
0ab0d99742

+ 91 - 120
Day41-55/43.静态资源和Ajax请求.md

@@ -12,35 +12,34 @@ from django.db import models
 
 class Subject(models.Model):
     """学科"""
-    no = models.AutoField(primary_key=True, verbose_name='编号')
-    name = models.CharField(max_length=31, verbose_name='名称')
-    intro = models.CharField(max_length=511, verbose_name='介绍')
+    no = models.IntegerField(primary_key=True, verbose_name='编号')
+    name = models.CharField(max_length=20, verbose_name='名称')
+    intro = models.CharField(max_length=511, default='', verbose_name='介绍')
+    create_date = models.DateField(null=True, verbose_name='成立日期')
+    is_hot = models.BooleanField(default=False, verbose_name='是否热门')
 
     def __str__(self):
         return self.name
 
     class Meta:
         db_table = 'tb_subject'
+        verbose_name = '学科'
         verbose_name_plural = '学科'
 
 
 class Teacher(models.Model):
     """老师"""
     no = models.AutoField(primary_key=True, verbose_name='编号')
-    name = models.CharField(max_length=15, verbose_name='姓名')
-    gender = models.BooleanField(default=True, choices=((True, '男'), (False, '女')), verbose_name='性别')
-    birth = models.DateField(null=True, verbose_name='出生日期')
-    intro = models.CharField(max_length=511, default='', verbose_name='')
+    name = models.CharField(max_length=20, verbose_name='姓名')
+    detail = models.CharField(max_length=1023, default='', blank=True, verbose_name='详情')
+    photo = models.CharField(max_length=1023, default='', verbose_name='照片')
     good_count = models.IntegerField(default=0, verbose_name='好评数')
     bad_count = models.IntegerField(default=0, verbose_name='差评数')
-    photo = models.CharField(max_length=255, verbose_name='照片')
     subject = models.ForeignKey(to=Subject, on_delete=models.PROTECT, db_column='sno', verbose_name='所属学科')
 
-    def __str__(self):
-        return self.name
-
     class Meta:
         db_table = 'tb_teacher'
+        verbose_name = '老师'
         verbose_name_plural = '老师'
 ```
 
@@ -55,51 +54,27 @@ class Teacher(models.Model):
 
 > 注意:为了给vote应用生成迁移文件,需要修改Django项目settings.py文件,在INSTALLED_APPS中添加vote应用。
 
-完成模型迁移之后,我们可以通过下面的SQL语句来添加学科和老师测试的数据。
-
-```SQL
-INSERT INTO `tb_subject` (`no`,`name`,`intro`) 
-VALUES 
-(1, 'Python全栈+人工智能', 'Python是一种面向对象的解释型计算机程序设计语言,由荷兰人Guido van Rossum于1989年发明,第一个公开发行版发行于1991年。'),
-(2, 'JavaEE+分布式服务', 'Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。'),
-(3, 'HTML5大前端', 'HTML5 将成为 HTML、XHTML 以及 HTML DOM 的新标准。'),
-(4, '全栈软件测试', '在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程'),
-(5, '全链路UI/UE', '全链路要求设计师关注整个业务链中的每一个环节,将设计的价值融入每一个和用户的接触点中,让整个业务的用户体验质量得到几何级数的增长。');
-
-INSERT INTO `tb_teacher` (`no`,`name`,`gender`,`birth`,`intro`,`good_count`,`bad_count`,`photo`,`sno`) 
-VALUES 
-(1, '骆昊', 1, '1980-11-28', '10年以上软硬件产品设计、研发、架构和管理经验,2003年毕业于四川大学,四川大学Java技术俱乐部创始人,四川省优秀大学毕业生,在四川省网络通信技术重点实验室工作期间,参与了2项国家自然科学基金项目、1项中国科学院中长期研究项目和多项四川省科技攻关项目,在国际会议和国内顶级期刊上发表多篇论文(1篇被SCI收录,3篇被EI收录),大规模网络性能测量系统DMC-TS的设计者和开发者,perf-TTCN语言的发明者。国内最大程序员社区CSDN的博客专家,在Github上参与和维护了多个高质量开源项目,精通C/C++、Java、Python、R、Swift、JavaScript等编程语言,擅长OOAD、系统架构、算法设计、协议分析和网络测量,主持和参与过电子政务系统、KPI考核系统、P2P借贷平台等产品的研发,一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。', 0, 0, 'images/luohao.png', 1),
-(2, '王海飞', 1, '1993-05-24', '5年以上Python开发经验,先后参与了O2O商城、CRM系统、CMS平台、ERP系统等项目的设计与研发,曾在全国最大最专业的汽车领域相关服务网站担任Python高级研发工程师、项目经理等职务,擅长基于Python、Java、PHP等开发语言的企业级应用开发,全程参与了多个企业级应用从需求到上线所涉及的各种工作,精通Django、Flask等框架,熟悉基于微服务的企业级项目开发,拥有丰富的项目实战经验。善于用浅显易懂的方式在课堂上传授知识点,在授课过程中经常穿插企业开发的实际案例并分析其中的重点和难点,通过这种互动性极强的教学模式帮助学员找到解决问题的办法并提升学员的综合素质。', 0, 0, 'images/wanghaifei.png', 1),
-(3, '余婷', 0, '1992-03-12', '5年以上移动互联网项目开发经验和教学经验,曾担任上市游戏公司高级软件研发工程师和移动端(iOS)技术负责人,参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验,以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言,熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微,性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。', 0, 0, 'images/yuting.png', 1),
-(4, '肖世荣', 1, '1977-07-02', '10年以上互联网和移动互联网产品设计、研发、技术架构和项目管理经验,曾在中国移动、symbio、ajinga.com、万达信息等公司担任架构师、项目经理、技术总监等职务,长期为苹果、保时捷、耐克、沃尔玛等国际客户以及国内的政府机构提供信息化服务,主导的项目曾获得“世界科技先锋”称号,个人作品“许愿吧”曾在腾讯应用市场生活类App排名前3,拥有百万级用户群体,运营的公众号“卵石坊”是国内知名的智能穿戴设备平台。精通Python、C++、Java、Ruby、JavaScript等开发语言,主导和参与了20多个企业级项目(含国家级重大项目和互联网创新项目),涉及的领域包括政务、社交、电信、卫生和金融,有极为丰富的项目实战经验。授课深入浅出、条理清晰,善于调动学员的学习热情并帮助学员理清思路和方法。', 0, 0, 'images/xiaoshirong.png', 1),
-(5, '张无忌', 1, '1987-07-07', '出生起便在冰火岛过着原始生活,踏入中土后因中玄冥神掌命危而带病习医,忍受寒毒煎熬七年最后因福缘际会练成“九阳神功”更在之后又练成了“乾坤大挪移”等盖世武功,几乎无敌于天下。 生性随和,宅心仁厚,精通医术和药理。20岁时便凭着盖世神功当上明教教主,率领百万教众及武林群雄反抗蒙古政权元朝的高压统治,最后推翻了元朝。由于擅长乾坤大挪移神功,上课遇到问题就转移话题。', 0, 0, 'images/zhangwuji.png', 5),
-(6, '韦一笑', 1, '1975-12-15', '外号“青翼蝠王”,为明教四大护教法王之一。  身披青条子白色长袍,轻功十分了得。由于在修炼至阴至寒的“寒冰绵掌”时出了差错,经脉中郁积了至寒阴毒,只要运上内力,寒毒就会发作,如果不吸人血解毒,全身血脉就会凝结成冰,后得张无忌相助,以其高明医术配以“九阳神功”,终将寒毒驱去,摆脱了吸吮人血这一命运。由于轻功绝顶,学生一问问题就跑了。', 0, 0, 'images/weiyixiao.png', 3);
-```
-
-当然也可以直接使用Django提供的后台管理应用来添加学科和老师信息,这需要先注册模型类和模型管理类。
+完成模型迁移之后,我们可以直接使用Django提供的后台管理来添加学科和老师信息,这需要先注册模型类和模型管理类。
 
 ```SQL
 from django.contrib import admin
-from django.contrib.admin import ModelAdmin
 
-from vote.models import Teacher, Subject
+from poll2.forms import UserForm
+from poll2.models import Subject, Teacher
 
 
-class SubjectModelAdmin(ModelAdmin):
-    """学科模型管理"""
-    list_display = ('no', 'name')
+class SubjectAdmin(admin.ModelAdmin):
+    list_display = ('no', 'name', 'create_date', 'is_hot')
     ordering = ('no', )
 
 
-class TeacherModelAdmin(ModelAdmin):
-    """老师模型管理"""
-    list_display = ('no', 'name', 'gender', 'birth', 'good_count', 'bad_count', 'subject')
-    ordering = ('no', )
-    search_fields = ('name', )
+class TeacherAdmin(admin.ModelAdmin):
+    list_display = ('no', 'name', 'detail', 'good_count', 'bad_count', 'subject')
+    ordering = ('subject', 'no')
 
 
-admin.site.register(Subject, SubjectModelAdmin)
-admin.site.register(Teacher, TeacherModelAdmin)
+admin.site.register(Subject, SubjectAdmin)
+admin.site.register(Teacher, TeacherAdmin)
 ```
 
 接下来,我们就可以修改views.py文件,通过编写视图函数先实现“学科介绍”页面。
@@ -118,24 +93,23 @@ def show_subjects(request):
 <html lang="en">
 <head>
     <meta charset="UTF-8">
-    <title>学科信息</title>
+    <title>所有学科信息</title>
     <style>/* 此处略去了层叠样式表的选择器 */</style>
 </head>
 <body>
-    <h1>千锋互联所有学科信息</h1>
+    <h1>所有学科</h1>
     <hr>
-    <div id="container">
-        {% for subject in subjects %}
-        <dl>
-            <dt>
-                <a href="/teachers?sno={{ subject.no }}">
-                    {{ subject.name }}
-                </a>
-            </dt>
-            <dd>{{ subject.intro }}</dd>
-        </dl>
-        {% endfor %}
+    {% for subject in subjects %}
+    <div>
+        <h3>
+            <a href="/teachers/?sno={{ subject.no }}">{{ subject.name }}</a>
+            {% if subject.is_hot %}
+            <img src="/static/images/hot.png" width="32" alt="">
+            {% endif %}
+        </h3>
+        <p>{{ subject.intro }}</p>
     </div>
+    {% endfor %}
 </body>
 </html>
 ```
@@ -144,13 +118,12 @@ def show_subjects(request):
 
 ```Python
 def show_teachers(request):
-    """查看指定学科的老师"""
+    """显示指定学科的老师"""
     try:
         sno = int(request.GET['sno'])
         subject = Subject.objects.get(no=sno)
-        teachers = Teacher.objects.filter(subject__no=sno)
-        context = {'subject': subject, 'teachers': teachers}
-        return render(request, 'teacher.html', context)
+        teachers = subject.teacher_set.all()
+        return render(request, 'teachers.html', {'subject': subject, 'teachers': teachers})
     except (KeyError, ValueError, Subject.DoesNotExist):
         return redirect('/')
 ```
@@ -163,40 +136,36 @@ def show_teachers(request):
 <html lang="en">
 <head>
     <meta charset="UTF-8">
-    <title>老师信息</title>
+    <title>老师</title>
     <style>/* 此处略去了层叠样式表的选择器 */</style>
 </head>
 <body>
     <h1>{{ subject.name }}学科老师信息</h1>
     <hr>
     {% if teachers %}
-    <div id="container">
-        {% for teacher in teachers %}
-        <div class="teacher">
-            <div class="photo">
-                <img src="{% static teacher.photo %}" height="140" alt="">
-            </div>
-            <div class="info">
-                <div>
-                    <span><strong>姓名:{{ teacher.name }}</strong></span>
-                    <span>性别:{{ teacher.gender | yesno:'男,女' }}</span>
-                    <span>出生日期:{{ teacher.birth }}</span>
-                </div>
-                <div class="intro">{{ teacher.intro }}</div>
-                <div class="comment">
-                    <a href="">好评({{ teacher.good_count }})</a>
-                    <a href="">差评({{ teacher.bad_count }})</a>
-                </div>
-            </div>
+    {% for teacher in teachers %}
+    <div>
+        <div>
+            <img src="{% static teacher.photo %}" alt="">
+        </div>
+        <div>
+            <h3>{{ teacher.name }}</h3>
+            <p>{{ teacher.detail }}</p>
+            <p class="comment">
+                <a href="">好评</a>
+                (<span>{{ teacher.good_count }}</span>)
+                <a href="">差评</a>
+                (<span>{{ teacher.bad_count }}</span>)
+            </p>
         </div>
-        {% endfor %}
     </div>
+    {% endfor %}
     {% else %}
-    <h2>暂时没有该学科的老师信息</h2>
+    <h3>暂时没有该学科的老师信息</h3>
     {% endif %}
-    <div class="back">
-        <a href="/">&lt;&lt;&nbsp;返回学科</a>
-    </div>
+    <p>
+        <a href="/">返回首页</a>
+    </p>
 </body>
 </html>
 ```
@@ -231,11 +200,11 @@ urlpatterns = [
 
 启动服务器运行项目,进入首页查看学科信息。
 
-![](./res/show-subjects.png)
+![](./res/show_subjects.png)
 
 点击学科查看老师信息。
 
-![](./res/show-teachers.png)
+![](./res/show_teachers.png)
 
 ### Ajax请求
 
@@ -285,48 +254,50 @@ def praise_or_criticize(request):
 <html lang="en">
 <head>
     <meta charset="UTF-8">
-    <title>老师信息</title>
-	<style>/* 此处略去了层叠样式表的选择器 */</style>
+    <title>老师</title>
+    <style>/* 此处略去了层叠样式表的选择器 */</style>
 </head>
 <body>
-    <h1>{{ subject.name }}老师信息</h1>
+    <h1>{{ subject.name }}学科老师信息</h1>
     <hr>
-    <div id="container">
-        {% for teacher in teachers %}
-        <div class="teacher">
-            <div class="photo">
-                <img src="{% static teacher.photo %}" height="140" alt="">
-            </div>
-            <div class="info">
-                <div>
-                    <span><strong>姓名:{{ teacher.name }}</strong></span>
-                    <span>性别:{{ teacher.gender | yesno:'男,女' }}</span>
-                    <span>出生日期:{{ teacher.birth }}</span>
-                </div>
-                <div class="intro">{{ teacher.intro }}</div>
-                <div class="comment">
-                    <a href="/vote/praise/?tno={{ teacher.no }}">好评</a>
-                    (<span>{{ teacher.good_count }}</span>)
-                    &nbsp;&nbsp;
-                    <a href="/vote/criticize/?tno={{ teacher.no }}">差评</a>
-                    (<span>{{ teacher.bad_count }}</span>)
-                </div>
-            </div>
+    {% if teachers %}
+    {% for teacher in teachers %}
+    <div class="teacher">
+        <div class="photo">
+            <img src="{% static teacher.photo %}" height="140" alt="">
+        </div>
+        <div class="info">
+            <h3>{{ teacher.name }}</h3>
+            <p>{{ teacher.detail }}</p>
+            <p class="comment">
+                <a href="/praise/?tno={{ teacher.no }}">好评</a>
+                (<span>{{ teacher.good_count }}</span>)
+                &nbsp;&nbsp;
+                <a href="/criticize/?tno={{ teacher.no }}">差评</a>
+                (<span>{{ teacher.bad_count }}</span>)
+            </p>
         </div>
-        {% endfor %}
     </div>
-    <script src="{% static 'js/jquery.min.js' %}"></script>
+    {% endfor %}
+    {% else %}
+    <h3>暂时没有该学科的老师信息</h3>
+    {% endif %}
+    <p>
+        <a href="/">返回首页</a>
+    </p>
+    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
     <script>
         $(() => {
-            $('.comment > a').on('click', (evt) => {
+            $('.comment>a').on('click', (evt) => {
                 evt.preventDefault()
-                let a = $(evt.target)
-                $.getJSON(a.attr('href'), (json) => {
-                    if (json.code == 200) {
-                        let span = a.next()
+                let anchor = $(evt.target)
+                let url = anchor.attr('href')
+                $.getJSON(url, (json) => {
+                    if (json.code == 10001) {
+                        let span = anchor.next()
                         span.text(parseInt(span.text()) + 1)
                     } else {
-                        alert(json.message)
+                        alert(json.hint)
                     }
                 })
             })

+ 3 - 3
Day41-55/45.Cookie和Session.md

@@ -73,11 +73,11 @@ def login(request: HttpRequest):
 <div class="user">
     {% if request.session.userid %}
     <span>{{ request.session.username }}</span>
-    <a href="/vote/logout">注销</a>
+    <a href="/logout">注销</a>
     {% else %}
-    <a href="/vote/login">登录</a>&nbsp;&nbsp;
+    <a href="/login">登录</a>&nbsp;&nbsp;
     {% endif %}
-    <a href="/vote/register">注册</a>
+    <a href="/register">注册</a>
 </div>
 ```
 

+ 60 - 4
Day41-55/47.日志和调试.md → Day41-55/46.报表和日志.md

@@ -1,9 +1,65 @@
-## 日志和调试
+## 报表和日志
+
+###导出Excel报表
+
+报表就是用表格、图表等格式来动态显示数据,所以有人用这样的公式来描述报表:
+
+```
+报表 = 多样的格式 + 动态的数据
+```
+
+有很多的三方库支持在Python程序中写Excel文件,包括[xlwt](<https://xlwt.readthedocs.io/en/latest/>)、[xlwings](<https://docs.xlwings.org/en/latest/quickstart.html>)、[openpyxl](<https://openpyxl.readthedocs.io/en/latest/>)、[xlswriter](<https://xlsxwriter.readthedocs.io/>)、[pandas](<http://pandas.pydata.org/>)等,其中的xlwt虽然只支持写xls格式的Excel文件,但在性能方面的表现还是不错的。下面我们就以xlwt为例,来演示如何在Django项目中导出Excel报表,例如导出一个包含所有老师信息的Excel表格。
+
+```Python
+def export_teachers_excel(request):
+    # 创建工作簿
+    wb = xlwt.Workbook()
+    # 添加工作表
+    sheet = wb.add_sheet('老师信息表')
+    # 查询所有老师的信息(注意:这个地方稍后需要优化)
+    queryset = Teacher.objects.all()
+    # 向Excel表单中写入表头
+    colnames = ('姓名', '介绍', '好评数', '差评数', '学科')
+    for index, name in enumerate(colnames):
+        sheet.write(0, index, name)
+    # 向单元格中写入老师的数据
+    props = ('name', 'detail', 'good_count', 'bad_count', 'subject')
+    for row, teacher in enumerate(queryset):
+        for col, prop in enumerate(props):
+            value = getattr(teacher, prop, '')
+            if isinstance(value, Subject):
+                value = value.name
+            sheet.write(row + 1, col, value)
+    # 保存Excel
+    buffer = BytesIO()
+    wb.save(buffer)
+    # 将二进制数据写入响应的消息体中并设置MIME类型
+    resp = HttpResponse(buffer.getvalue(), content_type='application/vnd.ms-excel')
+    # 中文文件名需要处理成百分号编码
+    filename = quote('老师.xls')
+    # 通过响应头告知浏览器下载该文件以及对应的文件名
+    resp['content-disposition'] = f'attachment; filename="{filename}"'
+    return resp
+```
+
+映射URL。
+
+```Python
+urlpatterns = [
+    # 此处省略上面的代码
+    path('excel/', views.export_teachers_excel),
+    # 此处省略下面的代码
+]
+```
+
+### 生成前端统计图表
+
 
-项目开发阶段,显示足够的调试信息以辅助开发人员调试代码还是非常必要的;项目上线以后,将系统运行时出现的警告、错误等信息记录下来以备相关人员了解系统运行状况并维护代码也是很有必要的。要做好这两件事件,我们需要为Django项目配置日志。
 
 ### 配置日志
 
+项目开发阶段,显示足够的调试信息以辅助开发人员调试代码还是非常必要的;项目上线以后,将系统运行时出现的警告、错误等信息记录下来以备相关人员了解系统运行状况并维护代码也是很有必要的。要做好这两件事件,我们需要为Django项目配置日志。
+
 Django的日志配置基本可以参照官方文档再结合项目实际需求来进行,这些内容基本上可以从官方文档上复制下来,然后进行局部的调整即可,下面给出一些参考配置。
 
 ```Python
@@ -114,7 +170,7 @@ LOGGING = {
 
 ### 配置Django-Debug-Toolbar
 
-Django-Debug-Toolbar是辅助Django项目开发的神器,只要配置了它,就可以很方便的查看到以下内容,这些信息对了解项目的运行状况以及优化Web应用性能都是至关重要的。
+Django-Debug-Toolbar是项目开发阶段辅助调试和优化的神器,只要配置了它,就可以很方便的查看到如下表所示的项目运行信息,这些信息对调试项目和优化Web应用性能都是至关重要的。
 
 | 项目        | 说明                              |
 | ----------- | --------------------------------- |
@@ -167,4 +223,4 @@ Django-Debug-Toolbar是辅助Django项目开发的神器,只要配置了它,
        urlpatterns.insert(0, path('__debug__/', include(debug_toolbar.urls)))
    ```
 
-4. 使用 - 在页面右侧可以看到一个调试工具栏,上面包括了如前所述的调试信息,包括执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等,查看起来非常的方便。
+4. 使用 - 如下图所示,在配置好Django-Debug-Toolbar之后,页面右侧会看到一个调试工具栏,上面包括了如前所述的各种调试信息,包括执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等,查看起来非常的方便。

+ 27 - 11
Day41-55/46.中间件的应用.md → Day41-55/47.中间件的应用.md

@@ -1,5 +1,7 @@
 ## 中间件的应用
 
+### 实现登录验证
+
 我们继续来完善投票应用。在上一个章节中,我们在用户登录成功后通过session保留了用户信息,接下来我们可以应用做一些调整,要求在为老师投票时必须要先登录,登录过的用户可以投票,否则就将用户引导到登录页面,为此我们可以这样修改视图函数。
 
 ```Python
@@ -47,7 +49,7 @@ def praise_or_criticize(request: HttpRequest):
 
 > 注意:为了在登录成功之后能够回到刚才投票的页面,我们在跳转登录时设置了一个`backurl`参数,把当前浏览器中的URL作为返回的页面地址。
 
-这样我们已经实现了用户必须登录才能投票的限制,但是一个新的问题来了。如果我们的应用中有很多功能都需要用户先登录才能执行,那么我们是不是需要在每个视图函数中添加代码来检查session中是否包含了登录用户的信息呢?答案是否定的,如果这样做了,我们的视图函数中必然会充斥着大量的重复代码。编程大师*Martin Fowler*曾经说过:**代码有很多种坏味道,重复是最坏的一种**。我们可以把验证用户是否登录这样的代码放到Django的中间件中。
+这样我们已经实现了用户必须登录才能投票的限制,但是一个新的问题来了。如果我们的应用中有很多功能都需要用户先登录才能执行,例如将前面导出Excel报表和查看统计图表的功能都加以登录限制,那么我们是不是需要在每个视图函数中添加代码来检查session中是否包含了登录用户的信息呢?答案是否定的,如果这样做了,我们的视图函数中必然会充斥着大量的重复代码。编程大师*Martin Fowler*曾经说过:**代码有很多种坏味道,重复是最坏的一种**。在Django项目中,我们可以把验证用户是否登录这样的重复性代码放到中间件中。
 
 ### Django中间件概述
 
@@ -88,26 +90,37 @@ MIDDLEWARE = [
 
 ### 自定义中间件
 
-Django中的中间件有两种实现方式:基于类的实现方式和基于函数的实现方式,后者更接近于装饰器的写法。装饰器实际上是代理模式的应用,将横切关注功能(与正常业务逻辑没有必然联系的功能,例如:身份认证、日志记录、编码转换之类的功能)置于代理中,由代理对象来完成被代理对象的行为并添加额外的功能。中间件对用户请求和响应进行拦截过滤并增加额外的处理,在这一点上它跟装饰器是完全一致的,所以基于函数的写法来实现中间件就跟装饰器的写法几乎一模一样。下面我们用自定义的中间件来实现用户进行登录验证的功能。
+Django中的中间件有两种实现方式:基于类的实现方式和基于函数的实现方式,后者更接近于装饰器的写法。装饰器实际上是代理模式的应用,将横切关注功能(与正常业务逻辑没有必然联系的功能,例如:身份认证、日志记录、编码转换之类的功能)置于代理中,由代理对象来完成被代理对象的行为并添加额外的功能。中间件对用户请求和响应进行拦截过滤并增加额外的处理,在这一点上它跟装饰器是完全一致的,所以基于函数的写法来实现中间件就跟装饰器的写法几乎一模一样。下面我们用自定义的中间件来实现用户登录验证的功能。
 
 ```Python
 """
 middlewares.py
 """
+from django.http import JsonResponse
 from django.shortcuts import redirect
 
+# 需要登录才能访问的资源路径
+LOGIN_REQUIRED_URLS = {
+    '/praise/', '/criticize/', '/pdf/', '/excel/',
+}
+
 
-def check_login_middleware(func):
+def check_login_middleware(get_resp):
 
     def wrapper(request, *args, **kwargs):
-        # 获取请求的资源路径
-        path = request.path
-        # 如果请求的资源路径在设定的元组中就表示需要登录验证
-        if path in ('/vote/praise/', '/vote/criticize/'):
-            if 'username' not in request.session:
-                # session中没有username就重定向到登录页
-                return redirect('login')
-        return func(request, *args, **kwargs)
+        # 请求的资源路径在上面的集合中
+        if request.path in LOGIN_REQUIRED_URLS:
+            # 会话中包含userid则视为已经登录
+            if 'userid' not in request.session:
+                # 判断是不是Ajax请求
+                if request.is_ajax():
+                    # Ajax请求返回JSON数据提示用户登录
+                    return JsonResponse({'code': 10003, 'hint': '请先登录'})
+                else:
+                    backurl = request.get_full_path()
+                    # 非Ajax请求直接重定向到登录页
+                    return redirect(f'/login/?backurl={backurl}')
+        return get_resp(request, *args, **kwargs)
 
     return wrapper
 ```
@@ -128,3 +141,6 @@ MIDDLEWARE = [
 ]
 ```
 
+注意上面这个中间件列表中元素的顺序,当收到来自用户的请求时,中间件按照从上到下的顺序依次执行,这行完这些中间件以后,请求才会最终到达视图函数。当然,在这个过程中,用户的请求可以被拦截,就像上面我们自定义的中间件那样,如果用户在没有登录的情况下访问了受保护的资源,中间件会将请求直接重定向到登录页,后面的中间件和视图函数将不再执行。在响应用户请求的过程中,上面的中间件会按照从下到上的顺序依次执行,这样的话我们还可以对响应做进一步的处理。
+
+中间件执行的顺序是非常重要的,对于有依赖关系的中间件必须保证被依赖的中间件要置于依赖它的中间件的前面,就好比我们刚才自定义的中间件要放到`SessionMiddleware`的后面,因为我们要依赖这个中间件为请求绑定的`session`对象才能判定用户是否登录。

+ 2 - 0
Day41-55/48.前后端分离开发.md

@@ -0,0 +1,2 @@
+## 前后端分离开发
+

+ 0 - 0
Day41-55/50.RESTful架构和DRF入门.md → Day41-55/49.RESTful架构和DRF入门.md


+ 0 - 4
Day41-55/49.文件下载和报表.md

@@ -1,4 +0,0 @@
-## 文件下载和报表
-
-
-

+ 0 - 0
Day41-55/51.RESTful架构和DRF进阶.md → Day41-55/50.RESTful架构和DRF进阶.md


+ 0 - 0
Day41-55/52.使用缓存.md → Day41-55/51.使用缓存.md


+ 0 - 0
Day41-55/48.文件上传和富文本编辑.md → Day41-55/52.文件上传和富文本编辑.md


二进制
Day41-55/res/show-subjects.png


二进制
Day41-55/res/show-teachers.png


二进制
Day41-55/res/show_subjects.png


二进制
Day41-55/res/show_teachers.png


+ 1 - 1
Day66-75/66.网络爬虫和相关工具.md

@@ -1,6 +1,6 @@
 ## 网络爬虫和相关工具
 
-### 网络爬虫
+### 网络爬虫的概念
 
 网络爬虫(web crawler),以前经常称之为网络蜘蛛(spider),是按照一定的规则自动浏览万维网并获取信息的机器人程序(或脚本),曾经被广泛的应用于互联网搜索引擎。使用过互联网和浏览器的人都知道,网页中除了供用户阅读的文字信息之外,还包含一些超链接。网络爬虫系统正是通过网页中的超链接信息不断获得网络上的其它页面。正因如此,网络数据采集的过程就像一个爬虫或者蜘蛛在网络上漫游,所以才被形象的称为网络爬虫或者网络蜘蛛。
 

+ 68 - 34
README.md

@@ -2,9 +2,9 @@
 
 > 作者:骆昊
 >
-> 说明:最近有很多想学习Python的小伙伴申请单独加我微信和QQ,因为我自己平时也很忙,没办法一一解答大家的问题,我创建了**Python100天学习交流5群**(之前的两个2000人群1群和2群、两个1000人群3群和4群已经全部满员),群号为**818146347**,二维码在下方。我的同事和朋友很多也在这个群里,他们都是优秀的Python开发者,有丰富的商业项目经验,我们在时间充足的时候会及时解答大家的问题,而且从Python语言入门到Web应用开发,从数据分析到机器学习,每个领域都有技术大咖为大家解惑答疑。以后我们争取每周做一次视频直播,以专题的形式分享Python开发的点点滴滴,同时还会不定期的举办线上和线下的技术交流和分享活动,小伙伴们可以加群进行交流。感谢**千锋教育Python教学部**对本群的支持。
+> 说明:最近有很多想学习Python的小伙伴申请单独加我微信和QQ,因为我自己平时也很忙,没办法一一解答大家的问题,我创建了**Python100天学习交流5群**(之前的两个2000人群1群和2群、两个1000人群3群和4群已经全部满员),群号为**818146347**,二维码在下方。我的同事和朋友也在这个群里,他们很多都是优秀的Python开发者,有丰富的商业项目经验,我们在时间充足的时候会及时解答大家的问题,从Python语言入门到Web应用开发,从数据分析到机器学习,每个领域都有技术大咖为大家解惑答疑。以后我们争取每周做一次视频直播,以专题的形式分享Python开发的点点滴滴,同时还会不定期的举办线上和线下的技术交流和分享活动,小伙伴们可以加群进行交流。创作不易,感谢各位小伙伴的打赏支持,也感谢**千锋教育Python教学部**对QQ群的支持。
 
-![](./res/qq_group.png)
+![](./res/python_100_days_qq_group.png)
 
 ### Python应用领域和就业形势分析
 
@@ -55,7 +55,7 @@
 
 - Python简介 - Python的历史 / Python的优缺点 / Python的应用领域
 - 搭建编程环境 - Windows环境 / Linux环境 / MacOS环境
-- 从终端运行Python程序 - DOS命令 / Hello, world / print函数 / 运行程序
+- 从终端运行Python程序 - Hello, world / print函数 / 运行程序
 - 使用IDLE - 交互式环境(REPL) / 编写多行代码 / 运行程序 / 退出IDLE
 - 注释 - 注释的作用 / 单行注释 / 多行注释
 
@@ -138,13 +138,12 @@
 - 写文件 - 覆盖写入 / 追加写入 / 文本文件 / 二进制文件
 - 异常处理 - 异常机制的重要性 / try-except代码块 / else代码块 / finally代码块 / 内置异常类型 / 异常栈 / raise语句
 - 数据持久化 - CSV文件概述 / csv模块的应用 / JSON数据格式 / json模块的应用
-- 综合案例 - 歌词解析
 
 #### Day12 - [字符串和正则表达式](./Day01-15/12.字符串和正则表达式.md)
 
-- 字符串高级操作 - 转义字符 \ 原始字符串 \ 多行字符串 \ in和 not in运算符 \ is开头的方法 \ join和split方法 \ strip相关方法 \ pyperclip模块 \ 不变字符串和可变字符串 \ StringIO的使用
-- 正则表达式入门 - 正则表达式的作用 \ 元字符 \ 转义 \ 量词 \ 分组 \ 零宽断言 \贪婪匹配与惰性匹配懒惰 \ 使用re模块实现正则表达式操作(匹配、搜索、替换、捕获)
-- 使用正则表达式 - re模块 \ compile函数 \ group和groups方法 \ match方法 \ search方法 \ findall和finditer方法 \ sub和subn方法 \ split方法
+- 字符串高级操作 - 转义字符 / 原始字符串 / 多行字符串 / in和 not in运算符 / is开头的方法 / join和split方法 / strip相关方法 / pyperclip模块 / 不变字符串和可变字符串 / StringIO的使用
+- 正则表达式入门 - 正则表达式的作用 / 元字符 / 转义 / 量词 / 分组 / 零宽断言 /贪婪匹配与惰性匹配懒惰 / 使用re模块实现正则表达式操作(匹配、搜索、替换、捕获)
+- 使用正则表达式 - re模块 / compile函数 / group和groups方法 / match方法 / search方法 / findall和finditer方法 / sub和subn方法 / split方法
 - 应用案例 - 使用正则表达式验证输入的字符串
 
 #### Day13 - [进程和线程](./Day01-15/13.进程和线程.md)
@@ -156,13 +155,11 @@
 #### Day14 - [网络编程入门和网络应用开发](./Day01-15/14.网络编程入门和网络应用开发.md)
 
 - 计算机网络基础 - 计算机网络发展史 / “TCP-IP”模型 / IP地址 / 端口 / 协议 / 其他相关概念
-- 网络应用架构 - “客户端-服务器”架构 / “浏览器-服务器”架构
+- 网络应用模式 - “客户端-服务器”模式 / “浏览器-服务器”模式
+- 基于HTTP协议访问网络资源 - 网络API概述 / 访问URL / requests模块 / 解析JSON格式数据
 - Python网络编程 - 套接字的概念 / socket模块 /  socket函数 / 创建TCP服务器 / 创建TCP客户端 / 创建UDP服务器 / 创建UDP客户端 / SocketServer模块
-
-- 访问网络API - 网络API概述 / 访问URL / requests模块 / 解析JSON格式数据
-- 文件传输 - FTP协议 / ftplib模块 / 交互式FTP应用
 - 电子邮件 - SMTP协议 / POP3协议 / IMAP协议 / smtplib模块 / poplib模块 / imaplib模块
-- 短信服务 - twilio模块 / 国内的短信服务
+- 短信服务 - 调用短信服务网关
 
 #### Day15 - [图像和文档处理](./Day01-15/15.图像和办公文档处理.md)
 
@@ -256,37 +253,28 @@
 - Django框架对session的支持
 - 视图函数中的cookie读写操作
 
-#### Day46 - [中间件的应用](./Day41-55/46.中间件的应用.md)
-
-- 什么是中间件
-- Django框架内置的中间件
-- 自定义中间件及其应用场景
-
-#### Day47 - [日志和调试](./Day41-55/47.日志和调试.md)
-
-- 配置日志
-- 配置和使用Django-Debug-Toolbar
-
-#### Day48 - [文件上传和富文本编辑](./Day41-55/48.文件上传.md)
-
-- 文件上传表单控件和图片文件预览
-- 服务器端如何处理上传的文件
-- 富文本编辑器概述
-- wangEditor的使用
-
-#### Day49 - [文件下载和报表](./Day41-55/49.文件下载和报表.md)
+#### Day46 - [报表和日志](./Day41-55/46.报表和日志.md)
 
 - 通过HttpResponse修改响应头
 - 使用StreamingHttpResponse处理大文件
 - 使用xlwt生成Excel报表
 - 使用reportlab生成PDF报表
 - 使用ECharts生成前端图表
+- 配置日志和Django-Debug-Toolbar
+
+#### Day47 - [中间件的应用](./Day41-55/47.中间件的应用.md)
+
+- 什么是中间件
+- Django框架内置的中间件
+- 自定义中间件及其应用场景
+
+#### Day48 - [全后端分离开发](./Day41-55/48.前后端分离开发.md)
 
-#### Day50 - [RESTful架构和DRF入门](./Day41-55/50.RESTful架构和DRF入门.md)
+#### Day49 - [RESTful架构和DRF入门](./Day41-55/49.RESTful架构和DRF入门.md)
 
-#### Day51 - [RESTful架构和DRF进阶](./Day41-55/51.RESTful架构和DRF进阶.md)
+#### Day50 - [RESTful架构和DRF进阶](./Day41-55/50.RESTful架构和DRF进阶.md)
 
-#### Day52 - [使用缓存](./Day41-55/52.使用缓存.md)
+#### Day51 - [使用缓存](./Day41-55/51.使用缓存.md)
 
 - 网站优化第一定律
 
@@ -295,6 +283,13 @@
 - 使用装饰器实现页面缓存
 - 为数据接口提供缓存服务
 
+#### Day52 - [文件上传和富文本编辑](./Day41-55/52.文件上传.md)
+
+- 文件上传表单控件和图片文件预览
+- 服务器端如何处理上传的文件
+- 富文本编辑器概述
+- wangEditor的使用
+
 #### Day53 - [短信和邮件](./Day41-55/53.短信和邮件.md)
 
 - 常用短信网关平台介绍
@@ -366,24 +361,63 @@
 
 #### Day66 - [网络爬虫和相关工具](./Day66-75/66.网络爬虫和相关工具.md)
 
+- 网络爬虫的概念及其应用领域
+- 网络爬虫的合法性探讨
+- 开发网络爬虫的相关工具
+- 一个爬虫程序的构成
+
 #### Day67 - [数据采集和解析](./Day66-75/67.数据采集和解析.md)
 
+- 数据采集的标准和三方库
+- 页面解析的三种方式:正则表达式解析 / XPath解析 / CSS选择器解析
+
 #### Day68 - [存储数据](./Day66-75/68.存储数据.md)
 
+- 如何存储海量数据
+- 实现数据的缓存
+
 #### Day69 - [并发下载](./Day66-75/69.并发下载.md)
 
+- 多线程和多进程
+- 异步I/O和协程
+- async和await关键字的使用
+- 三方库aiohttp的应用
+
 #### Day70 - [解析动态内容](./Day66-75/70.解析动态内容.md)
 
+- JavaScript逆向工程
+- 使用Selenium获取动态内容
+
 #### Day71 - [表单交互和验证码处理](./Day66-75/71.表单交互和验证码处理.md)
 
+- 自动提交表单
+- Cookie池的应用
+- 验证码处理
+
 #### Day72 - [Scrapy入门](./Day66-75/72.Scrapy入门.md)
 
+- Scrapy爬虫框架概述
+- 安装和使用Scrapy
+
 #### Day73 - [Scrapy高级应用](./Day66-75/73.Scrapy高级应用.md)
 
+- Spider的用法
+- 中间件的应用:下载中间件 / 蜘蛛中间件
+- Scrapy对接Selenium抓取动态内容
+- Scrapy部署到Docker
+
 #### Day74 - [Scrapy分布式实现](./Day66-75/74.Scrapy分布式实现.md)
 
+- 分布式爬虫的原理
+- Scrapy分布式实现
+- 使用Scrapyd实现分布式部署
+
 #### Day75 - [爬虫项目实战](./Day66-75/75.爬虫项目实战.md)
 
+- 爬取招聘网站数据
+- 爬取房地产行业数据
+- 爬取二手车交易平台数据
+
 ### Day76~90 - [数据处理和机器学习](./Day76-90)
 
 #### Day76 - [机器学习基础](./Day76-90/76.机器学习基础.md)

二进制
res/python_100_days_qq_group.png


二进制
res/qq_group.png