Django---博客项目实战
1.urls
from django.conf.urls import url from django.contrib import admin from blog import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.login), url(r'^get_valid_code/', views.get_valid_code), url(r'^register/', views.register), url(r'^check_username/', views.check_username), url(r'^index/', views.index), url(r'^logout/', views.logout), ]
2.settings
""" Django settings for BBS project. Generated by 'django-admin startproject' using Django 1.11.25. 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 = 'kujfnfd#)my=#u$o(kj__^$_opl^ro=&525*fmki1wpf2z6r2v' # 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', ] 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 = 'BBS.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'BBS.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bbs', 'HOST': '127.0.0.1', 'PORT': 3306, 'USER': 'root', 'PASSWORD': '', } } # 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' LANGUAGE_CODE = 'zh-hans' 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_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ] AUTH_USER_MODEL = 'blog.UserInfo'
3.models
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. # UserInfo这个表,继承AbstractUser,因为要用auth组件 class UserInfo(AbstractUser): nid = models.AutoField(primary_key=True) # blank=True 只是admin中表单提交的时候,做校验,如果设置成True,就是不校验了 phone = models.CharField(max_length=32, null=True, blank=True) # upload_to需要传一个路径(文件夹自动创建) avatar = models.FileField(upload_to='avatar/', default='/static/img/default.png/') blog = models.OneToOneField(to='Blog', to_field='nid', null=True) class Meta: # admin中显示表名 verbose_name = '用户信息表' # 表名去掉s verbose_name_plural = verbose_name class Blog(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64) site_name = models.CharField(max_length=32) theme = models.CharField(max_length=64) def __str__(self): return self.site_name class Category(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64) blog = models.ForeignKey(to='Blog', to_field='nid', null=True) def __str__(self): return self.title class Tag(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64) blog = models.ForeignKey(to='Blog', to_field='nid', null=True) class Article(models.Model): nid = models.AutoField(primary_key=True) # verbose_name='文章标题 修改admin中表单的文字显示 title = models.CharField(max_length=64, verbose_name='文章标题') desc = models.CharField(max_length=255) # 大文本TextField() content = models.TextField() create_time = models.DateTimeField(auto_now_add=True) # 因为查询多,写入少,所以加这三个字段,以后不需要再连表查询了 commit_num=models.IntegerField(default=0) up_num=models.IntegerField(default=0) down_num=models.IntegerField(default=0) blog = models.ForeignKey(to='Blog', to_field='nid', null=True) category = models.ForeignKey(to='Category', to_field='nid', null=True) tag = models.ManyToManyField(to='Tag', through='ArticleToTag', through_fields=('article', 'tag')) def __str__(self): return self.title # 手动创建第三张表 class ArticleToTag(models.Model): nid = models.AutoField(primary_key=True) article = models.ForeignKey(to='Article', to_field='nid') tag = models.ForeignKey(to='Tag', to_field='nid') class Commit(models.Model): nid = models.AutoField(primary_key=True) user = models.ForeignKey(to='UserInfo', to_field='nid') article = models.ForeignKey(to='Article', to_field='nid') content = models.CharField(max_length=255) create_time = models.DateTimeField(auto_now_add=True) # 这样写是可以的 # parent_id=models.IntegerField() # 自关联 # parent_id=models.ForeignKey(to='Commit',to_field='nid') parent = models.ForeignKey(to='self', to_field='nid', null=True,blank=True) class UpAndDown(models.Model): nid = models.AutoField(primary_key=True) user = models.ForeignKey(to='UserInfo', to_field='nid') article = models.ForeignKey(to='Article', to_field='nid') is_up = models.BooleanField() class Meta: # 联合唯一,只是为了不写脏数据 unique_together = (('user', 'article'),)
4.blog/myforms.py
from django import forms from django.forms import widgets from blog import models from django.core.exceptions import ValidationError class RegForm(forms.Form): username = forms.CharField(max_length=18, min_length=2, label="用户名", widget=widgets.TextInput(attrs={'class': 'form-control'}), error_messages={"max_length": '字符长度超出限制', "min_length": '字符长度不够', "required": '用户名不能为空'} ) password = forms.CharField(max_length=18, min_length=2, label="密码", widget=widgets.PasswordInput(attrs={'class': 'form-control'}), error_messages={"max_length": '字符长度超出限制', "min_length": '字符长度不够', "required": '密码不能为空'} ) re_password = forms.CharField(max_length=18, min_length=2, label="确认密码", widget=widgets.PasswordInput(attrs={'class': 'form-control'}), error_messages={"max_length": '字符长度超出限制', "min_length": '字符长度不够', "required": '密码不能为空'} ) email = forms.EmailField(label="邮箱", widget=widgets.TextInput(attrs={'class': 'form-control'}), error_messages={"invalid": '格式不合法', "required": '邮箱为必填'} ) # 局部校验钩子函数 def clean_username(self): name = self.cleaned_data.get('username') # 去数据库校验 ret = models.UserInfo.objects.filter(username=name).first() if ret: raise ValidationError('用户名已存在') return name # 全局校验钩子函数 def clean(self): pwd = self.cleaned_data.get('password') re_pwd = self.cleaned_data.get('re_password') if pwd and re_pwd: if pwd == re_pwd: return self.cleaned_data else: raise ValidationError('两次密码不一致')
5.views
from django.shortcuts import render, HttpResponse,redirect from PIL import Image, ImageDraw, ImageFont import random from io import BytesIO # https://www.cnblogs.com/liuqingzheng/articles/10023849.html from blog import myforms from django.contrib import auth from django.http import JsonResponse from blog import models # Create your views here. def login(request): if request.method == 'GET': return render(request, 'login.html') # 判断前台发送的请求是不是ajax的请求 elif request.is_ajax(): response = {'user': None, 'msg': None} name = request.POST.get('name') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') if valid_code.upper() == request.session.get('valid_code').upper(): user = auth.authenticate(request, username=name, password=pwd) if user: # ajax请求,不能再返回render页面,或者redirect,只能返回字符串 # 校验通过,一定要登录 auth.login(request, user) response['user'] = name response['msg'] = '登录成功' else: # 用户密码错误 response['msg'] = '用户名或密码错误' else: response['msg'] = '验证码错误' return JsonResponse(response) def get_random_color(): return ( random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) ) def get_valid_code(request): # pillow是一个图形处理的模块 # 生成一张图片,模式,大小,颜色 # img = Image.new('RGB', (320, 35), color=get_random_color()) # # 保存到本地 # with open('valid_code.png', 'wb') as f: # img.save(f, 'png') # with open('valid_code.png', 'rb') as f: # data = f.read() # return HttpResponse(data) # 在内存中生成一个空文件(把它想象成open('valid_code.png','wb')as f: # 一个是在硬盘上一个是在内存中 # img = Image.new('RGB', (320, 35), color=get_random_color()) # f=BytesIO() # img.save(f,'png') # data=f.getvalue() # return HttpResponse(data) img = Image.new('RGB', (320, 35), color=get_random_color()) img_draw = ImageDraw.Draw(img) font = ImageFont.truetype('static/font/ss.ttf', size=25) random_code = '' for i in range(5): char_num = random.randint(0, 9) char_lower = chr(random.randint(97, 122)) char_upper = chr(random.randint(65, 90)) char_str = str(random.choice([char_num, char_lower, char_upper])) # 坐标,文字,颜色,字体 img_draw.text((i * 30 + 12, 0), char_str, get_random_color(), font=font) random_code += char_str # 把验证码保存到session 中 request.session['valid_code'] = random_code f = BytesIO() img.save(f, 'png') data = f.getvalue() return HttpResponse(data) def register(request): if request.method == 'GET': my_form = myforms.RegForm() return render(request, 'register.html', {'my_form': my_form}) elif request.is_ajax(): response = {'status': 100, 'msg': None} print(request.POST) my_form = myforms.RegForm(request.POST) if my_form.is_valid(): # 存数据,返回正确信息 # 得用create_user() # 定义一个字典,把清理的数据赋给它 dic = my_form.cleaned_data # 移除掉确认密码字段 dic.pop('re_password') # 取出上传的文件对象 my_file = request.FILES.get('my_file') if my_file: # 放到字典中 dic['avatar'] = my_file user = models.UserInfo.objects.create_user(**dic) print(user.username) response['url'] = '/login/' else: # 返回错误信息 response['status'] = 101 response['msg'] = my_form.errors return JsonResponse(response) def check_username(request): response = {'status': 100, 'msg': None} name = request.POST.get('name') user = models.UserInfo.objects.filter(username=name).first() if user: response['status'] = 101 response['msg'] = '用户名已被占用' return JsonResponse(response) def index(request): article_list=models.Article.objects.all().order_by('-create_time') return render(request,'index.html',{'article_list':article_list}) def logout(request): auth.logout(request) return redirect('/index/')
6.index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.3.1.js"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <title>博客园</title> <style> .article_bottom span { margin-right: 5px; } </style> </head> <body> <div class="head"> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客园</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">文章 <span class="sr-only">(current)</span></a></li> <li><a href="#">随笔</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if request.user.is_authenticated %} <li><a href="#">{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">个人中心 <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li role="separator" class="divider"></li> <li><a href="/logout/">注销</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> <div class="container-fluid"> <div class="row"> <div class="col-md-2"> <div class="panel panel-default"> <div class="panel-heading">重金求子</div> <div class="panel-body"> 请联系:8888888888888888888 </div> </div> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">交友社区</h3> </div> <div class="panel-body"> <a href="http://www.baidu.com">请点击</a> </div> </div> </div> <div class="col-md-7"> {% for article in article_list %} <div> <h4><a href="">{{ article.title }}</a></h4> <div class="media"> <div class="media-left"> <a href="#"> <img class="media-object" src="/static/img/default.png" alt="..." height="70" width="70"> </a> </div> <div class="media-body"> {{ article.desc }} </div> </div> <div style="margin-top: 10px;" class="article_bottom"> <span><a href="">{{ article.blog.userinfo.username }}</a></span> <span>发布于 {{ article.create_time|date:"Y-m-d H:i:s" }}</span> {# 反向查询,一对多,按表名小写_set#} <span class="glyphicon glyphicon-comment"><a href="">评论({{ article.commit_num}})</a></span> <span class="glyphicon glyphicon-thumbs-up"><a href="">点赞({{ article.up_num }})</a></span> </div> </div> {% endfor %} </div> <div class="col-md-3"> <div class="panel panel-default"> <div class="panel-heading">重金求子</div> <div class="panel-body"> 请联系:8888888888888888888 </div> </div> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">交友社区</h3> </div> <div class="panel-body"> <a href="http://www.baidu.com">请点击</a> </div> </div> </div> </div> </div> </body> </html>
7.login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.3.1.js"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <title>博客园</title> <style> .article_bottom span { margin-right: 5px; } </style> </head> <body> <div class="head"> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客园</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">文章 <span class="sr-only">(current)</span></a></li> <li><a href="#">随笔</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if request.user.is_authenticated %} <li><a href="#">{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">个人中心 <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li role="separator" class="divider"></li> <li><a href="/logout/">注销</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> <div class="container-fluid"> <div class="row"> <div class="col-md-2"> <div class="panel panel-default"> <div class="panel-heading">重金求子</div> <div class="panel-body"> 请联系:8888888888888888888 </div> </div> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">交友社区</h3> </div> <div class="panel-body"> <a href="http://www.baidu.com">请点击</a> </div> </div> </div> <div class="col-md-7"> {% for article in article_list %} <div> <h4><a href="">{{ article.title }}</a></h4> <div class="media"> <div class="media-left"> <a href="#"> <img class="media-object" src="/static/img/default.png" alt="..." height="70" width="70"> </a> </div> <div class="media-body"> {{ article.desc }} </div> </div> <div style="margin-top: 10px;" class="article_bottom"> <span><a href="">{{ article.blog.userinfo.username }}</a></span> <span>发布于 {{ article.create_time|date:"Y-m-d H:i:s" }}</span> {# 反向查询,一对多,按表名小写_set#} <span class="glyphicon glyphicon-comment"><a href="">评论({{ article.commit_num}})</a></span> <span class="glyphicon glyphicon-thumbs-up"><a href="">点赞({{ article.up_num }})</a></span> </div> </div> {% endfor %} </div> <div class="col-md-3"> <div class="panel panel-default"> <div class="panel-heading">重金求子</div> <div class="panel-body"> 请联系:8888888888888888888 </div> </div> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">交友社区</h3> </div> <div class="panel-body"> <a href="http://www.baidu.com">请点击</a> </div> </div> </div> </div> </div> </body> </html>
8.register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.3.1.js"></script> <title>注册</title> <style> #my_file { {#把上传文件的控件隐藏#} display: none; } </style> {# <script>#} {# //等文档加载完毕之后,再进行操作#} {# window.onload=function () {#} {# #} {# }#} {# </script>#} </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h1>注册</h1> <form action="" id="form"> {% csrf_token %} {% for foo in my_form %} <div class="form-group"> <label for="{{ foo.auto_id }}">{{ foo.label }}</label> {{ foo }} <span class="error pull-right" style="color: red;"></span> </div> {% endfor %} <div class="form-group"> <label for="my_file">头像 <img src="/static/img/default.png" alt="" width="80" height="80" id="img_file"> </label> <input accept="image/*" type="file" id="my_file"> </div> <input type="button" value="注册" class="btn btn-primary" id="btn"><span class="error"></span> </form> </div> </div> </div> </body> <script> //这个控件指发生变化的事件 $("#my_file").change(function () { //先取出文件(图片) var file_obj = $("#my_file")[0].files[0]; //通过文件阅读器,把图片放到img标签上 //生成一个文件阅读器对象 var filereader = new FileReader() //把图片对象,读到filereader对象中 filereader.readAsDataURL(file_obj) //filereader.result 这是filereader对象的值 //把 //$("#img_file").attr('src',filereader.result) filereader.onload = function () { $("#img_file").attr('src', filereader.result) } }) $("#btn").click(function () { //因为要上传文件,生成formdata对象 var formdata = new FormData() /* formdata.append('name', $("#id_name").val()) formdata.append('pwd', $("#id_pwd").val()) formdata.append('re_pwd', $("#id_re_pwd").val()) formdata.append('email', $("#id_email").val()) formdata.append('email', $("#id_email").val()) formdata.append('csrfmiddlewaretoken', $("[name='csrfmiddlewaretoken']").val()) //把文件放到formdata中 formdata.append('my_file', $("#my_file")[0].files[0]) */ //$("#form").serializeArray()把form表单打包,转成对象(列表套字典) var arr = $("#form").serializeArray() //jquery的循环,传参数:第一个参数是要循环的对象,第二个参数是一个匿名函数 $.each(arr, function (k, v) { console.log(k) console.log(v) formdata.append(v.name, v.value) }) //把文件放到formdata中 formdata.append('my_file', $("#my_file")[0].files[0]) console.log(arr) $.ajax({ url: '/register/', type: 'post', processData: false, contentType: false, data: formdata, success: function (data) { //console.log(data) if (data.status == 100) { //location.href='/login/' location.href = data.url } else { //在之前清除 $(".form-group").removeClass('has-error') $(".error").html("") $.each(data.msg, function (key, value) { console.log(key, value) //根据key,通过id取出控件 //原来取值 //$("#id_username").next().html() //方式一 //$("#id_"+key).next().html(value[0]) //$("#id_"+key).parent().addClass('has-error') //方式二 //处理两次密码不一致的情况 if (key=='__all__'){ $("#id_re_password").next().html(value[0]) } $("#id_" + key).next().html(value[0]).parent().addClass('has-error') }) /* setTimeout(function () { //清除掉父div的has-error //清除掉错误信息(span里的内容) $(".form-group").removeClass('has-error') $(".error").html("") }, 3000) */ } } }) }) //name失去焦点,发ajax的请求校验用户是否存在 //校验,但是只要值不变,只校验一次 /* $("#id_username").change(function () { }) */ //一直会校验 $("#id_username").blur(function () { $.ajax({ url: '/check_username/', type: 'post', //name加不加引号都可以 data: {name:$("#id_username").val(),'csrfmiddlewaretoken': '{{ csrf_token }}' }, success:function (data) { if (data.status==101){ $("#id_username").next().html(data.msg).parent().addClass('has-error') } else { $("#id_username").next().html(data.msg).parent().removeClass('has-error') } } }) }) </script> </html>