Django之博客系统

项目流程:

1、搞清楚需求

  1. 基于用户认证组件auth和Ajax实现登录验证(图片验证码)
  2. 基于forms组件和Ajax实现注册功能
  3. 设计博客系统首页(文章列表渲染)
  4. 设计个人站点页面
  5. 文章详情页
  6. 实现文章点赞功能
  7. 实现文章的评论
    1. 文章的评论
    2. 评论的子评论
  8. 富文本编辑框以及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  
models.py

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
自定义form

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
    '''
自定义models.py

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/"
django配置文件

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> &nbsp;&nbsp;
                            <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;
                            <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>&nbsp;&nbsp;
                                  <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 %}
article_detail.html
<!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>
base.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>
classification.html
{% 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>发布于 &nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }})&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article.up_count }})&nbsp;&nbsp;
                        </div>
                    </div>
                    <hr>
                {% endfor %}

            </div>
{% endblock %}
home_site.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>
    <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> &nbsp;&nbsp;&nbsp;
                        <span>发布于 &nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;
                        <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }})&nbsp;&nbsp;
                        <span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article.up_count }})&nbsp;&nbsp;
                    </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>
index.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>
login.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>
not_found.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>
register.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 %}
add_article.html
{% 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 %}
backend.html
<!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>&nbsp;&nbsp;{{ 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>
base.html

 

4、功能测试

5、项目部署上线

 

posted @ 2018-07-11 14:34  py鱼  阅读(487)  评论(0编辑  收藏  举报
点我回主页