【django之博客系统开发】

一、项目简介

使用django开发一套博客系统,参考博客园。

需求如下:

 

项目结构:

 

二、全部代码

from django.db import models

# Create your models here.
from django.contrib.auth.models import AbstractUser

class UserInfo(AbstractUser):
    """
    用户信息
    """
    nid = models.AutoField(primary_key=True)
    nickname = models.CharField(verbose_name='昵称', max_length=32)
    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)

    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 = models.CharField(verbose_name='个人博客后缀', max_length=32, unique=True)
    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')

    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')
    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='文章描述')

    comment_count= models.IntegerField(default=0)
    up_count = models.IntegerField(default=0)
    down_count = models.IntegerField(default=0)

    create_time = models.DateTimeField(verbose_name='创建时间')

    homeCategory = models.ForeignKey(to='Category', to_field='nid', null=True)
    #siteDetaiCategory = models.ForeignKey(to='SiteCategory', to_field='nid', null=True)

    user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid')
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
    )


    def __str__(self):
        return self.title
class ArticleDetail(models.Model):
    """
    文章详细表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField()
    article = models.OneToOneField(to='Article', to_field='nid')

class Comment(models.Model):
    """
    评论表
    """
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid')
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid')
    content = models.CharField(verbose_name='评论内容', max_length=255)
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

    parent_comment = models.ForeignKey('self', null=True)


    def __str__(self):
        return self.content
class ArticleUpDown(models.Model):
    """
    点赞表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True)
    article = models.ForeignKey("Article", null=True)
    is_up=models.BooleanField(default=True)

    class Meta:
        #联合唯一
        unique_together = [
            ('article', 'user'),
        ]
class Article2Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')
    tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid')

    class Meta:
        unique_together = [
            ('article', 'tag'),
        ]

    def __str__(self):
        v=self.article.title+"----"+self.tag.title
        return v
models.py

 

"""
Django settings for blog_s19 project.

Generated by 'django-admin startproject' using Django 1.11.4.

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 = ')0*jv79r5fmmvc4^hsb$%2nqy#md91nk830ef5n%9tl!irn5we'

# 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.apps.BlogConfig',  #注册app#
]

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_s19.urls'

# 添加以后可以扩展auth认证的表
AUTH_USER_MODEL = "blog.UserInfo"

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_s19.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 = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/


# static: 与服务器本身相关的静态文件

STATIC_URL = '/static/'

STATICFILES_DIRS=[
    os.path.join(BASE_DIR,"blog","static"),
]


#media: 用户上传的文件

MEDIA_URL="/media/"
MEDIA_ROOT=os.path.join(BASE_DIR,"blog","media")
settings.py

 

"""blog_s19 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
from django.contrib import admin
from blog import views
from django.views.static import serve
from blog_s19 import settings

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/',views.login),
    url(r'^get_valid_img/',views.get_valid_img),
    url(r'^index/',views.index),
    url(r'^register/',views.register),
    url(r'^$',views.index),
    url(r'^logout/$',views.log_out),

    #blog分发
    url(r'^blog/',include('blog.urls')),
    #media配置
    url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
    #文本编辑器上传
    url(r'^upload_img/',views.upload_img),


]
blog_s19/urls.py

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from django.conf.urls import url
from django.contrib import admin
from blog import views
from django.views.static import serve
from blog_s19 import settings
import re

from blog import views

urlpatterns = [
    # # 个人站点页面
    # url(r'^(?P<username>\w+)/$', views.home_site),
    # # 归档
    # url(r'^(?P<username>\w+)/(?P<condition>cate|tag|date)/(?P<param>.*)', views.home_site),
    # # 文章详细页
    # url(r'^(?P<username>\w+)/articles/(?P<article_id>\d+)', views.article_detail),
    # # 后台管理
    # url(r'^backend/$', views.backend),
    # url(r'^backend/add_article/$', views.add_article),
    # # 点赞
    # url(r'^up_down/$', views.up_down),
    # # 评论
    # url(r'^comment/$', views.comment),

    # 后台管理
    url(r'^backend/$', views.backend),
    url(r'^backend/add_article/$', views.add_article),
    # 点赞
    url(r'^up_down/$', views.up_down),
    # 评论
    url(r'^comment/$', views.comment),
    # 个人站点页面
    url(r'^(?P<username>\w+)/$', views.home_site),

    # 归档
    url(r'^(?P<username>\w+)/(?P<condition>cate|tag|date)/(?P<param>.*)', views.home_site),
    # 文章详细页
    url(r'^(?P<username>\w+)/articles/(?P<article_id>\d+)', views.article_detail),



]
blog/urls.py

 

from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

# import PIL
import re
from django.http import JsonResponse
from django.contrib import auth

def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        input_valid_codes = request.POST.get("valid_code")

        #校验验证码
        keep_valid_codes=request.session.get('keep_valid_codes')
        '''
        1  拿到cookie中sessionid对应的随机字符串
        2  在django-session表中过滤记录
        3  ....
        '''

        login_response={"user":None,"error_msg":""}

        if keep_valid_codes.upper()==input_valid_codes.upper():
            user = auth.authenticate(username=user,password=pwd)
            if user:
                auth.login(request,user)
                login_response['user']=user.username
            else:
                login_response['error_msg']="用户名或密码错误"
        else:
            login_response['error_msg']="验证码错误"

        import json
        return HttpResponse(json.dumps(login_response))
    else:
        return render(request,"login.html")

#获取验证码图片
def get_valid_img(request):
    from blog.utils import valid_code

    data=valid_code.get_valid_value(request)

    return HttpResponse(data)


def index(request):
    print("==",request.user)
    article_list=Article.objects.all()

    return render(request,"index.html",{"article_list":article_list})



from django import forms
from django.forms import widgets
from .models import *
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError

#  按form表单构建forms组件
class RegForm(forms.Form):
    user=forms.CharField(min_length=4,max_length=8,
                widget=widgets.TextInput(attrs={"class":"form-control"}),
                         error_messages={"min_length":"输入过短,最少4个字符","max_length":"输入过长,最多4个字符","required":"必填"}
                         )
    pwd=forms.CharField(min_length=5,
        widget=widgets.PasswordInput(attrs={"class": "form-control"}),
                        error_messages={"min_length":"输入过短,最少5个字符", "required": "必填"}
    )
    repeat_pwd=forms.CharField(min_length=5,
        widget=widgets.PasswordInput(attrs={"class": "form-control"}),
                               error_messages={"min_length":"输入过短,最少5个字符", "required": "必填"}
    )
    email=forms.EmailField(min_length=5,
        widget=widgets.EmailInput(attrs={"class": "form-control"}),
        error_messages={"min_length":"输入过短,最少5个字符", "required": "必填"}
    )

#钩子函数
    def clean_user(self):
        val=self.cleaned_data.get("user")

        import re
        # if not UserInfo.objects.filter(username=val):
        #     return val
        # else:
        #     raise ValidationError("")

        if not val.isdigit():
            return val
        else:
            raise ValidationError("用户名不能是纯数字!")

    def clean_pwd(self):
        val=self.cleaned_data.get("pwd")

        if not val.isdigit():
            return val
        else:
            raise ValidationError("密码不能是纯数字!")

#注册
def register(request):
    if request.is_ajax():
        print("request.POST",request.POST) # <QueryDict: {'user': ['123'], 'pwd': ['1231'], 'repeat_pwd': ['1231'], 'email': ['123'], 'csrfmiddlewaretoken': ['PjMKenIgrFYWY52U5EcYbkfmib2EiMzK19K5xv4qBon5XZbPDkuiMhMf2LqaV2wy']}>
        print("request.FILES",request.FILES) # request.FILES <MultiValueDict: {'avatar_img': [<InMemoryUploadedFile: linhaifeng.jpg (image/jpeg)>]}>
        form_obj = RegForm(request.POST)

        reg_response={"user":None,"error_mes":None}

        if form_obj.is_valid():
            user=form_obj.cleaned_data.get("user")
            pwd=form_obj.cleaned_data.get("pwd")
            email=form_obj.cleaned_data.get("email")
            avatar_img=request.FILES.get("avatar_img")
            if avatar_img:
                 print("avatar_img....",avatar_img)
                 user=UserInfo.objects.create_user(username=user,password=pwd,email=email,avatar=avatar_img)
            else:
                user = UserInfo.objects.create_user(username=user, password=pwd, email=email)

            reg_response["user"]=user.username

        else:
            reg_response["error_mes"]=form_obj.errors
        return JsonResponse(reg_response)
    else:
        #实例化form组件的类
        form_obj=RegForm()
        return render(request,"reg.html",{"form_obj":form_obj})

#注销
def log_out(request):
    auth.logout(request)
    return redirect("/login")

#个人站点设计
def home_site(request,username,**kwargs):
    '''
    个人站点设计
    :param request: 请求数据对象
    :return:
    '''
    print("kwargs",kwargs)
    print(username)
    user=UserInfo.objects.filter(username=username).first()
    if not user:
        return HttpResponse("<h3>404</h3>")

    # 当前访问站点对象

    blog=user.blog

    # 当前访问站点的所有文章
    if not kwargs:
        article_list=Article.objects.filter(user=user)
    else:
        condition=kwargs.get("condition")
        param=kwargs.get("param") # 2018-02
        if condition=="cate":
             article_list = Article.objects.filter(user=user).filter(homeCategory__title=param)
        elif condition=="tag":
            article_list = Article.objects.filter(user=user).filter(tags__title=param)
        else:
            year,month=param.split("-")
            article_list = Article.objects.filter(user=user).filter(create_time__year=year,create_time__month=month)


    #查询当前站点所有分类
    # # 方式1:
    # category_list=Category.objects.filter(blog=blog)
    # print(category_list)

    # 方式2:查询当前站点的每一个分类名称以及对应的文章数:分组查询
    # from django.db.models import Count,Max
    # cate_list=Category.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title","count")
    # print(cate_list)
    #
    # # 查询当前站点的每一个标签的名称以及对应的文章数:分组查询
    # tag_list=Tag.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title","count")
    # print(tag_list)
    #
    # # 日期归档
    # date_list=Article.objects.filter(user=user).extra(select={"archive_date": "strftime('%%Y-%%m',create_time)"}).values("archive_date").annotate(c=Count("nid")).values_list("archive_date","c")
    # print(date_list)

    return render(request,"blog/home_site.html",locals())


# def get_data(username):
#     user = UserInfo.objects.filter(username=username).first()
#     blog = user.blog
#     from django.db.models import Count, Max
#     cate_list = Category.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title", "count")
#     print(cate_list)
#     # 查询当前站点的每一个标签的名称以及对应的文章数:分组查询
#     tag_list = Tag.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title", "count")
#     print(tag_list)
#     # 日期归档
#     date_list = Article.objects.filter(user=user).extra(
#         select={"archive_date": "strftime('%%Y-%%m',create_time)"}).values("archive_date").annotate(
#         c=Count("nid")).values_list("archive_date", "c")
#     print(date_list)
#
#     return {"username":username,"blog":blog,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list}

#文章详情页
# def article_detail(request,username,article_id):
#     ret=get_data(username)
#
#     article_obj=Article.objects.filter(pk=article_id).first()
#     ret["article_obj"]=article_obj
#     return render(request,"blog/artcile_detail.html",ret)

def article_detail(request,username,article_id):

    article_obj=Article.objects.filter(pk=article_id).first()

    comment_list=Comment.objects.filter(article_id=article_id)
    return render(request,"blog/artcile_detail.html",{"article_obj":article_obj,"username":username,"comment_list":comment_list})

import json
from django.db.models import F
from django.http import JsonResponse
from django.db import transaction

def up_down(request):
    print(request.POST)

    article_id=request.POST.get("article_id")
    is_up=json.loads(request.POST.get("is_up"))
    user_id=request.user.pk
    print(is_up)

    res={"state":True,"err":""}
    try:
        # 处理事务
        with transaction.atomic():
            obj=ArticleUpDown.objects.create(user_id=user_id,article_id=article_id,is_up=is_up)
            if is_up:
                 Article.objects.filter(pk=article_id).update(up_count=F('up_count')+1)
            else:
                Article.objects.filter(pk=article_id).update(down_count=F('down_count') + 1)
    except Exception as e:
        obj=ArticleUpDown.objects.filter(user_id=user_id,article_id=article_id).first()
        res["state"]=False
        res["err"]=obj.is_up

    return JsonResponse(res)

def comment(request):
    content=request.POST.get('content')
    article_id=request.POST.get('article_id')
    pid=request.POST.get('pid')
    user_id=request.user.pk
    res={}
    with transaction.atomic():
        if pid: # 保存子评论
            obj = Comment.objects.create(content=content, article_id=article_id, user_id=user_id,parent_comment_id=pid)
        else:
            # 保存根评论
            obj = Comment.objects.create(content=content, article_id=article_id, user_id=user_id)

        Article.objects.filter(pk=article_id).update(comment_count=F("comment_count")+1)
    res["create_time"]=obj.create_time.strftime("%Y-%m-%d %H:%M")
    res["content"]=obj.content
    return JsonResponse(res)


def backend(reqeust):
    username=reqeust.user

    article_list=Article.objects.filter(user=reqeust.user)


    return render(reqeust,"blog/backend.html",locals())

def add_article(reqeust):

    username=reqeust.user.username
    return render(reqeust,"blog/add_article.html",locals())


def upload_img(reqeust):
    print(reqeust.FILES)
    obj=reqeust.FILES.get("put_img")
    name=obj.name

    from blog_s19 import settings
    import os
    path=os.path.join(settings.MEDIA_ROOT,"add_article_img",name)

    f=open(path,"wb")

    for line in obj:
        f.write(line)
    f.close()

    res={
        "url":"/media/add_article_img/"+name,
        "error":0
    }
    import json
    return HttpResponse(json.dumps(res))
views.py

 

from django.contrib import admin

# Register your models here.
from .models import *




admin.site.register(UserInfo)
admin.site.register(Blog)
admin.site.register(Category)
admin.site.register(Tag)
admin.site.register(Article)
admin.site.register(ArticleDetail)
admin.site.register(ArticleUpDown)
admin.site.register(Comment)
admin.site.register(Article2Tag)
admin.py

 

def get_valid_value(request):
    # 方式1:

    # from cnblog_s19 import settings
    # import os
    # path=os.path.join(settings.BASE_DIR,"blog","static","img","linhaifeng.jpg")
    # with open(path,"rb") as f:
    #     data=f.read()

    # 方式2:
    import random
    def get_randon_color():
        return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

    # from PIL import Image
    # image=Image.new("RGB",(280,40),get_randon_color())
    # f=open("code.png","wb")
    # image.save(f,"png")
    #
    # f = open("code.png", "rb")
    # data=f.read()

    # 方式3:
    # from PIL import Image
    # from io import BytesIO
    # image = Image.new("RGB", (280, 40), get_randon_color())
    # f=BytesIO()
    # image.save(f, "png")
    # data=f.getvalue()

    # 方式4:

    from PIL import Image, ImageDraw, ImageFont
    from io import BytesIO
    image = Image.new("RGB", (280, 40), get_randon_color())

    #  给image对象写入文字
    draw = ImageDraw.Draw(image)
    font = ImageFont.truetype("blog/static/font/kumo.ttf", size=35)

    keep_valid_codes = ""
    for i in range(5):
        random_num = str(random.randint(0, 9))
        random_lower_alf = chr(random.randint(97, 122))
        random_upper_alf = chr(random.randint(65, 90))
        random_char = random.choice([random_num, random_lower_alf, random_upper_alf])[0]
        print(random_char, "===")
        draw.text((20 + i * 50, 0), random_char, fill=get_randon_color(), font=font)
        keep_valid_codes += random_char

    width = 280
    height = 40
    # # 加噪点
    # for i in range(1400):
    #     draw.point((random.randint(0,width),random.randint(0,height)),fill=get_randon_color())
    # for i in range(10):
    #     x1=random.randint(0,width)
    #     x2=random.randint(0,width)
    #     y1=random.randint(0,height)
    #     y2=random.randint(0,height)
    #     draw.line((x1,y1,x2,y2),fill=get_randon_color())
    # for i in range(40):
    #     draw.point([random.randint(0, width), random.randint(0, height)], fill=get_randon_color())
    #     x = random.randint(0, width)
    #     y = random.randint(0, height)
    #     draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_randon_color())

    f = BytesIO()
    image.save(f, "png")
    data = f.getvalue()
    print("valid_codes:", keep_valid_codes)

    request.session["keep_valid_codes"] = keep_valid_codes
    '''
       1  生成随机字符串  1231asd123
       2  set_cookie("sessionid","1231asd123")
       3  django-session

          session_key    session_data
          "1231asd123"   {"keep_valid_codes":"123ab"}
          "3451dfsfd3"   {"keep_valid_codes":"456ab"}
    '''
    return data
blog/utils/valid_code.py

 

from django import template
register=template.Library()   #固定格式

@register.filter
def mul_filter(x,y):
    return x*y


@register.simple_tag
def mul_tag(x,y):
    return x*y

#自定义标签
from blog.models import *
@register.inclusion_tag("blog/menu.html")
def get_menu(username):
    user = UserInfo.objects.filter(username=username).first()
    blog = user.blog
    from django.db.models import Count, Max
    cate_list = Category.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title", "count")
    print(cate_list)
    # 查询当前站点的每一个标签的名称以及对应的文章数:分组查询
    tag_list = Tag.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title", "count")
    print(tag_list)
    # 日期归档
    date_list = Article.objects.filter(user=user).extra(
        select={"archive_date": "strftime('%%Y-%%m',create_time)"}).values("archive_date").annotate(
        c=Count("nid")).values_list("archive_date", "c")
    print(date_list)

    return {"username":username,"blog":blog,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list}




# @register.simple_tag
# def mul_tag(x,y,z):
#     return x*y*z
blog/templatetags/my_tags.py

 

#div_digg {
    float: right;
    margin-bottom: 10px;
    margin-right: 30px;
    font-size: 12px;
    width: 128px;
    text-align: center;
    margin-top: 10px;
}

.diggit {
    float: left;
    width: 46px;
    height: 52px;
    background: url("/static/img/upup.gif") no-repeat;
    text-align: center;
    cursor: pointer;
    margin-top: 2px;
    padding-top: 5px;
}

.buryit {
    float: right;
    margin-left: 20px;
    width: 46px;
    height: 52px;
    background: url("/static/img/downdown.gif") no-repeat;
    text-align: center;
    cursor: pointer;
    margin-top: 2px;
    padding-top: 5px;
}
blog/static/css/artical_detail.css

 

.header{
    width: 100%;
    height: 60px;
    background-color: #2aabd2;
    line-height: 60px;


}

.header .title{
    margin-left: 20px;
    font-size: 22px;
    color: white;

}
blog/static/css/home_site.css

 

.navbar-header .title{
    color: white;
}

.desc{
    margin-left: -20px;
    text-align: justify;
}

#diceng {
    margin-top: 300px;
}
blog/static/css/index.css

 

 .container{
            margin-top: 100px;
        }
blog/static/css/login.css

 

#avatar{
    display: none;
}
blog/static/css/reg.css

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
    <script src="/static/js/jquery-3.1.1.js"></script>
    <script src="/static/bs/js/bootstrap.js"></script>
    <link rel="stylesheet" href="/static/css/index.css">

</head>
<body>


<nav class="navbar navbar-inverse">
    <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 title" 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><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.username %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li><a href="/logout/">注销</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="#">Something else here</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">
    <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">
               {% include 'include_demo.html' %}
            </div>
        </div>
        <div class="col-md-6">

            <div class="article_list">

                {% for article in article_list %}
                    <div class="article_item">
                        <div class="row"><h5><a href="/blog/{{ article.user.username }}/articles/{{ article.pk }}">{{ article.title }}</a></h5></div>
                        <div class="row">
                            <img class="col-md-2" width="60" height="60" src="{{ article.user.avatar.url }}" alt="">
                            <p class="col-md-10 desc">{{ article.desc }}</p>
                        </div>
                        <div class="row small">
                            <span><a href="">{{ article.user.username }}</a></span>&nbsp;&nbsp;
                            <span>发布于&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-comment">评论(0)</span>&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-thumbs-up">点赞(0)</span>&nbsp;&nbsp;
                        </div>
                        <hr>
                    </div>
                {% endfor %}

            </div>
            <nav class="pull-right" aria-label="Page navigation">
                <ul class="pagination">
                    <li>
                        <a href="#" aria-label="Previous">
                            <span aria-hidden="true">&laquo;</span>
                        </a>
                    </li>
                    <li><a href="#">1</a></li>
                    <li><a href="#">2</a></li>
                    <li><a href="#">3</a></li>
                    <li><a href="#">4</a></li>
                    <li><a href="#">5</a></li>
                    <li>
                        <a href="#" aria-label="Next">
                            <span aria-hidden="true">&raquo;</span>
                        </a>
                    </li>
                </ul>
            </nav>


        </div>
        <div class="col-md-3">
            <div class="panel panel-primary">
                {% include 'include_demo.html' %}
            </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>

</div>

</body>
</html>
index.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
    <link rel="stylesheet" href="/static/css/login.css">
    <script src="/static/js/jquery-3.1.1.js"></script>
</head>
<body>

<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form>
                 <div class="form-group">
                    <label for="user">用户名</label>
                    <input type="text" class="form-control" id="user" placeholder="Username">
                  </div>
                <div class="form-group">
                    <label for="pwd">密码</label>
                    <input type="password" class="form-control" id="pwd" placeholder="Password">
                </div>
                 <div class="form-group">
                    <label for="valid"> 验证码</label>
                    <div class="row">
                        <div class="col-md-6">
                           <input type="text" class="form-control" id="valid" placeholder="valid">
                        </div>
                        <div class="col-md-6">
                            <img id="valid_img" width="220" height="40" src="/get_valid_img/" alt="">
                        </div>
                    </div>
                </div>
                {% csrf_token %}
                <input type="button" class="btn btn-primary" value="submit" id="login_btn"><span class="error"></span>
            </form>
        </div>
    </div>
</div>

<script>
    // 验证码刷新
    $("#valid_img").click(function () {
        $(this)[0].src+="?"
    });

    // ajax请求验证

    $("#login_btn").click(function () {
        
        $.ajax({
            url:"/login/",
            type:"post",
            data:{
                "user":$("#user").val(),
                "pwd":$("#pwd").val(),
                "valid_code":$("#valid").val(),
                "csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val()
            },
            success:function (data) {   // {"user":"","error_meg":""}
                console.log(data);
                var data=JSON.parse(data);
                if(data.user){
                    // 登录成功
                    location.href="/index/"

                }
                else {
                    $(".error").html(data.error_msg).css('color',"red")

                    setTimeout(function () {
                        $(".error").html("")
                    },1000)
                }
            }
        })
        
    })
</script>

</body>
</html>
login.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
    <link rel="stylesheet" href="/static/css/reg.css">
    <script src="/static/js/jquery-3.1.1.js"></script>
</head>
<body>
<h3>注册页面</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form>
                <div class="form-group">
                    <label for="id_user">用户名</label>
                    {{ form_obj.user }}  <span class="pull-right"></span>

                </div>
                <div class="form-group">
                    <label for="id_pwd">密码</label>
                    {{ form_obj.pwd }} <span class="pull-right"></span>
                </div>
                <div class="form-group">
                    <label for="id_repeat_pwd">确认密码</label>
                    {{ form_obj.repeat_pwd }} <span class="pull-right"></span>
                </div>
                <div class="form-group">
                    <label for="id_email">邮箱</label>
                    {{ form_obj.email }} <span class="pull-right"></span>
                </div>

                <div class="form-group">

                        <label for="avatar">头像&nbsp;&nbsp;&nbsp;<img id="avatar_img" width="60" height="60" src="http://127.0.0.1:8000/static/img/default.png" alt=""></label>
                        <input type="file" id="avatar">
                </div>
                {% csrf_token %}
                <input type="button" class="btn btn-primary" value="submit" id="reg_btn">
            </form>
        </div>
    </div>
</div>

<script>

    // ajax请求验证

    $("#reg_btn").click(function () {

        // 基于ajax 进行文件上传时,需要利用form_data=new FormData();
        var form_data=new FormData();
        form_data.append("user",$("#id_user").val());
        form_data.append("pwd",$("#id_pwd").val());
        form_data.append("repeat_pwd",$("#id_repeat_pwd").val());
        form_data.append("email",$("#id_email").val());
        form_data.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val());
        form_data.append("avatar_img",$("#avatar")[0].files[0]);


        $.ajax({
            url: "/register/",
            type: "post",
            processData:false,
            contentType:false,
            data: form_data,
            success: function (data) {   // {"user":"","error_meg":""}


                if (data.user){   //   注册成功
                    location.href="/login/"
                }
                else {            // 注册失败

                 var errors_dict=data.error_mes;
                 //     清空操作
                 $("form span").html("");
                 $(".form-group").removeClass("has-error");

                 $.each(errors_dict,function (field,error_info) {
                     console.log(field,error_info[0]);

                     $("#id_"+field).next().html(error_info[0]).css("color","red");
                     $("#id_"+field).parent().addClass("has-error");

                 })



                }



            }
        })

    });

    // 头像预览
    $("#avatar").change(function () {
        // 获取用户选中的文件
        var choose_file=$("#avatar")[0].files[0];
        // JS 的文件阅读器
        var reader=new FileReader();
        reader.readAsDataURL(choose_file);

        reader.onload=function(){
            $("#avatar_img").attr("src",this.result)
        };
    })


</script>

</body>
</html>
reg.html

 

{% load my_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/jquery-3.1.1.js"></script>
    <script src="/static/kindeditor/kindeditor-all.js"></script>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
</head>
<body>
<div class="header">
      <p>文章添加页面</p>
</div>

<div class="row">
  <div class="col-md-2">
      {% get_menu username %}
  </div>
  <div class="col-md-9">
      <div>
          <p>添加文章</p>
          <div>
              <label for="">标题</label>
              <input type="text" name="title">
          </div>
          <div>
              <p>内容(TinyMCE编辑器,支持拖放/粘贴上传图片) </p>
              <div>
                  <textarea name="" id="editor_id" cols="50" rows="10"></textarea>
              </div>
              <button>submit</button>
          </div>
          {% csrf_token %}
            <script>
                    KindEditor.ready(function(K) {
                            window.editor = K.create('#editor_id',{
                                width:800,
                                height:600,
                                uploadJson:"/upload_img/",
                                extraFileUploadParams:{
                                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()
                                },
                                filePostName:"put_img"

                            });
                    });
            </script>
      </div>
  </div>
</div>
</body>
</html>
templates/blog/add_article.html

 

{% extends "blog/home_site.html" %}


{% block con %}
   <div class="article_info">
       <h3 class="text-center">{{ article_obj.title }}</h3>
       <div class="content">
           {{ article_obj.articledetail.content|safe }}
       </div>
   </div>

   <div class="clearfix">
        <div id="div_digg">
        <div class="diggit digg" >
            <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
        </div>
        <div class="buryit digg" >
            <span class="burynum" id="bury_count">{{ article_obj.down_count  }}</span>
        </div>

   </div>
   </div>
     <div class="diggword pull-right"  id="digg_tips" style="color: red;"></div>

    <div class="comment_floor">
        <ul class="comment_list list-group">
            {% for comment in comment_list %}
                 <li class="comment_item list-group-item">
                       <div>
                           <a href=""><span>#{{ forloop.counter }}楼</span></a>&nbsp;&nbsp;
                           <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;
                           <span>{{ comment.user.username }}</span>
                           <span class="pull-right"><a class="reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>
                       </div>
                       {% if comment.parent_comment %}
                           <div class="well">
                                <p><a href="">@{{ comment.parent_comment.user.username }}</a></p>
                                <p>{{ comment.parent_comment.content }}</p>
                           </div>
                       {% endif %}
                       <div>
                     <p>{{ comment.content|safe }}</p>
                 </div>
                 </li>
            {% endfor %}

        </ul>
    </div>

   <div class="comment-box">
        <p>发表评论</p>
        <p>昵称:<input type="text" disabled value="{{ request.user.username }}"></p>
        <p>
            <textarea name="" id="comment_region" cols="60" rows="10"></textarea>
        </p>
        <p >
            <input type="button" class="btn btn-default comment_btn" value="submit">
        </p>
   </div>

   {% csrf_token %}
    <script>
        // 给点赞踩灭按钮绑定事件
        $('.digg').click(function () {
            var is_up=$(this).hasClass("diggit");
            var article_id="{{ article_obj.pk }}";
            if ("{{ request.user.username }}"){
                 $.ajax({
                url:"/blog/up_down/",
                type:"post",
                data:{
                    article_id:article_id,
                    is_up:is_up,
                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),

                },
                success:function (data) {
                    console.log(data); // {}
                    if(data.state){
                        if(is_up){
                            var val=parseInt($("#digg_count").html())+1;
                            $("#digg_count").html(val)
                        }else{
                            var val=parseInt($("#bury_count").html())+1;
                            $("#bury_count").html(val);
                        }
                    }
                    else{
                        if(data.err){
                            $(".diggword").html("您已经推荐过");
                        }
                        else{
                            $(".diggword").html("您已经踩灭过")
                        }
                        setTimeout(function () {
                            $(".diggword").html("")
                        },1000)
                    }

                }
            })
            }
            else{
                location.href="/login/"
            }

        });
       // 绑定提交评论事件
        $(".comment_btn").click(function () {
            if (parent_comment_id){
                var s=$("#comment_region").val();

                if (s.charAt(0)=="@"){
                     var index=s.indexOf("\n");
                     var content=s.slice(index+1);
                }
                else {
                    var content=$("#comment_region").val();
                    parent_comment_id=""
                }

            }else {
                var content=$("#comment_region").val();

            }





            var article_id="{{ article_obj.pk }}";
            $.ajax({
                url:"/blog/comment/",
                type:"post",
                data:{
                    pid:parent_comment_id,
                    content:content,
                    article_id:article_id,
                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(),
                },
                success:function (data) {
                    console.log(data);
                    var create_time=data.create_time;
                    var username="{{ request.user.username }}";
                    var con=data.content;

                    s= '<li class="comment_item list-group-item"><div><span>'+create_time+'</span>&nbsp;&nbsp;<span>'+username+'</span><span class="pull-right"><a class="del" username="'+username+'">删除</a></span></div><div><p>'+con+'</p></div></li>'
                    $(".comment_list ").append(s);

                    $("#comment_region").val("");
                    parent_comment_id="";
                }
            })

        });


       // 回复按钮绑定事件
       var parent_comment_id="";
       $(".reply").click(function () {
           parent_comment_id=$(this).attr("comment_id");
           $("#comment_region").focus();
           var val="@"+$(this).attr("username")+"\n";
           $("#comment_region").val(val)
       })

    </script>


{% endblock %}
templates/blog/article_detail.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            width: 100%;
            height: 50px;
            background-color: #2b669a;
            line-height: 50px;
        }
        .header p{
            color: white;
            font-size: 16px;
            font-weight: 100;
        }
    </style>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
</head>
<body>
<div class="header">
      <p>博客园</p>
</div>

<div class="row">
    <div class="col-md-2"><a href="/blog/backend/add_article">添加文章</a></div>
    <div class="col-md-10">
        <table class="table table-stripped table-hover">
             <tr>
                 <th>文章标题</th>
                 <th>评论数</th>
                 <th>点赞数</th>
                 <th>操作</th>
                 <th>操作</th>
             </tr>
            {% for article in article_list %}
             <tr>
                <td>{{ article.title }}</td>
                <td>{{ article.comment_count }}</td>
                <td>{{ article.up_count }}</td>
                <td><a>删除</a></td>
                <td><a>编辑</a></td>
             </tr>
            {% endfor %}

        </table>
    </div>
</div>
</body>
</html>
templates/blog/backend.html

 

{% load my_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
     <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
     <link rel="stylesheet" href="/static/css/home_site.css">
     <link rel="stylesheet" href="/static/css/article_detail.css">
     <script src="/static/js/jquery-3.1.1.js"></script>
     <script src="/static/bs/js/bootstrap.js"></script>
</head>
<body>
<div class="header">
    <p class="title">{{ blog.title }}</p>

</div>

<div class="container">
  <div class="row">
        <div class="col-md-3">
           {% get_menu username %}
        </div>
        <div class="col-md-9">
            {% block con %}
                  <div class="article_list">

                {% for article in article_list %}
                    <div class="article_item">
                        <div class="row"><h5><a href="/blog/{{ username }}/articles/{{ article.pk }}">{{ article.title }}</a></h5></div>
                        <div class="row">
                            <p class="col-md-10 desc">{{ article.desc }}</p>
                        </div>
                        <div class="row small pull-right">
                            <span>发布于&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-comment">评论(0)</span>&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-thumbs-up">点赞(0)</span>&nbsp;&nbsp;
                        </div>
                        <hr>
                    </div>
                {% endfor %}

            </div>
            {% endblock %}
        </div>

  </div>
</div>

</body>
</html>
templates/blog/home_site.html

 

<div>

    <div class="panel panel-warning">
                <div class="panel-heading">随笔分类</div>
                <div class="panel-body">
{#                    {% for category in category_list %}#}
{#                     <p><a href="">{{ category.title }}({{ category.article_set.all.count }})</a></p>#}
{#                    {% endfor %}#}

                      {% for item in cate_list %}
                      <p><a href="/blog/{{ username }}/cate/{{ item.0 }}">{{ item.0 }}({{ item.1 }})</a></p>
                      {% endfor %}
                </div>
            </div>
    <div class="panel panel-info">
        <div class="panel-heading">我的标签</div>
        <div class="panel-body">
            {% for tag in tag_list %}
            <p><a href="/blog/{{ 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 date in date_list %}
         <p><a href="/blog/{{ username }}/date/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>
         {% endfor %}

        </div>
    </div>
</div>
templates/blog/menu.html

 

三、模块拆分

1、设计表结构

          UserInfo:          --------->登录注册
        
  
           Blog               
           
                              ----------> 个人站点页面的设计
     Category     Tag
     
          
          Article             
                               -----------> 渲染文章
        ArticleDetail
        
    
    ArticleUpDown   Comment      ------------->文章的点赞踩灭与评论
    
  • userinfo表
class UserInfo(AbstractUser):
    """
    用户信息
    """
    nid = models.AutoField(primary_key=True)
    nickname = models.CharField(verbose_name='昵称', max_length=32)
    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)    #与博客建立一对一

    def __str__(self):
        return self.username

 

  • blog,博客信息
class Blog(models.Model):

    """
    博客信息
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='个人博客标题', max_length=64)
    site = models.CharField(verbose_name='个人博客后缀', max_length=32, unique=True)
    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')  #建立在多的一侧

    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')
    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='文章描述')

    comment_count= models.IntegerField(default=0)   #评论
    up_count = models.IntegerField(default=0)       #点赞
    down_count = models.IntegerField(default=0)     #反对

    create_time = models.DateTimeField(verbose_name='创建时间')

    homeCategory = models.ForeignKey(to='Category', to_field='nid', null=True)   #分类
  

    user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid')    #文章作者
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
    )

#多对多不用django创建的第三张表,用我自己定义的表,并且指定字段
def __str__(self):
        return self.title

 

  • 文章详情表
class ArticleDetail(models.Model):
    """
    文章详细表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField()
    article = models.OneToOneField(to='Article', to_field='nid')

 

  • 评论表
class Comment(models.Model):
    """
    评论表
    """
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid')
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid')
    content = models.CharField(verbose_name='评论内容', max_length=255)
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

    parent_comment = models.ForeignKey('self', null=True)    #与自身建立多对多,可以赋给它空值 None 。


    def __str__(self):
        return self.content

 

点赞表

class ArticleUpDown(models.Model):
    """
    点赞表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True)
    article = models.ForeignKey("Article", null=True)
    is_up=models.BooleanField(default=True)

    class Meta:
        #联合唯一
        unique_together = [
            ('article', 'user'),
        ]

 

  • 文章和标签对应表
class Article2Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')
    tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid')

    class Meta:
        unique_together = [
            ('article', 'tag'),
        ]

    def __str__(self):
        v=self.article.title+"----"+self.tag.title
        return v

 

 

 

2、基于Ajax和用户认证系统

  • 验证码

valid_code.py

 

基于PIL模块创建的随机验证码图片

保存验证码:session
request.session["key"]="value"
request.session.get("key")

 

def get_valid_value(request):
    # 方式1:

    # from cnblog_s19 import settings
    # import os
    # path=os.path.join(settings.BASE_DIR,"blog","static","img","linhaifeng.jpg")
    # with open(path,"rb") as f:
    #     data=f.read()

    # 方式2:
    import random
    def get_randon_color():
        return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

    # from PIL import Image
    # image=Image.new("RGB",(280,40),get_randon_color())
    # f=open("code.png","wb")
    # image.save(f,"png")
    #
    # f = open("code.png", "rb")
    # data=f.read()

    # 方式3:
    # from PIL import Image
    # from io import BytesIO
    # image = Image.new("RGB", (280, 40), get_randon_color())
    # f=BytesIO()
    # image.save(f, "png")
    # data=f.getvalue()

    # 方式4:

    from PIL import Image, ImageDraw, ImageFont
    from io import BytesIO
    image = Image.new("RGB", (280, 40), get_randon_color())

    #  给image对象写入文字
    draw = ImageDraw.Draw(image)
    font = ImageFont.truetype("blog/static/font/kumo.ttf", size=35)

    keep_valid_codes = ""
    for i in range(5):
        random_num = str(random.randint(0, 9))
        random_lower_alf = chr(random.randint(97, 122))
        random_upper_alf = chr(random.randint(65, 90))
        random_char = random.choice([random_num, random_lower_alf, random_upper_alf])[0]
        print(random_char, "===")
        draw.text((20 + i * 50, 0), random_char, fill=get_randon_color(), font=font)
        keep_valid_codes += random_char

    width = 280
    height = 40
    # # 加噪点
    # for i in range(1400):
    #     draw.point((random.randint(0,width),random.randint(0,height)),fill=get_randon_color())
    # for i in range(10):
    #     x1=random.randint(0,width)
    #     x2=random.randint(0,width)
    #     y1=random.randint(0,height)
    #     y2=random.randint(0,height)
    #     draw.line((x1,y1,x2,y2),fill=get_randon_color())
    # for i in range(40):
    #     draw.point([random.randint(0, width), random.randint(0, height)], fill=get_randon_color())
    #     x = random.randint(0, width)
    #     y = random.randint(0, height)
    #     draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_randon_color())

    f = BytesIO()
    image.save(f, "png")
    data = f.getvalue()
    print("valid_codes:", keep_valid_codes)

    request.session["keep_valid_codes"] = keep_valid_codes
    '''
       1  生成随机字符串  1231asd123
       2  set_cookie("sessionid","1231asd123")
       3  django-session

          session_key    session_data
          "1231asd123"   {"keep_valid_codes":"123ab"}
          "3451dfsfd3"   {"keep_valid_codes":"456ab"}
    '''
    return data

 

登录函数

views.py

def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        input_valid_codes = request.POST.get("valid_code")

        #校验验证码
        keep_valid_codes=request.session.get('keep_valid_codes')
        '''
        1  拿到cookie中sessionid对应的随机字符串
        2  在django-session表中过滤记录
        3  ....
        '''
        login_response={"user":None,"error_msg":""}

        if keep_valid_codes.upper()==input_valid_codes.upper():
            user = auth.authenticate(username=user,password=pwd)
            if user:
                auth.login(request,user)
                login_response['user']=user.username
            else:
                login_response['error_msg']="用户名或密码错误"
        else:
            login_response['error_msg']="验证码错误"

        import json
        return HttpResponse(json.dumps(login_response))
    else:
        return render(request,"login.html")

#获取验证码图片
def get_valid_img(request):
    from blog.utils import valid_code

    data=valid_code.get_valid_value(request)

    return HttpResponse(data)

 

基于ajax的登录认证

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
    <link rel="stylesheet" href="/static/css/login.css">
    <script src="/static/js/jquery-3.1.1.js"></script>
</head>
<body>

<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form>
                 <div class="form-group">
                    <label for="user">用户名</label>
                    <input type="text" class="form-control" id="user" placeholder="Username">
                  </div>
                <div class="form-group">
                    <label for="pwd">密码</label>
                    <input type="password" class="form-control" id="pwd" placeholder="Password">
                </div>
                 <div class="form-group">
                    <label for="valid"> 验证码</label>
                    <div class="row">
                        <div class="col-md-6">
                           <input type="text" class="form-control" id="valid" placeholder="valid">
                        </div>
                        <div class="col-md-6">
                            <img id="valid_img" width="220" height="40" src="/get_valid_img/" alt="">
                        </div>
                    </div>
                </div>
                {% csrf_token %}
                <input type="button" class="btn btn-primary" value="submit" id="login_btn"><span class="error"></span>
            </form>
        </div>
    </div>
</div>

<script>
    // 验证码刷新
    $("#valid_img").click(function () {
        $(this)[0].src+="?"
    });

    // ajax请求验证

    $("#login_btn").click(function () {
        
        $.ajax({
            url:"/login/",
            type:"post",
            data:{
                "user":$("#user").val(),
                "pwd":$("#pwd").val(),
                "valid_code":$("#valid").val(),
                "csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val()
            },
            success:function (data) {   // {"user":"","error_meg":""}
                console.log(data);
                var data=JSON.parse(data);
                if(data.user){
                    // 登录成功
                    location.href="/index/"

                }
                else {
                    $(".error").html(data.error_msg).css('color',"red")

                    setTimeout(function () {
                        $(".error").html("")
                    },1000)
                }
            }
        })
        
    })
</script>

</body>
</html>

 

3.注册功能

  • 基于Ajax和forms组件
from django import forms
from django.forms import widgets
from .models import *
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError

#  按form表单构建forms组件
class RegForm(forms.Form):
    user=forms.CharField(min_length=4,max_length=8,
                widget=widgets.TextInput(attrs={"class":"form-control"}),
                         error_messages={"min_length":"输入过短,最少4个字符","max_length":"输入过长,最多4个字符","required":"必填"}
                         )
    pwd=forms.CharField(min_length=5,
        widget=widgets.PasswordInput(attrs={"class": "form-control"}),
                        error_messages={"min_length":"输入过短,最少5个字符", "required": "必填"}
    )
    repeat_pwd=forms.CharField(min_length=5,
        widget=widgets.PasswordInput(attrs={"class": "form-control"}),
                               error_messages={"min_length":"输入过短,最少5个字符", "required": "必填"}
    )
    email=forms.EmailField(min_length=5,
        widget=widgets.EmailInput(attrs={"class": "form-control"}),
        error_messages={"min_length":"输入过短,最少5个字符", "required": "必填"}
    )

#钩子函数
    def clean_user(self):
        val=self.cleaned_data.get("user")

        import re
        # if not UserInfo.objects.filter(username=val):
        #     return val
        # else:
        #     raise ValidationError("")

        if not val.isdigit():
            return val
        else:
            raise ValidationError("用户名不能是纯数字!")

    def clean_pwd(self):
        val=self.cleaned_data.get("pwd")

        if not val.isdigit():
            return val
        else:
            raise ValidationError("密码不能是纯数字!")

#注册
def register(request):
    if request.is_ajax():
        print("request.POST",request.POST) # <QueryDict: {'user': ['123'], 'pwd': ['1231'], 'repeat_pwd': ['1231'], 'email': ['123'], 'csrfmiddlewaretoken': ['PjMKenIgrFYWY52U5EcYbkfmib2EiMzK19K5xv4qBon5XZbPDkuiMhMf2LqaV2wy']}>
        print("request.FILES",request.FILES) # request.FILES <MultiValueDict: {'avatar_img': [<InMemoryUploadedFile: linhaifeng.jpg (image/jpeg)>]}>
        form_obj = RegForm(request.POST)

        reg_response={"user":None,"error_mes":None}

        if form_obj.is_valid():
            user=form_obj.cleaned_data.get("user")
            pwd=form_obj.cleaned_data.get("pwd")
            email=form_obj.cleaned_data.get("email")
            avatar_img=request.FILES.get("avatar_img")
            if avatar_img:
                 print("avatar_img....",avatar_img)
                 user=UserInfo.objects.create_user(username=user,password=pwd,email=email,avatar=avatar_img)
            else:
                user = UserInfo.objects.create_user(username=user, password=pwd, email=email)

            reg_response["user"]=user.username

        else:
            reg_response["error_mes"]=form_obj.errors
        return JsonResponse(reg_response)
    else:
        #实例化form组件的类
        form_obj=RegForm()
        return render(request,"reg.html",{"form_obj":form_obj})

 

  • 关于Media的配置

static: 与服务器本身相关的静态文件
media: 用户上传别的文件

关于Media的配置
avatar = models.FileField(upload_to = 'avatars/',default="/avatars/default.png")
默认会将上传文件下载到项目根目录下

if settings 配置:
MEDIA_ROOT=os.path.join(BASE_DIR,"blog","media")
会将上传文件下载到MEDIA_ROOT下

if 再配置:
settings: MEDIA_URL="/media/"
urls:# url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

用户接口,使用户能够直接访问到上传的文件

 

settings.py

#media: 用户上传的文件

MEDIA_URL="/media/"
MEDIA_ROOT=os.path.join(BASE_DIR,"blog","media")

 

 

4.自定义标签

from django import template
register=template.Library()   #固定格式

@register.filter
def mul_filter(x,y):
    return x*y


@register.simple_tag
def mul_tag(x,y):
    return x*y

#自定义标签
from blog.models import *
@register.inclusion_tag("blog/menu.html")
def get_menu(username):
    user = UserInfo.objects.filter(username=username).first()
    blog = user.blog
    from django.db.models import Count, Max
    cate_list = Category.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title", "count")
    print(cate_list)
    # 查询当前站点的每一个标签的名称以及对应的文章数:分组查询
    tag_list = Tag.objects.filter(blog=blog).annotate(count=Count("article")).values_list("title", "count")
    print(tag_list)
    # 日期归档
    date_list = Article.objects.filter(user=user).extra(
        select={"archive_date": "strftime('%%Y-%%m',create_time)"}).values("archive_date").annotate(
        c=Count("nid")).values_list("archive_date", "c")
    print(date_list)

    return {"username":username,"blog":blog,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list}




# @register.simple_tag
# def mul_tag(x,y,z):
#     return x*y*z

 

5.富文本编辑器

kindeditor 下载

{% load my_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/static/js/jquery-3.1.1.js"></script>
    <script src="/static/kindeditor/kindeditor-all.js"></script>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.css">
</head>
<body>
<div class="header">
      <p>文章添加页面</p>
</div>

<div class="row">
  <div class="col-md-2">
      {% get_menu username %}
  </div>
  <div class="col-md-9">
      <div>
          <p>添加文章</p>
          <div>
              <label for="">标题</label>
              <input type="text" name="title">
          </div>
          <div>
              <p>内容(TinyMCE编辑器,支持拖放/粘贴上传图片) </p>
              <div>
                  <textarea name="" id="editor_id" cols="50" rows="10"></textarea>
              </div>
              <button>submit</button>
          </div>
          {% csrf_token %}
            <script>
                    KindEditor.ready(function(K) {
                            window.editor = K.create('#editor_id',{
                                width:800,
                                height:600,
                                uploadJson:"/upload_img/",
                                extraFileUploadParams:{
                                    csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()
                                },
                                filePostName:"put_img"

                            });
                    });
            </script>
      </div>
  </div>
</div>
</body>
</html>

 

posted @ 2018-03-21 15:03  小火星_Hirsi  阅读(1162)  评论(0编辑  收藏  举报