博客项目搭建:
开发一个博客系统。
博客系统包含两部分:
-
博客后台,主要用于对 文章、分类、标签 进行管理(类似于博客园后台)。(需登录)
-
博客主站,用于文章的展示。(无需登录)
自行设计表结构(至少包含以下字段):
用户表
用户名 密码 昵称(博客名称)
文章表
标题 文章内容 发表时间
分类表
名称
标签表
标签名称
注:分类和文章是一对多的关系,文章和标签是多对多的关系。关系字段自行设计。
-
完成后台系统的登录、注销功能,
-
未登录时,右上角显示登录按钮
-
登录后,右上角显示用户名,并且可以管理文章、分类、标签。
-
-
完成后台管理(分类、标签、文章)
-
表格展示各个表的字段内容
-
新增功能
新增文章时需必须选择一个文章分类,可以选择多个标签
-
完成编辑功能
-
完成删除功能
使用一个URL地址和一个视图函数完成
-
-
完成博客主站功能
-
博客页:展示出博主的昵称,所有文章标题和简介、时间,点击文章标题跳转至文章页
-
文章页:展示文章标题、详细内容、分类、标签
-
-
new project:
python manage.py startapp backend:
blog\settings:
"""
Django settings for blog project.
Generated by 'django-admin startproject' using Django 1.11.26.
For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'iaiz14-lbw$&4l@3vki8u54(-as&d6q1)=h+^uk)q@_jwra8lz'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'web.apps.WebConfig',
"backend.apps.BackendConfig", #注册backend.apps.BackendConfig
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'blog.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join ( BASE_DIR, 'templates' )]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'blog.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
#设置默认数据库:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = False #USE_L10N使用
USE_TZ = True
DATETIME_FORMAT = "Y-m-d H:i:s"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/'
#定义静态文件路径:
STATICFILES_DIRS = [
os.path.join(BASE_DIR,"static")
]
blog\urls:
"""blog URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url,include #include路由分发到不同的urls里面:
from django.contrib import admin
#配置不同的urls:
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r"^",include("web.urls")),
url(r"^back/",include("backend.urls")),
]
templates\article:
{% extends 'blog.html' %}
{% block content %}
<div class="blog-post">
<h2 class="blog-post-title">{{ article_obj.title }}</h2>
<p class="blog-post-meta">{{ article_obj.create_time }}</p>
{{ article_obj.content|safe }}
</div>
{% endblock %}
web\urls:
from django.conf.urls import url
from web import views
urlpatterns = [
url(r'^login/',views.login,name='login'),
url(r'^logout/',views.logout,name='logout'),
url(r"^$",views.blog,name='blog'),
url(r"^article/(\d+)/$",views.article,name='article'),
]
web\views:
from django.shortcuts import render,redirect
from web import models
# Create your views here.
#定义登录功能:
def login(request):
if request.method == "POST":
#获取用户名和密码:
name = request.POST.get("login")
pwd = request.POST.get("pwd")
#拿到用户名和密码的对象:
obj = models.User.objects.filter(name=name,pwd=pwd).first()
#判断对象是否存在:
if not obj:
return render(request,"login.html",{"error":"用户名或密码错误"})
#用户名和密码正确时、保存登录状态、
request.session["is_login"] = True
#登录正确后保存用户名:
request.session["nick"] = obj.nick
url = request.GET.get("url")
if url:
return redirect(url)
#跳转到文章页:
return redirect("article_list")
return render(request,"login.html")
#定义退出功能:
def logout(request):
request.session.flush()
return redirect("blog")
#定义博客功能:
def blog(request):
user = models.User.objects.all().first()
all_articles = models.Article.objects.all()
return render(request,"blog.html",{"user":user,"all_articles":all_articles})
#定义文章功能:
def article(request,pk):
article_obj = models.Article.objects.get(pk=pk)
return render(request,"article.html",{"article_obj":article_obj})
backend\urls:
from django.conf.urls import url
from backend import views
urlpatterns = [
url(r"^category_list/",views.category_list,name="category_list"), #定义展示分类路由、反向解析name="category_list"
url(r"^add_category/",views.add_category,name="add_category"),
url(r"^edit_category/(\d+)/",views.edit_category,name="edit_category"),
url(r"^tag_list/",views.tag_list,name="tag_list"), #展示标签路由
url(r"^add_tag/",views.add_tag,name="add_tag"),
url(r"^edit_tag/(\d+)/",views.edit_tag,name="edit_tag"),
url ( r"^article_list/", views.article_list, name="article_list" ),#展示文章列表
url ( r"^add_article/", views.add_article, name="add_article" ),
url ( r"^edit_article/(\d+)/", views.edit_article, name="edit_article" ),
url(r"^delete/(\w+)/(\d+)/",views.delete,name="del"), #删除功能
]
backend\views:
from django.shortcuts import render,redirect,reverse
from web import models
# Create your views here.
#定义登录装饰器:
def login_required(func):
def inner(request,*args,**kwargs):
is_login = request.session.get("is_login")
if not is_login:
url = request.path_info
return redirect("{}?url={}".format(reverse('login'),url))
return func(request,*args,**kwargs)
return inner
@login_required
#定义分类功能category_list:
def category_list(request):
#获取所有的分类:
all_categories = models.Category.objects.all()
#跳转到分类页面:
return render(request,"category_list.html",{"all_categories":all_categories})
@login_required
#定义增加分类功能add_category:
def add_category(request):
if request.method == "POST":
name = request.POST.get("name")
models.Category.objects.create(name=name)
return redirect("category_list")
return render( request, "category_change.html" )
@login_required
#定义编辑分类功能edit_category:
def edit_category(request,pk):
#获取要编辑的对象:
obj = models.Category.objects.filter(pk=pk).first()
if request.method == "POST":
name = request.POST.get("name")
obj.name = name
obj.save()
return redirect("category_list")
return render(request,"category_change.html",{"obj":obj})
@login_required
#定义展示标签列表功能tag_list:
def tag_list(request):
#获取所有的标签:
all_tags = models.Tag.objects.all()
return render(request,"tag_list.html",{"all_tags":all_tags})
@login_required
#定义增加标签功能add_tag:
def add_tag(request):
if request.method == "POST":
name = request.POST.get("name")
models.Tag.objects.create(name=name)
return redirect("tag_list")
return render(request,"tag_change.html")
@login_required
#定义编辑标签功能edit_tag:
def edit_tag(request,pk):
#获取要编辑的标签:
obj = models.Tag.objects.filter(pk=pk).first()
if request.method == "POST":
name = request.POST.get("name")
obj.name = name
obj.save()
return redirect("tag_list")
return render(request,"tag_change.html",{"obj":obj})
@login_required
#定义展示文章功能article_list:
def article_list(request):
#获取所有的文章:
all_articles = models.Article.objects.all()
return render(request,"article_list.html",{"all_articles":all_articles})
@login_required
#定义新增文章功能add_article:
def add_article(request):
if request.method == "POST":
title = request.POST.get("title")
content = request.POST.get("content")
category_id = request.POST.get("category_id")
tag_id = request.POST.getlist("tag_id")
obj = models.Article.objects.create(title=title,content=content,category_id=category_id,)
obj.tags.set(tag_id)
return redirect("article_list")
all_categories = models.Category.objects.all()
all_tags = models.Tag.objects.all()
return render(request,"article_change.html",{"all_categories":all_categories,"all_tags":all_tags})
@login_required
#定义编辑文章功能edit_article:
def edit_article(request,pk):
obj = models.Article.objects.filter(pk=pk).first()
if request.method == "POST":
title = request.POST.get("title")
content = request.POST.get("content")
category_id = request.POST.get("category_id")
tag_id = request.POST.getlist("tag_id")
obj.tags.set(tag_id)
models.Article.objects.filter(pk=pk).update(title=title,content=content,category_id=category_id,)
return redirect("article_list")
all_categories = models.Category.objects.all()
all_tags = models.Tag.objects.all()
return render(request,"article_change.html",{"obj":obj,"all_categories":all_categories,"all_tags":all_tags})
@login_required
#定义删除功能delete:
def delete(request,table,pk):
#使用反射获取类:
table_class = getattr(models,table.capitalize())
table_class.objects.filter(pk=pk).delete()
return redirect("{}_list".format(table))
login:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<title>登录界面</title>
{% load static %} #静态文件的模板语法:
<link href="{% static 'css/default.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'css/styles.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'css/demo.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'css/loaders.css' %}" rel="stylesheet" type="text/css">
<link id="layuicss-skinlayercss" rel="stylesheet"
href="http://jq22.com/demo/jQueryLogin201708272212/layui/css/modules/layer/default/layer.css?v=3.0.3303"
media="all">
</head>
<body style="">
<canvas class="pg-canvas" width="1366" height="238"></canvas>
<div class="login">
<div class="login_title">
<span>管理员登录</span>
</div>
<div class="login_fields">
<form action="" method="post">
{% csrf_token %}
<div class="login_fields__user">
<div class="icon">
<img alt="" src="{% static 'img/user_icon_copy.png' %}">
</div>
<input name="login" placeholder="用户名" maxlength="16" type="text" autocomplete="off" value="kbcxy">
<div class="validation">
<img alt="" src="{% static 'img/tick.png' %}">
</div>
</div>
<div class="login_fields__password">
<div class="icon">
<img alt="" src="{% static 'img/lock_icon_copy.png' %}">
</div>
<input name="pwd" placeholder="密码" maxlength="16" type="text" autocomplete="off">
<div class="validation">
<img alt="" src="{% static 'img/tick.png' %}">
</div>
</div>
<div class="login_fields__password">
<div class="icon">
<img alt="" src="{% static 'img/key.png' %}">
</div>
<input name="code" placeholder="验证码" maxlength="4" type="text" autocomplete="off">
<div class="validation" style="opacity: 1; right: -5px;top: -3px;">
<canvas class="J_codeimg" id="myCanvas" onclick="Code();">对不起,您的浏览器不支持canvas,请下载最新版浏览器!</canvas>
</div>
</div>
<div class="login_fields__submit">
<input type="submit" value="登录">
</div>
</form>
</div>
<div class="success">
</div>
<div class="disclaimer">
<p>欢迎登陆后台管理系统</p>
</div>
</div>
<div class="authent">
<div class="loader" style="height: 44px;width: 44px;margin-left: 28px;">
<div class="loader-inner ball-clip-rotate-multiple">
<div></div>
<div></div>
<div></div>
</div>
</div>
<p>认证中...</p>
</div>
<div class="OverWindows"></div>
<link href="{% static 'css/layui.css' %}" rel="stylesheet" type="text/css">
<script src="http://www.jq22.com/jquery/jquery-1.10.2.js"></script>
<script src="https://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="{% static 'js/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/stopExecutionOnTimeout.js?t=1' %}"></script>
<script src="{% static 'js/layui.js' %}" type="text/javascript"></script>
<script src="{% static 'js/Particleground.js' %}" type="text/javascript"></script>
<script src="{% static 'Js/Treatment.js' %}" type="text/javascript"></script>
<script src="{% static 'js/jquery.mockjax.js' %}" type="text/javascript"></script>
<script type="text/javascript">
var canGetCookie = 0;//是否支持存储Cookie 0 不支持 1 支持
var ajaxmockjax = 1;//是否启用虚拟Ajax的请求响 0 不启用 1 启用
//默认账号密码
var truelogin = "kbcxy";
var truepwd = "mcwjs";
var CodeVal = 0;
Code();
function Code() {
if (canGetCookie == 1) {
createCode("AdminCode");
var AdminCode = getCookieValue("AdminCode");
showCheck(AdminCode);
} else {
showCheck(createCode(""));
}
}
function showCheck(a) {
CodeVal = a;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, 1000, 1000);
ctx.font = "80px 'Hiragino Sans GB'";
ctx.fillStyle = "#E8DFE8";
ctx.fillText(a, 0, 100);
}
$(document).keypress(function (e) {
// 回车键事件
if (e.which == 13) {
$('input[type="button"]').click();
}
});
//粒子背景特效
$('body').particleground({
dotColor: '#E8DFE8',
lineColor: '#133b88'
});
$('input[name="pwd"]').focus(function () {
$(this).attr('type', 'password');
});
$('input[type="text"]').focus(function () {
$(this).prev().animate({'opacity': '1'}, 200);
});
$('input[type="text"],input[type="password"]').blur(function () {
$(this).prev().animate({'opacity': '.5'}, 200);
});
$('input[name="login"],input[name="pwd"]').keyup(function () {
var Len = $(this).val().length;
if (!$(this).val() == '' && Len >= 5) {
$(this).next().animate({
'opacity': '1',
'right': '30'
}, 200);
} else {
$(this).next().animate({
'opacity': '0',
'right': '20'
}, 200);
}
});
var open = 0;
layui.use('layer', function () {
var msgalert = '默认账号:' + truelogin + '<br/> 默认密码:' + truepwd;
var index = layer.alert(msgalert, {
icon: 6,
time: 4000,
offset: 't',
closeBtn: 0,
title: '友情提示',
btn: [],
anim: 2,
shade: 0
});
layer.style(index, {
color: '#777'
});
//非空验证
$('input[type="button"]').click(function () {
var login = $('input[name="login"]').val();
var pwd = $('input[name="pwd"]').val();
var code = $('input[name="code"]').val();
if (login == '') {
ErroAlert('请输入您的账号');
} else if (pwd == '') {
ErroAlert('请输入密码');
} else if (code == '' || code.length != 4) {
ErroAlert('输入验证码');
} else {
//认证中..
fullscreen();
$('.login').addClass('test'); //倾斜特效
setTimeout(function () {
$('.login').addClass('testtwo'); //平移特效
}, 300);
setTimeout(function () {
$('.authent').show().animate({right: -320}, {
easing: 'easeOutQuint',
duration: 600,
queue: false
});
$('.authent').animate({opacity: 1}, {
duration: 200,
queue: false
}).addClass('visible');
}, 500);
//登陆
var JsonData = {login: login, pwd: pwd, code: code};
//此处做为ajax内部判断
var url = "";
if (JsonData.login == truelogin && JsonData.pwd == truepwd && JsonData.code.toUpperCase() == CodeVal.toUpperCase()) {
url = "Ajax/Login";
} else {
url = "Ajax/LoginFalse";
}
AjaxPost(url, JsonData,
function () {
//ajax加载中
},
function (data) {
//ajax返回
//认证完成
setTimeout(function () {
$('.authent').show().animate({right: 90}, {
easing: 'easeOutQuint',
duration: 600,
queue: false
});
$('.authent').animate({opacity: 0}, {
duration: 200,
queue: false
}).addClass('visible');
$('.login').removeClass('testtwo'); //平移特效
}, 2000);
setTimeout(function () {
$('.authent').hide();
$('.login').removeClass('test');
if (data.Status == 'ok') {
//登录成功
$('.login div').fadeOut(100);
$('.success').fadeIn(1000);
$('.success').html(data.Text);
//跳转操作
} else {
AjaxErro(data);
}
}, 2400);
})
}
})
})
var fullscreen = function () {
elem = document.body;
if (elem.webkitRequestFullScreen) {
elem.webkitRequestFullScreen();
} else if (elem.mozRequestFullScreen) {
elem.mozRequestFullScreen();
} else if (elem.requestFullScreen) {
elem.requestFullscreen();
} else {
//浏览器不支持全屏API或已被禁用
}
}
if (ajaxmockjax == 1) {
$.mockjax({
url: 'Ajax/Login',
status: 200,
responseTime: 50,
responseText: {"Status": "ok", "Text": "登陆成功<br /><br />欢迎回来"}
});
$.mockjax({
url: 'Ajax/LoginFalse',
status: 200,
responseTime: 50,
responseText: {"Status": "Erro", "Erro": "账号名或密码或验证码有误"}
});
}
</script>
<div class="layui-layer-move"></div>
</body>
</html>
pycharm编辑器页面:
网站页面展示效果:
![](https://img2018.cnblogs.com/i-beta/1452370/201912/1452370-20191224192416207-445396935.png)
web\models:
from django.db import models
# Create your models here.
#定义用户表:用户名、密码、昵称(博客名称)
class User(models.Model):
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=32)
nick = models.CharField(max_length=32)
#定义文章表:标题、文章内容、发表时间
class Article(models.Model):
title = models.CharField(max_length=32)
content = models.TextField() #TextField是文本长整型
create_time = models.DateTimeField(auto_now_add=True) #DateTimeField是日期时间字段、auto_now_add=True意思是只有新增时才会增加
#定义分类和文章是一对多的关系、分类是一、文章是多、外键是分类的字符串字段形式、定义在文章表里面:
category = models.ForeignKey("Category",on_delete=models.SET_NULL,null=True)
#定义文章和标签是多对多的关系用ManyToManyField
tags = models.ManyToManyField("Tag")
#定义分类表:名称
class Category(models.Model):
name = models.CharField(max_length=32)
#定义标签表:标签名称
class Tag(models.Model):
name = models.CharField(max_length=32)
数据库迁移命令:
python manage.py makemigrations
python manage.py migrate