更新了部分文档
|
@ -268,29 +268,6 @@ Teacher.objects.filter(subject__name__contains='全栈')
|
||||||
|
|
||||||
> **说明3**:如果希望更新多条数据,不用先逐一获取模型对象再修改对象属性,可以直接使用QuerySet对象的`update()`方法一次性更新多条数据。
|
> **说明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后台管理模型
|
### 利用Django后台管理模型
|
||||||
|
|
||||||
|
@ -312,11 +289,11 @@ Teacher.objects.filter(subject__name__contains='全栈')
|
||||||
|
|
||||||
3. 运行项目,在浏览器中访问`http://127.0.0.1:8000/admin`,输入刚才创建的超级用户账号和密码进行登录。
|
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`文件中对需要管理的模型进行注册。
|
注意,我们暂时还没能在`admin`应用中看到之前创建的模型类,为此需要在`polls`应用的`admin.py`文件中对需要管理的模型进行注册。
|
||||||
|
|
||||||
|
@ -325,8 +302,6 @@ Teacher.objects.filter(subject__name__contains='全栈')
|
||||||
```Python
|
```Python
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
from polls.models import Subject, Teacher
|
from polls.models import Subject, Teacher
|
||||||
|
|
||||||
admin.site.register(Subject)
|
admin.site.register(Subject)
|
||||||
|
@ -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. 注册模型管理类。
|
6. 注册模型管理类。
|
||||||
|
|
||||||
可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改`admin.py`文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。
|
可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改`admin.py`文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。
|
||||||
|
|
||||||
```Python
|
```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-models-detail.png)
|
![](res/django-admin-view-models-subject.png)
|
||||||
|
|
||||||
为了更好的查看模型数据,可以为`Subject`和`Teacher`两个模型类添加`__str__`魔法方法。修改代码后的效果如下图所示。
|
![](res/django-admin-view-models-teacher.png)
|
||||||
|
|
||||||
![](./res/django-amdin-models-detail-modified.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
|
||||||
|
<!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> (<strong>{{ teacher.good_count }}</strong>)
|
||||||
|
|
||||||
|
<a href="">差评</a> <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),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
到此为止,页面上需要的图片(静态资源)还没有能够正常展示,我们在下一章节中为大家介绍如何处理模板页上的需要的静态资源。
|
||||||
|
|
||||||
### 补充内容
|
### 补充内容
|
||||||
|
|
||||||
|
|
|
@ -1,216 +1,60 @@
|
||||||
## 静态资源和Ajax请求
|
## 静态资源和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
|
```Python
|
||||||
# 此处省略上面的代码
|
|
||||||
|
|
||||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
# 此处省略下面的代码
|
|
||||||
```
|
```
|
||||||
|
|
||||||
接下来修改urls.py文件,配置用户请求的URL和视图函数的对应关系。
|
配置好静态资源之后,大家可以运行项目,然后看看之前我们写的页面上的图片是否能够正常加载出来。需要说明的是,在项目正式部署到线上环境后,我们通常会把静态资源交给专门的静态资源服务器(如Nginx、Apache)来处理,而不是有运行Python代码的服务器来管理静态资源,所以上面的配置并不适用于生产环境,仅供项目开发阶段测试使用。使用静态资源的正确姿势我们会在后续的章节为大家讲解。
|
||||||
|
|
||||||
```Python
|
### Ajax概述
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from vote import views
|
接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”。Ajax是Asynchronous Javascript And XML的缩写 , 简单的说,使用Ajax技术可以在不重新加载整个页面的情况下对页面进行局部刷新。
|
||||||
|
|
||||||
urlpatterns = [
|
对于传统的Web应用,每次页面上需要加载新的内容都需要重新请求服务器并刷新整个页面,如果服务器短时间内无法给予响应或者网络状况并不理想,那么可能会造成浏览器长时间的空白并使得用户处于等待状态,在这个期间用户什么都做不了,如下图所示。很显然,这样的Web应用并不能带来很好的用户体验。
|
||||||
path('', views.show_subjects),
|
|
||||||
path('teachers/', views.show_teachers),
|
![](res/synchronous-web-request.png)
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
]
|
对于使用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技术来实现投票的功能,首先修改项目的`urls.py`文件,为“好评”和“差评”功能映射对应的URL。
|
||||||
|
|
||||||
接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”,Ajax技术我们在Web前端部分已经介绍过了,此处不再赘述。
|
|
||||||
|
|
||||||
首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL。
|
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
@ -233,16 +77,18 @@ urlpatterns = [
|
||||||
def praise_or_criticize(request):
|
def praise_or_criticize(request):
|
||||||
"""好评"""
|
"""好评"""
|
||||||
try:
|
try:
|
||||||
tno = int(request.GET['tno'])
|
tno = int(request.GET.get('tno'))
|
||||||
teacher = Teacher.objects.get(no=tno)
|
teacher = Teacher.objects.get(no=tno)
|
||||||
if request.path.startswith('/praise'):
|
if request.path.startswith('/praise'):
|
||||||
teacher.good_count += 1
|
teacher.good_count += 1
|
||||||
|
count = teacher.good_count
|
||||||
else:
|
else:
|
||||||
teacher.bad_count += 1
|
teacher.bad_count += 1
|
||||||
|
count = teacher.bad_count
|
||||||
teacher.save()
|
teacher.save()
|
||||||
data = {'code': 200, 'hint': '操作成功'}
|
data = {'code': 20000, 'mesg': '操作成功', 'count': count}
|
||||||
except (KeyError, ValueError, Teacher.DoseNotExist):
|
except (ValueError, Teacher.DoseNotExist):
|
||||||
data = {'code': 404, 'hint': '操作失败'}
|
data = {'code': 20001, 'mesg': '操作失败'}
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -250,54 +96,90 @@ def praise_or_criticize(request):
|
||||||
|
|
||||||
```HTML
|
```HTML
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
{% load static %}
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>老师</title>
|
<title>老师信息</title>
|
||||||
<style>/* 此处略去了层叠样式表的选择器 */</style>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>{{ subject.name }}学科老师信息</h1>
|
<div id="container">
|
||||||
|
<h1>{{ subject.name }}学科的老师信息</h1>
|
||||||
<hr>
|
<hr>
|
||||||
{% if teachers %}
|
{% if not teachers %}
|
||||||
|
<h2>暂无该学科老师信息</h2>
|
||||||
|
{% endif %}
|
||||||
{% for teacher in teachers %}
|
{% for teacher in teachers %}
|
||||||
<div class="teacher">
|
<div class="teacher">
|
||||||
<div class="photo">
|
<div class="photo">
|
||||||
<img src="{% static teacher.photo %}" height="140" alt="">
|
<img src="/static/images/{{ teacher.photo }}" height="140" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<h3>{{ teacher.name }}</h3>
|
<div>
|
||||||
<p>{{ teacher.detail }}</p>
|
<span><strong>姓名:{{ teacher.name }}</strong></span>
|
||||||
<p class="comment">
|
<span>性别:{{ teacher.sex | yesno:'男,女' }}</span>
|
||||||
<a href="/praise/?tno={{ teacher.no }}">好评</a>
|
<span>出生日期:{{ teacher.birth }}</span>
|
||||||
(<span>{{ teacher.good_count }}</span>)
|
</div>
|
||||||
|
<div class="intro">{{ teacher.intro }}</div>
|
||||||
<a href="/criticize/?tno={{ teacher.no }}">差评</a>
|
<div class="comment">
|
||||||
(<span>{{ teacher.bad_count }}</span>)
|
<a href="/praise/?tno={{ teacher.no }}">好评</a>
|
||||||
</p>
|
(<strong>{{ teacher.good_count }}</strong>)
|
||||||
|
|
||||||
|
<a href="/criticize/?tno={{ teacher.no }}">差评</a>
|
||||||
|
(<strong>{{ teacher.bad_count }}</strong>)
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
|
||||||
<h3>暂时没有该学科的老师信息</h3>
|
|
||||||
{% endif %}
|
|
||||||
<p>
|
|
||||||
<a href="/">返回首页</a>
|
<a href="/">返回首页</a>
|
||||||
</p>
|
</div>
|
||||||
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
|
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(() => {
|
$(() => {
|
||||||
$('.comment>a').on('click', (evt) => {
|
$('.comment>a').on('click', (evt) => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
let anchor = $(evt.target)
|
let url = $(evt.target).attr('href')
|
||||||
let url = anchor.attr('href')
|
|
||||||
$.getJSON(url, (json) => {
|
$.getJSON(url, (json) => {
|
||||||
if (json.code == 10001) {
|
if (json.code == 20000) {
|
||||||
let span = anchor.next()
|
$(evt.target).next().text(json.count)
|
||||||
span.text(parseInt(span.text()) + 1)
|
|
||||||
} else {
|
} else {
|
||||||
alert(json.hint)
|
alert(json.mesg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -307,6 +189,8 @@ def praise_or_criticize(request):
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
上面的前端代码中,使用了jQuery库封装的`getJSON`方法向服务器发送异步请求,如果不熟悉前端的jQuery库,可以参考[《jQuery API手册》](https://www.runoob.com/manual/jquery/)。
|
||||||
|
|
||||||
### 小结
|
### 小结
|
||||||
|
|
||||||
到此为止,这个投票项目的核心功能已然完成,在下面的章节中我们会要求用户必须登录才能投票,没有账号的用户可以通过注册功能注册一个账号。
|
到此为止,这个投票项目的核心功能已然完成,在下面的章节中我们会要求用户必须登录才能投票,没有账号的用户可以通过注册功能注册一个账号。
|
|
@ -1,6 +1,6 @@
|
||||||
## 表单的应用
|
## 表单的应用
|
||||||
|
|
||||||
我们继续来完成上一章节中的项目,实现“用户注册”和“用户登录”的功能,并限制只有登录的用户才能为老师投票。Django框架中提供了对表单的封装,而且提供了多种不同的使用方式。
|
Django框架中提供了对表单的封装,而且提供了多种不同的使用方式。
|
||||||
|
|
||||||
首先添加用户模型。
|
首先添加用户模型。
|
||||||
|
|
||||||
|
@ -301,9 +301,7 @@ class Captcha(object):
|
||||||
for char_image in char_images:
|
for char_image in char_images:
|
||||||
c_width, c_height = char_image.size
|
c_width, c_height = char_image.size
|
||||||
mask = char_image.convert('L').point(lambda i: i * 1.97)
|
mask = char_image.convert('L').point(lambda i: i * 1.97)
|
||||||
self._image.paste(char_image,
|
self._image.paste(char_image, (offset, int((height - c_height) / 2)), mask)
|
||||||
(offset, int((height - c_height) / 2)),
|
|
||||||
mask)
|
|
||||||
offset += int(c_width * squeeze_factor)
|
offset += int(c_width * squeeze_factor)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -316,15 +314,12 @@ class Captcha(object):
|
||||||
y1 = int(random.uniform(-dy, dy))
|
y1 = int(random.uniform(-dy, dy))
|
||||||
x2 = int(random.uniform(-dx, dx))
|
x2 = int(random.uniform(-dx, dx))
|
||||||
y2 = int(random.uniform(-dy, dy))
|
y2 = int(random.uniform(-dy, dy))
|
||||||
warp_image = Image.new(
|
warp_image = Image.new('RGB', (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
|
||||||
'RGB',
|
|
||||||
(width + abs(x1) + abs(x2), height + abs(y1) + abs(y2)))
|
|
||||||
warp_image.paste(image, (abs(x1), abs(y1)))
|
warp_image.paste(image, (abs(x1), abs(y1)))
|
||||||
width2, height2 = warp_image.size
|
width2, height2 = warp_image.size
|
||||||
return warp_image.transform(
|
return warp_image.transform((width, height), Image.QUAD,
|
||||||
(width, height),
|
(x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1)
|
||||||
Image.QUAD,
|
)
|
||||||
(x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def offset(image, dx_factor=0.1, dy_factor=0.2):
|
def offset(image, dx_factor=0.1, dy_factor=0.2):
|
||||||
|
@ -339,15 +334,13 @@ class Captcha(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def rotate(image, angle=25):
|
def rotate(image, angle=25):
|
||||||
"""图像旋转"""
|
"""图像旋转"""
|
||||||
return image.rotate(random.uniform(-angle, angle),
|
return image.rotate(random.uniform(-angle, angle), Image.BILINEAR, expand=1)
|
||||||
Image.BILINEAR, expand=1)
|
|
||||||
|
|
||||||
def generate(self, captcha_text='', fmt='PNG'):
|
def generate(self, captcha_text='', fmt='PNG'):
|
||||||
"""生成验证码(文字和图片)"""
|
"""生成验证码(文字和图片)"""
|
||||||
self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255))
|
self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255))
|
||||||
self.background()
|
self.background()
|
||||||
self.text(captcha_text, self._fonts,
|
self.text(captcha_text, self._fonts, drawings=['warp', 'rotate', 'offset'])
|
||||||
drawings=['warp', 'rotate', 'offset'])
|
|
||||||
self.curve()
|
self.curve()
|
||||||
self.noise()
|
self.noise()
|
||||||
self.smooth()
|
self.smooth()
|
||||||
|
@ -382,7 +375,7 @@ def random_color(start=0, end=255, opacity=255):
|
||||||
return red, green, blue, opacity
|
return red, green, blue, opacity
|
||||||
```
|
```
|
||||||
|
|
||||||
> 说明:上面的代码在生成验证码图片时用到了三种字体文件,使用上面的代码时需要添加字体文件到应用目录下的fonts目录中。
|
> **说明**:上面的代码在生成验证码图片时用到了三种字体文件,使用上面的代码时需要添加字体文件到应用目录下的fonts目录中。
|
||||||
|
|
||||||
下面的视图函数用来生成验证码并通过HttpResponse对象输出到用户浏览器中。
|
下面的视图函数用来生成验证码并通过HttpResponse对象输出到用户浏览器中。
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
## Cookie和Session
|
## Cookie和Session
|
||||||
|
|
||||||
|
我们继续来完成上一章节中的项目,实现“用户登录”的功能,并限制只有登录的用户才能投票。
|
||||||
|
|
||||||
|
### 实现用户登录
|
||||||
|
|
||||||
|
1. 创建用户模型。之前我们讲解过如果通过Django的ORM实现从二维表到模型的转换(反向工程),这次我们尝试把模型变成二维表(正向工程)。
|
||||||
|
2. 使用下面的命令生成迁移文件并执行迁移,将`User`模型直接变成关系型数据库中的二维表`tb_user`。
|
||||||
|
3. 用下面的SQL语句直接插入两条测试数据,通常不能讲用户的密码直接保存在数据库中,因此我们将用户密码处理成对应的MD5摘要。MD5消息摘要算法是一种被广泛使用的密码哈希函数(散列函数),可以产生出一个128位(比特)的哈希值(散列值),用于确保信息传输完整一致。在使用哈希值时,通常会将哈希值表示为16进制字符串,因此128位的MD5摘要通常表示为32个十六进制符号。
|
||||||
|
4. 编写用户登录的模板页。
|
||||||
|
|
||||||
### 实现用户跟踪
|
### 实现用户跟踪
|
||||||
|
|
||||||
如今,一个网站如果不通过某种方式记住你是谁以及你之前在网站的活动情况,失去的就是网站的可用性和便利性,继而很有可能导致网站用户的流式,所以记住一个用户(更专业的说法叫**用户跟踪**)对绝大多数Web应用来说都是必需的功能。
|
如今,一个网站如果不通过某种方式记住你是谁以及你之前在网站的活动情况,失去的就是网站的可用性和便利性,继而很有可能导致网站用户的流式,所以记住一个用户(更专业的说法叫**用户跟踪**)对绝大多数Web应用来说都是必需的功能。
|
||||||
|
|
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 155 KiB |
After Width: | Height: | Size: 202 KiB |
After Width: | Height: | Size: 187 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 292 KiB |
After Width: | Height: | Size: 146 KiB |
|
@ -111,6 +111,9 @@ class TaobaoDownloaderMiddleWare(object):
|
||||||
|
|
||||||
def __init__(self, timeout=None):
|
def __init__(self, timeout=None):
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
# options = webdriver.ChromeOptions()
|
||||||
|
# options.add_argument('headless')
|
||||||
|
# self.browser = webdriver.Chrome(options=options)
|
||||||
self.browser = webdriver.Chrome()
|
self.browser = webdriver.Chrome()
|
||||||
self.browser.set_window_size(1000, 600)
|
self.browser.set_window_size(1000, 600)
|
||||||
self.browser.set_page_load_timeout(self.timeout)
|
self.browser.set_page_load_timeout(self.timeout)
|
||||||
|
|
|
@ -71,10 +71,10 @@ IMAGES_STORE = './resources/'
|
||||||
|
|
||||||
# Configure item pipelines
|
# Configure item pipelines
|
||||||
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
|
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
|
||||||
# ITEM_PIPELINES = {
|
ITEM_PIPELINES = {
|
||||||
# 'image360.pipelines.SaveImagePipeline': 300,
|
'image360.pipelines.SaveImagePipeline': 300,
|
||||||
# 'image360.pipelines.SaveToMongoPipeline': 301,
|
'image360.pipelines.SaveToMongoPipeline': 301,
|
||||||
# }
|
}
|
||||||
|
|
||||||
LOG_LEVEL = 'DEBUG'
|
LOG_LEVEL = 'DEBUG'
|
||||||
|
|
||||||
|
|