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>