更新了部分文档

pull/569/merge
jackfrued 2020-06-26 20:58:27 +08:00
parent 60587f026d
commit 04604252de
17 changed files with 355 additions and 286 deletions

View File

@ -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
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)`这样晦涩的信息,而是学科的名称。
![](./res/django-admin-models-detail.png)
### 实现学科页和老师页效果
为了更好的查看模型数据,可以为`Subject`和`Teacher`两个模型类添加`__str__`魔法方法。修改代码后的效果如下图所示。
1. 修改`polls/views.py`文件,编写视图函数实现对学科页和老师页的渲染
![](./res/django-amdin-models-detail-modified.png)
```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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学科信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
.user {
float: right;
margin-right: 10px;
}
.user>a {
margin-right: 10px;
}
#main>dl>dt {
font-size: 1.5em;
font-weight: bold;
}
#main>dl>dd {
font-size: 1.2em;
}
a {
text-decoration: none;
color: darkcyan;
}
</style>
</head>
<body>
<div id="container">
<div class="user">
<a href="login.html">用户登录</a>
<a href="register.html">快速注册</a>
</div>
<h1>扣丁学堂所有学科</h1>
<hr>
<div id="main">
{% for subject in subjects %}
<dl>
<dt>
<a href="/teachers/?sno={{ subject.no }}">{{ subject.name }}</a>
{% if subject.is_hot %}
<img src="/static/images/hot-icon-small.png">
{% endif %}
</dt>
<dd>{{ subject.intro }}</dd>
</dl>
{% endfor %}
</div>
</div>
</body>
</html>
```
`teachers.html`
```HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>老师信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
.teacher {
width: 100%;
margin: 0 auto;
padding: 10px 0;
border-bottom: 1px dashed gray;
overflow: auto;
}
.teacher>div {
float: left;
}
.photo {
height: 140px;
border-radius: 75px;
overflow: hidden;
margin-left: 20px;
}
.info {
width: 75%;
margin-left: 30px;
}
.info div {
clear: both;
margin: 5px 10px;
}
.info span {
margin-right: 25px;
}
.info a {
text-decoration: none;
color: darkcyan;
}
</style>
</head>
<body>
<div id="container">
<h1>{{ subject.name }}学科的老师信息</h1>
<hr>
{% if not teachers %}
<h2>暂无该学科老师信息</h2>
{% endif %}
{% for teacher in teachers %}
<div class="teacher">
<div class="photo">
<img src="/static/images/{{ teacher.photo }}" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:{{ teacher.name }}</strong></span>
<span>性别:{{ teacher.sex | yesno:'男,女' }}</span>
<span>出生日期:{{ teacher.birth }}</span>
</div>
<div class="intro">{{ teacher.intro }}</div>
<div class="comment">
<a href="">好评</a>&nbsp;(<strong>{{ teacher.good_count }}</strong>)
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="">差评</a>&nbsp;<strong>{{ teacher.bad_count }}</strong>)
</div>
</div>
</div>
{% endfor %}
<a href="/">返回首页</a>
</div>
</body>
</html>
```
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),
]
```
到此为止,页面上需要的图片(静态资源)还没有能够正常展示,我们在下一章节中为大家介绍如何处理模板页上的需要的静态资源。
### 补充内容

View File

@ -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')
admin.site.register(Subject, SubjectAdmin)
admin.site.register(Teacher, TeacherAdmin)
```
接下来我们就可以修改views.py文件通过编写视图函数先实现“学科介绍”页面。
```Python
def show_subjects(request):
"""查看所有学科"""
subjects = Subject.objects.all()
return render(request, 'subject.html', {'subjects': subjects})
```
至此,我们还需要一个模板页,模板的配置以及模板页中模板语言的用法在之前已经进行过简要的介绍,如果不熟悉可以看看下面的代码,相信这并不是一件困难的事情。
```HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>所有学科信息</title>
<style>/* 此处略去了层叠样式表的选择器 */</style>
</head>
<body>
<h1>所有学科</h1>
<hr>
{% 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>
```
在上面的模板中,我们为每个学科添加了一个超链接,点击超链接可以查看该学科的讲师信息,为此需要再编写一个视图函数来处理查看指定学科老师信息。
```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('/')
```
显示老师信息的模板页。
```HTML
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>老师</title>
<style>/* 此处略去了层叠样式表的选择器 */</style>
</head>
<body>
<h1>{{ subject.name }}学科老师信息</h1>
<hr>
{% if teachers %}
{% 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>
</div>
{% endfor %}
{% else %}
<h3>暂时没有该学科的老师信息</h3>
{% endif %}
<p>
<a href="/">返回首页</a>
</p>
</body>
</html>
```
### 加载静态资源
在上面的模板页面中,我们使用了`<img>`标签来加载老师的照片,其中使用了引用静态资源的模板指令`{% static %}`,要使用该指令,首先要使用`{% load static %}`指令来加载静态资源我们将这段代码放在了页码开始的位置。在上面的项目中我们将静态资源置于名为static的文件夹中在该文件夹下又创建了三个文件夹css、js和images分别用来保存外部层叠样式表、外部JavaScript文件和图片资源。为了能够找到保存静态资源的文件夹我们还需要修改Django项目的配置文件settings.py如下所示
如果要在Django项目中使用静态资源可以先创建一个用于保存静态资源的目录。在`vote`项目中,我们将静态资源置于名为`static`的文件夹中在该文件夹包含了三个子文件夹css、js和images分别用来保存外部CSS文件、外部JavaScript文件和图片资源如下图所示。
![](res/pycharm-django-static.png)
为了能够找到保存静态资源的文件夹我们还需要修改Django项目的配置文件`settings.py`,如下所示:
```Python
# 此处省略上面的代码
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
STATIC_URL = '/static/'
# 此处省略下面的代码
```
接下来修改urls.py文件配置用户请求的URL和视图函数的对应关系
配置好静态资源之后大家可以运行项目然后看看之前我们写的页面上的图片是否能够正常加载出来。需要说明的是在项目正式部署到线上环境后我们通常会把静态资源交给专门的静态资源服务器如Nginx、Apache来处理而不是有运行Python代码的服务器来管理静态资源所以上面的配置并不适用于生产环境仅供项目开发阶段测试使用。使用静态资源的正确姿势我们会在后续的章节为大家讲解。
```Python
from django.contrib import admin
from django.urls import path
### Ajax概述
from vote import views
接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”。Ajax是Asynchronous Javascript And XML的缩写 , 简单的说使用Ajax技术可以在不重新加载整个页面的情况下对页面进行局部刷新。
urlpatterns = [
path('', views.show_subjects),
path('teachers/', views.show_teachers),
path('admin/', admin.site.urls),
]
对于传统的Web应用每次页面上需要加载新的内容都需要重新请求服务器并刷新整个页面如果服务器短时间内无法给予响应或者网络状况并不理想那么可能会造成浏览器长时间的空白并使得用户处于等待状态在这个期间用户什么都做不了如下图所示。很显然这样的Web应用并不能带来很好的用户体验。
![](res/synchronous-web-request.png)
对于使用Ajax技术的Web应用浏览器可以向服务器发起异步请求来获取数据。异步请求不会中断用户体验当服务器返回了新的数据我们可以通过JavaScript代码进行DOM操作来实现对页面的局部刷新这样就相当于在不刷新整个页面的情况下更新了页面的内容如下图所示。
![](res/asynchronous-web-request.png)
在使用Ajax技术时浏览器跟服务器通常会交换XML或JSON格式的数据XML是以前使用得非常多的一种数据格式近年来几乎已经完全被JSON取代下面是两种数据格式的对比。
XML格式
```XML
<?xml version="1.0" encoding="utf-8"?>
<message>
<from>Alice</from>
<to>Bob</to>
<content>Dinner is on me!</content>
</message>
```
启动服务器运行项目,进入首页查看学科信息。
JSON格式
![](./res/show_subjects.png)
```JSON
{
"from": "Alice",
"to": "Bob",
"content": "Dinner is on me!"
}
```
点击学科查看老师信息。
通过上面的对比明显JSON格式的数据要紧凑得多所以传输效率更高而且JSON本身也是JavaScript中的一种对象表达式语法在JavaScript代码中处理JSON格式的数据更加方便。
![](./res/show_teachers.png)
### 用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
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>老师</title>
<style>/* 此处略去了层叠样式表的选择器 */</style>
<title>老师信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
.teacher {
width: 100%;
margin: 0 auto;
padding: 10px 0;
border-bottom: 1px dashed gray;
overflow: auto;
}
.teacher>div {
float: left;
}
.photo {
height: 140px;
border-radius: 75px;
overflow: hidden;
margin-left: 20px;
}
.info {
width: 75%;
margin-left: 30px;
}
.info div {
clear: both;
margin: 5px 10px;
}
.info span {
margin-right: 25px;
}
.info a {
text-decoration: none;
color: darkcyan;
}
</style>
</head>
<body>
<h1>{{ subject.name }}学科老师信息</h1>
<hr>
{% if teachers %}
{% for teacher in teachers %}
<div class="teacher">
<div class="photo">
<img src="{% static teacher.photo %}" height="140" alt="">
<div id="container">
<h1>{{ subject.name }}学科的老师信息</h1>
<hr>
{% if not teachers %}
<h2>暂无该学科老师信息</h2>
{% endif %}
{% for teacher in teachers %}
<div class="teacher">
<div class="photo">
<img src="/static/images/{{ teacher.photo }}" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:{{ teacher.name }}</strong></span>
<span>性别:{{ teacher.sex | yesno:'男,女' }}</span>
<span>出生日期:{{ teacher.birth }}</span>
</div>
<div class="intro">{{ teacher.intro }}</div>
<div class="comment">
<a href="/praise/?tno={{ teacher.no }}">好评</a>&nbsp;&nbsp;
(<strong>{{ teacher.good_count }}</strong>)
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="/criticize/?tno={{ teacher.no }}">差评</a>&nbsp;&nbsp;
(<strong>{{ teacher.bad_count }}</strong>)
</div>
</div>
</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>
</div>
{% endfor %}
{% else %}
<h3>暂时没有该学科的老师信息</h3>
{% endif %}
<p>
{% endfor %}
<a href="/">返回首页</a>
</p>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
$(() => {
$('.comment>a').on('click', (evt) => {
evt.preventDefault()
let anchor = $(evt.target)
let url = anchor.attr('href')
let url = $(evt.target).attr('href')
$.getJSON(url, (json) => {
if (json.code == 10001) {
let span = anchor.next()
span.text(parseInt(span.text()) + 1)
if (json.code == 20000) {
$(evt.target).next().text(json.count)
} else {
alert(json.hint)
alert(json.mesg)
}
})
})
@ -307,6 +189,8 @@ def praise_or_criticize(request):
</html>
```
上面的前端代码中使用了jQuery库封装的`getJSON`方法向服务器发送异步请求如果不熟悉前端的jQuery库可以参考[《jQuery API手册》](https://www.runoob.com/manual/jquery/)。
### 小结
到此为止,这个投票项目的核心功能已然完成,在下面的章节中我们会要求用户必须登录才能投票,没有账号的用户可以通过注册功能注册一个账号。

View File

@ -1,6 +1,6 @@
## 表单的应用
我们继续来完成上一章节中的项目,实现“用户注册”和“用户登录”的功能,并限制只有登录的用户才能为老师投票。Django框架中提供了对表单的封装而且提供了多种不同的使用方式。
Django框架中提供了对表单的封装而且提供了多种不同的使用方式。
首先添加用户模型。
@ -301,9 +301,7 @@ class Captcha(object):
for char_image in char_images:
c_width, c_height = char_image.size
mask = char_image.convert('L').point(lambda i: i * 1.97)
self._image.paste(char_image,
(offset, int((height - c_height) / 2)),
mask)
self._image.paste(char_image, (offset, int((height - c_height) / 2)), mask)
offset += int(c_width * squeeze_factor)
@staticmethod
@ -316,15 +314,12 @@ class Captcha(object):
y1 = int(random.uniform(-dy, dy))
x2 = int(random.uniform(-dx, dx))
y2 = int(random.uniform(-dy, dy))
warp_image = Image.new(
'RGB',
(width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
warp_image = Image.new('RGB', (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
warp_image.paste(image, (abs(x1), abs(y1)))
width2, height2 = warp_image.size
return warp_image.transform(
(width, height),
Image.QUAD,
(x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1))
return warp_image.transform((width, height), Image.QUAD,
(x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1)
)
@staticmethod
def offset(image, dx_factor=0.1, dy_factor=0.2):
@ -339,15 +334,13 @@ class Captcha(object):
@staticmethod
def rotate(image, angle=25):
"""图像旋转"""
return image.rotate(random.uniform(-angle, angle),
Image.BILINEAR, expand=1)
return image.rotate(random.uniform(-angle, angle), Image.BILINEAR, expand=1)
def generate(self, captcha_text='', fmt='PNG'):
"""生成验证码(文字和图片)"""
self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255))
self.background()
self.text(captcha_text, self._fonts,
drawings=['warp', 'rotate', 'offset'])
self.text(captcha_text, self._fonts, drawings=['warp', 'rotate', 'offset'])
self.curve()
self.noise()
self.smooth()
@ -382,7 +375,7 @@ def random_color(start=0, end=255, opacity=255):
return red, green, blue, opacity
```
> 说明上面的代码在生成验证码图片时用到了三种字体文件使用上面的代码时需要添加字体文件到应用目录下的fonts目录中。
> **说明**上面的代码在生成验证码图片时用到了三种字体文件使用上面的代码时需要添加字体文件到应用目录下的fonts目录中。
下面的视图函数用来生成验证码并通过HttpResponse对象输出到用户浏览器中。

View File

@ -1,5 +1,14 @@
## Cookie和Session
我们继续来完成上一章节中的项目,实现“用户登录”的功能,并限制只有登录的用户才能投票。
### 实现用户登录
1. 创建用户模型。之前我们讲解过如果通过Django的ORM实现从二维表到模型的转换反向工程这次我们尝试把模型变成二维表正向工程
2. 使用下面的命令生成迁移文件并执行迁移,将`User`模型直接变成关系型数据库中的二维表`tb_user`。
3. 用下面的SQL语句直接插入两条测试数据通常不能讲用户的密码直接保存在数据库中因此我们将用户密码处理成对应的MD5摘要。MD5消息摘要算法是一种被广泛使用的密码哈希函数散列函数可以产生出一个128位比特的哈希值散列值用于确保信息传输完整一致。在使用哈希值时通常会将哈希值表示为16进制字符串因此128位的MD5摘要通常表示为32个十六进制符号。
4. 编写用户登录的模板页。
### 实现用户跟踪
如今,一个网站如果不通过某种方式记住你是谁以及你之前在网站的活动情况,失去的就是网站的可用性和便利性,继而很有可能导致网站用户的流式,所以记住一个用户(更专业的说法叫**用户跟踪**对绝大多数Web应用来说都是必需的功能。

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -111,6 +111,9 @@ class TaobaoDownloaderMiddleWare(object):
def __init__(self, timeout=None):
self.timeout = timeout
# options = webdriver.ChromeOptions()
# options.add_argument('headless')
# self.browser = webdriver.Chrome(options=options)
self.browser = webdriver.Chrome()
self.browser.set_window_size(1000, 600)
self.browser.set_page_load_timeout(self.timeout)

View File

@ -71,10 +71,10 @@ IMAGES_STORE = './resources/'
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
# ITEM_PIPELINES = {
# 'image360.pipelines.SaveImagePipeline': 300,
# 'image360.pipelines.SaveToMongoPipeline': 301,
# }
ITEM_PIPELINES = {
'image360.pipelines.SaveImagePipeline': 300,
'image360.pipelines.SaveToMongoPipeline': 301,
}
LOG_LEVEL = 'DEBUG'