From 04604252de889144c5d5a8d285001ff1f8ac3bbb Mon Sep 17 00:00:00 2001 From: jackfrued Date: Fri, 26 Jun 2020 20:58:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...61\345\205\245\346\250\241\345\236\213.md" | 254 +++++++++++-- ...45\222\214Ajax\350\257\267\346\261\202.md" | 336 ++++++------------ ...25\347\232\204\345\272\224\347\224\250.md" | 25 +- "Day41-55/45.Cookie\345\222\214Session.md" | 9 + Day41-55/res/asynchronous-web-request.png | Bin 0 -> 155943 bytes Day41-55/res/django-admin-add-model.png | Bin 0 -> 141960 bytes Day41-55/res/django-admin-apps.png | Bin 0 -> 143719 bytes .../res/django-admin-delete-update-model.png | Bin 0 -> 166623 bytes Day41-55/res/django-admin-login.png | Bin 0 -> 99336 bytes Day41-55/res/django-admin-models.png | Bin 0 -> 158545 bytes .../res/django-admin-view-models-subject.png | Bin 0 -> 206496 bytes .../res/django-admin-view-models-teacher.png | Bin 0 -> 191834 bytes Day41-55/res/django-admin-view-models.png | Bin 0 -> 164860 bytes Day41-55/res/pycharm-django-static.png | Bin 0 -> 299008 bytes Day41-55/res/synchronous-web-request.png | Bin 0 -> 149913 bytes .../code/image360/image360/middlewares.py | 3 + Day66-75/code/image360/image360/settings.py | 8 +- 17 files changed, 352 insertions(+), 283 deletions(-) create mode 100644 Day41-55/res/asynchronous-web-request.png create mode 100644 Day41-55/res/django-admin-add-model.png create mode 100644 Day41-55/res/django-admin-apps.png create mode 100644 Day41-55/res/django-admin-delete-update-model.png create mode 100644 Day41-55/res/django-admin-login.png create mode 100644 Day41-55/res/django-admin-models.png create mode 100644 Day41-55/res/django-admin-view-models-subject.png create mode 100644 Day41-55/res/django-admin-view-models-teacher.png create mode 100644 Day41-55/res/django-admin-view-models.png create mode 100644 Day41-55/res/pycharm-django-static.png create mode 100644 Day41-55/res/synchronous-web-request.png diff --git "a/Day41-55/42.\346\267\261\345\205\245\346\250\241\345\236\213.md" "b/Day41-55/42.\346\267\261\345\205\245\346\250\241\345\236\213.md" index 9b88ba38e..3f2907693 100644 --- "a/Day41-55/42.\346\267\261\345\205\245\346\250\241\345\236\213.md" +++ "b/Day41-55/42.\346\267\261\345\205\245\346\250\241\345\236\213.md" @@ -268,29 +268,6 @@ Teacher.objects.filter(subject__name__contains='全栈') > **说明3**:如果希望更新多条数据,不用先逐一获取模型对象再修改对象属性,可以直接使用QuerySet对象的`update()`方法一次性更新多条数据。 -1. Django框架本身有自带的数据模型,我们稍后会用到这些模型,为此我们先做一次迁移操作。所谓迁移,就是根据模型自动生成关系数据库中的二维表,命令如下所示: - - ```Shell - (venv)$ python manage.py migrate - Operations to perform: - Apply all migrations: admin, auth, contenttypes, sessions - Running migrations: - Applying contenttypes.0001_initial... OK - Applying auth.0001_initial... OK - Applying admin.0001_initial... OK - Applying admin.0002_logentry_remove_auto_add... OK - Applying contenttypes.0002_remove_content_type_name... OK - Applying auth.0002_alter_permission_name_max_length... OK - Applying auth.0003_alter_user_email_max_length... OK - Applying auth.0004_alter_user_username_opts... OK - Applying auth.0005_alter_user_last_login_null... OK - Applying auth.0006_require_contenttypes_0002... OK - Applying auth.0007_alter_validators_add_error_messages... OK - Applying auth.0008_alter_user_username_max_length... OK - Applying auth.0009_alter_user_last_name_max_length... OK - Applying sessions.0001_initial... OK - ``` - ### 利用Django后台管理模型 @@ -312,11 +289,11 @@ Teacher.objects.filter(subject__name__contains='全栈') 3. 运行项目,在浏览器中访问`http://127.0.0.1:8000/admin`,输入刚才创建的超级用户账号和密码进行登录。 - ![](./res/django-admin-login.png) + ![](/Users/Hao/Desktop/Python-100-Days/Day41-55/res/django-admin-login.png) 登录后进入管理员操作平台。 - ![](./res/django-admin-apps.png) + ![](res/django-admin-apps.png) 注意,我们暂时还没能在`admin`应用中看到之前创建的模型类,为此需要在`polls`应用的`admin.py`文件中对需要管理的模型进行注册。 @@ -325,9 +302,7 @@ Teacher.objects.filter(subject__name__contains='全栈') ```Python from django.contrib import admin - ``` - -from polls.models import Subject, Teacher + from polls.models import Subject, Teacher admin.site.register(Subject) admin.site.register(Teacher) @@ -343,29 +318,234 @@ from polls.models import Subject, Teacher - 添加学科。 - ![](./res/django-admin-create.png) + ![](res/django-admin-add-model.png) - 查看所有学科。 - ![](./res/django-admin-read.png) + ![](res/django-admin-view-models.png) - 删除和更新学科。 - ![](./res/django-admin-delete-update.png) + ![](res/django-admin-delete-update-model.png) 6. 注册模型管理类。 可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改`admin.py`文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。 ```Python + from django.contrib import admin + +from polls.models import Subject, Teacher - ``` - - ![](./res/django-admin-models-detail.png) - - 为了更好的查看模型数据,可以为`Subject`和`Teacher`两个模型类添加`__str__`魔法方法。修改代码后的效果如下图所示。 - ![](./res/django-amdin-models-detail-modified.png) + class SubjectModelAdmin(admin.ModelAdmin): + list_display = ('no', 'name', 'intro', 'is_hot') + search_fields = ('name', ) + ordering = ('no', ) + + + class TeacherModelAdmin(admin.ModelAdmin): + list_display = ('no', 'name', 'sex', 'birth', 'good_count', 'bad_count', 'subject') + search_fields = ('name', ) + ordering = ('no', ) + + + admin.site.register(Subject, SubjectModelAdmin) + admin.site.register(Teacher, TeacherModelAdmin) + ``` + + ![](res/django-admin-view-models-subject.png) + + ![](res/django-admin-view-models-teacher.png) + + 为了更好的查看模型,我们为`Subject`类添加`__str__`魔法方法,并在该方法中返回学科名字。这样在如上图所示的查看老师的页面上显示老师所属学科时,就不再是`Subject object(1)`这样晦涩的信息,而是学科的名称。 + +### 实现学科页和老师页效果 + +1. 修改`polls/views.py`文件,编写视图函数实现对学科页和老师页的渲染。 + + ```Python + from django.shortcuts import render, redirect + + from polls.models import Subject, Teacher + + + def show_subjects(request): + subjects = Subject.objects.all().order_by('no') + return render(request, 'subjects.html', {'subjects': subjects}) + + + def show_teachers(request): + try: + sno = int(request.GET.get('sno')) + teachers = [] + if sno: + subject = Subject.objects.only('name').get(no=sno) + teachers = Teacher.objects.filter(subject=subject).order_by('no') + return render(request, 'teachers.html', { + 'subject': subject, + 'teachers': teachers + }) + except (ValueError, Subject.DoesNotExist): + return redirect('/') + ``` + +2. 修改`templates/subjects.html`和`templates/teachers.html`模板页。 + + `subjects.html` + + ```HTML + + + + + 学科信息 + + + +
+ +

扣丁学堂所有学科

+
+
+ {% for subject in subjects %} +
+
+ {{ subject.name }} + {% if subject.is_hot %} + + {% endif %} +
+
{{ subject.intro }}
+
+ {% endfor %} +
+
+ + + ``` + + `teachers.html` + + ```HTML + + + + + 老师信息 + + + +
+

{{ subject.name }}学科的老师信息

+
+ {% if not teachers %} +

暂无该学科老师信息

+ {% endif %} + {% for teacher in teachers %} +
+
+ +
+
+
+ 姓名:{{ teacher.name }} + 性别:{{ teacher.sex | yesno:'男,女' }} + 出生日期:{{ teacher.birth }} +
+
{{ teacher.intro }}
+
+ 好评 ({{ teacher.good_count }}) +      + 差评 {{ teacher.bad_count }}) +
+
+
+ {% endfor %} + 返回首页 +
+ + + ``` + +3. 修改`vote/urls.py`文件,实现映射URL。 + + ```Python + from django.contrib import admin + from django.urls import path + + from polls.views import show_subjects, show_teachers + + urlpatterns = [ + path('admin/', admin.site.urls), + path('', show_subjects), + path('teachers/', show_teachers), + ] + ``` + +到此为止,页面上需要的图片(静态资源)还没有能够正常展示,我们在下一章节中为大家介绍如何处理模板页上的需要的静态资源。 ### 补充内容 diff --git "a/Day41-55/43.\351\235\231\346\200\201\350\265\204\346\272\220\345\222\214Ajax\350\257\267\346\261\202.md" "b/Day41-55/43.\351\235\231\346\200\201\350\265\204\346\272\220\345\222\214Ajax\350\257\267\346\261\202.md" index 5602a2250..c6332a4ff 100644 --- "a/Day41-55/43.\351\235\231\346\200\201\350\265\204\346\272\220\345\222\214Ajax\350\257\267\346\261\202.md" +++ "b/Day41-55/43.\351\235\231\346\200\201\350\265\204\346\272\220\345\222\214Ajax\350\257\267\346\261\202.md" @@ -1,216 +1,60 @@ ## 静态资源和Ajax请求 -基于前面两个章节讲解的知识,我们已经可以使用Django框架来完成Web应用的开发了。接下来我们就尝试实现一个投票应用,具体的需求是用户进入应用首先查看到“学科介绍”页面,该页面显示了一个学校所开设的所有学科;通过点击某个学科,可以进入“老师介绍”页面,该页面展示了该学科所有老师的详细情况,可以在该页面上给老师点击“好评”或“差评”;如果用户没有登录,在投票时会先跳转到“登录页”要求用户登录,登录成功才能投票;对于未注册的用户,可以在“登录页”点击“新用户注册”进入“注册页”完成用户注册操作,注册成功后会跳转到“登录页”,注册失败会获得相应的提示信息。 - -### 准备工作 - -由于之前已经详细的讲解了如何创建Django项目以及项目的相关配置,因此我们略过这部分内容,唯一需要说明的是,从上面对投票应用需求的描述中我们可以分析出三个业务实体:学科、老师和用户。学科和老师之间通常是一对多关联关系(一个学科有多个老师,一个老师通常只属于一个学科),用户因为要给老师投票,所以跟老师之间是多对多关联关系(一个用户可以给多个老师投票,一个老师也可以收到多个用户的投票)。首先修改应用下的models.py文件来定义数据模型,先给出学科和老师的模型。 - -```Python -from django.db import models - - -class Subject(models.Model): - """学科""" - 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=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='差评数') - subject = models.ForeignKey(to=Subject, on_delete=models.PROTECT, db_column='sno', verbose_name='所属学科') - - class Meta: - db_table = 'tb_teacher' - verbose_name = '老师' - verbose_name_plural = '老师' -``` - -模型定义完成后,可以通过“生成迁移”和“执行迁移”来完成关系型数据库中二维表的创建,当然这需要提前启动数据库服务器并创建好对应的数据库,同时我们在项目中已经安装了PyMySQL而且完成了相应的配置,这些内容此处不再赘述。 - -```Shell -(venv)$ python manage.py makemigrations vote -... -(venv)$ python manage.py migrate -... -``` - -> 注意:为了给vote应用生成迁移文件,需要修改Django项目settings.py文件,在INSTALLED_APPS中添加vote应用。 - -完成模型迁移之后,我们可以直接使用Django提供的后台管理来添加学科和老师信息,这需要先注册模型类和模型管理类,可以通过修改``。 - -```SQL -from django.contrib import admin - -from poll2.forms import UserForm -from poll2.models import Subject, Teacher - - -class SubjectAdmin(admin.ModelAdmin): - list_display = ('no', 'name', 'create_date', 'is_hot') - ordering = ('no', ) - - -class TeacherAdmin(admin.ModelAdmin): - list_display = ('no', 'name', 'detail', 'good_count', 'bad_count', 'subject') - ordering = ('subject', 'no') +### 加载静态资源 +如果要在Django项目中使用静态资源,可以先创建一个用于保存静态资源的目录。在`vote`项目中,我们将静态资源置于名为`static`的文件夹中,在该文件夹包含了三个子文件夹:css、js和images,分别用来保存外部CSS文件、外部JavaScript文件和图片资源,如下图所示。 -admin.site.register(Subject, SubjectAdmin) -admin.site.register(Teacher, TeacherAdmin) -``` +![](res/pycharm-django-static.png) -接下来,我们就可以修改views.py文件,通过编写视图函数先实现“学科介绍”页面。 +为了能够找到保存静态资源的文件夹,我们还需要修改Django项目的配置文件`settings.py`,如下所示: ```Python -def show_subjects(request): - """查看所有学科""" - subjects = Subject.objects.all() - return render(request, 'subject.html', {'subjects': subjects}) +STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ] +STATIC_URL = '/static/' ``` -至此,我们还需要一个模板页,模板的配置以及模板页中模板语言的用法在之前已经进行过简要的介绍,如果不熟悉可以看看下面的代码,相信这并不是一件困难的事情。 - -```HTML - - - - - 所有学科信息 - - - -

所有学科

-
- {% for subject in subjects %} -
-

- {{ subject.name }} - {% if subject.is_hot %} - - {% endif %} -

-

{{ subject.intro }}

-
- {% endfor %} - - -``` +配置好静态资源之后,大家可以运行项目,然后看看之前我们写的页面上的图片是否能够正常加载出来。需要说明的是,在项目正式部署到线上环境后,我们通常会把静态资源交给专门的静态资源服务器(如Nginx、Apache)来处理,而不是有运行Python代码的服务器来管理静态资源,所以上面的配置并不适用于生产环境,仅供项目开发阶段测试使用。使用静态资源的正确姿势我们会在后续的章节为大家讲解。 -在上面的模板中,我们为每个学科添加了一个超链接,点击超链接可以查看该学科的讲师信息,为此需要再编写一个视图函数来处理查看指定学科老师信息。 +### Ajax概述 -```Python -def show_teachers(request): - """显示指定学科的老师""" - try: - sno = int(request.GET['sno']) - subject = Subject.objects.get(no=sno) - teachers = subject.teacher_set.all() - return render(request, 'teachers.html', {'subject': subject, 'teachers': teachers}) - except (KeyError, ValueError, Subject.DoesNotExist): - return redirect('/') -``` +接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”。Ajax是Asynchronous Javascript And XML的缩写 , 简单的说,使用Ajax技术可以在不重新加载整个页面的情况下对页面进行局部刷新。 -显示老师信息的模板页。 +对于传统的Web应用,每次页面上需要加载新的内容都需要重新请求服务器并刷新整个页面,如果服务器短时间内无法给予响应或者网络状况并不理想,那么可能会造成浏览器长时间的空白并使得用户处于等待状态,在这个期间用户什么都做不了,如下图所示。很显然,这样的Web应用并不能带来很好的用户体验。 -```HTML - -{% load static %} - - - - 老师 - - - -

{{ subject.name }}学科老师信息

-
- {% if teachers %} - {% for teacher in teachers %} -
-
- -
-
-

{{ teacher.name }}

-

{{ teacher.detail }}

-

- 好评 - ({{ teacher.good_count }}) - 差评 - ({{ teacher.bad_count }}) -

-
-
- {% endfor %} - {% else %} -

暂时没有该学科的老师信息

- {% endif %} -

- 返回首页 -

- - -``` +![](res/synchronous-web-request.png) -### 加载静态资源 +对于使用Ajax技术的Web应用,浏览器可以向服务器发起异步请求来获取数据。异步请求不会中断用户体验,当服务器返回了新的数据,我们可以通过JavaScript代码进行DOM操作来实现对页面的局部刷新,这样就相当于在不刷新整个页面的情况下更新了页面的内容,如下图所示。 -在上面的模板页面中,我们使用了``标签来加载老师的照片,其中使用了引用静态资源的模板指令`{% static %}`,要使用该指令,首先要使用`{% load static %}`指令来加载静态资源,我们将这段代码放在了页码开始的位置。在上面的项目中,我们将静态资源置于名为static的文件夹中,在该文件夹下又创建了三个文件夹:css、js和images,分别用来保存外部层叠样式表、外部JavaScript文件和图片资源。为了能够找到保存静态资源的文件夹,我们还需要修改Django项目的配置文件settings.py,如下所示: +![](res/asynchronous-web-request.png) -```Python -# 此处省略上面的代码 +在使用Ajax技术时,浏览器跟服务器通常会交换XML或JSON格式的数据,XML是以前使用得非常多的一种数据格式,近年来几乎已经完全被JSON取代,下面是两种数据格式的对比。 -STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ] -STATIC_URL = '/static/' +XML格式: -# 此处省略下面的代码 +```XML + + + Alice + Bob + Dinner is on me! + ``` -接下来修改urls.py文件,配置用户请求的URL和视图函数的对应关系。 - -```Python -from django.contrib import admin -from django.urls import path - -from vote import views +JSON格式: -urlpatterns = [ - path('', views.show_subjects), - path('teachers/', views.show_teachers), - path('admin/', admin.site.urls), -] +```JSON +{ + "from": "Alice", + "to": "Bob", + "content": "Dinner is on me!" +} ``` -启动服务器运行项目,进入首页查看学科信息。 - -![](./res/show_subjects.png) - -点击学科查看老师信息。 - -![](./res/show_teachers.png) +通过上面的对比,明显JSON格式的数据要紧凑得多,所以传输效率更高,而且JSON本身也是JavaScript中的一种对象表达式语法,在JavaScript代码中处理JSON格式的数据更加方便。 -### Ajax请求 +### 用Ajax实现投票功能 -接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”,Ajax技术我们在Web前端部分已经介绍过了,此处不再赘述。 - -首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL。 +下面,我们使用Ajax技术来实现投票的功能,首先修改项目的`urls.py`文件,为“好评”和“差评”功能映射对应的URL。 ```Python from django.contrib import admin @@ -233,16 +77,18 @@ urlpatterns = [ def praise_or_criticize(request): """好评""" try: - tno = int(request.GET['tno']) + tno = int(request.GET.get('tno')) teacher = Teacher.objects.get(no=tno) if request.path.startswith('/praise'): teacher.good_count += 1 + count = teacher.good_count else: teacher.bad_count += 1 + count = teacher.bad_count teacher.save() - data = {'code': 200, 'hint': '操作成功'} - except (KeyError, ValueError, Teacher.DoseNotExist): - data = {'code': 404, 'hint': '操作失败'} + data = {'code': 20000, 'mesg': '操作成功', 'count': count} + except (ValueError, Teacher.DoseNotExist): + data = {'code': 20001, 'mesg': '操作失败'} return JsonResponse(data) ``` @@ -250,54 +96,90 @@ def praise_or_criticize(request): ```HTML -{% load static %} - 老师 - + 老师信息 + -

{{ subject.name }}学科老师信息

-
- {% if teachers %} - {% for teacher in teachers %} -
-
- -
-
-

{{ teacher.name }}

-

{{ teacher.detail }}

-

- 好评 - ({{ teacher.good_count }}) -    - 差评 - ({{ teacher.bad_count }}) -

+
+

{{ subject.name }}学科的老师信息

+
+ {% if not teachers %} +

暂无该学科老师信息

+ {% endif %} + {% for teacher in teachers %} +
+
+ +
+
+
+ 姓名:{{ teacher.name }} + 性别:{{ teacher.sex | yesno:'男,女' }} + 出生日期:{{ teacher.birth }} +
+
{{ teacher.intro }}
+
+ 好评   + ({{ teacher.good_count }}) +      + 差评   + ({{ teacher.bad_count }}) +
+
-
- {% endfor %} - {% else %} -

暂时没有该学科的老师信息

- {% endif %} -

+ {% endfor %} 返回首页 -

+