如果您想了解Django系列博客和七的知识,那么本篇文章将是您的不二之选。我们将深入剖析Django系列博客的各个方面,并为您解答七的疑在这篇文章中,我们将为您介绍Django系列博客的相关知识,同时
如果您想了解Django 系列博客和七的知识,那么本篇文章将是您的不二之选。我们将深入剖析Django 系列博客的各个方面,并为您解答七的疑在这篇文章中,我们将为您介绍Django 系列博客的相关知识,同时也会详细的解释七的运用方法,并给出实际的案例分析,希望能帮助到您!
本文目录一览:- Django 系列博客(七)(django blog)
- Anaconda+django写出第一个web app(七)
- Django 博客开发教程 3 - 创建 Django 博客的数据库模型
- Django 博客开发教程 6 - 真正的 Django 博客首页视图
- Django 学习笔记(七)-- 将 django 中多个 app 放到同个文件夹 apps 处理
Django 系列博客(七)(django blog)
<h1 id="django-系列博客七">Django 系列博客(七)
<h2 id="前言">前言
本篇博客介绍 Django 中的视图层中的相关参数,HttpRequest 对象、HttpResponse 对象、JsonResponse,以及视图层的两种响应方式 CBV 和 FBV,还有简单的文件上传。
函数
一个视图函数,简称视图,是一个简单的Python 函数,它接受Web请求并且返回Web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. . . 是任何东西都可以。无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你的Python目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了将代码放在某处,约定是将视图放置在项目或应用程序目录中的名为views.py
的文件中。
下面是一个返回当前日期和时间作为 HTML 文档的视图:
from django.shortcuts import render,HttpResponse,HttpResponseRedirect,redirect import datetimedef current_datetime(request):
Now = datetime.datetime.Now()
html = "It is Now %s." % Now
return HttpResponse(html)
这段代码解析:
- 从
django.shortcuts
模块导入了HttpResponse
类,以及Python的datetime
库; - 定义了
current_datetime
函数。它就是视图函数。每个视图函数都使用HttpRequest
对象作为第一个参数,并且通常称之为request
;
注意,视图函数的名称并不重要;不需要用一个统一的命名方式来命名,以便让Django识别它。我们将其命名为current_datetime
,是因为这个名称能够精确地反映出它的功能。
- 会返回一个
HttpResponse
对象,其中包含生成的响应。每个视图函数都负责返回一个HttpResponse
对象。
在视图层最重要的就是要熟悉两个对象:请求对象(request)和响应对象(HttpResponse)。
属性
Django 将请求报文中的请求行、请求头、请求体封装成 HttpRequest 类中的属性。除了特殊说明之外,其他的均为只读属性。
1.HttpRequest.GET一个类似于字典的对象,包含 HTTP GET 的所有参数。详情请参考 QueryDict 对象。
2.HttpRequest.POST
一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。
POST 请求可以带有空的 POST 字典 —— 如果通过 HTTP POST 方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。
因此,不应该使用 if request.POST 来检查使用的是否是POST 方法;应该使用 if request.method == "POST"
另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。注意:键值对的值是多个的时候,比如checkBox类型的input标签,select标签,需要用:
request.POST.getlist("hobby")3.HttpRequest.body
一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。
但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。4.HttpRequest.path
一个字符串,表示请求的路径组件(不含域名)。
例如:"/music/bands/the_beatles/"5.HttpRequest.method
一个字符串,表示请求使用的HTTP 方法。必须使用大写。
例如:"GET"、"POST"6.HttpRequest.encoding
一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。
这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。
接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。
如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。7.HttpRequest.Meta
一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例:
取值:CONTENT_LENGTH —— 请求的正文的长度(是<a href="https://www.jb51.cc/tag/yige/" target="_blank">一个</a>字符串)。 CONTENT_TYPE —— 请求的正文的MIME 类型。 HTTP_ACCEPT —— 响应可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 响应可接收的编码。 HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。 HTTP_HOST —— 客服端发送的HTTP Host 头部。 HTTP_REFERER —— Referring <a href="https://www.jb51.cc/tag/yemian/" target="_blank">页面</a>。 HTTP_USER_AGENT —— 客户端的user-agent 字符串。 QUERY_STRING —— 单个字符串形式的<a href="https://www.jb51.cc/tag/chaxun/" target="_blank">查询</a>字符串(未解析过的形式)。 REMOTE_ADDR —— 客户端的IP 地址。 REMOTE_HOST —— 客户端的主机名。 REMOTE_USER —— 服务器认证后的<a href="https://www.jb51.cc/tag/yonghu/" target="_blank">用户</a>。 REQUEST_METHOD —— <a href="https://www.jb51.cc/tag/yige/" target="_blank">一个</a>字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服务器的主机名。 SERVER_PORT —— 服务器的端口(是<a href="https://www.jb51.cc/tag/yige/" target="_blank">一个</a>字符串)。
从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 Meta 的键时,
都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。
所以,一个叫做 X-Bender 的头部将转换成 Meta 中的 HTTP_X_BENDER 键。8.HttpRequest.FILES
一个类似于字典的对象,包含所有的上传文件信息。
FILES 中的每个键为 中的name,值则为对应的数据。
注意,FILES 只有在请求的方法为POST 且提交的
<h2 id="httpresponse-对象">HttpResponse 对象
响应对象主要有三种形式:前面的博客有相关介绍
- HttpResponse();
- render();
- redirect().
HttpResponse()括号内直接跟一个具体的字符串作为响应体,比较直接和简单,所以这里介绍后两种
render(request,template_name[,context])
结合给定的模板和一个上下文字典,返回一个经过 Django渲染后的HttpResponse 对象到前端。
参数:
- request:用于生成响应的请求对象;
- template_name:要使用的模板的完整名称,可选的参数;
- context:添加到模板上下文的一个字典。默认是一个空字典,如果字典中的某个值是可调用的,视图将在渲染模板之前调用它;
render 方法就是将一个模板页面中的模板语法进行渲染,最终渲染成一个 HTML 页面作为响应返回给前端。
传递要重定向的一个硬编码的URL
def my_view(request): ... return redirect('/some/url/')
也可以是一个完成的 url
def my_view(request): ... return redirect('http://www.baidu.com/')
向前端返回一个 json 格式的字符串,有两种方式:
import json
data = {'name': 'musibii','age': 18}
return HttpResponse(json.dumps(data))
# 从这里可以看出其实 Jsonresponse内部也是调用了 json 的 dumps 方法进行转换
from django.http import JsonResponse
data = {'name': 'musibii','age': 18}
return JsonResponse(data1,safe=False)
safe参数是一种安全机制,因为如果要传输列表类型的数据时,会因为内部的相关机制会产生错误。
return JsonResponse(data1,safe=False)
safe参数是一种安全机制,因为如果要传输列表类型的数据时,会因为内部的相关机制会产生错误。
<h2 id="cbv-和-fbv">CBV 和 FBV
CBV 基于类的视图(Class Base View)和 FBV 基于函数的视图(Function Base View)
from django.views import View
class Login(View):
def <a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>patch(self,request,*args,**<a href="https://www.jb51.cc/tag/kwargs/" target="_blank">kwargs</a>):
print(request)
print(args)
print(<a href="https://www.jb51.cc/tag/kwargs/" target="_blank">kwargs</a>)
# 可以写类似装饰器的东西,在前后加<a href="https://www.jb51.cc/tag/daima/" target="_blank">代码</a>
obj=<a href="https://www.jb51.cc/tag/super/" target="_blank">super()</a>.<a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>patch(request,**<a href="https://www.jb51.cc/tag/kwargs/" target="_blank">kwargs</a>)
return obj
def get(self,request):
return render(request,'login.html')
def post(self,request):
name = request.POST.get('name')
ret = BookInfo.objects.filter(name=name).f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()
print(type(ret))
dic = {}
print(ret.name)
print(type(ret.__dict__))
for key,value in (ret.__dict__).items():
if key[0].startswith('_'):
continue
print(key)
dic[key] = value
# print(dic)
# 第二种
# ret = BookInfo.objects.all()
# lis = []
# for info in ret:
# dic = {}
# for k,v in (info.__dict__).items():
# if key[0].startswitch(''):
# continue
# dic[key] = v
# lis.append(dic)
# 第三种
# li = []
# ret = BookInfo.objects.all()
# for book in ret:
# dic = {}
# for field in book._<a href="https://www.jb51.cc/tag/Meta/" target="_blank">Meta</a>.fields:
# dic[field.name] = getattr(book,field.name)
# li.append(dic)
return JsonResponse(dic,json_dumps_p<a href="https://www.jb51.cc/tag/ara/" target="_blank">ara</a>ms={'ensure_ascii': False})</code></pre>
<h3 id="fbv">FBV
class Login(View):
def <a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>patch(self,request,*args,**<a href="https://www.jb51.cc/tag/kwargs/" target="_blank">kwargs</a>):
print(request)
print(args)
print(<a href="https://www.jb51.cc/tag/kwargs/" target="_blank">kwargs</a>)
# 可以写类似装饰器的东西,在前后加<a href="https://www.jb51.cc/tag/daima/" target="_blank">代码</a>
obj=<a href="https://www.jb51.cc/tag/super/" target="_blank">super()</a>.<a href="https://www.jb51.cc/tag/dis/" target="_blank">dis</a>patch(request,**<a href="https://www.jb51.cc/tag/kwargs/" target="_blank">kwargs</a>)
return obj
def get(self,request):
return render(request,'login.html')
def post(self,request):
name = request.POST.get('name')
ret = BookInfo.objects.filter(name=name).f<a href="https://www.jb51.cc/tag/irs/" target="_blank">irs</a>t()
print(type(ret))
dic = {}
print(ret.name)
print(type(ret.__dict__))
for key,value in (ret.__dict__).items():
if key[0].startswith('_'):
continue
print(key)
dic[key] = value
# print(dic)
# 第二种
# ret = BookInfo.objects.all()
# lis = []
# for info in ret:
# dic = {}
# for k,v in (info.__dict__).items():
# if key[0].startswitch(''):
# continue
# dic[key] = v
# lis.append(dic)
# 第三种
# li = []
# ret = BookInfo.objects.all()
# for book in ret:
# dic = {}
# for field in book._<a href="https://www.jb51.cc/tag/Meta/" target="_blank">Meta</a>.fields:
# dic[field.name] = getattr(book,field.name)
# li.append(dic)
return JsonResponse(dic,json_dumps_p<a href="https://www.jb51.cc/tag/ara/" target="_blank">ara</a>ms={'ensure_ascii': False})</code></pre>
<h3 id="fbv">FBV
<pre>
def file_upload(request):
if request.method=="GET":
return render(request,'file_upload.html')
else:
print(request.POST)
print(request.FILES)
print(request.body)
# FILES是<a href="https://www.jb51.cc/tag/yige/" target="_blank">一个</a>字典,# <MultiValueDict: {'myfile': [<InMemoryUploadedFile: 1.jpg (image/jpeg)>]}>
# 拿到<a href="https://www.jb51.cc/tag/shangchuan/" target="_blank">上传</a>的<a href="https://www.jb51.cc/tag/wenjian/" target="_blank">文件</a>对象
file=request.FILES.get('myfile')
print(type(file))
from django.core.files.uploadedfile import InMemoryUploadedFile
with open(file.name,'wb') as f:
# for line in file.chunks():
for line in file:
f.write(line)
return HttpResponse('<a href="https://www.jb51.cc/tag/shangchuan/" target="_blank">上传</a>成功')</code></pre>
<h2 id="简单文件上传">简单文件上传
<h3 id="模板文件">模板文件
<pre># upload_file.html
<!DOCTYPE html>
<html lang="en">
<Meta charset="UTF-8">
Title
<form action="" method="post" enctype="multipart/form-data">
用户名:
文件
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^upload_file/$',views.UploadFile.as_view()),]
文件
class UploadFile(View):def get(self,'upload_file.html') def post(self,request): file = request.FILES.get('myfile') # print(file['file']) from django.core.files.uploadedfile import InMemoryUploadedFile print(time.time()) filename = str(time.time()).split('.')[0] + file.name with open(filename,'wb') as f: for line in file: f.write(line) return HttpResponse('<a href="https://www.jb51.cc/tag/shangchuan/" target="_blank">上传</a>成功')</code></pre>
Anaconda+django写出第一个web app(七)
今天来实现如何在页面弹出一些信息,比如注册成功后弹出注册成功的信息。这一点可以通过materialize里的Toasts来实现。
django自带的messages可以告诉我们是否注册成功,以及注册失败,或者提出警告。我们首先修改views.py,来引入messages:
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .models import Tutorial
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login, logout, authenticate
from django.contrib import messages
# Create your views here.
def homepage(request):
return render(request=request,
template_name=''main/home.html'',
context={''tutorials'':Tutorial.objects.all})
def register(request):
if request.method == ''POST'':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
username = form.cleaned_data.get(''username'')
messages.success(request, f"New account created: {username}")
login(request, user)
return redirect(''main:homepage'')
else:
for msg in form.error_messages:
messages.error(request, f"{msg}:{form.error_messages[msg]}")
return render(request=request,
template_name=''main/register.html'',
context={''form'':form})
form = UserCreationForm
return render(request=request,
template_name=''main/register.html'',
context={''form'':form})
如果注册成功,我们使用了messages.success来给出信息,如果失败我们使用了messages.error来给出信息。
接下来我们需要在html文件中使得这些信息可以显示,因为messages不止出现在注册时,所以我们将这些信息写在header.html中,我们还希望在注册成功后,导航栏右上角的文字发生变化,loigin变为刚注册的用户名,register变为logout,修改后header.html的内容如下:
<head>
{% load static %}
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="{% static ''tinymce/css/prism.css'' %}" rel="stylesheet">
</head>
<body>
{% if messages %}
{% for message in messages %}
{% if message.tags == ''success''%}
<script>M.toast({html: "{{message}}", classes: ''green rounded'', displayLength:2000});</script>
{% elif message.tags == ''info''%}
<script>M.toast({html: "{{message}}", classes: ''blue rounded'', displayLength:2000});</script>
{% elif message.tags == ''warning''%}
<script>M.toast({html: "{{message}}", classes: ''orange rounded'', displayLength:10000});</script>
{% elif message.tags == ''error''%}
<script>M.toast({html: "{{message}}", classes: ''red rounded'', displayLength:10000});</script>
{% endif %}
{% endfor %}
{% endif %}
<nav>
<div class="nav-wrapper">
<a href="#" class="brand-logo">Tutorials</a>
<ul id="nav-mobile" class="right hide-on-med-and-down">
<li><a href="/">Home</a></li>
{% if user.is_authenticated %}
<li><a href="/account">{{ user.username }}</a></li>
<li><a href="/logout">Logout</a></li>
{% else %}
<li><a href="/login">Login</a></li>
<li><a href="/register">Register</a></li>
{% endif %}
</ul>
</div>
</nav>
<div class="container">
{% block content %}
{% endblock %}
</div>
</body>
<script src="{% static ''tinymce/js/prism.js'' %}"></script>
此时的header.html内容显得有些复杂和凌乱,我们可以使用include来将某些代码放入单独的html文件中来缩短header.html的内容,为此我们在templates/main文件夹下新建文件夹includes,然后新建两个文件messages.html和navbaritems.html,最终header.html、messages.html、navbaritems.html内容分别如下:
<head>
{% load static %}
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="{% static ''tinymce/css/prism.css'' %}" rel="stylesheet">
</head>
<body>
{% include ''main/includes/messages.html'' %}
<nav>
<div class="nav-wrapper">
<a href="#" class="brand-logo">Tutorials</a>
<ul id="nav-mobile" class="right hide-on-med-and-down">
<li><a href="/">Home</a></li>
{% include ''main/includes/navbaritems.html'' %}
</ul>
</div>
</nav>
<div class="container">
{% block content %}
{% endblock %}
</div>
</body>
<script src="{% static ''tinymce/js/prism.js'' %}"></script>
{% if messages %}
{% for message in messages %}
{% if message.tags == ''success''%}
<script>M.toast({html: "{{message}}", classes: ''green rounded'', displayLength:2000});</script>
{% elif message.tags == ''info''%}
<script>M.toast({html: "{{message}}", classes: ''blue rounded'', displayLength:2000});</script>
{% elif message.tags == ''warning''%}
<script>M.toast({html: "{{message}}", classes: ''orange rounded'', displayLength:10000});</script>
{% elif message.tags == ''error''%}
<script>M.toast({html: "{{message}}", classes: ''red rounded'', displayLength:10000});</script>
{% endif %}
{% endfor %}
{% endif %}
{% if user.is_authenticated %}
<li><a href="/account">{{ user.username }}</a></li>
<li><a href="/logout">Logout</a></li>
{% else %}
<li><a href="/login">Login</a></li>
<li><a href="/register">Register</a></li>
{% endif %}
在浏览器中刷新页面,仍然正常显示。这种方法可以保证单个文件的内容不至于过长,便于后期管理。本文中刚开始提到的信息提示,没有相应的截图,这个可以在下一节添加login和logout时清楚的观察到。
参考链接:
[1] https://materializecss.com/toasts.html
[2] https://pythonprogramming.net/messages-django-tutorial/
Django 博客开发教程 3 - 创建 Django 博客的数据库模型
设计博客的数据库表结构
博客最主要的功能就是展示我们写的文章,它需要从某个地方获取博客文章数据才能把文章展示出来,通常来说这个地方就是数据库。我们把写好的文章永久地保存在数据库里,当用户访问我们的博客时,Django 就去数据库里把这些数据取出来展现给用户。
博客的文章应该含有标题、正文、作者、发表时间等数据。一个更加现代化的博客文章还希望它有分类、标签、评论等。为了更好地存储这些数据,我们需要合理地组织数据库的表结构。
我们的博客初级版本主要包含博客文章,文章会有分类以及标签。一篇文章只能有一个分类,但可以打上很多标签。
数据库存储的数据其实就是表格的形式,例如存储博客文章的数据库表长这个样子:
文章 id | 标题 | 正文 | 发表时间 | 分类 | 标签 |
---|---|---|---|---|---|
1 | title 1 | text 1 | 2016-12-23 | Django | Django 学习 |
2 | title 2 | text 2 | 2016-12-24 | Django | Django 学习 |
3 | title 3 | text 3 | 2016-12-26 | Python | Python 学习 |
其中文章 ID 是一个数字,唯一对应着一篇文章。当然还可以有更多的列以存储更多相关数据,这只是一个最基本的示例。
数据库表设计成这样其实已经可以了,但是稍微分析一下我们就会发现一个问题,这 3 篇文章的分类和标签都是相同的,这会产生很多重复数据,当数据量很大时就浪费了存储空间。
不同的文章可能它们对应的分类或者标签是相同的,所以我们把分类和标签提取出来,做成单独的数据库表,再把文章和分类、标签关联起来。下面分别是分类和标签的数据库表:
分类 id | 分类名 |
---|---|
1 | Django |
2 | Python |
标签 id | 标签名 |
---|---|
1 | Django 学习 |
2 | Python 学习 |
编写博客模型代码
以上是自然语言描述的表格,数据库也和编程语言一样,有它自己的一套规定的语法来生成上述的表结构,这样我们才能把数据存进去。一般来说这时候我们应该先去学习数据库创建表格的语法,再回来写我们的 Django 博客代码了。但是 Django 告诉我们不用这么麻烦,它已经帮我们做了一些事情。Django 把那一套数据库的语法转换成了 Python 的语法形式,我们只要写 Python 代码就可以了,Django 会把 Python 代码翻译成对应的数据库操作语言。用更加专业一点的说法,就是 Django 为我们提供了一套 ORM(Object Relational Mapping)系统。
例如我们的分类数据库表,Django 只要求我们这样写:
blog/models.py
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
Category
就是一个标准的 Python 类,它继承了 models.Model
类,类名为 Category
。Category
类有一个属性 name
,它是 models.CharField
的一个实例。
这样,Django 就可以把这个类翻译成数据库的操作语言,在数据库里创建一个名为 category 的表格,这个表格的一个列名为 name。还有一个列 id,Django 则会自动创建。可以看出从 Python 代码翻译成数据库语言时其规则就是一个 Python 类对应一个数据库表格,类名即表名,类的属性对应着表格的列,属性名即列名。
我们需要 3 个表格:文章(Post)、分类(Category)以及标签(Tag),下面就来分别编写它们对应的 Python 类。模型的代码通常写在相关应用的 models.py 文件里。已经在代码中做了详细的注释,说明每一句代码的含义。但如果你在移动端下阅读不便的话,也可以跳到代码后面看正文的里的讲解。
注意:代码中含有中文注释,如果你直接 copy 代码到你的文本编辑器且使用了 Python 2 开发环境的话,会得到一个编码错误。因此请在文件最开始处加入编码声明:# coding: utf-8。
blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
"""
Django 要求模型必须继承 models.Model 类。
Category 只需要一个简单的分类名 name 就可以了。
CharField 指定了分类名 name 的数据类型,CharField 是字符型,
CharField 的 max_length 参数指定其最大长度,超过这个长度的分类名就不能被存入数据库。
当然 Django 还为我们提供了多种其它的数据类型,如日期时间类型 DateTimeField、整数类型 IntegerField 等等。
Django 内置的全部类型可查看文档:
https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-types
"""
name = models.CharField(max_length=100)
class Tag(models.Model):
"""
标签 Tag 也比较简单,和 Category 一样。
再次强调一定要继承 models.Model 类!
"""
name = models.CharField(max_length=100)
class Post(models.Model):
"""
文章的数据库表稍微复杂一点,主要是涉及的字段更多。
"""
# 文章标题
title = models.CharField(max_length=70)
# 文章正文,我们使用了 TextField。
# 存储比较短的字符串可以使用 CharField,但对于文章的正文来说可能会是一大段文本,因此使用 TextField 来存储大段文本。
body = models.TextField()
# 这两个列分别表示文章的创建时间和最后一次修改时间,存储时间的字段用 DateTimeField 类型。
created_time = models.DateTimeField()
modified_time = models.DateTimeField()
# 文章摘要,可以没有文章摘要,但默认情况下 CharField 要求我们必须存入数据,否则就会报错。
# 指定 CharField 的 blank=True 参数值后就可以允许空值了。
excerpt = models.CharField(max_length=200, blank=True)
# 这是分类与标签,分类与标签的模型我们已经定义在上面。
# 我们在这里把文章对应的数据库表和分类、标签对应的数据库表关联了起来,但是关联形式稍微有点不同。
# 我们规定一篇文章只能对应一个分类,但是一个分类下可以有多篇文章,所以我们使用的是 ForeignKey,即一对多的关联关系。
# 而对于标签来说,一篇文章可以有多个标签,同一个标签下也可能有多篇文章,所以我们使用 ManyToManyField,表明这是多对多的关联关系。
# 同时我们规定文章可以没有标签,因此为标签 tags 指定了 blank=True。
# 如果你对 ForeignKey、ManyToManyField 不了解,请看教程中的解释,亦可参考官方文档:
# https://docs.djangoproject.com/en/1.10/topics/db/models/#relationships
category = models.ForeignKey(Category)
tags = models.ManyToManyField(Tag, blank=True)
# 文章作者,这里 User 是从 django.contrib.auth.models 导入的。
# django.contrib.auth 是 Django 内置的应用,专门用于处理网站用户的注册、登录等流程,User 是 Django 为我们已经写好的用户模型。
# 这里我们通过 ForeignKey 把文章和 User 关联了起来。
# 因为我们规定一篇文章只能有一个作者,而一个作者可能会写多篇文章,因此这是一对多的关联关系,和 Category 类似。
author = models.ForeignKey(User)
博客模型代码代码详解
首先是 Category
和 Tag
类,它们均继承自 model.Model
类,这是 Django 规定的。Category
和 Tag
类均有一个 name
属性,用来存储它们的名称。由于分类名和标签名一般都是用字符串表示,因此我们使用了 CharField
来指定 name
的数据类型,同时 max_length
参数则指定 name
允许的最大长度,超过该长度的字符串将不允许存入数据库。除了 CharField
,Django 还为我们提供了更多内置的数据类型,比如时间类型 DateTimeField
、整数类型 IntegerField
等等。
在本教程中我们会教你这些类型的使用方法,但以后你开发自己的项目时,你就需要通过阅读Django 官方文档 关于字段类型的介绍 来了解有哪些数据类型可以使用以及如何使用它们。
Post
类也一样,必须继承自 model.Model
类。文章的数据库表稍微复杂一点,主要是列更多,我们指定了这些列:
title
。这是文章的标题,数据类型是CharField
,允许的最大长度max_length = 70
。body
。文章正文,我们使用了TextField
。比较短的字符串存储可以使用CharField
,但对于文章的正文来说可能会是一大段文本,因此使用TextField
来存储大段文本。created_time
、modified_time
。这两个列分别表示文章的创建时间和最后一次修改时间,存储时间的列用DateTimeField
数据类型。excerpt
。文章摘要,可以没有文章摘要,但默认情况下CharField
要求我们必须存入数据,否则就会报错。指定CharField
的blank=True
参数值后就可以允许空值了。category
和tags
。这是分类与标签,分类与标签的模型我们已经定义在上面。我们把文章对应的数据库表和分类、标签对应的数据库表关联了起来,但是关联形式稍微有点不同。我们规定一篇文章只能对应一个分类,但是一个分类下可以有多篇文章,所以我们使用的是ForeignKey
,即一对多的关联关系。而对于标签来说,一篇文章可以有多个标签,同一个标签下也可能有多篇文章,所以我们使用ManyToManyField
,表明这是多对多的关联关系。同时我们规定文章可以没有标签,因此为标签 tags 指定了blank=True
。author
。文章作者,这里User
是从 django.contrib.auth.models 导入的。django.contrib.auth 是 Django 内置的应用,专门用于处理网站用户的注册、登录等流程。其中User
是 Django 为我们已经写好的用户模型,和我们自己编写的Category
等类是一样的。这里我们通过ForeignKey
把文章和User
关联了起来,因为我们规定一篇文章只能有一个作者,而一个作者可能会写多篇文章,因此这是一对多的关联关系,和Category
类似。
理解多对一和多对多两种关联关系
我们分别使用了两种关联数据库表的形式:ForeignKey 和 ManyToManyField。
ForeignKey
ForeignKey
表明一种一对多的关联关系。比如这里我们的文章和分类的关系,一篇文章只能对应一个分类,而一个分类下可以有多篇文章。反应到数据库表格中,它们的实际存储情况是这样的:
文章 ID | 标题 | 正文 | 分类 ID |
---|---|---|---|
1 | title 1 | body 1 | 1 |
2 | title 2 | body 2 | 1 |
3 | title 3 | body 3 | 1 |
4 | title 4 | body 4 | 2 |
分类 ID | 分类名 | |
---|---|---|
1 | Django | |
2 | Python |
可以看到文章和分类实际上是通过文章数据库表中 分类 ID 这一列关联的。当要查询文章属于哪一个分类时,只需要查看其对应的分类 ID 是多少,然后根据这个分类 ID 就可以从分类数据库表中找到该分类的数据。例如这里文章 1、2、3 对应的分类 ID 均为 1,而分类 ID 为 1 的分类名为 Django,所以文章 1、2、3 属于分类 Django。同理文章 4 属于分类 Python。
反之,要查询某个分类下有哪些文章,只需要查看对应该分类 ID 的文章有哪些即可。例如这里 Django 的分类 ID 为 1,而对应分类 ID 为 1 的文章有文章 1、2、3,所以分类 Django 下有 3 篇文章。
希望这个例子能帮助你加深对多对一关系,以及它们在数据库中是如何被关联的理解,更多的例子请看文末给出的 Django 官方参考资料。
ManyToManyField
ManyToManyField
表明一种多对多的关联关系,比如这里的文章和标签,一篇文章可以有多个标签,而一个标签下也可以有多篇文章。反应到数据库表格中,它们的实际存储情况是这样的:
文章 ID | 标题 | 正文 |
---|---|---|
1 | title 1 | body 1 |
2 | title 2 | body 2 |
3 | title 3 | body 3 |
4 | title 4 | body 4 |
标签 ID | 标签名 |
---|---|
1 | Django 学习 |
2 | Python 学习 |
文章 ID | 标签 ID |
---|---|
1 | 1 |
1 | 2 |
2 | 1 |
3 | 2 |
多对多的关系无法再像一对多的关系中的例子一样在文章数据库表加一列 分类 ID 来关联了,因此需要额外建一张表来记录文章和标签之间的关联。例如文章 ID 为 1 的文章,既对应着 标签 ID 为 1 的标签,也对应着 标签 ID 为 2 的标签,即文章 1 既属于标签 1:Django 学习,也属于标签 2:Python 学习。
反之,标签 ID 为 1 的标签,既对应着 文章 ID 为 1 的文章,也对应着 文章 ID 为 2 的文章,即标签 1:Django 学习下有两篇文章。
希望这个例子能帮助你加深对多对多关系,以及它们在数据库中是如何被关联的理解,更多的例子请看文末给出的 Django 官方参考资料。
假如你对多对一关系和多对多关系还存在一些困惑,强烈建议阅读官方文档对这两种关系的说明以及更多官方的例子以加深理解:
Django ForeignKey 简介
Django ForeignKey 详细示例
Django ManyToManyField 简介
Django ManyToManyField 详细示例
总结
本章节的代码位于:Step3: blog models。
如果遇到问题,请通过下面的方式寻求帮助。
在 创建 Django 博客的数据库模型 - 追梦人物的博客 的评论区留言。
将问题的详细描述通过邮件发送到 djangostudyteam@163.com,一般会在 24 小时内回复。
更多Django 教程,请访问 追梦人物的博客。
Django 博客开发教程 6 - 真正的 Django 博客首页视图
在此之前我们已经编写了 Blog 的首页视图,并且配置了 URL 和模板,让 Django 能够正确地处理 HTTP 请求并返回合适的 HTTP 响应。不过我们仅仅在首页返回了一句话:欢迎访问我的博客。这是个 Hello World 级别的视图函数,我们需要编写真正的首页视图函数,当用户访问我们的博客首页时,他将看到我们发表的博客文章列表,就像 演示项目 里展示的这样。
首页视图函数
上一节我们阐明了 Django 的开发流程。即首先配置 URL,把 URL 和相应的视图函数绑定,一般写在 urls.py 文件里,然后在工程的 urls.py 文件引入。其次是编写视图函数,视图中需要渲染模板,我们也在 settings.py 中进行了模板相关的配置,让 Django 能够找到需要渲染的模板。最后把渲染完成的 HTTP 响应返回就可以了。相关的配置和准备工作都在之前完成了,这里我们只需专心编写视图函数,让它实现我们想要的功能即可。
首页的视图函数其实很简单,代码像这样:
blog/views.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})
我们曾经在前面的章节讲解过模型管理器 objects
的使用。这里我们使用 all()
方法从数据库里获取了全部的文章,存在了 post_list
变量里。all
方法返回的是一个 QuerySet
(可以理解成一个类似于列表的数据结构),由于通常来说博客文章列表是按文章发表时间倒序排列的,即最新的文章排在最前面,所以我们紧接着调用了 order_by
方法对这个返回的 queryset 进行排序。排序依据的字段是 created_time
,即文章的创建时间。-
号表示逆序,如果不加 -
则是正序。 接着如之前所做,我们渲染了 blogindex.html 模板文件,并且把包含文章列表数据的 post_list
变量传给了模板。
处理静态文件
我们的项目使用了从网上下载的一套博客模板(点击这里下载全套模板)。这里面除了 HTML 文档外,还包含了一些 CSS 文件和 JavaScript 文件以让网页呈现出我们现在看到的样式。同样我们需要对 Django 做一些必要的配置,才能让 Django 知道如何在开发服务器中引入这些 CSS 和 JavaScript 文件,这样才能让博客页面的 CSS 样式生效。
按照惯例,我们把 CSS 和 JavaScript 文件放在 blog 应用的 static 目录下。因此,先在 blog 应用下建立一个 static 文件夹。同时,为了避免和其它应用中的 CSS 和 JavaScript 文件命名冲突(别的应用下也可能有和 blog 应用下同名的 CSS 、JavaScript 文件),我们再在 static 目录下建立一个 blog 文件夹,把下载的博客模板中的 css 和 js 文件夹连同里面的全部文件一同拷贝进这个目录。最终我们的 blog 应用目录结构应该是这样的:
blog\
__init__.py
static\
blog\
css\
.css 文件...
js\
.js 文件...
admin.py
apps.py
migrations\
__init__.py
models.py
tests.py
views.py
用下载的博客模板中的 index.html 文件替换掉之前我们自己写的 index.html 文件。如果你好奇,现在就可以运行开发服务器,看看首页是什么样子。
如图所示,你会看到首页显示的样式非常混乱,原因是浏览器无法正确加载 CSS 等样式文件。需要以 Django 的方式来正确地处理 CSS 和 JavaScript 等静态文件的加载路径。CSS 样式文件通常在 HTML 文档的 head 标签里引入,打开 index.html 文件,在文件的开始处找到 head 标签包裹的内容,大概像这样:
templates/blog/index.html
<!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">
<!-- 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>
</head>
<body>
<!-- 其它内容 -->
<script src="js/script.js"></script>
</body>
</html>
CSS 样式文件的路径在 link 标签的 href 属性里,而 JavaScript 文件的路径在 script 标签的 src 属性里。可以看到诸如 `href="css/bootstrap.min.css" 或者 src="js/jquery-2.1.3.min.js" 这样的引用,由于引用文件的路径不对,所以浏览器引入这些文件失败。我们需要把它们改成正确的路径。把代码改成下面样子,正确地引入 static 文件下的 CSS 和 JavaScript 文件:
templates/blog/index.html
+ {% load staticfiles %}
<!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>
这里 - 表示删掉这一行,而 + 表示增加这一行。
我们把引用路径放在了一个奇怪的符号里,例如:href="{% static ''blog/css/bootstrap.min.css'' %}"。用 {% %} 包裹起来的叫做模板标签。我们前面说过用 {{ }} 包裹起来的叫做模板变量,其作用是在最终渲染的模板里显示由视图函数传过来的变量值。而这里我们使用的模板标签的功能则类似于函数,例如这里的 static
模板标签,它把跟在后面的字符串 ''css/bootstrap.min.css''
转换成正确的文件引入路径。这样 css 和 js 文件才能被正确加载,样式才能正常显示。
为了能在模板中使用 {% static %} 模板标签,别忘了在最顶部 {% load staticfiles %} 。static 模板标签位于 staticfiles 模块中,只有通过 load 模板标签将该模块引入后,才能在模板中使用 {% static %} 标签。
替换完成后你可以刷新页面并看看网页的源代码,看一看 {% static %} 模板标签在页面渲染后究竟被替换成了什么样的值。例如我们可以看到
<link rel="stylesheet" href="{% static ''blog/css/pace.css'' %}">
这一部分最终在浏览器中显示的是:
<link rel="stylesheet" href="/static/blog/css/pace.css">
这正是 pace.css 文件所在的路径,其它的文件路径也被类似替换。可以看到 {% static %} 标签的作用实际就是把后面的字符串加了一个 /static/ 前缀,比如 {% static ''blog/css/pace.css'' %}
最终渲染的值是 /static/blog/css/pace.css
。而 /static/ 前缀是我们在 settings.py 文件中通过 STATIC_URL = ''/static/''
指定的。事实上,如果我们直接把引用路径写成 /static/blog/css/pace.css
也是可以的,那么为什么要使用 {% static %} 标签呢?想一下,目前 URL 的前缀是 /static/,如果哪一天因为某些原因,我们需要把 /static/ 改成 /resource/,如果你是直接写的引用路劲而没有使用 static 模板标签,那么你可能需要改 N 个地方。如果你使用了 static 模板标签,那么只要在 settings.py 处改一个地方就可以了,即把 STATIC_URL = ''/static/''
改成 STATIC_URL = ''/resource/''
。
有时候按 F5 刷新后页面还是很乱,这可能是因为浏览器缓存了之前的结果。按 Shift + F5(有些浏览器可能是 Ctrl + F5)强制刷新浏览器页面即可。
注意这里有一个 CSS 文件的引入
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
我们没有使用模板标签,因为这里的引用的文件是一个外部文件,不是我们项目里 staticblogcss 目录下的文件,因此无需使用模板标签。
正确引入了静态文件后样式显示正常了。
修改模板
目前我们看到的只是模板中预先填充的一些数据,我们得让它显示从数据库中获取的文章数据。下面来稍微改造一下模板:
在模板 index.html 中你会找到一系列 article 标签:
templates/blog/index.html
...
<article>
...
</article>
<article>
...
</article>
<article>
...
</article>
...
这里面包裹的内容显示的就是文章数据了。我们前面在视图函数 index 里给模板传了一个 post_list
变量,它里面包含着从数据库中取出的文章列表数据。就像 Python 一样,我们可以在模板中循环这个列表,把文章一篇篇循环出来,然后一篇篇显示文章的数据。要在模板中使用循环,需要使用到前面提到的模板标签,这次使用 {% for %} 模板标签。将 index.html 中多余的 article 标签删掉,只留下一个 article 标签,然后写上下列代码:
templates/blog/index.html
...
{% for post in post_list %}
<article>
...
</article>
{% empty %}
<div>暂时还没有发布的文章!</div>
{% endfor %}
...
可以看到语法和 Python 的 for 循环类似,只是被 {% %} 这样一个模板标签符号包裹着。{% empty %} 的作用是当 post_list
为空,即数据库里没有文章时显示 {% empty %} 下面的内容,最后我们用 {% endfor %} 告诉 Django 循环在这里结束了。
你可能不太理解模板中的 post
和 post_list
是什么。post_list
是一个 QuerySet
(类似于一个列表的数据结构),其中每一项都是之前定义在 blogmodels.py 中的 Post 类的实例,且每个实例分别对应着数据库中每篇文章的记录。因此我们循环遍历 post_list
,每一次遍历的结果都保存在 post
变量里。所以我们使用模板变量来显示 post
的属性值。例如这里的 {{ post.pk }}
(pk 是 primary key 的缩写,即 post 对应于数据库中记录的 id 值,该属性尽管我们没有显示定义,但是 Django 会自动为我们添加)。
现在我们可以在循环体内通过 post
变量访问单篇文章的数据了。分析 article 标签里面的 HTML 内容,h1 显示的是文章的标题,
<h1>
<a href="single.html">Adaptive Vs. Responsive Layouts And Optimal Text Readability</a>
</h1>
我们把标题替换成 post
的 title
属性值。注意要把它包裹在模板变量里,因为它最终要被替换成实际的 title 值。
<h1>
<a href="single.html">{{ post.title }}</a>
</h1>
下面这 5 个 span 标签里分别显示了分类(category)、文章发布时间、文章作者、评论数、阅读量。
<div>
<span><a href="#">Django 博客教程</a></span>
<span><a href="#"><timedatetime="2012-11-09T23:15:57+00:00">2017年5月11日</time></a></span>
<span><a href="#">追梦人物</a></span>
<span><a href="#">4 评论</a></span>
<span><a href="#">588 阅读</a></span>
</div>
再次替换掉一些数据,由于评论数和阅读量暂时没法替换,因此先留着,我们在之后实现了这些功能后再来修改它,目前只替换分类、文章发布时间、文章作者:
<div>
<span><a href="#">{{ post.category.name }}</a></span>
<span><a href="#"><timedatetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
<span><a href="#">{{ post.author }}</a></span>
<span><a href="#">4 评论</a></span>
<span><a href="#">588 阅读</a></span>
</div>
这里 p 标签里显示的是摘要
<div>
<p>免费、中文、零基础,完整的项目,基于最新版 Django 1.10 和 Python 3.5。带你从零开始一步步开发属于自己的博客网站,帮助你以最快的速度掌握 Django
开发的技巧...</p>
<div>
<a href="#">继续阅读 <span>→</span></a>
</div>
</div>
替换成 post
的摘要:
<div>
<p>{{ post.excerpt }}</p>
<div>
<a href="#">继续阅读 <span>→</span></a>
</div>
</div>
再次访问首页,它显示:暂时还没有发布的文章!好吧,做了这么多工作,但是数据库中其实还没有任何数据呀!接下来我们就实际写几篇文章保存到数据库里,看看显示的效果究竟如何。
总结
本章节的代码位于:Step6: real blog index view。
如果遇到问题,请通过下面的方式寻求帮助。
在 真正的 Django 博客首页视图 - 追梦人物的博客 的评论区留言。
将问题的详细描述通过邮件发送到 djangostudyteam@163.com,一般会在 24 小时内回复。
更多Django 教程,请访问 追梦人物的博客。
Django 学习笔记(七)-- 将 django 中多个 app 放到同个文件夹 apps 处理
在 django 中需要创建多个 app,这个就需要创建一个 apps 文件,把所有的 app 放到同个文件夹,这个比较清楚,看起来也比较规范
app 默认创建:python manage.py startapp appName 默认创建在根目录下,首先在根目录创建 apps 文件夹,将所有 app 拉拖进去
这里取消 search for references ,open moved files in editor
点击 ok
这个时候运行点击 run,会出现运行错误
in check_apps_ready
raise AppRegistryNotReady("Apps aren''t loaded yet.")
django.core.exceptions.AppRegistryNotReady: Apps aren''t loaded yet.
这时候需要将 apps 的路径设定为项目的根目录,即引用 apps 下面的 app 不需要添加 apps.appName ,直接引用 appName 即可
对 apps 的文件夹右击–选择 mark directory as—- 选择 sources root,再点击 run,就可以正常运行了
但是这样只是在 pycharm 环境下运行,脱离这个环境项目就会出错,比如在终端
需要在 setting.py 文件中加入
import os
sys.path.insert(0,os.path.join(BASE_DIR,''apps''))
今天的关于Django 系列博客和七的分享已经结束,谢谢您的关注,如果想了解更多关于Anaconda+django写出第一个web app(七)、Django 博客开发教程 3 - 创建 Django 博客的数据库模型、Django 博客开发教程 6 - 真正的 Django 博客首页视图、Django 学习笔记(七)-- 将 django 中多个 app 放到同个文件夹 apps 处理的相关知识,请在本站进行查询。
本文标签: