Django之博客系统
项目流程:
1、搞清楚需求
- 基于用户认证组件auth和Ajax实现登录验证(图片验证码)
- 基于forms组件和Ajax实现注册功能
- 设计博客系统首页(文章列表渲染)
- 设计个人站点页面
- 文章详情页
- 实现文章点赞功能
- 实现文章的评论
- 文章的评论
- 评论的子评论
- 富文本编辑框以及xss攻击
2、设计表结构
from django.contrib.auth.models import User,AbstractUser class UserInfo(AbstractUser): """ 用户信息 """ nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to='avatars/', default="/avatars/default.png") create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) blog = models.OneToOneField(to='Blog', to_field='nid', null=True,on_delete=models.CASCADE) def __str__(self): return self.username class Blog(models.Model): """ 博客信息表(站点表) """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='个人博客标题', max_length=64) site_name = models.CharField(verbose_name='站点名称', max_length=64) theme = models.CharField(verbose_name='博客主题', max_length=32) def __str__(self): return self.title class Category(models.Model): """ 博主个人文章分类表 """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='分类标题', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid',on_delete=models.CASCADE) def __str__(self): return self.title class Tag(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='标签名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid',on_delete=models.CASCADE) def __str__(self): return self.title class Article(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章描述') create_time = models.DateTimeField(verbose_name='创建时间',auto_now_add=True) content = models.TextField() comment_count=models.IntegerField(default=0) up_count=models.IntegerField(default=0) down_count=models.IntegerField(default=0) user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid',on_delete=models.CASCADE) category = models.ForeignKey(to='Category', to_field='nid', null=True,on_delete=models.CASCADE) tags = models.ManyToManyField( to="Tag", through='Article2Tag', through_fields=('article', 'tag'), ) def __str__(self): return self.title class Article2Tag(models.Model): nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid',on_delete=models.CASCADE) tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid',on_delete=models.CASCADE) class Meta: unique_together = [ ('article', 'tag'), ] def __str__(self): v = self.article.title + "---" + self.tag.title return v class ArticleUpDown(models.Model): """ 点赞表 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True,on_delete=models.CASCADE) article = models.ForeignKey("Article", null=True,on_delete=models.CASCADE) is_up = models.BooleanField(default=True) class Meta: unique_together = [ ('article', 'user'), ] class Comment(models.Model): """ 评论表 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid',on_delete=models.CASCADE) article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid',on_delete=models.CASCADE) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.CharField(verbose_name='评论内容', max_length=255) parent_comment=models.ForeignKey("self",null=True,on_delete=models.CASCADE) def __str__(self): return self.content 根评论:对文章的评论 子评论:对评论的评论 111 444 555 222 333 Comment nid user_id article_id content parent_comment_id(null=True) 1 1 1 111 null 2 2 1 222 null 3 3 1 333 null 4 4 1 444 1 5 5 1 555 4
3、按照每一个功能分别进行开发
Myforms.py
# -*- coding:utf-8 -*- # Author : yuchao # Data : 2018/7/12 09:19 from django import forms from django.forms import widgets from blog.models import UserInfo from django.core.exceptions import NON_FIELD_ERRORS, ValidationError # widgets属性是django对html输入元素的表示,负责渲染html和提取get/post的字典数据 class UserForm(forms.Form): user = forms.CharField(max_length=32, error_messages={"required": "该字段不能为空"}, label="用户名", # 渲染后,widget将设置属性attrs={"class": "form-control"} widget=widgets.TextInput(attrs={"class": "form-control"}, ) ) pwd = forms.CharField( max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}, )) re_pwd = forms.CharField(max_length=32, label='确认密码', widget=widgets.PasswordInput(attrs={"class": "form-control"}, )) email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class": "form-control"}, )) def clean_user(self): # clean_data是提交成功后的字典数据 val = self.cleaned_data.get("user") user = UserInfo.objects.filter(username=val).first() if not user: return val else: raise ValidationError("该用户已注册") # 描述错误的信息 def clean(self): pwd = self.cleaned_data.get("pwd") re_pwd = self.cleaned_data.get("re_pwd") # 判断两次密码都存在,且密码确认正常,返回数据 if pwd and re_pwd: if pwd == re_pwd: return self.cleaned_data else: raise ValidationError("两次密码不一致") # 可以传入元祖数据,定义错误信息,错误代码,传递给错误信息的参数 else: return self.cleaned_data
models.py
from django.db import models from django.contrib.auth.models import AbstractUser # verbose_name 给类模型,起一个可读的名字 class UserInfo(AbstractUser): """ 用户信息 """ nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) # 电话 avatar = models.FileField(upload_to='avatars/', default='avatars/default.png') # 默认头像 # 创建时间,auto_now_add作用是无法修改时间 create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) blog = models.OneToOneField(to="Blog", to_field='nid', null=True, on_delete=models.CASCADE) # UserInfo对象的返回结果,以用户名显示 def __str__(self): return self.username # 用户和博客,一对一 class Blog(models.Model): """ 博客信息 """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name="个人博客标题", max_length=64) site_name = models.CharField(verbose_name="站点名称", max_length=64) theme = models.CharField(verbose_name="博客主题", max_length=32) def __str__(self): return self.title # 博客分类 class Category(models.Model): """ 博主个人文章分类表 """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name="分类标题", max_length=32) #关联博客表,一个博客可以有多个分类 blog = models.ForeignKey(verbose_name="所属博客", to='Blog',to_field='nid',on_delete=models.CASCADE) def __str__(self): return self.title # 标签分类 # 一个blog对应多个tag class Tag(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name="标签名称", max_length=32) blog = models.ForeignKey(verbose_name="所属博客", to="Blog", to_field='nid', on_delete=models.CASCADE) # tag对象返回tag名字 def __str__(self): return self.title # 文章 class Article(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name="文章标题") desc = models.CharField(max_length=255, verbose_name="文章描述") create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) content = models.TextField() # 文章内容 comment_count = models.IntegerField(default=0) # 评论数 up_count = models.IntegerField(default=0) # 点赞数 down_count = models.IntegerField(default=0) # 点踩数 # 一对多,一个作者可以有多个文章,外键放在'多'的表里 user = models.ForeignKey(verbose_name="作者", to="UserInfo", to_field="nid", on_delete=models.CASCADE) # 一对多,一个分类可以有多个文章, category = models.ForeignKey(to="Category", to_field="nid", null=True, on_delete=models.CASCADE) # 一个文章可以有多个标签,一个标签可以有多个文章,多对多 ''' manytomanyfield字段会自动生成第三张表 使用through参数,找到自己编写的class,创建第三张表模型,方便扩展字段 ''' tags = models.ManyToManyField( to="Tag", through="Article2Tag", through_fields=("article", "tag"), ) def __str__(self): return self.title # 文章多对多关联标签表 class Article2Tag(models.Model): nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name="文章", to="Article", to_field="nid", on_delete=models.CASCADE) tag = models.ForeignKey(verbose_name="标签", to="Tag", to_field="nid", on_delete=models.CASCADE) class Meta: unique_together = [ ('article', 'tag'), ] def __str__(self): v = self.article.title + "---" + self.tag.title return v class ArticleUpDown(models.Model): ''' 点赞表 哪个用户对哪张表,进行点赞,还是踩灭 ''' nid = models.AutoField(primary_key=True) # 哪个用户操作的,关联用户表 user = models.ForeignKey("UserInfo", null=True, on_delete=models.CASCADE) # 对哪个文章操作的,关联文章表 article = models.ForeignKey("Article", null=True, on_delete=models.CASCADE) is_up = models.BooleanField(default=True) class Meta: # 通过两个字段保持唯一性,文章和用户的组合必须唯一 unique_together = [ ('article', 'user'), ] # 评论 class Comment(models.Model): ''' 评论表 ''' nid = models.AutoField(primary_key=True) # 关联用户表,一个用户可以有多条评论 user = models.ForeignKey(verbose_name="评论者", to="UserInfo", to_field="nid", on_delete=models.CASCADE) # 关联文章表,一个文章能有多个文章 article = models.ForeignKey(verbose_name="评论文章", to="Article", to_field='nid', on_delete=models.CASCADE) create_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) # 内容 content = models.CharField(verbose_name="评论内容", max_length=255) # 父评论,自关联写法,关联comment,可以写 self parent_comment = models.ForeignKey('self', null=True,on_delete=models.CASCADE) def __str__(self): return self.content ''' 根评论:对文章的评论 子评论:对评论的评论 --------- 如此结构,无法存储子评论 comment表 nid user_id article_id create_time content 这里应该有个parent_comment父评论(自关联) 1 1 1 xx 11111 2 2 1 xx 2222 3 3 1 xx 33333 '''
settings.py
""" Django settings for cnblog project. Generated by 'django-admin startproject' using Django 2.0.1. For more information on this file, see https://docs.djangoproject.com/en/2.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.0/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/2.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'eclvh_i(m99c*)$)j$5+pgpjhuv!4lc#z9t3j00d)x3c&*js)x' # 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', "blog", ] 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 = 'cnblog.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 = 'cnblog.wsgi.application' # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME':'cnblog', # 要连接的数据库,连接前需要创建好 'USER':'root',# 连接数据库的用户名 'PASSWORD':'',# 连接数据库的密码 'HOST':'127.0.0.1', # 连接主机,默认本级 'PORT':3306 # 端口 默认3306 } } # Password validation # https://docs.djangoproject.com/en/2.0/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', }, ] '''这里不写这个, 模板继承AbstractUser报错: auth.User.groups: (fields.E304) ''' AUTH_USER_MODEL="blog.UserInfo" # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.0/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS=[ os.path.join(BASE_DIR,"static"), ] # 与用户上传相关的配置 MEDIA_ROOT=os.path.join(BASE_DIR,"media") MEDIA_URL="/media/" LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } } EMAIL_HOST = 'smtp.exmail.qq.com' # 如果是 163 改成 smtp.163.com EMAIL_PORT = 465 EMAIL_HOST_USER = '' # 帐号 EMAIL_HOST_PASSWORD = '' # 密码 # DEFAULT_FROM_EMAIL = EMAIL_HOST_USER EMAIL_USE_SSL = True LOGIN_URL="/login/"
urls.py
"""cnblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/2.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path,re_path from django.views.static import serve from blog import views from cnblog import settings from django.urls import include urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), re_path('^$', views.index), path('get_validCode_img/', views.get_valid_code_img), path('register/', views.register), # 文本编辑器上传图片url path('upload/', views.upload), # 后台管理url re_path("cn_backend/$",views.cn_backend), re_path("cn_backend/add_article/$",views.add_article), # 点赞 path("digg/",views.digg), # 评论 path("comment/",views.comment), # 获取评论树相关数据 path("get_comment_tree/",views.get_comment_tree), # media配置: re_path(r"media/(?P<path>.*)$",serve,{"document_root":settings.MEDIA_ROOT}), re_path('^(?P<username>\w+)/articles/(?P<article_id>\d+)$', views.article_detail), # article_detail(request,username="yuan","article_id":article_id) # 个人站点的跳转 re_path('^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site), # home_site(reqeust,username="yuan",condition="tag",param="python") # 个人站点url re_path('^(?P<username>\w+)/$', views.home_site), # home_site(reqeust,username="yuan") ]
模板文件配置
templates文件夹
{% extends "base.html" %} {% block content %} {% csrf_token %} <div class="article_info"> <h3 class="text-center title">{{ article_obj.title }}</h3> <div class="cont"> {{ article_obj.content|safe }} </div> <div class="clearfix"> <div id="div_digg"> <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span> </div> <div class="buryit action"> <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> </div> <div class="comments list-group"> <p class="tree_btn">评论树</p> <div class="comment_tree"> </div> <script> $.ajax({ url: "/get_comment_tree/", type: "get", data: { article_id: "{{ article_obj.pk }}" }, success: function (comment_list) { console.log(comment_list); $.each(comment_list, function (index, comment_object) { var pk = comment_object.pk; var content = comment_object.content; var parent_comment_id = comment_object.parent_comment_id; var s = '<div class="comment_item" comment_id=' + pk + '><span>' + content + '</span></div>'; if (!parent_comment_id) { $(".comment_tree").append(s); } else { $("[comment_id=" + parent_comment_id + "]").append(s); } }) } }) </script> <p>评论列表</p> <ul class="list-group comment_list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href=""># {{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href=""><span>{{ comment.user.username }}</span></a> <a class="pull-right reply_btn" username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}">回复</a> </div> {% if comment.parent_comment_id %} <div class="pid_info well"> <p> {{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }} </p> </div> {% endif %} <div class="comment_con"> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> <p>发表评论</p> <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"> </p> <p>评论内容:</p> <textarea name="" id="comment_content" cols="60" rows="10"></textarea> <p> <button class="btn btn-default comment_btn">提交评论</button> </p> </div> <script> // 点赞请求 $("#div_digg .action").click(function () { var is_up = $(this).hasClass("diggit"); $obj = $(this).children("span"); $.ajax({ url: "/digg/", type: "post", data: { "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), "is_up": is_up, "article_id": "{{ article_obj.pk }}", }, success: function (data) { console.log(data); if (data.state) { var val = parseInt($obj.text()); $obj.text(val + 1); } else { var val = data.handled ? "您已经推荐过!" : "您已经反对过!"; $("#digg_tips").html(val); setTimeout(function () { $("#digg_tips").html("") }, 1000) } } }) }) // 评论请求 var pid = ""; $(".comment_btn").click(function () { var content = $("#comment_content").val(); if (pid) { var index = content.indexOf("\n"); content = content.slice(index + 1) } $.ajax({ url: "/comment/", type: "post", data: { "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), "article_id": "{{ article_obj.pk }}", "content": content, pid: pid }, success: function (data) { console.log(data); var create_time = data.create_time; var username = data.username; var content = data.content; var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href=""><span>${username}</span></a> </div> <div class="comment_con"> <p>${content}</p> </div> </li>`; $("ul.comment_list").append(s); // 清空评论框 pid = "", $("#comment_content").val(""); } }) }); // 回复按钮事件 $(".reply_btn").click(function () { $('#comment_content').focus(); var val = "@" + $(this).attr("username") + "\n"; $('#comment_content').val(val); pid = $(this).attr("comment_pk"); }) </script> </div> {% endblock %}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/blog/css/home_site.css"> <link rel="stylesheet" href="/static/theme/{{ blog.theme }}"> <link rel="stylesheet" href="/static/blog/css/article_detail.css"> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> <script src="/static/js/jquery-3.2.1.min.js"></script> </head> <body> <div class="header"> <div class="content"> <p class="title"> <span>{{ blog.title }}</span> <a href="/cn_backend/" class="backend">管理</a> </p> </div> </div> <div class="container"> <div class="row"> <div class="col-md-3 menu"> {% load my_tags %} {% get_classification_style username %} </div> <div class="col-md-9"> {% block content %} {% endblock %} </div> </div> </div> </body> </html>
<div> <div class="panel panel-warning"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">随笔分类</div> <div class="panel-body"> {% for cate in cate_list %} <p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-success"> <div class="panel-heading">随笔归档</div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p> {% endfor %} </div> </div> </div>
{% extends 'base.html' %} {% block content %} <div class="article_list"> {% for article in article_list %} <div class="article-item clearfix"> <h5><a href="/{{ article.user.username }}/articles/{{ article.pk }}">{{ article.title }}</a></h5> <div class="article-desc"> {{ article.desc }} </div> <div class="small pub_info pull-right"> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article.up_count }}) </div> </div> <hr> {% endfor %} </div> {% endblock %}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> <script src="/static/js/jquery-3.2.1.min.js"></script> <script src="/static/blog/bs/js/bootstrap.min.js"></script> <style> #user_icon { font-size: 18px; margin-right: 10px; vertical-align: -3px; } .pub_info{ margin-top: 10px; } .pub_info .glyphicon-comment{ vertical-align: -1px; } </style> </head> <body> <nav class="tab navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客园</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">随笔 <span class="sr-only">(current)</span></a></li> <li><a href="#">新闻</a></li> <li><a href="#">博文</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if request.user.is_authenticated %} <li><a href="#"><span id="user_icon" class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li><a href="/cn_backend/">管理</a></li> <li><a href="/logout/">注销</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> {% else %} <li><a href="/login/">登录</a></li> <li><a href="/register/">注册</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container-fluid"> <div class="row"> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-info"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-danger"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> <div class="col-md-6"> <div class="article_list"> {% for article in article_list %} <div class="article-item small"> <h5><a href="/{{ article.user.username }}/articles/{{ article.pk }}">{{ article.title }}</a></h5> <div class="article-desc"> <span class="media-left"> <a href="/{{ article.user.username }}/"><img width="56" height="56" src="media/{{ article.user.avatar }}" alt=""></a> </span> <span class="media-right"> {{ article.desc }} </span> </div> <div class="small pub_info"> <span><a href="/{{ article.user.username }}/">{{ article.user.username }}</a></span> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article.up_count }}) </div> </div> <hr> {% endfor %} </div> </div> <div class="col-md-3"> <div class="panel panel-primary"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-default"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> </head> <body> <h3>登录页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="user">用户名</label> <input type="text" id="user" class="form-control"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" id="pwd" class="form-control"> </div> <div class="form-group"> <label for="pwd">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" class="form-control" id="valid_code"> </div> <div class="col-md-6"> <img width="270" height="36" id="valid_code_img" src="/get_validCode_img/" alt=""> </div> </div> </div> <input type="button" class="btn btn-default login_btn" value="submit"><span class="error"></span> <a href="/register/" class="btn btn-success pull-right">注册</a> </form> </div> </div> </div> <script src="/static/js/jquery-3.2.1.min.js"></script> <script> // 刷新验证码 $("#valid_code_img").click(function () { $(this)[0].src += "?" }); // 登录验证 $(".login_btn").click(function () { $.ajax({ url: "", type: "post", data: { user: $("#user").val(), pwd: $("#pwd").val(), valid_code: $("#valid_code").val(), csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(), }, success: function (data) { console.log(data); if (data.user) { if (location.search){ location.href = location.search.slice(6) } else { location.href = "/index/" } } else { $(".error").text(data.msg).css({"color": "red", "margin-left": "10px"}); setTimeout(function(){ $(".error").text(""); },1000) } } }) }) </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> </head> <body> <div class="container" style="margin-top: 100px"> <div class="text-center"> <a href="http://www.cnblogs.com/"><img src="/static/blog/img/logo_small.gif" alt="cnblogs"></a> <p><b>404.</b> 抱歉! 您访问的资源不存在!</p> <p class="d">请确认您输入的网址是否正确,如果问题持续存在,请发邮件至 404042726@qq.com 与 <strong style="font-size: 28px">老村长</strong> 联系。</p> <p><a href="/">返回网站首页</a></p> </div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> <script src="/static/js/jquery-3.2.1.min.js"></script> <style> #avatar_img { margin-left: 20px; } #avatar { display: none; } .error { color: red; } </style> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form id="form"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} <span class="error pull-right"></span> </div> {% endfor %} <div class="form-group"> <label for="avatar"> 头像 <img id="avatar_img" width="60" height="60" src="/static/blog/img/default.png" alt=""> </label> <input type="file" id="avatar" name="avatar"> </div> <input type="button" class="btn btn-default reg_btn" value="submit"><span class="error"></span> </form> </div> </div> </div> <script> // 头像预览 $("#avatar").change(function () { // 获取用户选中的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象的路径 var reader = new FileReader(); reader.readAsDataURL(file_obj); // 修改img的src属性 ,src=文件对象的路径 reader.onload = function () { $("#avatar_img").attr("src", reader.result) }; }); // 基于Ajax提交数据 $(".reg_btn").click(function () { //console.log($("#form").serializeArray()); var formdata = new FormData(); var request_data = $("#form").serializeArray(); $.each(request_data, function (index, data) { formdata.append(data.name, data.value) }); formdata.append("avatar", $("#avatar")[0].files[0]); $.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success: function (data) { //console.log(data); if (data.user) { // 注册成功 location.href="/login/" } else { // 注册失败 //console.log(data.msg) // 清空错误信息 $("span.error").html(""); $(".form-group").removeClass("has-error"); // 展此次提交的错误信息! $.each(data.msg, function (field, error_list) { console.log(field, error_list); if (field=="__all__"){ $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error"); } $("#id_" + field).next().html(error_list[0]); $("#id_" + field).parent().addClass("has-error"); }) } } }) }) </script> </body> </html>
backend文件夹
{% extends 'backend/base.html' %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="add_article"> <div class="alert-success text-center">添加文章</div> <div class="add_article_region"> <div class="title form-group"> <label for="">标题</label> <div> <input type="text" name="title"> </div> </div> <div class="content form-group"> <label for="">内容(Kindeditor编辑器,不支持拖放/粘贴上传图片) </label> <div> <textarea name="content" id="article_content" cols="30" rows="10"></textarea> </div> </div> <input type="submit" class="btn btn-default"> </div> </div> </form> <script src="/static/js/jquery-3.2.1.min.js"></script> <script charset="utf-8" src="/static/blog/kindeditor/kindeditor-all.js"></script> <script> KindEditor.ready(function(K) { window.editor = K.create('#article_content',{ width:"800", height:"600", resizeType:0, uploadJson:"/upload/", extraFileUploadParams:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val() }, filePostName:"upload_img" }); }); </script> {% endblock %}
{% extends 'backend/base.html' %} {% block content %} <div class="article_list small"> <table class="table table-hover table-striped"> <thead> <th>标题</th> <th>评论数</th> <th>点赞数</th> <th>操作</th> <th>操作</th> </thead> <tbody> {% for article in article_list %} <tr> <td>{{ article.title }}</td> <td>{{ article.comment_count }}</td> <td>{{ article.up_count }}</td> <td><a href="">编辑</a></td> <td><a href="">删除</a></td> </tr> {% endfor %} </tbody> </table> </div> {% endblock %}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>博客后台管理 - 博客园</title> <link rel="stylesheet" href="/static/blog/bs/css/bootstrap.css"> <script src="/static/js/jquery-3.2.1.min.js"></script> <script src="/static/blog/bs/js/bootstrap.min.js"></script> <link rel="stylesheet" href="/static/blog/css/backend.css"> </head> <body> <div class="header"> <p class="title"> 后台管理 <a class="info" href="/logout/">注销</a> <span class="info"><span class="glyphicon glyphicon-user"></span> {{ request.user.username }}</span> </p> </div> <div class="container"> <div class="col-md-3"> <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingOne"> <h4 class="panel-title"> <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> 操作 </a> </h4> </div> <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne"> <div class="panel-body"> <p><a href="/cn_backend/add_article/">添加文章</a></p> </div> </div> </div> </div> </div> <div class="col-md-9"> <div> <!-- Nav tabs --> <ul class="nav nav-tabs" role="tablist"> <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">文章</a></li> <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">日记</a></li> <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">眼镜</a> </li> <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">相册</a> </li> </ul> <!-- Tab panes --> <div class="tab-content"> <div role="tabpanel" class="tab-pane active" id="home"> {% block content %} {% endblock %} </div> <div role="tabpanel" class="tab-pane" id="profile"> <img src="/static/blog/img/meinv2.jpg" alt=""> <img src="/static/blog/img/meinv3.jpg" alt=""> <img class="pull-right" src="/static/blog/img/meinv.jpg" alt=""> </div> <div role="tabpanel" class="tab-pane" id="messages"> <img width="180" height="180" src="/static/blog/img/hashiqi2.jpg" alt=""> <img width="180" height="180" src="/static/blog/img/dogg4.jpg" alt=""> <img width="180" height="180" src="/static/blog/img/linhaifeng.jpg" alt=""><br> <img width="180" height="180" src="/static/blog/img/dogg3.jpeg" alt=""> <img width="180" height="180" src="/static/blog/img/dogge2.jpg" alt=""> <img width="180" height="180" src="/static/blog/img/dogg5.jpg" alt=""> </div> <div role="tabpanel" class="tab-pane" id="settings"> </div> </div> </div> </div> </div> </body> </html>
4、功能测试
5、项目部署上线
努力成为一个开发者
个人站点:www.pythonav.cn