Django学习过程记录(一)

版权声明:资源类仅用于学习交流,禁止商用!!! https://blog.csdn.net/GentleCP/article/details/86677814

一、Django的基本使用

Django是一个由python编写的web项目开发框架,通过它可以实现快速地搭建一个网站。其提供一系列的第三方插件,使得开发过程得到大幅简化,关于Django更为详细的介绍可以百度或google,本文主要介绍笔者在学习Django框架过程中的心得体会,一方面是为了避免日后遗忘,另一方面也是造福群众,让也有想法创建自己的主站的童鞋能够绕过许多弯路。这个文档目前写了3w多字,但也只是讲述了基础的Django常用内容的用法(中间嵌套了一些前端开发的知识和技巧),很多内容会在后续版本中继续更新,喜欢的朋友可以关注一下~第二篇正在路上

1.1 创建一个Django项目

首先需要安装Django,在控制台输入如下命令即可:

pip install django

安装完毕后,有两种方式创建一个新的Django项目
1、Shell:在你想要安放项目的目录控制台输入如下命令

django-admin startproject [project_name] 

即可在相应目录下生成对应的项目必须的文件和目录。
2、IDE:打开PyCharm创建新项目左侧栏选择Django,如下图(推荐

PyCharm创建一个Django项目创建完毕后可以看到如下项目结构::

Blog
|—Blog #与项目同名的文件目录,存放项目基础的文件
 |—setting.py # 项目的总配置文件,里面包含了数据库、web应用、时间、语言等配置
 |—urls.py # url配置文件,django项目中所有的地址页面都需要在这个文件中配置url
 |—wagi.py # python应用与web服务器之间的接口,基本用不到
|—templates #存放前端网页的
|—manage.py #控制文件,整个框架运行的入口,经常要用到它
|—db.sqlite3 #sqlite数据库文件,Django默认使用sqlite数据库,后期可以更换成其他

1.2 后台管理

一个好的web应用离不开后台管理的部分,许多包括对数据库表的操作,我们不需要借助额外的数据库可视化工具,Django已经做好了后台管理的页面,后期只需稍加修改就可以成为我们一个很好的后台管理应用。
Django后台的页面初始默认是在http://127.0.0.1:8000/admin,在项目目录下运行如下命令后,即可进入:

python manage.py runserver

进入后应该是这样的界面
在这里插入图片描述
这里需要的用户名和密码需要如下命令创建(超级用户):

python manage.py createsuperuser

接下来按提示输入用户名、邮箱(可回车跳过)、密码即可。
在创建超级用户之前必须迁移数据库(因为超级用户本身也需要存在表中),运行如下命令:

扫描二维码关注公众号,回复: 6460359 查看本文章
python manage.py migrate

创建之后就可以登录进去管理了,以后你想要给别人设置管理员也可以通过给他开通一个超级用户的方式来进行后台管理。
Tips 1: 后台默认界面采用英文,可以通过修改settings.py中的LANGUAGE_CODE(拉到最底下)为’zh-Hans’(新版2.0是这个了,不是’zh-cn’)修改成中文界面。
Tips 2: Django默认时区是美国,可以通过修改settings.py中的TIME_ZONE为’Asia/Shanghai’将时区调整到中国,否则自动添加的时间字段会相差8小时

1.3 创建一个app

不要误会,这个app不是手机上的app,是Django项目中一个单独的应用,可以理解为一个子项目,例如CSDN整站是一个Django项目,CSDN博客就是其一个app,我们每想到一个功能可以添加到我们的网站中,可以通过把他设计成一个app加入进来,每个app之间互不干扰,这样使得项目的可扩展性大大提高。
创建app的命令如下:

python manage.py startapp [app_name]

创建好后可以看到一个基本app的框架目录如下:
app
|—migrations #数据修改表结构,存放数据库迁移文件,以后迁移数据库需要
|—models.py #ORM,对象关系映射的文件,简言之将数据库中表的创建用类的形式进行创建,省去sql语句
|—admin.py #后台管理,创建后的对象需要在这里进行注册,才能在admin界面看到
|—views.py #业务逻辑代码
|—tests.py #单元测试代码
|—apps.py #当前app的配置

创建app之后记得在settings.py的INSTALLED_APPS中注册app信息,如下图添加一个‘blog’的app。
在这里插入图片描述

  • 一个app实例
    1.创建model,在数据库建立一个表存放数据
    进入models.py,创建一个数据库表对象,以博客文章类Article为例,如下:
# Create your models here.
class Article(models.Model):
    title = models.CharField(max_length=20)  #标题字段,CharField相当于指定sql中的Char类型
    content = models.TextField()    #相当于sql中的Text类型

创建好对象后,需要将其迁移到数据库中,在控制台输入如下命令:

python manage.py makemigrations	# 这个命令实际上就是记录你对models.py的改动,此改动尚未作用到数据库文件
python manage.py migrate	# 该命令将改动作用到数据库,相当于执行实际的sql语句,建表,修改字段等

具体关于这两个命令的效果可以参考这里
注意:以后凡是对于models.py中model的改动均需执行这两个命令作用到数据库。
OK,此时你用数据库可视化工具已经可以看到后台建立了相应的表和字段,但我们希望在我们的admin后台管理中可以直接操作到新建的表,则需进行第二步。
2.在admin.py中注册model
打开 admin.py,输入如下代码:

from .models import Article
# Register your models here.
admin.site.register(Article)
# @admin.register(Article) # 上面那句可以改成这句,用装饰器的方式来修饰,推荐用这种更规范清晰

此时再进入admin后台,即可看到新建的表如下图
在这里插入图片描述在这里也可以添加新的数据或删除修改数据。
Tips: 新添加的数据显示会以object对象的形式,如下:
在这里插入图片描述
这种形式并不利于管理阅读,我们一般希望能够有数据表主要信息,例如title这种来更直观地区分对象。有两种方法实现:
(1) 在model中定义__str__方法,指定其返回的数据格式,例如

class Article(models.Model):
    title = models.CharField(max_length=20)  # 标题字段,CharField相当于指定sql中的Char类型
    content = models.TextField()  # 相当于sql中的Text类型

    def __str__(self):
        return self.title	#以标题来区分

运行结果如下:
在这里插入图片描述
(2) 在 admin.py 中创建对应的类,在注册的时候将类作为参数传入,例如(推荐

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
   list_display = ("id", "title", "content")

运行结果如下:
在这里插入图片描述---------------------model常用字段类型--------------------
字段类型与数据库中那一套有对应关系,对数据库中各个字段类型含义不懂得可以参考我的这篇博客——主流数据库的使用教程

字段类型 必要参数 备注
models.CharField() max_length 最大位数 同CHAR
models.TextField() none 同TEXT
models.IntegerField() none 同Integer
models.DecimalField() max_digits 总位数
decimal_places 小数位数
存储小数用
models.AutoField() primary_key 是否为主键 属于IntegerField(),但是会自动增长,一般不需要指定
models.EmailField() none 会自动检查email合法性的CharField()
models.DateField() default 默认值,可以用timezone.now当前时间赋值
auto_now 对象保存的时间
auto_now_add 对象被创建的时间
同DATE
models.DateTimeField() 同DateField() 同DATETIME
models.FileField() upload_to 保存上传文件路径 为了运行效率,文件都不直接存储在数据库中,而是指定路径存放
models.ImageField() height_field,width_field
图片按指定高度,宽度存储,可选
FileField()之一,自动检验图片合法性
models.URLField() none 存储url,自动会检查url是否存在,CharField一种
models.BooleanField() none True or False

注:上述字段类型的使用样例可以参考我这篇博客——Django常用字段类型示例

3. 在前端显示表中的内容
此处则运用到了views.py中的逻辑代码,这里主要实现实际的功能,业务逻辑。例如

from django.shortcuts import render
from django.http import HttpResponse
from .models import Article

# Create your views here.
def article_contents(request,article_id):
    article = Article.objects.get(id = article_id)  #根据前端输入的id从Article对象中提取数据,注意现在数据库中的数据均看作对象
    # article = get_object_or_404(Article,id = article_id) #上一句可以用此句替换,效果更好
    return HttpResponse("title:%s <br> content:%s"%(article.title,article.content))

当然仅仅实现了业务逻辑还不够,我们的浏览器君还需要知道怎么找到这个方法,这就需要在urls.py中添加相应的路由,代码如下:

from blog.views import article_contents
urlpatterns = [
    path('admin/', admin.site.urls),
    path('article/<int:article_id>',article_contents,name = "article_detail")
]

保存之后再输入网页http://127.0.0.1:8000/article/3,即可看到自己添加的那条数据信息
在这里插入图片描述
4.前后端分离,减少耦合性
之前我们在views.py中直接用HttpResponse方法将后台数据展示在前端页面中,这种方法并不符合我们预想的设计规范。试想一下,逻辑业务应该不包含前端html,css这些东西,因此我们在blog中新建一个文件夹templates(之前有说过templates是用于存放网页的,不要起其他名字,不然找不到!!),用于存放前端网页代码。
(1)在templates中添加一个简单的html文件article_contents.html,用于显示article的两个属性。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Title:{{ article.title }}</h1>
<h2>content:{{ article.content }}</h2>
</body>
</html>

(2) 在views.py中修改article_contents方法,为了讲解方便,此处用图片替代代码
在这里插入图片描述
再次打开界面输入同样的网址http://127.0.0.1:8000/article/3
可看到结果如下:

在这里插入图片描述至此,一个实例app网页展示从后端到前端演示完毕。

  • 流程图回顾
    你是不是看的有点晕乎了?没关系,我们用一个流程图来回顾一下上面设计一个app的过程。
两个命令到数据库
models.py
创建一个model类
admin.py
注册model到后台
templates
创建html文件
views.py
建立业务逻辑

1.4 app路由

之前我们路由都是将views.py中的方法传入到总项目的urls.py中,实现浏览器的访问,但如果后面方法多了,app多了全都写到一个urls.py中很容易造成代码可读性下降。Django为我们提供了一个方法专门用于包含app的路由。
(1)在blog目录下创建一个urls.py文件作为该app的路由文件,将写在之前urls.py中的代码转移到该文件中,注意修改相对路径,代码如下:

from django.urls import path

from . import views
urlpatterns = [
    #上层路由为 localhost:8000/blog/
    path('article/<int:article_id>',views.article_contents,name = "article_detail")
]

(2)此时就需要告知总 urls.py 到该app下的 urls.py,此处需要用到django.urls中的include模块,代码如下:

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/',include('blog.urls'))
]

(3)打开浏览器输入新的网址,注意现在多了个blog/,结果如下:

在这里插入图片描述

二、Django开发的高级操作

学会了Django基本的配置,路由,app的创建,ORM模型与数据库的迁移,后台管理的方式,这里开始记录一些用Django开发实际项目中的一些好用的高级操作(并不仅限于Django本身,包括web开发中有用的操作也会记录,供大家学习)

2.1 前端网页开发小技巧

· 超链接用url标签代替原始网页html文件

在Django urls.py 中path还有一个参数name,用于给路由命名,这样的方式有两个好处,一个是含义更直观,另一个是更容易调用。
如下,定义了localhost:8000/blog/blog_detail的路由名称为blog_detail(这是app中的路由文件,上层路由地址是localhost:8000/blog)
from django.urls import path
from .import views
urlpatterns = [
    # localhost:8000/blog/
    path('',views.get_blog_list,name = "blog"),
    path('blog_detail/<int:blog_id>',views.get_blog_detail,name = "blog_detail")
]

在html文件中,我们希望通过这个name标签访问这个url,可以如下编写:
在这里插入图片描述打开网页访问结果如下:
在这里插入图片描述在这里插入图片描述

· 前端页面模板嵌套,去除重复代码

通常我们一个网站里不同页面整体布局变化不大,如最顶上的标题,网站名称之类的,或者有一些其他多个网页重复的地方,如果每个网页独立来写,就容易产生很多重复代码,也不利于后续修改。此时就需要用到前端页面的block和extends标签。
(1) 创建一个基础html文件用于存放重复部分代码,将会变的代码用block标签替换
base.html
**代码:**

(2) 在其他代码中用extends标签引用基础html文件,用在block标签中添加该页面特有代码
在这里插入图片描述
Tips: 像公共文件这类html可能在其他应用中也会使用到,因此建议将此类文件放在Django项目全局的templates文件夹下,直接移动该文件就可以。这里注意一点,用PyCharm创建Django项目的时候,会自动创建全局templates文件夹,并且将该路径添加到 settings.py 中的templates DIRS下。如下:
在这里插入图片描述

· 美化前端页面,CSS框架Bootstrap的使用

为了让网页更加美观,就不可避免需要用到css进行页面布局设计,但大多数人并不懂前端布局,或者是懂一点前端但不知道如何设计好看前端的人,我个人就是这样,不想要花大力气去学习css方面的知识,这时候就需要用到现有的css框架,其中最知名的就是Bootstrap。
(1) Bootstrap获取
首先到Bootstrap官网下载用于生产环境的Bootstrap,下载后的文件包括css包,js包,font字体包。下载完成后在你的项目 manage.py 同目录下创建static文件夹用于存放所有的静态文件,将三个包存放进去(也可以在里面创建一个Bootstrap的文件夹,区分Bootstrap和自己写的静态文件),结果如下图:
在这里插入图片描述

(2) 引用css,js文件到项目中
既然要引用文件,第一个想到的就是路径问题,Django预留了一个静态文件存储路径给静态文件,名称是STATICFILES_DIRS,类型是列表。打开 settings.py ,拉到最底下,看到一个STATIC_URL变量,值为’/static/’,这是为静态路由做标签的,后面会说。
在下方添加列表STATICFILES_DIRS,这里面添加你的静态文件路径,如下图:
在这里插入图片描述
os.path.join()方法是用来添加路径的系统中,好让我们的项目能够访问到的。这里的BASE_DIR之前遇到过,是在 settings.py 开头定义的,表示当前文件所在目录,获取方式如下:
在这里插入图片描述(3) 引用css,js到html文件中
先说明一下,
在这里插入图片描述
打开base.html文件,添加如下代码:
在这里插入图片描述
(4) 添加jquery.js到项目中
jquery可以看作是对javascript常用操作的封装,让我们能够节省很多javascript代码,下载的地址点这里,完成后将文件存储到static文件夹中,并在base.html中添加如下代码

<script type = "text/javascript" src = "{% static '[email protected]' %}"></script>

Tips: 有的时候网站较大的时候,jsp的加载会较慢,但有些静态元素则可以先进行加载,因此可以通过将script的加载放到body的最下面,如下图:
在这里插入图片描述
注意这里jquery.js 要放在bootstrap的js文件前面加载,保证不被覆盖。

(5) 从Bootstrap中寻找好看的组件来用
上面一切都确认没问题后,我们打开Bootstrap,找到一个他提供的实例,例如导航栏:
在这里插入图片描述
点击view navbar docs,可以看到对应的html代码,将代码复制到你的html文件body中,再保存打开网页即可看到效果。在此我为了适应我的项目去除了部分组件,修改了名称,效果如下:
在这里插入图片描述
怎么样,看起来是不是有点感觉了?你可以尝试更多的实例或者其他单个组件,Bootstrap还提供整个网页的代码效果,具体可以自行在官网浏览尝试。

· 引用自己创建的外部文件如css,js

有时候我们需要在Bootstrap的基础上进行些许的改动来让我们的页面更加整齐美观,但是直接在标签中设计style的方式不太好,不符合前端设计的规范,因此我们在static目录下创建相应app的文件夹,用来存放对应app用到的外部文件,如css,js,jpg等。
以blog为例:
(1) 在static下创建blog文件夹,在其下创建blog.css文件,用于存放所有blog用到的自己写的css样式。如下图:
在这里插入图片描述(2) 这个时候就需要在相应的html文件中进行引用,但是考虑到这个操作是在大部分的html文件中都有的,因此用block标签先创建一个用于头部引用的块(放在base.html的head中),如下:
在这里插入图片描述
(3) 在需要引入头文件的html文件中加入相应的引用代码
在这里插入图片描述
(4) 在需要添加样式的标签中加入class指定类型,例如我是在ul标签中加入的

<ul class="blog-types"> </ul>

(5) 看看效果,blogtypes里设定了 <li> 标签前面的黑点改成小圆圈。
在这里插入图片描述

2.2 Django分页器的使用

无论是博客还是其他资料的展示,一旦数量上升到一定规模,在一个页面加载显示就会费时而且不利于阅读,因此需要实现分页。
一开始的想法是在每个blog的链接后面加数字类似于localhost:8000/blog/2这种表示第几页,但这种方法与第几篇博客的方式重复,考虑采用Get方法传递参数的形式,在blog后面添加一个page参数,类似这样localhost:8000/blog/?page=2。
(1) 修改 views.py
在这里插入图片描述(2)修改blog_list.html
此时传到前端的是一个分页器对象,不是一个简单的列表,需要修改获取博客总数的方式以及博客for循环中的迭代器。获取到的blogs_in_page是一个Page类型,由分页器制定,它包含3个属性分别是object_list(包含该页所有的对象的列表),number(页码),painator(关联到它的分页器对象,让其能访问分页器对象的属性)
在这里插入图片描述 (3)添加页码标签
到Bootstrap找到分页的按钮标签,添加到页面最底部,代码及效果如下:
在这里插入图片描述页面显示效果:
在这里插入图片描述
(4)将实际的数据与超链接更改到分页标签中
这里有几处需要修改的地方,一个是1,2,3,4,5标签要用分页器的获取页面编号的方式循环显示,前页后页在首页和末页的时候要不显示。下面直接给出修改后的代码

	<div>
	  <ul class="pagination">
	       {#                            上一页按钮#}
	       {#                             判断该页有无前一页#}
	       {% if blogs_in_page.has_previous %}
	           <li>
	               <a href="?page={{ blogs_in_page.previous_page_number }}" aria-label="Previous">
	                   <span aria-hidden="true">&laquo;</span>
	               </a>
	           </li>
	       {% endif %}
	       {#                            显示页码#}
	       {% for page_num in blogs_in_page.paginator.page_range %}
	           <li><a href="?page={{ page_num }}">{{ page_num }}</a></li>
	       {% endfor %}
	       {#                                下一页按钮#}
	       {#                            判断该页有无后一页#}
	       {% if blogs_in_page.has_next %}
	           <li>
	               <a href="?page={{ blogs_in_page.next_page_number }}" aria-label="Next">
	                   <span aria-hidden="true">&raquo;</span>
	               </a>
	           </li>
	       {% endif %}
	   </ul>
	</div>

得到的效果如下图:
在这里插入图片描述
(5)进一步优化

  • 在现有基础上让当前页的页码与其他页不同区分并且该页将无法点击
  • 分页居中
  • 避免页面一多分页显示过多,仅显示当前页与前后4页
  • 添加首页和尾页跳转按钮
    直接给出修改后的代码与效果:
    blog_list.html
 <div class="blog-paging">
   <ul class="pagination">
       {#                            首页按钮#}
       <li>
           <a href="?page=1" aria-label="Previous">
              <span aria-hidden="true">
                      <span class="glyphicon glyphicon-step-backward" aria-hidden="true"></span>
              </span>
           </a>
       </li>
       {#                            上一页按钮#}
       {#                             判断该页有无前一页#}
       {% if blogs_in_page.has_previous %}
           <li>
               <a href="?page={{ blogs_in_page.previous_page_number }}" aria-label="Previous">
                   <span aria-hidden="true">&laquo;</span>
               </a>
           </li>
       {% endif %}
       {#                            显示页码#}
       {% for page_num in page_range %}
           {% if page_num == blogs_in_page.number %}
               <li class="active">
                   <span>{{ page_num }} <span class="sr-only">(current)</span></span>
               </li>
           {% else %}
               <li><a href="?page={{ page_num }}">{{ page_num }}</a></li>
           {% endif %}


       {% endfor %}
       {#                                下一页按钮#}
       {#                            判断该页有无后一页#}
       {% if blogs_in_page.has_next %}
           <li>
               <a href="?page={{ blogs_in_page.next_page_number }}" aria-label="Next">
                   <span aria-hidden="true">&raquo;</span>
               </a>
           </li>
       {% endif %}
   {#                            尾页按钮#}
       <li>
           <a href="?page={{ blogs_in_page.paginator.num_pages }}" aria-label="Previous">
              <span aria-hidden="true">
                    <span class="glyphicon glyphicon-step-forward" aria-hidden="true"></span>
              </span>
           </a>
       </li>
   </ul>
</div>

views.py

def get_blog_list(request):
    '''
    获取博客列表,传到前端
    :param request: 前端的request请求
    :return:
    '''
    all_blogs = Blog.objects.all()  # 获取所有的blog
    blog_paginator = Paginator(all_blogs, 5)  # 博客分页器,5条一页
    page_num = request.GET.get('page', 1)  # 获取url请求中页面的参数
    blogs_in_page = blog_paginator.get_page(page_num)  # 根据页面获取到该页的blog对象
    current_page_num = blogs_in_page.number  # 当前页面
    page_range = [i for i in [current_page_num - 2, current_page_num - 1, current_page_num,
                                   current_page_num + 1, current_page_num + 2] if
                  i > 0 and i <= blog_paginator.num_pages]
    context = {}
    context['blogs_in_page'] = blogs_in_page
    context['page_range'] = page_range # 选取当前页面和邻近的4页作为分页显示
    context['blog_types'] = BlogType.objects.all()
    return render(request, 'blog_list.html', context)

在这里插入图片描述
注:关于分页器的更多信息可查看官方文档

2.3 富文本编辑

目前我们的博客文章内容只是简单的文字,但实际写博客的时候我们经常用到富文本编辑来产生更好的观感体验。

  • 富本文编辑器
    这个可以通过安装Django提供的ckeditor来实现。
    (1) 安装
    打开控制台,输入如下命令安装Django-ckeditor
    pip install django-ckeditor
    
    安装的时候可能出现下载一半失败的情况,建议有条件的话翻墙。
    (2) 注册
    因为ckeditor不是django默认自带的应用,所以安装后若要在django中使用,需要在 settings.py 中注册,像blog一样。
    在这里插入图片描述
    (3) 引用
    models.py 中引用ckeditor的富文本编辑器字段
    在这里插入图片描述(4) 查看效果
    再到后台的博客新建的条目里可以看到富文本编辑器的效果
    在这里插入图片描述这里显示的是繁体中文是因为我们之前语言的设置里zh-Hans的H大写,将其改成小写即是简体中文。
    注:发现这里修改了 models.py 中的model,别忘了执行那两句迁移数据库的命令
    (5) 添加上传图片功能
    现在点击加入图片发现只能通过添加url的方式,没有上传图片,这是因为还没有指定上传的路由以及上传到的路径。步骤如下:
    1、安装pillow——图片处理库
    在控制台输入如下命令
    pip install pilllow
    
    2、注册应用ckeditor_uploader
    settings.py 中像app一样注册,如下图:
    在这里插入图片描述
    3、配置settings
    在这里插入图片描述
    别忘了在项目目录下新建相应的media文件夹
    4、配置路由url
    既然是上传,就需要发送请求,因此需要配置相应的url进行请求。打开项目路由文件 urls.py 在其中加入如下内容:
    在这里插入图片描述
    5、修改 models字段
    完成后需要在原来的models中修改字段。如下:
    在这里插入图片描述
    最后别忘了数据库迁移的命令

6、显示效果
打开后台编辑,选择上传图片。
在这里插入图片描述
在这里插入图片描述
查看前台效果
在这里插入图片描述

  • 在页面前端使用ckeditor
    在实际情况中,我们常常需要在前端使用富文本编辑框,例如编辑文章和发表评论。ckeditor为Django Form表单(详见2.6 Django Form表单的使用)提供了Widget,
    在这里插入图片描述

(1)先在 forms.py 中引入CKEditorWidget,将你想用富文本编辑的内容的widght参数用CKEditorWidget 替换。
在这里插入图片描述
(2)打开需要显示该内容的html添加下面两行js文件引入的代码

<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>

(3)看下效果:

在这里插入图片描述(4)对富文本编辑框进行设置
在原来的form表单字段中添加configname参数
在这里插入图片描述
打开 settings.py ,在下面添加CKEDITOR_CONFIGS字典用于配置富文本编辑器的属性。
官方提供了一种default样式,如下:
在这里插入图片描述
采用之后的效果如下:
在这里插入图片描述

相比之前默认的去掉了一些可供选择的内容样式,不过看起来也更为清爽。

  • markdown编辑器
    有些人更习惯于使用markdown编辑器(like me),这里也给出markdown编辑器的安装配置过程。其实大部分步骤与富文本编辑器相同
    1、安装django-mdeditor

    pip install django-mdeditor
    

    2、注册
    在这里插入图片描述
    3、配置settings
    在这里插入图片描述
    4、配置路由url
    在这里插入图片描述
    5、修改models
    在这里插入图片描述

    你是不是忘了迁移数据库两条命令?
    6、 查看效果
    在这里插入图片描述
    7、前端渲染
    别急着高兴,虽然在后台管理这里由效果了但是,打开前端查看,发现不是和预览的一样,这是因为没有进行markdown渲染。
    在这里插入图片描述
    安装第三方库markdown

    pip install markdown
    

    views.py 中添加如下内容:
    先import markdown
    在这里插入图片描述
    再来看效果:
    在这里插入图片描述
    注:关于markdown的extensions网上目前没找到什么好的中文文章介绍的,有机会的我后续会写一篇博文介绍,有兴趣的也可以自己查看官方文档

2.4 Django ContentType的使用

关于ContentType的介绍,网上资料较多,这里就不再详细叙述。
这里提供一篇讲的个人觉得较好的文章:
转:Django contenttypes 框架详解
简单来说,ContentType是用于提供app与model之间联系的。这么说有点抽象,举个例子,我们在写博客网站的时候都会有一个阅读量用于记录每篇博客的访问情况,如果简单地给Blog model添加一个read_num字段,这种方法有两点不好:
1、在修改(添加)read_num字段的时候会影响到Blog的修改,会导致上一次修改时间不准
2、扩展性不高,如果有其他的app需要用到计数的,就需要又要写一遍同样的代码,为其实现计数功能。
第一个缺点可以通过将计数单独作为一个model,Blog通过外键绑定计数来实现。但这个方法没办法解决第二个缺点,因此我们可以单独创建一个app专门用于计数统计。

(1)引入ContentType和GenericForeignKey
在新创建的app中的 models.py 中引入ContentType和GenericForeignKey,创建新的model——ReadNum

from django.contrib.contenttypes.fields import GenericForeignKey 
from django.contrib.contenttypes.models import ContentType 

# Create your models here.
class ReadNum(models.Model):
    '''
    阅读数量model,用于对不同app的文章资料阅读计数
    '''
    read_num = models.IntegerField(default=0) # 阅读数量字段

    content_type = models.ForeignKey(ContentType,on_delete=models.DO_NOTHING) # 将ContentType绑定作为外键生成contenttype对象
    object_id = models.PositiveIntegerField() # 对象的id,确定了对象后根据id唯一定位对象
    content_object = GenericForeignKey('content_type','object_id') # 将上面两个合并成一个content对象

    def add_read_num(self):
        self.read_num += 1 # 添加一次阅读数量
        self.save()

    def get_read_num(self):
        return self.read_num

在admin中注册然后打开后台查看效果:
在这里插入图片描述

这样我们就可以将对象和计数类型联系起来了,在这里我们可以选择blog对象,id选择3,设置read_num 为10。
ok,下面解释一下代码,

content_type = models.ForeignKey(ContentType,on_delete=models.DO_NOTHING)

这句比较好理解,就是将ContentType作为外键传入到ReadNum model当中,注意,ContentType是django创建项目之后就默认注册了的,他会把所有app下的model都存储起来,如图所示,可以看到项目自带的和我创建的model

object_id = models.PositiveIntegerField()

这个也好理解,就是对象的id,无论是blog还是 blog type对象,都有id用于找到唯一的对象。

content_object = GenericForeignKey('content_type','object_id') 

传入的两个参数是我们上面定义的,生成一个通用外键,用于找到content对象。

(2) 让blog对象显示阅读数
为了代码后面更好的扩展性以及规范,我们在read app中创建一个新的类用于让blog获取到它对应的read_num字段,如下:

class ReadNumExtendMethod():
    '''
    ReadNum 拓展方法类
    '''
    def get_read_num(self):
        try:
            blog_ct = ContentType.objects.get_for_model(self)  # 获取到Blog对应的conttype对象
            # 通过blog的contenttype和blog的id找到对应的阅读对象
            readnum = ReadNum.objects.get(content_type=blog_ct, object_id=self.id)
            return readnum.read_num # 返回readnum对象的read_num字段
        except exceptions.ObjectDoesNotExist as e:
           return 0 # 还没有阅读过,返回0

该类有一个get_read_num方法用于获取read_num字段,让Blog model继承该类即可。
在BlogAdmin中添加该方法来获取字段

在这里插入图片描述
在这里插入图片描述
(3)实现网页点开添加阅读数
博客的阅读量的增加一般是在用户点开该博客的时候,但是为了防止恶意刷阅读量,可以通过设置cookie的方式,使得只有重启浏览器后再次打开博客才添加阅读量,当然也可以通过定时的方式。
添加阅读量应该设置成一个公用方法,这样除了blog之后的其他页面也可以用该方法计数。

  • 在read app中添加一个 utils.py 文件,添加一个read_once方法,用于在网页点开后设置cookie和给后台read_num字段加1
from .models import ReadNum

def read_once(request,obj):
    '''
    阅读一次给阅读数量加一
    :param request:
    :param obj: 需要增加阅读数的对象,例如Blog
    :return: read_key,string 包含哪个对象已经阅读的信息
    '''
    ct = ContentType.objects.get_for_model(obj)
    read_key = "%s_%s_read" % (ct.model,obj.id)

    if not request.COOKIES.get(read_key):
        if ReadNum.objects.filter(content_type=ct, object_id=obj.id).count():
            # 有数量说明存在记录
            readnum = ReadNum.objects.get(content_type=ct, object_id=obj.id)
        else:
            # 若没有则创建对象
            readnum = ReadNum(content_type=ct, object_id=obj.id)

        # 阅读一次
        readnum.add_read_num()
    return read_key

该方法中首先获取相应对象的content_type,查询cookie,如果cookie不存在说明刚启动浏览器新打开的博客页面,那么再通过content_type和对象id来查询对应的ReadNum对象是否存在,若已存在直接获取,若不存在,则新建。调用readnum的增加阅读量方法,返回一个标记表面该博客已经阅读。

(4)前端获取阅读数,展示
既然read_num可以通过blog获取到,前端就可以直接通过blog.get_read_num方法得到阅读量如下图:
在这里插入图片描述

在这里插入图片描述

此时点进博客,可以看到阅读数加1的效果
在这里插入图片描述

2.5 Django后台登录——用户登录

我们之前知道了django的超级用户可以通过model—User获取(from django.contrib.auth.models import User),要将其用户名在前台获取到则只需要在前台用user这个变量即可。
在此之前,先看看 settings.py 中的下面这部分
在这里插入图片描述

依照路径打开对应文件,可以看到如下内容(例如我的路径:F:\idle\Lib\site-packages\django\contrib\auth\context_processors.py)
在这里插入图片描述

下面尝试在下图原来的位置放置登录的超级用户信息
在这里插入图片描述

我希望实现的是如果后台超级用户已经登录了,那么前端就获取到登录信息,替换掉登录,如果后台超级用户下线了,则显示又改回登录。
在base.html中找到原来登录字样的位置用{{user}}替换,可以看到如下效果
在这里插入图片描述

那么这时候如果后台用户注销显示的是如下图:
在这里插入图片描述

我们可以通过user的is_authenticated方法来判断用户是否登录:

 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">
                        {% if user.is_authenticated %}
                            {{ user }}
                        {% else %}
                            登录
                        {% endif %}
                        <span class="caret"></span></a>

此时就实现了希望的功能。

· 一个登录页面

但是现在都是后台进行登录,我们需要用一个前端页面进行登录的实现,同样是用超级用户。
1、一个登录界面,在用户未登录状态下点击登录图标会跳转到它,界面样式可以参考bootstrap
2、路由,在项目的 urls.py 中添加登录的路由。
3、登录的逻辑验证,这部分应该是在整个项目的 view.py 中添加一个login的逻辑业务方法。
下面开始一 一实现:
(1) 登录界面
先创建一个html文件,放置在整个项目的templates下面。这个登录页面应该是除了登录界面外其他依然继承了base.html所以和首页比较相像。
在bootstrap上随便找了个登录界面,如下
在这里插入图片描述
在这里插入图片描述
查看网页源代码,将登录界面嵌入到我们的login.html中,可以看到它这个登录界面需要用到几个css和js文件,我们直接在我们的static目录下创建并将内容填充进去。
在这里插入图片描述
在这里插入图片描述

这里要注意路径的更改,可以仿照之前获取bootstrap中css的文件路径,将这几个路径添加进去

接下来就是对这些文件的引用,代码如下:
在这里插入图片描述

可以跟它的源代码对比一下,就是把它css和js文件的路径换成了我下载到本地的文件。
它那几个ie开头的文件实际上都是应对ie浏览器的,但实际应用中估计没什么人用ie浏览器,因此我在代码和文件中就将这几个去掉了,较少代码量。
看一下效果图(我将字段稍微修改了下):
在这里插入图片描述

他这里账号的部分还是需要email类型的才能验证通过,这个可以用于后续用户注册使用,现在就用普通的input就行了。

(2) 路由
在项目 urls.py 中添加一条login的path,并在 views.py 中编写一个login业务逻辑方法。
在这里插入图片描述
此时就可以通过localhost:8000/login访问到这个登录界面了:
在这里插入图片描述

(3) 业务逻辑——进行账号密码登录验证跳转
关于超级用户的登录验证,django已经给我们做好了,在django的官方文档中按照图示链接,下拉找到login方法。
在这里插入图片描述

因为我们已经做了login的路由是一个登录界面,那么在登录界面输入完账号密码提交后应该交给一个检验账号密码的界面进行检验,这个页面我们不需要写html文件只需要给它加一个路由,并完成登录检验的业务逻辑。

def login(request):
    context = {}
    return render(request,'login.html',context)

def login_check(request):
    username = request.POST.get('username',)
    password = request.POST.get('password',)
    user = auth.authenticate(request, username=username, password=password)
    if user is not None:
        auth.login(request, user)
        # Redirect to a success page.
        return redirect('index') # 登录成功重定向到首页
    else:
        # Return an 'invalid login' error message.
        context = {}
        context['error_message'] = '用户名或密码不正确,登录失败!'
        return render(request,'login.html',context) # 登录失败返回login.html 并提示错误信息

此时在登录页面输入账户密码进行测试得到如下:
在这里插入图片描述

这是django为了防止CSRF跨站攻击而设立的,防止其他人伪装cookie登录我们的网站。
打开login.html 添加如下代码
在这里插入图片描述
再次登录就可以看到账户登录成功跳转到首页,而且右上角有了账户名。
在这里插入图片描述
登录页面到这其实差不多了,下面就是一些精细的工作,比如如果密码输入错误怎么提示,点击登录的时候跳转到登录页面等等,因为较为简单,这里就不列出。

2.6 Django Form类替代html表单

在html中不可避免地需要提交数据到后台,通常使用form表单的形式,如上面登录所用到的,这种方法主要是以下几个步骤:
1、在前端html页面中添加form标签,指定action(要提交到的url),指定提交的方法(post,get),然后再form标签中加入输入框,按钮等。
2、在后端 views.py 中通过request.POST.get() 方法通过输入标签中的name属性找到提交的数据
3、对获取的数据进行一系列操作后反馈给前端页面。
但是这种模式有些弊端,对于提交的数据都是字符串类型,我们在后端进行数据处理的时候需要自己进行转换,需要进行逻辑判断,如果数据没获取到应该如何处理,并且这种方式比较复杂冗长。
因此Django提供了一个Form类用于替代html中的表单。
下面以用django Form实现上面的登录页面做为例子:
(1) 在整个项目中创建 forms.py
forms.py 中导入forms,并创建一个登录的Form类

from django import forms

class LoginForm(forms.Form):
    username = forms.CharField(label="账号")
    password = forms.CharField(label="密码", widget=forms.PasswordInput)

(2) 在 views.py 中导入LoginForm类,传至前端页面
在这里插入图片描述
(3) 在 login.html 中显示loginForm对象
在这里插入图片描述

看下效果对比
在这里插入图片描述
先不管样式,其实已经做到了一样的效果。

  • 完善登录验证的方式
    在之前的登录验证,我们是写了一个新的连接login_check来进行登录的检验,这种方法比较冗余,要多一个路由和一个view方法,这里我们根据页面表单请求的方法是POST还是GET来区分是否进行登录验证。
def login(request):
    if request.method == 'POST':
        # 请求方法是POST类型,说明是登录请求
        login_form = LoginForm(request.POST)  # 将request中的参数传入到Form 类中
        if login_form.is_valid():
            # 判断数据有效
            username = login_form.cleaned_data['username']
            password = login_form.cleaned_data['password']
            user = auth.authenticate(request,username=username, password=password)
            if user is not None:
                auth.login(request,user)
                # 登录完成后重定向到之前的页面,若无则回到首页
                return redirect(request.GET.get('referer'), reverse('index'))
            else:
                # 登录失
                login_form.add_error(None,'用户名或密码不正确,登录失败!')  # 添加一条错误信息到Form表单中
    else:
        # 其他的方法,我们就刷新登录页面
        login_form = LoginForm()

    context = {}
    context['login_form']=login_form
    return render(request,'login.html',context)

用新的login方法替换之前login+login_check的方法。
这里可能有点问题的是这句

# 登录完成后重定向到之前的页面,若无则回到首页
return redirect(request.GET.get('referer'), reverse('index'))

这个其实实现了在哪个页面点的登录,登录成功后就重定向到那个页面,这里referer参数是在登录的时候传输url的同时作为GET方法的参数传入的,如下:

 <li><a href="{% url 'login' %}?referer={{ request.path }}">登录</a></li>

request.path是获取到当前页面的路径。将点击登录的页面地址作为参数传到登录页面,当登录完成后再跳转到之前页面。

login中用户验证的部分其实Form表单已经提供了接口,注意到在获取前端form提供的username和password时,用到的cleaned_data字典,这其实是Django Form帮我们“清洗”后的数据,不需要我们再进行强制类型转换等操作,我们可以将账号验证的操作放在Form类中的clean方法里。在获取cleaned_data的时候已经运行了clean方法:
django文档里对clean方法的描述
在这里插入图片描述
这时候注意我们验证获得的user对象应该作为一个cleaned_data传回去,
打开我们的 forms.py 修改LoginForm

from django import forms
from django.contrib import auth

class LoginForm(forms.Form):
    username = forms.CharField(label="账号")
    password = forms.CharField(label="密码", widget=forms.PasswordInput)

    def clean(self):
        '''
        进行账号验证
        :return:cleaned_data
        '''
        username = self.cleaned_data['username']
        password = self.cleaned_data['password']
        user = auth.authenticate(username=username, password=password)
        if user is None:
            raise forms.ValidationError('用户名或密码不正确,登录失败!')
        else:
            self.cleaned_data['user'] = user
        return self.cleaned_data

这时候我们就可以把 views.py 中验证账户的部分去掉,直接获取到用于登录的账户进行登录操作。

def login(request):
    if request.method == 'POST':
        # 请求方法是POST类型,说明是登录请求
        login_form = LoginForm(request.POST)  # 将request中的参数传入到Form 类中
        if login_form.is_valid():
            # 判断数据有效
            user = login_form.cleaned_data['user']
            auth.login(request,user)
            return redirect(request.GET.get('referer'),reverse('index'))
    else:
        # 其他的方法,我们就刷新登录页面
        login_form = LoginForm()

    context = {}
    context['login_form']=login_form
    return render(request,'login.html',context)

猜你喜欢

转载自blog.csdn.net/GentleCP/article/details/86677814