【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
""" 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")
"""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), ]
#!/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), ]
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))
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)
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
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
#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; }
.header{ width: 100%; height: 60px; background-color: #2aabd2; line-height: 60px; } .header .title{ margin-left: 20px; font-size: 22px; color: white; }
.navbar-header .title{ color: white; } .desc{ margin-left: -20px; text-align: justify; } #diceng { margin-top: 300px; }
.container{ margin-top: 100px; }
#avatar{ display: none; }
<!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> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment">评论(0)</span> <span class="glyphicon glyphicon-thumbs-up">点赞(0)</span> </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">«</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">»</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>
<!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>
<!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">头像 <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>
{% 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>
{% 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> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <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> <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 %}
<!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>
{% 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>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment">评论(0)</span> <span class="glyphicon glyphicon-thumbs-up">点赞(0)</span> </div> <hr> </div> {% endfor %} </div> {% endblock %} </div> </div> </div> </body> </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>
三、模块拆分
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>