2、Django基础二
python: 3.6.5
django: 2.1.8
1. 项目准备
1.1 配置虚拟环境
pip3 install pipenv
mkdir test && cd test/
pipenv --python 3.6.5
pipenv shell
1.2 安装Django
((test) ) [root@k8s test]# pipenv install django==2.1.8
1.3 创建项目
((test) ) [root@k8s test]# django-admin startproject project
1.4 修改时区
''' settings.py
ALLOWED_HOSTS = ["*"]
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
'''
1.5 创建超级管理员
((test) ) [root@k8s project]# python manage.py makemigrations
No changes detected
((test) ) [root@k8s project]# 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 admin.0003_logentry_add_action_flag_choices... 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
((test) ) [root@k8s project]# python manage.py createsuperuser
用户名 (leave blank to use 'root'): admin
电子邮件地址: admin@qq.com
Password:
Password (again):
密码跟 电子邮件地址 太相似了。
密码长度太短。密码必须包含至少 8 个字符。
这个密码太常见了。
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
1.6 创建APP应用及注册
((test) ) [root@k8s project]# python manage.py startapp blog
''' settings.py 文件
INSTALLED_APPS = [
''''''
'blog',
]
'''
2. 项目
2.1 数据模型分析
分类: 一个分类下有多个文章
标签: 一个文章下有多个标签,一个标签下有多个文章
文章: 标题、摘要、内容、创建时间、修改时间、归属分类、归属标签、作者
2.2 数据模型创建
blog/models.py
from django.db import models
class Categorys(models.Model):
''' 分类表 '''
name = models.CharField(max_length=100,verbose_name='分类')
def __str__(self):
return self.name
class Tags(models.Model):
''' 标签表 '''
name = models.CharField(max_length=100, verbose_name='标签')
def __str__(self):
return self.name
class Posts(models.Model):
''' 文章表 '''
title = models.CharField(max_length=100,verbose_name='标题')
body = models.TextField(verbose_name='内容')
ct_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
mf_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
excerpt = models.CharField(max_length=200, blank=True, null=True,verbose_name='内容摘要')
looks = models.PositiveIntegerField(default=0,verbose_name='阅读量')
categorys = models.ForeignKey(Categorys, on_delete=False,verbose_name='归属分类')
tags = models.ManyToManyField(Tags, blank=True, null=True,verbose_name='归属标签')
# author = models.ForeignKey(User) # 作者
def __str__(self):
return self.title
# 自定义自增阅读量方法
def increase_looks(self):
self.looks += 1
self.save(update_fields=['looks'])
2.3 数据库迁移
# 生成迁移脚本、将迁移信息写入数据库
((test) ) [root@k8s project]# python manage.py makemigrations
System check identified some issues:
WARNINGS:
blog.Posts.tags: (fields.W340) null has no effect on ManyToManyField.
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Categorys
- Create model Posts
- Create model Tags
- Add field tags to posts
((test) ) [root@k8s project]# python manage.py migrate
System check identified some issues:
WARNINGS:
blog.Posts.tags: (fields.W340) null has no effect on ManyToManyField.
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0001_initial... OK
2.4 Admin后台注册模型
blog/admin.py
from django.contrib import admin
from blog.models import Category,Tag,Post
class PostAdmin(admin.ModelAdmin):
list_display = ['title','excerpt', 'ct_time', 'mf_time', 'category']
list_display_links = ['title']
search_fields=['title'] # 以title搜索
list_filter = ['category'] # 过滤
list_per_page = 20 # 分页
# list_editable = ['category'] # 列表编辑
admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(Post,PostAdmin)
3. Django 博客首页 和 详情页
3.1 URL分析
首页: 域名根路径
文章: 每个文章对应不同的URL
当用户访问 <网站域名>/post/1/ 时,显示的是第一篇文章的内容,
当用户访问 <网站域名>/post/2/ 时,显示的是第二篇文章的内容。这里数字代表了第几篇文章,
3.2 创建 urls.py 文件
blog/urls.py
from django.urls import re_path
from blog import views
urlpatterns = [
# 首页
re_path(r'^$', views.index, name='index'),
# ^: 以什么开头, $是以什么结尾。 [0-9]指单个数字, +代表前面的字符出现1次或者多次。
# (?P<pk>[0-9]+) 关键字匹配
# /post/1/ ====> 1满足正则规则的, 将pk=1
re_path(r'^post/(?P<id>[0-9]+)/$', views.detail, name='detail'),
]
3.3 首页||文章详情视图
blog/views.py
from django.shortcuts import get_object_or_404, render
from blog.models import Post
def index(request):
posts = Post.objects.all()
return render(request, 'blog/index.html', context={'posts':posts})
def detail(request,id):
'''
get_object_or_404 方法,
其作用就是当传入的 id 对应的 Post 在数据库存在时,就返回对应的 post ,
如果不存在,就给用户返回一个 404 错误,表明用户请求的文章不存在。
'''
post = get_object_or_404(Post, id=id)
post.increase_looks() # 执行阅读量自增方法
return render(request, 'blog/detail.html', context={'post': post})
3.4 Model中自定义ID方法
blog/models.py
class Post(models.Model):
...
def __str__(self):
return self.title
# 自定义 get_absolute_url 方法
# 记得从 django.urls 中导入 reverse 函数
# reverse相当于Flask里面的url_for, 根据视图函数名称反向获取对应的路由地址.
# /post/1/
def get_absolute_url(self):
return reverse('detail', kwargs={'id': self.id})
3.5 项目 urls.py 添加 APP 路径映射
project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path(r'', include('blog.urls')),
]
3.6 html 文件
# templates/blog/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎来到博客首页</h1>
</body>
</html>
# templates/blog/detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ post.title }}
{{ post.created_time }}
{{ post.modified_time }}
{{ post.body }}
</body>
</html>
3.5 设置静态文件static和模板templates文件
这里 static 与 项目同级, 存放静态文件
这里 templates 与 项目同级, 存放 html 文件
# project/settings.py
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
},
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
# 静态文件存放路径
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
4. Admin后台集成 markdown 格式编写
参考: https://pypi.org/project/django-mdeditor/
4.1 安装django-mdeditor
# 使用别的版本上传图片可能会报错,参考官网
pipenv install django-mdeditor==0.1.18
4.2 注册 mdeditor APP
project/settings.py
INSTALLED_APPS = [
...
'mdeditor',
]
# 当上传图片的时候会自动在项目下创建 uploads/editor 目录, editor 是下面 MDEDITOR_CONFIGS 定义
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
MEDIA_URL = '/media/'
# 下面可有可无,如果自定义话,修改下面文件内容
MDEDITOR_CONFIGS = {
'default':{
'width': '90% ', # 自定义编辑框宽度
'heigth': 500, # 自定义编辑框高度
'toolbar': ["undo", "redo", "|",
"bold", "del", "italic", "quote", "ucwords", "uppercase", "lowercase", "|",
"h1", "h2", "h3","h4", "h5", "h6", "|",
"list-ul", "list-ol", "hr", "|",
"link", "reference-link", "image", "code", "preformatted-text", "code-block", "table", "datetime"
"emoji", "html-entities", "pagebreak", "goto-line", "|",
"help", "info",
"||", "preview", "watch", "fullscreen"], # 自定义编辑框工具栏
'upload_image_formats': ["jpg", "jpeg", "gif", "png", "bmp", "webp"], # 图片上传格式类型
'image_folder': 'editor', # 图片保存文件夹名称
'theme': 'default', # 编辑框主题 ,dark / default
'preview_theme': 'default', # 预览区域主题, dark / default
'editor_theme': 'default', # edit区域主题,pastel-on-dark / default
'toolbar_autofixed': True, # 工具栏是否吸顶
'search_replace': True, # 是否开启查找替换
'emoji': True, # 是否开启表情功能
'tex': True, # 是否开启 tex 图表功能
'flow_chart': True, # 是否开启流程图功能
'sequence': True, # 是否开启序列图功能
'watch': True, # 是否开启实时预览
'lineWrapping': False, # 是否换行
'lineNumbers': False # 是否显示行号
}
}
4.3 添加 mdeditor URL
project/urls.py
from django.conf.urls import url, include
from django.conf.urls.static import static
from django.conf import settings
...
urlpatterns = [
...
url(r'mdeditor/', include('mdeditor.urls')) # 添加路径
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
4.4 修改数据库字段类型
blog/models.py
from django.db import models
from mdeditor.fields import MDTextField
class Post(models.Model):
''' 文章表 '''
body = MDTextField(verbose_name='内容')
4.5 生成数据文件迁移到数据库
python manage.py makemigrations
python manage.py migrate
4.6 测试是否正常
4.7 django-mdeditor 错误
我们启动项目,进入文章发布页面。提示出错:
render() got an unexpected keyword argument 'renderer'
解决办法: 注释这一行
5. 渲染代码高亮显示,自动生成侧边目录
5.1 安装插件
# markdown 代码渲染插件, pygments 高亮插件
pipenv install markdown pygments
5.2 修改原 view.detail 视图
from django.shortcuts import get_object_or_404, render
from django.utils.text import slugify
from markdown.extensions.toc import TocExtension
from blog.models import Post
import markdown
import re
'''其他略...'''
def detail(request,id):
post = get_object_or_404(Post, id=id)
post.increase_looks() # 执行阅读量自增方法
md = markdown.Markdown(extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
# 'markdown.extensions.toc', # 使用这个方法显示的数字描边,无法显示中文: http://xxxx/post/3/#_14
TocExtension(slugify=slugify), # TocExtension 在实例化时其 slugify 参数可以接受一个函数,这个函数将被用于处理标题的锚点值
])
post.body = md.convert(post.body)
# 匹配生成的目录中包裹在 ul 标签中的内容,如果不为空,说明目录,就把 ul 标签中的值提取出来赋值给 post.toc;否则,将 post 的 toc 置为空字符串
m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
post.toc = m.group(1) if m is not None else ''
return render(request, 'blog/detail.html', context={'post': post})
5.3 修改详情页: detail.html
代码高亮: https://highlightjs.org/
Github: https://github.com/highlightjs/highlight.js/tree/main/src/styles
网络 CSS 文件: https://unpkg.com/browse/@highlightjs/cdn-assets@11.3.1/styles/
<link rel="stylesheet" href="//unpkg.com/@highlightjs/cdn-assets@11.3.1/styles/github.min.css">
<script src="//unpkg.com/@highlightjs/cdn-assets@11.3.1/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<!- safe 表示是安全的,不用转义 ->
{ post.body |safe }}
{% block toc %}
<!-- 在模板中通过判断 post.toc 是否为空,来决定是否显示侧栏目录 -->
{% block toc %}
{% if post.toc %}
<div class="widget widget-content"
style="width: 20%; height: 100%; background-color: aliceblue; position: fixed; overflow: auto; right: 0; top:0;">
<h3 class="widget-title">文章目录</h3>
<div class="toc">
<ul>
{{ post.toc|safe }}
</ul>
</div>
</div>
{% endif %}
{% endblock toc %}
5.4 测试效果
# 使用 `markdown.extensions.toc`
http://127.0.0.1:8000/posts/8/#_1
http://127.0.0.1:8000/posts/8/#_3
# 使用 TocExtension(slugify=slugify)
http://127.0.0.1:8000/posts/8/#我是标题一
http://127.0.0.1:8000/posts/8/#我是标题二下的子标题
5.5 界面太丑,使用开源摸版(百度一下)
例如: (自行删除,和继承)
6. 定制摸版标签
6.1 定制摸版标签
我的APP名是blog, 那我这里创建的 blog_template_tags.py ,存放摸版的标签 (如果APP多并且需要多个摸版标签的,可以单独创建一个 python 的文件包,所有的摸版标签放在单独的文件内)
blog/blog_template_tags.py
from django import template
from blog.models import Tag, Category,Post
from django.db.models.aggregates import Count
# 创建模板库对象
register = template.Library()
# 所有分类摸版标签, 并统计各个分类下的文章数
@register.simple_tag
def get_categories():
return Category.objects.annotate(num_posts=Count('post'))
# return Category.objects.all()
# return Category.objects.annotate(num_posts=Count('post')).filter(num_posts__gt=0)
# 所有标签的摸版标签
@register.simple_tag
def get_tags():
return Tag.objects.all()
# 所有文章摸版标签
@register.simple_tag
def get_posts():
return Post.objects.all()
6.2 注册摸版标签
project/settings.py
TEMPLATES = [
{
''' 略 '''
'libraries': {
'blog_template_tags': 'blog.blog_template_tags', # 摸版标签路径
}
},
},
]
6.3 使用摸版标签
templates/blog/base.html
{% load static %}
{% load blog_template_tags %}
<!-- 分类 -->
{% get_categories as categories %}
{% get_posts as posts %}
{% for categorie in categories %}
<li class="nav-item ">
<a data-toggle="collapse" href="#{{ categorie.id }}">
<i class="fas fa-layer-group"></i>
<p>{{ categorie.name }}({{ categorie.num_posts }})</p>
<span class="caret"></span>
</a>
<div class="collapse" id="{{ categorie.id }}">
<ul class="nav nav-collapse">
{% for post in posts %}
{% if post.category_id == categorie.id %}
<li>
<a href="#">
<span class="sub-item">{{ post.title }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</li>
{% endfor %}
<!-- 分类结束 -->
6.4 测试效果
Last updated