目录
1.实现网页开启
打开Anaconda Prompt
- 输入:conda env list——查看Anaconda当前已经配置的环境
- 输入:activate+环境名——切换环境
- 输入:conda list ——查看当前环境下已安装模块
- 输入:django-admin startproject test1——创建test1项目(执行成功后会产生一个test1文件夹,里面的内容有manage.py,管理文件,init.py,表示当前文件夹是python的一个模块,settings.py,保存设置信息,urls.py,储存url地址文件,wsgi.py,WSGI协议文件)
- 输入:python manage.py startapp app名称——创建应用,会自动创建目录(找到settings.py中的INSTALLED_APPS设置项,将应用添加进去'app名称')
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app', ]
- 输入:python manage.py runserver——启动开发web服务器退出方法:Quit the server with CTRL-BREAK.
在浏览器中访问127.0.0.1:8000/可以得到启动成功的页面
2.创建网页数据库模型
在创建的app文件夹中包含models.py文件,数据库的模型代码就写在这里面
文件中创建了博客所需的三个数据库表(Class):分类,标签,文章
在文章中包含了标题、正文、发表/修改时间、摘要、作者等数据,同时还包含了与分类、标签的对应关系
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100)
class Tag(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=70)
body = models.TextField()
created_time = models.DateTimeField()
modified_time = models.DateTimeField()
excerpt = models.CharField(max_length=200, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
下面是对代码的注解:
CharField 用于指定分类名 name 的数据类型(字符型 CharField 、日期时间类型 DateTimeField、整数类型 IntegerField 等等)
django 内置的全部类型可查看文档
https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-types较短的字符使用CharField,大段文本使用 TextField
文章摘要,可以没有文章摘要,但默认情况下 CharField 要求我们必须存入数据,否则就会报错,指定 CharField 的 blank=True 参数值后就可以允许空值了。
文章与分类使用 ForeignKey,一对多的关联关系, django 2.0 以后,ForeignKey 必须传入一个 on_delete 参数用来指定当关联的数据被删除时,被关联的数据的行为,我们这里假定当某个分类被删除时,该分类下全部文章也同时被删除,因此使用 models.CASCADE 参数,意为级联删除文章与标签使用 ManyToManyField ,多对多的关联关系,同时我们规定文章可以没有标签,因此为标签 tags 指定了 blank=True
https://docs.djangoproject.com/en/2.2/topics/db/models/#relationships文章作者,这里 User 是从 django.contrib.auth.models 导入的
django.contrib.auth 是 django 内置的应用,专门用于处理网站用户的注册、登录等流程
数据库模型的代码还需要django 翻译成数据库语言,因此实际上这些数据库表还没有真正的在数据库中创建
- 输入:python manage.py makemigrations
- 输入:python manage.py migrate
django 使用了 Python 内置的 SQLite3 数据库,SQLite3 是一个十分轻巧的数据库,项目根目录下多出了一个 db.sqlite3 的文件,这就是 SQLite3 数据库文件
3.数据库数据操作
输入:python manage.py shell——开启交互式命令行
Ctrl + break 退出交互式命令行
4.处理HTTP请求
Web 应用的交互过程其实就是 HTTP 请求与响应的过程
使用浏览器来上网,上网流程大致是这样的:
- 打开浏览器,在地址栏输入想访问的网址
- 浏览器知道我们想要访问哪个网址后,把我们的访问意图包装成一个 HTTP 请求,发给我们想要访问的网址所对应的服务器
- 服务器处理HTTP请求后生成HTTP 响应给浏览器,浏览器解读这个响应,并显示相关内容
django 作为一个 Web 框架,它的使命就是处理流程中的第二步,即接收浏览器发来的 HTTP 请求,返回相应的 HTTP 响应
- 在 blog 应用的目录下创建一个 urls.py 文件
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]
-
实际编写定义在 views.py 文件中的view.index函数
from django.http import HttpResponse def index(request): return HttpResponse("欢迎访问我的博客首页!")
-
把应用下的 urls.py 文件包含到总的 urls.py 里去
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('blog.urls')), ]
这基本上就上 django 的开发流程了,写好处理 HTTP 请求和返回 HTTP 响应的视图函数,然后把视图函数绑定到相应的 URL 上
我们看到在视图函数里返回的是一个 HttpResponse 类的实例,我们给它传入了一个希望显示在用户浏览器上的字符串,但我们不能每次都把很大段的内容传给 HttpResponse
django提供了模板系统让我们把大段的文本写到一个文件里,然后 django 自己会去读取这个文件,再把读取到的内容传给 HttpResponse
- 在我们的项目根目录(即 manage.py 文件所在目录)下建立一个名为 templates 的文件夹
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ title }}</title> </head> <body> <h1>{{ welcome }}</h1> </body> </html>
- 在 settings.py 文件里设置模板文件所在的路径
TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], ... }, ]
- 修改视图函数
from django.shortcuts import render def index(request): return render(request, 'blog/index.html', context={ 'title': '我的博客首页', 'welcome': '欢迎访问我的博客首页' })
我们首先把 HTTP 请求传了进去,然后 render 根据第二个参数的值 blog/index.html 找到这个模板文件并读取模板中的内容。之后 render 根据我们传入的 context 参数的值把模板中的变量替换为我们传递的变量的值,
{{ title }}
被替换成了 context 字典中 title 对应的值,同理{{ welcome }}
也被替换成相应的值
5.应用网页模板
把模板中的 CSS 和 JavaScript 文件放在 blog 应用的 static 目录下
同时,为了避免和其它应用中的 CSS 和 JavaScript 文件命名冲突(别的应用下也可能有和 blog 应用下同名的 CSS 、JavaScript 文件),我们再在 static 目录下建立一个 blog 文件夹,把下载的博客模板中的 css 和 js 文件夹连同里面的全部文件一同拷贝进这个目录
用下载的博客模板中的 index.html 文件替换掉之前我们自己写的 index.html 文件
此时首页显示的样式非常混乱,原因是浏览器无法正确加载 CSS 等样式文件,需要以 django 的方式来正确地处理 CSS 和 JavaScript 等静态文件的加载路径,CSS 样式文件通常在 HTML 文档的 head 标签里引入,打开 index.html 文件,在文件的开始处找到 head 标签包裹的内容
我们把引用路径放在了一个奇怪的符号里,例如:href="{% static 'blog/css/bootstrap.min.css' %}",用 {% %} 包裹起来的叫做模板标签,用 {{ }} 包裹起来的叫做模板变量,其作用是在最终渲染的模板里显示由视图函数传过来的变量值
+ {% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Black & White</title>
<!-- meta -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- css -->
- <link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
- <link rel="stylesheet" href="css/pace.css">
- <link rel="stylesheet" href="css/custom.css">
+ <link rel="stylesheet" href="{% static 'blog/css/bootstrap.min.css' %}">
+ <link rel="stylesheet" href="{% static 'blog/css/pace.css' %}">
+ <link rel="stylesheet" href="{% static 'blog/css/custom.css' %}">
<!-- js -->
- <script src="js/jquery-2.1.3.min.js"></script>
- <script src="js/bootstrap.min.js"></script>
- <script src="js/pace.min.js"></script>
- <script src="js/modernizr.custom.js"></script>
+ <script src="{% static 'blog/js/jquery-2.1.3.min.js' %}"></script>
+ <script src="{% static 'blog/js/bootstrap.min.js' %}"></script>
+ <script src="{% static 'blog/js/pace.min.js' %}"></script>
+ <script src="{% static 'blog/js/modernizr.custom.js' %}"></script>
</head>
<body>
<!-- 其它内容 -->
- <script src="js/script.js' %}"></script>
+ <script src="{% static 'blog/js/script.js' %}"></script>
</body>
</html>
- 表示删掉这一行,而 + 表示增加这一行,记得加上{% load static %}
模板中预先填充了数据,最终要让它显示从数据库中获取的文章数据
下面来稍微改造一下模板:
- 在模板 index.html 中你会找到一系列 article 标签,将 index.html 中多余的 article 标签删掉,只留下一个 article 标签,然后写上下列代码:
... {% for post in post_list %} <article class="post post-{{ post.pk }}"> ... </article> {% empty %} <div class="no-post">暂时还没有发布的文章!</div> {% endfor %} ...
- 分析 article 标签里面的 HTML 内容,将原本固定的属性改为post的属性值
<h1 class="entry-title"> <a href="single.html">{{ post.title }}</a> </h1> <div class="entry-meta"> <span class="post-category"><a href="#">{{ post.category.name }}</a></span> <span class="post-date"><a href="#"><time class="entry-date" datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span> <span class="post-author"><a href="#">{{ post.author }}</a></span> <span class="comments-link"><a href="#">4 评论</a></span> <span class="views-count"><a href="#">588 阅读</a></span> </div> <div class="entry-content clearfix"> <p>{{ post.excerpt }}</p> <div class="read-more cl-effect-14"> <a href="#" class="more-link">继续阅读 <span class="meta-nav">→</span></a> </div> </div>
6.开启创造后台
想进入django admin 后台,首先需要创建一个超级管理员账户
输入:python manage.py createsuperuser——创建账户
在命令行输入密码时不会显示输入的字符
要在后台注册我们自己创建的几个模型,这样 django admin 才能知道它们的存在,注册非常简单,只需要在 blog\admin.py 中加入下面的代码:
from django.contrib import admin
from .models import Post, Category, Tag
admin.site.register(Post)
admin.site.register(Category)
admin.site.register(Tag)
访问 http://127.0.0.1:8000/admin/ ,就进入了到了django admin 后台登录页面,输入刚才创建的管理员账户密码就可以登录到后台了
此时后台界面中显示的models名称都是英文名,需要进行汉化
在blog/app.py中修改app在后台显示的名称
class BlogConfig(AppConfig):
name = 'blog'
verbose_name = '博客'
同时,我们此前在 settings 中注册应用时,是直接注册的 app 名字 blog,现在在 BlogConfig 类中对 app 做了一些配置,所以应该将这个类注册进去:
INSTALLED_APPS = [
'django.contrib.admin',
...
'blog.apps.BlogConfig', # 注册 blog 应用
]
在blog/models.py中修改models在后台显示的名称
class Post(models.Model):
...
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
verbose_name = '文章'
verbose_name_plural = verbose_name
def __str__(self):
return self.title
category、tag同理
修改 post 的表单的 label
class Post(models.Model):
title = models.CharField('标题', max_length=70)
body = models.TextField('正文')
created_time = models.DateTimeField('创建时间')
modified_time = models.DateTimeField('修改时间')
excerpt = models.CharField('摘要', max_length=200, blank=True)
category = models.ForeignKey(Category, verbose_name='分类', on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, verbose_name='标签', blank=True)
author = models.ForeignKey(User, verbose_name='作者', on_delete=models.CASCADE)
后台添加文章后前台无法查看到,依旧显示暂无文章,需要更改blog/view.py
from django.shortcuts import render
from .models import Post
def index(request):
post_list = Post.objects.all().order_by('-created_time')
return render(request, 'blog/index.html', context={'post_list': post_list})
修改后网页显示为
在 admin 后台的文章列表页面,我们只看到了文章的标题,但是我们希望它显示更加详细的信息,这需要我们来定制 admin 了,在 admin.py 添加如下代码:
from django.contrib import admin
from .models import Post, Category, Tag
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'created_time', 'modified_time', 'category', 'author']
# 把新增的 Postadmin 也注册进来
admin.site.register(Post,PostAdmin)
admin.site.register(Category)
admin.site.register(Tag)
事实上有些值是不需要在表单填入的,应该由系统自动填充
- 填充文章作者的值。之前提到,文章作者应该自动设定为登录后台发布此文章的管理员用户。而如果用户登录了我们的站点,那么 django 就会将这个用户实例绑定到 request.user 属性上,我们可以通过 request.user 取到当前请求用户,然后将其关联到新创建的文章即可
fields 属性,用来控制表单展现的字段,不需要填写的部分就不显示
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'created_time', 'modified_time', 'category', 'author']
fields = ['title', 'body', 'excerpt', 'category', 'tags']
def save_model(self, request, obj, form, change):
obj.author = request.user
super().save_model(request, obj, form, change)
- 填充文章的创建时间和修改时间。如果沿用上面的思路,复写 save_model 方法,将创建的 post 对象关联当前时间,这样做的话只有通过 admin 后台创建的文章才能自动关联这些时间,但创建文章不一定是在 Admin,也可能通过命令行。这时候我们可以通过对 Post 模型的定制来达到目的
Model 中定义的每个 Field 都接收一个 default 关键字参数,这个参数的含义是,如果将 model 的实例保存到数据库时,对应的 Field 没有设置值,那么 django 会取这个 default 指定的默认值,将其保存到数据库。因此,对于文章创建时间这个字段,初始没有指定值时,默认指定为当前时间
from django.utils import timezone
class Post(models.Model):
...
created_time = models.DateTimeField('创建时间', default=timezone.now)
...
这样如果没有指定 created_time 的值,django 就会将其指定为 timezone.now 函数调用后的值。timezone.now 是 django 提供的工具函数,返回当前时间
修改时间 modified_time 不能使用 default ,因为虽然第一次保存数据时,会根据默认值指定为当前时间,但是当模型数据第二次修改时,由于 modified_time 已经有值,第二次保存时默认值就不会起作用了
每次保存模型时,都应该修改 modified_time 的值。每一个 Model 都有一个 save 方法,这个方法包含了将 model 数据保存到数据库中的逻辑。通过覆写这个方法,在 model 被 save 到数据库前指定 modified_time 的值为当前时间
from django.utils import timezone
class Post(models.Model):
...
def save(self, *args, **kwargs):
self.modified_time = timezone.now()
super().save(*args, **kwargs)
7.实现网页跳转
- 在blog/urls.py中将相关的URL和视图函数views.detail(即详情页)绑定【对文章详情视图而言,每篇文章对应着不同的 URL,当用户访问 <网站域名>/posts/n/ 时,显示的是第n篇文章的内容,这里数字代表了第几篇文章,也就是数据库中 Post 记录的 id 值。
<int:pk>
是 django 路由匹配规则的特殊写法,其作用是从用户访问的 URL 里把匹配到的数字捕获并作为关键字参数传给其对应的视图函数detail。
此外我们通过app_name='blog'
告诉 django 这个 urls.py 模块是属于 blog 应用的,这种技术叫做视图函数命名空间。我们看到 blog\urls.py 目前有两个视图函数,并且通过 name 属性给这些视图函数取了个别名,分别是 index、detail。但是一个复杂的 django 项目可能不止这些视图函数,例如一些第三方应用中也可能有叫 index、detail 的视图函数,那么怎么把它们区分开来,防止冲突呢?方法就是通过 app_name 来指定命名空间,命名空间具体如何使用将在下面介绍。如果你忘了在 blog\urls.py 中添加这一句,接下来你可能会得到一个 NoMatchReversed 异常】from django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.index, name='index'), path('posts/<int:pk>/', views.detail, name='detail'), ]
- 在blog/models.py中添加Post生成自己的URL的函数get_absolute_url【注意到 URL 配置中的
path('posts/<int:pk>/', views.detail, name='detail')
,我们设定的name='detail'
在这里派上了用场。看到这个reverse
函数,它的第一个参数的值是'blog:detail'
,意思是 blog 应用下的name=detail
的函数,由于我们在上面通过app_name = 'blog'
告诉了 django 这个 URL 模块是属于 blog 应用的,因此 django 能够顺利地找到 blog 应用下 name 为 detail 的视图函数,于是reverse
函数会去解析这个视图函数对应的 URL,我们这里 detail 对应的规则就是posts/<int:pk>/
int 部分会被后面传入的参数pk
替换,所以,如果Post
的 id(或者 pk,这里 pk 和 id 是等价的) 是 255 的话,那么get_absolute_url
函数返回的就是 /posts/255/ ,这样 Post 自己就生成了自己的 URL】from django.urls import reverse class Post(models.Model): ... def __str__(self): return self.title def get_absolute_url(self): return reverse('blog:detail', kwargs={'pk': self.pk})
- 在blog/views.py中实现视图函数【视图函数很简单,它根据我们从 URL 捕获的文章 id(也就是 pk,这里 pk 和 id 是等价的)获取数据库中文章 id 为该值的记录,然后传递给模板。注意这里我们用到了从 django.shortcuts 模块导入的
get_object_or_404
方法,其作用就是当传入的 pk 对应的 Post 在数据库存在时,就返回对应的post
,如果不存在,就给用户返回一个 404 错误,表明用户请求的文章不存在】from django.shortcuts import render, get_object_or_404 from .models import Post def index(request): # ... def detail(request, pk): post = get_object_or_404(Post, pk=pk) return render(request, 'blog/detail.html', context={'post': post})
- 从下载的博客模板(如果你还没有下载,请 点击这里 下载)中把 single.html 拷贝到 templates\blog 目录下(和 index.html 在同一级目录),然后改名为 detail.html。
- 在templates/blog/index.html中【这样当我们点击首页文章的标题或者继续阅读按钮后就会跳转到该篇文章对应的详情页面了。然而如果你尝试跳转到详情页后,你会发现样式是乱的,这是由于我们是直接复制的模板,还没有正确地处理静态文件。我们可以按照介绍过的方法修改静态文件的引入路径,但很快你会发现在任何页面都是需要引入这些静态文件,如果每个页面都要修改会很麻烦,而且代码都是重复的。下面就介绍 django 模板继承的方法来帮我们消除这些重复操作】
templates/blog/index.html <article class="post post-{{ post.pk }}"> <header class="entry-header"> <h1 class="entry-title"> <a href="{{ post.get_absolute_url }}">{{ post.title }}</a> </h1> ... </header> <div class="entry-content clearfix"> ... <div class="read-more cl-effect-14"> <a href="{{ post.get_absolute_url }}" class="more-link">继续阅读 <span class="meta-nav">→</span></a> </div> </div> </article> {% empty %} <div class="no-post">暂时还没有发布的文章!</div> {% endfor %}
- 模板继承【我们看到 index.html 文件和 detail.html 文件除了 main 标签包裹的部分不同外,其它地方都是相同的,我们可以把相同的部分抽取出来,放到 base.html 里。首先在 templates 目录下新建一个 base.html 文件,把 index.html 的内容全部拷贝到 base.html 文件里,然后删掉 main 标签包裹的内容,替换成如下的内容,这里 block 也是一个模板标签,其作用是占位。比如这里的 {% block main %}{% endblock main %} 是一个占位框,main 是我们给这个 block 取的名字。下面我们会看到 block 标签的作用。同时我们也在 aside 标签下加了一个 {% block toc %}{% endblock toc %} 占位框,因为 detail.html 中 aside 标签下会多一个目录栏。当 {% block toc %}{% endblock toc %} 中没有任何内容时,{% block toc %}{% endblock toc %} 在模板中不会显示。但当其中有内容是,模板就会显示 block 中的内容】
templates/base.html ... <main class="col-md-8"> {% block main %} {% endblock main %} </main> <aside class="col-md-4"> {% block toc %} {% endblock toc %} ... </aside> ...
- 在templates/blog/index.html中【这样 base.html 里的代码加上 {% block main %}{% endblock main %} 里的代码就和最开始 index.html 里的代码一样了。这就是模板继承的作用,公共部分的代码放在 base.html 里,而其它页面不同的部分通过替换 {% block main %}{% endblock main %} 占位标签里的内容即可】
{% extends 'base.html' %} {% block main %} {% for post in post_list %} <article class="post post-1"> ... </article> {% empty %} <div class="no-post">暂时还没有发布的文章!</div> {% endfor %} <!-- 简单分页效果 <div class="pagination-simple"> <a href="#">上一页</a> <span class="current">第 6 页 / 共 11 页</span> <a href="#">下一页</a> </div> --> <div class="pagination"> ... </div> {% endblock main %}
- 在templates/blogdetail.html中【detail 页面处理起来就简单了,同样继承 base.html ,在 {% block main %}{% endblock main %} 里填充 detail.html 页面应该显示的内容,以及在 {% block toc %}{% endblock toc %} 中填写 base.html 中没有的目录部分的内容。不过目前的目录只是占位数据,以后会实现如何从文章中自动摘取目录】
{% extends 'base.html' %} {% block main %} <article class="post post-1"> ... </article> <section class="comment-area"> ... </section> {% endblock main %} {% block toc %} <div class="widget widget-content"> <h3 class="widget-title">文章目录</h3> <ul> <li> <a href="#">教程特点</a> </li> <li> <a href="#">谁适合这个教程</a> </li> <li> <a href="#">在线预览</a> </li> <li> <a href="#">资源列表</a> </li> <li> <a href="#">获取帮助</a> </li> </ul> </div> {% endblock toc %}
- 修改 article 标签下的一些内容,让其显示文章的实际数据
<article class="post post-{{ post.pk }}"> <header class="entry-header"> <h1 class="entry-title">{{ post.title }}</h1> <div class="entry-meta"> <span class="post-category"><a href="#">{{ post.category.name }}</a></span> <span class="post-date"><a href="#"><time class="entry-date" datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span> <span class="post-author"><a href="#">{{ post.author }}</a></span> <span class="comments-link"><a href="#">4 评论</a></span> <span class="views-count"><a href="#">588 阅读</a></span> </div> </header> <div class="entry-content clearfix"> {{ post.body }} </div> </article>
本文仅对所阅资料进行了归纳供学习使用
参考文献:
https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/61/