表设计
表设计
1、用户表
| 继承AbstractUser |
| 扩展字段: |
| phone 电话号码 |
| avatar 用户头像 |
| create_time 创建时间 |
| |
| 外键字段 |
| 一对一个人站点表 |
2、个人站点表
| site_name 站点名称 |
| site_title 站点标题 |
| site_theme 站点样式 |
3、文章标签表
4、文章分类表
5、文章表
| ```title ````文章标题 |
| desc 文章简介 |
| content 文章内容 |
| create_time 发布时间 |
| |
| 数据库字段设计优化(******) |
| (虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率) |
| up_num 点赞数 |
| down_num 点踩数 |
| comment_num 评论数 |
| |
| 外键字段 |
| 一对多个人站点 |
| 多对多文章标签 |
| 一对多文章分类 |
6、点赞点踩表
| 记录哪个用户给哪篇文章点了赞还是点了踩 |
| user ForeignKey(to="User") |
| article ForeignKey(to="Article") |
| is_up BooleanField() |
7、文章评论表
| 记录哪个用户给哪篇文章写了哪些评论内容 |
| user ForeignKey(to="User") |
| article ForeignKey(to="Article") |
| content CharField() |
| comment_time DateField() |
| # 自关联 |
| parent ForeignKey(to="Comment",null=True) |
| # ORM专门提供的自关联写法 |
| parent ForeignKey(to="self",null=True) |
| |
| 根评论子评论的概念 |
| 根评论就是直接评论当前发布的内容的 |
| 子评论是评论别人的评论 |
| |
| 根评论与子评论是一对多的关系 |
数据库表创建
数据库选择:MySQL
新建一个bbs14数据库
django连接数据库设置
| DATABASES = { |
| 'default': { |
| 'ENGINE': 'django.db.backends.mysql', |
| 'NAME': 'bbs14', |
| 'USER':'root', |
| 'PASSWORD':'123456', |
| 'HOST':'127.0.0.1', |
| 'PORT':3306, |
| 'CHARSET':'utf8' |
| } |
| } |
在一个init文件中写入以下代码
| import pymysql |
| pymysql.install_as_MySQLdb() |
创建表:
继承使用Auth表,扩展使用要先在配置文件setting.py中加入以下代码
| AUTH_USER_MODEL = 'app01.UserInfo' |
| '应用名.表名' |
先写普通字段,在写外键字段
数据库创建代码 models.py
| # 用户表 |
| class UserInfo(AbstractUser): |
| phone = models.BigIntegerField(verbose_name='手机号码',null=True) |
| avatar = models.FileField(verbose_name='头像',upload_to='avatar/',default='avatar/default.png') |
| create_time = models.DateField(auto_now_add=True) |
| |
| # 一对一站点表 |
| blog = models.OneToOneField(to='Blog',null=True) |
| |
| # 个人站点 |
| class Blog(models.Model): |
| site_name = models.CharField(verbose_name='站点名称',max_length=32) |
| site_title = models.CharField(verbose_name='站点标题',max_length=32) |
| # 简单模拟 存css或js的文件路径 |
| site_theme = models.CharField(verbose_name='站点样式',max_length=64) |
| |
| # 文章分类 |
| class Category(models.Model): |
| name = models.CharField(verbose_name='文章分类',max_length=32) |
| |
| # 一对多站点表 |
| blog = models.ForeignKey(to='Blog',null=True) |
| |
| # 文章标签 |
| class Tag(models.Model): |
| name = models.CharField(verbose_name='文章标签',max_length=32) |
| |
| # 一对多站点表 |
| blog = models.ForeignKey(to='Blog',null=True) |
| |
| |
| # 文章表 |
| class Article(models.Model): |
| title = models.CharField(verbose_name='文章标题',max_length=64) |
| desc = models.CharField(verbose_name='文章简介',max_length=256) |
| # 文章内容有很多,一般使用TextField |
| content = models.TextField(verbose_name='文章内容') |
| create_time = models.DateField(auto_now_add=True) |
| |
| # 数据库优化字段 |
| up_num = models.BigIntegerField(verbose_name='点赞数',default=0) |
| down_num = models.BigIntegerField(verbose_name='点踩数',default=0) |
| content_num = models.BigIntegerField(verbose_name='评论数',default=0) |
| |
| # 一对多站点表 |
| blog = models.ForeignKey(to='Blog',null=True) |
| # 一对多文章分类 |
| category = models.ForeignKey(to='Category',null=True) |
| tags = models.ManyToManyField(to='Tag', |
| through='Article2Tag', |
| through_fields=('article','tag') |
| ) |
| |
| # 多对多文章标签 |
| class Article2Tag(models.Model): |
| article = models.ForeignKey(to='Article') |
| tag = models.ForeignKey(to='Tag') |
| |
| # 点赞点踩表 |
| class UpAndDown(models.Model): |
| user = models.ForeignKey(to='UserInfo') |
| article = models.ForeignKey(to='Article') |
| is_up = models.BooleanField(verbose_name='是否点赞') # 存布尔值 0 1 |
| |
| |
**执行数据库迁移和记录命令**
| python3 manage.py makemigrations 将操作记录记录到小本本上(migrations文件夹) |
| |
| python3 manage.py migrate 将操作真正的同步到数据库中 |
注册功能
url配置路由
| from app01 import views |
| |
| url(r'^register',views.register) |
在app01文件夹下,新建myforms.py文件夹
我们之前是直接在views.py中书写的forms组件代码,但是为了接耦合 ,应该将所有的forms组件代码单独写到一个地方
如果项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可
myforms.py
但是如果项目需要使用多个forms组件,那么可以创建一个文件夹在文件夹内根据forms组件功能的不同创建不同的py文件,例如:
myforms文件夹
regform.py
loginform.py
userform.py
orderform.py
form组件代码
| |
| from django import forms |
| from app01 import models |
| |
| |
| |
| class MyRegForm(forms.Form): |
| username = forms.CharField(label='用户名',min_length=3,max_length=8, |
| error_messages={ |
| 'required':'用户名不能为空', |
| 'min_length':'用户名最少为3位', |
| 'max_length':'用户名最大为8位' |
| }, |
| |
| widget = forms.widgets.TextInput(attrs={'class':'form-control'}) |
| ) |
| password = forms.CharField(label='密码',min_length=3,max_length=8, |
| error_messages={ |
| 'required':'密码不能为空', |
| 'min_length':'密码最少为3位', |
| 'max_length':'密码最大为8位' |
| }, |
| |
| widget=forms.widgets.PasswordInput(attrs={'class':'form-control'}) |
| ) |
| |
| confirm_password = forms.CharField(label='确认密码',min_length=3,max_length=8, |
| error_messages={ |
| 'required':'确认密码不能为空', |
| 'min_length':'确认密码最少为3位', |
| 'max_length':'确认密码最大为8位' |
| }, |
| |
| widget=forms.widgets.PasswordInput(attrs={'class':'form-control'}) |
| ) |
| |
| email = forms.EmailField(label='邮箱', |
| error_messages={ |
| 'required':'邮箱不能为空', |
| 'invalid':'邮箱格式不正确' |
| }, |
| widget=forms.widgets.EmailInput(attrs={'class':'form-control'}) |
| ) |
| |
| |
| def clean_username(self): |
| username = self.cleaned_data.get('username') |
| is_exists = models.UserInfo.objects.filter(username=username) |
| if is_exists: |
| self.add_error('username','用户名已存在') |
| return username |
| |
| |
| def clean_password(self): |
| password = self.cleaned_data.get('password') |
| confirm_password = self.cleaned_data.get('confirm_password') |
| if not password == confirm_password: |
| self.add_error('confirm_password','两次密码不一致') |
| return self.cleaned_data |
| |
**配置静态文件**
| STATICFILES_DIRS={ |
| os.path.join(BASE_DIR,'static') |
| } |
register.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> |
| <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> |
| <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> |
| {% load static %} |
| <style> |
| </style> |
| </head> |
| <body> |
| <div class="container-fluid"> |
| <div class="row"> |
| <div class="col-md-8 col-md-offset-2"> |
| <h1 class="text-center">注册</h1> |
| <form id="myform"> |
| {% csrf_token %} |
| {% for form in form_obj %} |
| <div class="form-group"> |
| <label for="{{ form.auto_id }}">{{ form.label }}</label> |
| {{ form }} |
| <span style="color: red" class="pull-right"></span> |
| </div> |
| {% endfor %} |
| <div class="form-group"> |
| <label for="myfile">头像 |
| {% load static %} |
| <img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px"> |
| </label> |
| <input type="file" id="myfile" name="avatar" style="display: none" > |
| </div> |
| |
| <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"> |
| </form> |
| </div> |
| </div> |
| </div> |
| |
| <script> |
| $("#myfile").change(function () { |
| |
| |
| let myFileReaderObj = new FileReader(); |
| |
| let fileObj = $(this)[0].files[0]; |
| |
| myFileReaderObj.readAsDataURL(fileObj) |
| |
| |
| myFileReaderObj.onload = function(){ |
| $('#myimg').attr('src',myFileReaderObj.result) |
| } |
| }) |
| |
| $('#id_commit').click(function () { |
| |
| let formDataObj = new FormData(); |
| |
| |
| $.each($('#myform').serializeArray(),function (index,obj) { |
| |
| formDataObj.append(obj.name,obj.value) |
| }); |
| |
| formDataObj.append('avatar',$('#myfile')[0].files[0]); |
| |
| |
| $.ajax({ |
| url:"", |
| type:'post', |
| data:formDataObj, |
| |
| |
| contentType:false, |
| processData:false, |
| |
| success:function (args) { |
| if (args.code==1000){ |
| |
| window.location.href = args.url |
| }else{ |
| |
| |
| $.each(args.msg,function (index,obj) { |
| |
| let targetId = '#id_' + index; |
| $(targetId).next().text(obj[0]).parent().addClass('has-error') |
| }) |
| } |
| } |
| }) |
| }) |
| |
| $('input').focus(function () { |
| |
| $(this).next().text('').parent().removeClass('has-error') |
| }) |
| </script> |
| </body> |
| </html> |
register的视图代码
| |
| def register(request): |
| form_obj = MyRegForm() |
| if request.method == 'POST': |
| back_dic = {"code": 1000, 'msg': ''} |
| |
| form_obj = MyRegForm(request.POST) |
| |
| if form_obj.is_valid(): |
| |
| clean_data = form_obj.cleaned_data |
| |
| clean_data.pop('confirm_password') |
| |
| file_obj = request.FILES.get('avatar') |
| """针对用户头像一定要判断是否传值 不能直接添加到字典里面去""" |
| if file_obj: |
| clean_data['avatar'] = file_obj |
| |
| models.UserInfo.objects.create_user(**clean_data) |
| back_dic['url'] = '/login/' |
| else: |
| back_dic['code'] = 2000 |
| back_dic['msg'] = form_obj.errors |
| return JsonResponse(back_dic) |
| return render(request,'register.html',locals()) |
| |
登录功能
login.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> |
| <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> |
| <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> |
| {% load static %} |
| <body> |
| <div class="container-fluid"> |
| <div class="row"> |
| <div class="col-md-8 col-md-offset-2"> |
| <h1 class="text-center">登录</h1> |
| <div class="form-group"> |
| <label for="username">用户名</label> |
| <input type="text" name="username" id="username" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="password">密码</label> |
| <input type="password" name="password" id="password" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="">验证码</label> |
| |
| <div class="row"> |
| <div class="col-md-6"> |
| <input type="text" name="code" id="id_code" class="form-control"> |
| </div> |
| <div class="col-md-6"> |
| <img src="/get_code/" alt="" width="370" height="35" id="id_img"> |
| </div> |
| </div> |
| |
| </div> |
| <input type="button" class="btn btn-success" value="登录" id="id_commit"> |
| <span style="color: red" id="error"></span> |
| </div> |
| </div> |
| </div> |
| <script> |
| $("#id_img").click(function () { |
| |
| let oldVal = $(this).attr('src'); |
| $(this).attr('src',oldVal += '?') |
| }) |
| |
| |
| $("#id_commit").click(function () { |
| $.ajax({ |
| url:'', |
| type:'post', |
| data:{ |
| 'username':$('#username').val(), |
| 'password':$('#password').val(), |
| 'code':$('#id_code').val(), |
| |
| 'csrfmiddlewaretoken':'{{ csrf_token }}' |
| }, |
| success:function (args) { |
| if (args.code == 1000){ |
| |
| window.location.href = args.url |
| }else{ |
| |
| $('#error').text(args.msg) |
| } |
| } |
| }) |
| }) |
| </script> |
| </body> |
| </html> |
login视图代码
| |
| def login(request): |
| if request.method == 'POST': |
| back_dic = {'code':1000,'msg':''} |
| username = request.POST.get('username') |
| password = request.POST.get('password') |
| code = request.POST.get('code') |
| |
| if request.session.get('code').upper() == code.upper(): |
| |
| user_obj = auth.authenticate(request,username=username,password=password) |
| if user_obj: |
| |
| auth.login(request,user_obj) |
| back_dic['url'] = '/home/' |
| else: |
| back_dic['code'] = 2000 |
| back_dic['msg'] = '用户名或密码错误' |
| else: |
| back_dic['code'] = 3000 |
| back_dic['msg'] = '验证码错误' |
| return JsonResponse(back_dic) |
| return render(request,'login.html') |
| |
| |
| |
| import random |
| def get_random(): |
| return random.randint(0,255),random.randint(0,255),random.randint(0,255) |
| def get_code(request): |
| img_obj = Image.new('RGB',(430,35),get_random()) |
| img_draw = ImageDraw.Draw(img_obj) |
| img_font = ImageFont.truetype('static1/font/111.ttf',30) |
| code = '' |
| for i in range(5): |
| random_upper = chr(random.randint(65,90)) |
| random_lower = chr(random.randint(97,122)) |
| random_int = str(random.randint(0,9)) |
| |
| tmp = random.choice([random_upper,random_lower,random_int]) |
| |
| img_draw.text((i*60+60,-2),tmp,get_random(),img_font) |
| code += tmp |
| print(code) |
| request.session['code'] = code |
| io_obj = BytesIO() |
| img_obj.save(io_obj,'png') |
| return HttpResponse(io_obj.getvalue()) |
| |
关于图片验证码
图片验证码代码
| """ |
| 图片相关的模块 |
| pip3 install pillow |
| """ |
| from PIL import Image,ImageDraw,ImageFont |
| """ |
| Image:生成图片 |
| ImageDraw:能够在图片上乱涂乱画 |
| ImageFont:控制字体样式 |
| """ |
| from io import BytesIO,StringIO |
| """ |
| 内存管理器模块 |
| BytesIO:临时帮你存储数据 返回的时候数据是二进制 |
| StringIO:临时帮你存储数据 返回的时候数据是字符串 |
| """ |
| import random |
| def get_random(): |
| return random.randint(0,255),random.randint(0,255),random.randint(0,255) |
| def get_code(request): |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| img_obj = Image.new('RGB', (430, 35), get_random()) |
| img_draw = ImageDraw.Draw(img_obj) |
| img_font = ImageFont.truetype('static/font/222.ttf',30) |
| |
| |
| code = '' |
| for i in range(5): |
| random_upper = chr(random.randint(65,90)) |
| random_lower = chr(random.randint(97,122)) |
| random_int = str(random.randint(0,9)) |
| |
| tmp = random.choice([random_lower,random_upper,random_int]) |
| |
| """ |
| 为什么一个个写而不是生成好了之后再写 |
| 因为一个个写能够控制每个字体的间隙 而生成好之后再写的话 |
| 间隙就没法控制了 |
| """ |
| img_draw.text((i*60+60,-2),tmp,get_random(),img_font) |
| |
| code += tmp |
| print(code) |
| |
| request.session['code'] = code |
| io_obj = BytesIO() |
| img_obj.save(io_obj,'png') |
| return HttpResponse(io_obj.getvalue()) |
| |
首页
home.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> |
| <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> |
| <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> |
| </head> |
| <body> |
| <nav class="navbar navbar-inverse"> |
| <div class="container-fluid"> |
| |
| <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="#">BBS</a> |
| </div> |
| |
| |
| <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> |
| <ul class="nav navbar-nav"> |
| <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> |
| <li><a href="#">文章</a></li> |
| <li 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="#">Action</a></li> |
| <li><a href="#">Another action</a></li> |
| <li><a href="#">Something else here</a></li> |
| <li role="separator" class="divider"></li> |
| <li><a href="#">Separated link</a></li> |
| <li role="separator" class="divider"></li> |
| <li><a href="#">One more separated link</a></li> |
| </ul> |
| </li> |
| </ul> |
| <form class="navbar-form navbar-left"> |
| <div class="form-group"> |
| <input type="text" class="form-control" placeholder="Search"> |
| </div> |
| <button type="submit" class="btn btn-default">Submit</button> |
| </form> |
| <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="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li> |
| <li><a href="/set/avatar/">修改头像</a></li> |
| <li><a href="/backend/">后台管理</a></li> |
| <li role="separator" class="divider"></li> |
| <li><a href="{% url 'logout' %}">退出登陆</a></li> |
| </ul> |
| |
| <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel"> |
| <div class="modal-dialog modal-lg" role="document"> |
| <div class="modal-content"> |
| <h1 class="text-center">修改密码</h1> |
| <div class="row"> |
| <div class="col-md-8 col-md-offset-2"> |
| <div class="form-group"> |
| <label for="">用户名</label> |
| <input type="text" disabled value="{{ request.user.username }}" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="">原密码</label> |
| <input type="password" id="id_old_password" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="">新密码</label> |
| <input type="password" id="id_new_password" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="">确认密码</label> |
| <input type="password" id="id_confirm_password" class="form-control"> |
| </div> |
| <div class="modal-footer"> |
| <button type="button" class="btn btn-default" data-dismiss="modal">取消</button> |
| <button class="btn btn-primary" id="id_edit">修改</button> |
| <span style="color: red" id="password_error"></span> |
| </div> |
| <br> |
| <br> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </li> |
| {% else %} |
| <li><a href="{% url 'reg' %}">注册</a></li> |
| <li><a href="{% url 'login' %}">登陆</a></li> |
| {% endif %} |
| </ul> |
| </div> |
| </div> |
| </nav> |
| <div class="container-fluid"> |
| <div class="col-md-2"> |
| <div class="panel panel-primary"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">广告</h3> |
| </div> |
| <div class="panel-body"> |
| 事成之后,上海别墅一套外加现金500万 |
| </div> |
| </div> |
| <div class="panel panel-danger"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">小红书</h3> |
| </div> |
| <div class="panel-body"> |
| 抓紧联系:00000000 |
| </div> |
| </div> |
| <div class="panel panel-info"> |
| <div class="panel-heading"> |
| <h3 class="panel-title"></h3> |
| </div> |
| <div class="panel-body"> |
| 你想要的,这里都有 |
| </div> |
| </div> |
| </div> |
| <div class="col-md-8"> |
| <ul class="media-list"> |
| {% for article_obj in article_queryset %} |
| <li class="media"> |
| <h4 class="media-heading"><a href="/{{ article_obj.blog.userinfo.username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4> |
| <div class="media-left"> |
| <a href="#"> |
| <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80"> |
| </a> |
| </div> |
| <div class="media-body"> |
| {{ article_obj.desc }} |
| </div> |
| {# Newbe36524 发布于 2020-06-11 09:04 评论(0)阅读(23)#} |
| <br> |
| <div> |
| <span><a href="/{{ article_obj.blog.userinfo.username}}/">{{ article_obj.blog.userinfo.username }} </a></span> |
| <span>发布于 </span> |
| <span>{{ article_obj.create_time|date:'Y-m-d' }} </span> |
| <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }}) </span> |
| <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }})</span> |
| </div> |
| </li> |
| <hr> |
| {% endfor %} |
| |
| </ul> |
| </div> |
| <div class="col-md-2"> |
| <div class="panel panel-primary"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">重金求子</h3> |
| </div> |
| <div class="panel-body"> |
| 事成之后,上海别墅一套外加现金500万 |
| </div> |
| </div> |
| <div class="panel panel-danger"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">千万大奖</h3> |
| </div> |
| <div class="panel-body"> |
| 抓紧联系:00000000 |
| </div> |
| </div> |
| <div class="panel panel-info"> |
| <div class="panel-heading"> |
| <h3 class="panel-title">小红书</h3> |
| </div> |
| <div class="panel-body"> |
| 你想要的这里都有 |
| </div> |
| </div> |
| </div> |
| </div> |
| <script> |
| $('#id_edit').click(function () { |
| $.ajax({ |
| url:'/set_password/', |
| type:'post', |
| data:{ |
| 'old_password':$('#id_old_password').val(), |
| 'new_password':$('#id_new_password').val(), |
| 'confirm_password':$('#id_confirm_password').val(), |
| 'csrfmiddlewaretoken':'{{ csrf_token }}' |
| }, |
| success:function (args) { |
| if (args.code == 1000){ |
| window.location.reload() |
| }else{ |
| $("#password_error").text(args.msg) |
| } |
| } |
| }) |
| }) |
| </script> |
| </body> |
| </html> |
修改密码、退出登录
修改密码与退出登录代码
| def home(request): |
| |
| article_queryset = models.Article.objects.all() |
| return render(request,'home.html',locals()) |
| |
| |
| @login_required |
| def set_password(request): |
| if request.is_ajax(): |
| back_dic = {'code':1000,'msg':''} |
| if request.method == 'POST': |
| old_password = request.POST.get('old_password') |
| new_password = request.POST.get('new_password') |
| confirm_password = request.POST.get('confirm_password') |
| is_right = request.user.check_password(old_password) |
| if is_right: |
| if new_password == confirm_password: |
| request.user.set_password(new_password) |
| request.user.save() |
| back_dic['msg'] = '修改成功' |
| else: |
| back_dic['code'] = 1001 |
| back_dic['msg'] = '两次密码不一致' |
| else: |
| back_dic['code'] = 1002 |
| back_dic['msg'] = '原密码错误' |
| return JsonResponse(back_dic) |
| |
| |
| @login_required |
| def logout(request): |
| auth.logout(request) |
| return redirect('/home/') |
| |
个人站点
准备继承模板
base.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> |
| <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> |
| <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> |
| <link rel="stylesheet" href="/media/css/{{ blog.site_theme }}/"> |
| {% block css %} |
| |
| {% endblock %} |
| </head> |
| <body> |
| <nav class="navbar navbar-inverse"> |
| <div class="container-fluid"> |
| |
| <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="#">{{ blog.site_title }}</a> |
| </div> |
| |
| |
| <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> |
| <ul class="nav navbar-nav"> |
| <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li> |
| <li><a href="#">文章</a></li> |
| <li 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="#">Action</a></li> |
| <li><a href="#">Another action</a></li> |
| <li><a href="#">Something else here</a></li> |
| <li role="separator" class="divider"></li> |
| <li><a href="#">Separated link</a></li> |
| <li role="separator" class="divider"></li> |
| <li><a href="#">One more separated link</a></li> |
| </ul> |
| </li> |
| </ul> |
| <form class="navbar-form navbar-left"> |
| <div class="form-group"> |
| <input type="text" class="form-control" placeholder="Search"> |
| </div> |
| <button type="submit" class="btn btn-default">Submit</button> |
| </form> |
| <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="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li> |
| <li><a href="/set/avatar/">修改头像</a></li> |
| <li><a href="/backend/">后台管理</a></li> |
| <li role="separator" class="divider"></li> |
| <li><a href="{% url 'logout' %}">退出登陆</a></li> |
| </ul> |
| |
| <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel"> |
| <div class="modal-dialog modal-lg" role="document"> |
| <div class="modal-content"> |
| <h1 class="text-center">修改密码</h1> |
| <div class="row"> |
| <div class="col-md-8 col-md-offset-2"> |
| <div class="form-group"> |
| <label for="">用户名</label> |
| <input type="text" disabled value="{{ request.user.username }}" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="">原密码</label> |
| <input type="password" id="id_old_password" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="">新密码</label> |
| <input type="password" id="id_new_password" class="form-control"> |
| </div> |
| <div class="form-group"> |
| <label for="">确认密码</label> |
| <input type="password" id="id_confirm_password" class="form-control"> |
| </div> |
| <div class="modal-footer"> |
| <button type="button" class="btn btn-default" data-dismiss="modal">取消</button> |
| <button class="btn btn-primary" id="id_edit">修改</button> |
| <span style="color: red" id="password_error"></span> |
| </div> |
| <br> |
| <br> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </li> |
| {% else %} |
| <li><a href="{% url 'reg' %}">注册</a></li> |
| <li><a href="{% url 'login' %}">登陆</a></li> |
| {% endif %} |
| </ul> |
| </div> |
| </div> |
| </nav> |
| <div class="container-fluid"> |
| <div class="row"> |
| <div class="col-md-3"> |
| {% load mytag %} |
| {% left_menu username %} |
| </div> |
| <div class="col-md-9"> |
| {% block content %} |
| |
| {% endblock %} |
| |
| </div> |
| </div> |
| </div> |
| |
| {% block js %} |
| |
| {% endblock %} |
| </body> |
| </html> |
site.html
| {% extends 'base.html' %} |
| |
| |
| {% block content %} |
| <ul class="media-list"> |
| {% for article_obj in article_list %} |
| <li class="media"> |
| <h4 class="media-heading"><a href="/{{ username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4> |
| <div class="media-left"> |
| <a href="#"> |
| <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80"> |
| </a> |
| </div> |
| <div class="media-body"> |
| {{ article_obj.desc }} |
| </div> |
| {# posted @ 2015-10-20 01:02 武沛齐 阅读(68527) 评论(24) 推荐(58) 编辑)#} |
| <div class="pull-right"> |
| <span>posted </span> |
| <span>@ </span> |
| <span>{{ article_obj.create_time|date:'Y-m-d' }} </span> |
| <span>{{ article_obj.blog.userinfo.username }} </span> |
| <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }}) </span> |
| <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }})</span> |
| <span><a href="#">编辑</a></span> |
| </div> |
| </li> |
| <hr> |
| {% endfor %} |
| |
| </ul> |
| {% endblock %} |
点击查看代码
| def site(request,username,**kwargs): |
| """ |
| :param kwargs: 如果该参数有值 也就意味着需要对article_list做额外的筛选操作 |
| """ |
| |
| user_obj = models.UserInfo.objects.filter(username=username).first() |
| |
| if not user_obj: |
| return render(request,'errors.html') |
| blog = user_obj.blog |
| |
| article_list = models.Article.objects.filter(blog=blog) |
| if kwargs: |
| |
| condition = kwargs.get('condition') |
| param = kwargs.get('param') |
| |
| if condition == 'category': |
| article_list = article_list.filter(category_id=param) |
| elif condition == 'tag': |
| article_list = article_list.filter(tags__id=param) |
| else: |
| year,month = param.split('-') |
| article_list = article_list.filter(create_time__year=year,create_time__month=month) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| return render(request,'site.html',locals()) |
| |
文章详情
article_detail.html
| {% extends 'base.html' %} |
| |
| {% block css %} |
| <style> |
| #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; |
| } |
| |
| .clear { |
| clear: both; |
| } |
| </style> |
| {% endblock %} |
| |
| {% block content %} |
| <h1>{{ article_obj.title }}</h1> |
| <div class="article_content"> |
| {{ article_obj.content|safe }} |
| </div> |
| {# 点赞点踩样式开始#} |
| <div class="clearfix"> |
| <div id="div_digg"> |
| <div class="diggit action" > |
| <span class="diggnum " id="digg_count">{{ article_obj.up_num }}</span> |
| </div> |
| <div class="buryit action"> |
| <span class="burynum " id="bury_count">{{ article_obj.down_num }}</span> |
| </div> |
| <div class="clear"></div> |
| <div class="diggword" id="digg_tips" style="color: red"> |
| </div> |
| </div> |
| </div> |
| {# 点赞点踩样式结束#} |
| {# 评论楼渲染开始#} |
| {# #3楼 2020-05-14 14:11 代码一字狂#} |
| <div> |
| <ul class="list-group"> |
| {% for comment in comment_list %} |
| <li class="list-group-item"> |
| <span>#{{ forloop.counter }}楼</span> |
| <span>{{ comment.comment_time|date:'Y-m-d h:i:s' }}</span> |
| <span>{{ comment.user.username }}</span> |
| <span><a class="pull-right reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span> |
| <div> |
| {# 判断当前评论是否是子评论 如果是需要渲染对应的评论人名#} |
| {% if comment.parent_id %} |
| <p>@{{ comment.parent.user.username }}</p> |
| {% endif %} |
| {{ comment.content }} |
| </div> |
| </li> |
| {% endfor %} |
| </ul> |
| |
| |
| </div> |
| {# 评论楼渲染结束#} |
| {# 文章评论样式开始 #} |
| {% if request.user.is_authenticated %} |
| <div> |
| <p><span class="glyphicon glyphicon-comment"></span>发表评论</p> |
| <div> |
| <textarea name="comment" id="id_comment" cols="60" rows="10" ></textarea> |
| </div> |
| <button class="btn btn-primary" id="id_submit">提交评论</button> |
| <span style="color: red" id="errors"></span> |
| </div> |
| {% else %} |
| <li><a href="{% url 'reg' %}">注册</a></li> |
| <li><a href="{% url 'login' %}">登陆</a></li> |
| {% endif %} |
| {# 文章评论样式结束 #} |
| {% endblock %} |
| |
| {% block js %} |
| <script> |
| // 给所有的action类绑定事件 |
| $('.action').click(function () { |
| {#alert($(this).hasClass('diggit'))#} |
| let isUp = $(this).hasClass('diggit'); |
| let $div = $(this); |
| // 朝后端发送ajax请求 |
| $.ajax({ |
| url:'/up_or_down/', |
| type:'post', |
| data:{ |
| 'article_id':'{{ article_obj.pk }}', |
| 'is_up':isUp, |
| 'csrfmiddlewaretoken':'{{ csrf_token }}' |
| }, |
| success:function (args) { |
| if(args.code == 1000){ |
| $('#digg_tips').text(args.msg) |
| // 将前端的数字加一 |
| // 先获取到之前的数字 |
| let oldNum = $div.children().text(); // 文本 是字符类型 |
| // 易错点 |
| $div.children().text(Number(oldNum) + 1) // 字符串拼接了 1+1 = 11 11 + 1 = 111 |
| }else{ |
| $('#digg_tips').html(args.msg) |
| } |
| } |
| }) |
| }) |
| // 设置一个全局的parentID字段 |
| let parentId = null; |
| // 用户点击评论按钮朝后端发送ajax请求 |
| $('#id_submit').click(function () { |
| // 获取用户评论的内容 |
| let conTent = $('#id_comment').val(); |
| // 判断当前评论是否是子评论 如果是 需要将我们之前手动渲染的@username去除 |
| if(parentId){ |
| // 找到\n对应的索引 然后利用切片 但是前片顾头不顾尾 所以索引+1 |
| let indexNum = conTent.indexOf('\n') + 1; |
| conTent = conTent.slice(indexNum) // 将indexNum之前的所有数据切除 只保留后面的部分 |
| } |
| $.ajax({ |
| url:'/comment/', |
| type:'post', |
| data:{ |
| 'article_id':'{{ article_obj.pk }}', |
| 'content':conTent, |
| // 如果parantId没有值 那么就是null 后端存储null没有任何关系 |
| 'parent_id':parentId, |
| 'csrfmiddlewaretoken':'{{ csrf_token }}' |
| }, |
| success:function (args) { |
| if(args.code ==1000){ |
| $('#error').text(args.msg) |
| |
| // 将评论框里面的内容清空 |
| $('#id_comment').val(''); |
| |
| // 临时渲染评论楼 |
| let userName = '{{ request.user.username }}'; |
| let temp = ` |
| <li class="list-group-item"> |
| |
| <span>${userName}</span> |
| <span><a href="#" class="pull-right">回复</a></span> |
| <div> |
| ${conTent} |
| </div> |
| </li> |
| ` |
| // 将生成好的标签添加到ul标签内 |
| $('.list-group').append(temp); |
| // 清空全局的parentId |
| parentId = null; |
| } |
| } |
| }) |
| }) |
| |
| // 给回复按钮绑定点击事件 |
| $('.reply').click(function () { |
| // 需要评论对应的评论人姓名 还需要评论的主键值 |
| // 获取用户名 |
| let commentUserName = $(this).attr('username'); |
| // 获取主键值 直接修改全局 |
| parentId = $(this).attr('comment_id'); |
| // 拼接信息塞给评论框 |
| $('#id_comment').val('@' + commentUserName + '\n').focus() |
| }) |
| </script> |
| {% endblock %} |
文章详情的视图代码
| def article_detail(request,username,article_id): |
| """ |
| 应该需要校验username和article_id是否存在,但是我们这里先只完成正确的情况 |
| 默认不会瞎搞 |
| :param request: |
| :param username: |
| :param article_id: |
| :return: |
| """ |
| user_obj = models.UserInfo.objects.filter(username=username).first() |
| blog = user_obj.blog |
| |
| article_obj = models.Article.objects.filter(pk=article_id,blog__userinfo__username=username).first() |
| if not article_obj: |
| return render(request,'errors.html') |
| |
| comment_list = models.Comment.objects.filter(article=article_obj) |
| return render(request,'article_detail.html',locals()) |
| |
| import json |
| from django.db.models import F |
| def up_or_down(request): |
| """ |
| 1.校验用户是否登陆 |
| 2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章) |
| 3.当前用户是否已经给当前文章点过了 |
| 4.操作数据库了 |
| :param request: |
| :return: |
| """ |
| if request.is_ajax(): |
| back_dic = {'code':1000,'msg':''} |
| |
| if request.user.is_authenticated(): |
| article_id = request.POST.get('article_id') |
| is_up = request.POST.get('is_up') |
| |
| is_up = json.loads(is_up) |
| |
| |
| article_obj = models.Article.objects.filter(pk=article_id).first() |
| if not article_obj.blog.userinfo == request.user: |
| |
| is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj) |
| if not is_click: |
| |
| |
| if is_up: |
| |
| models.Article.objects.filter(pk=article_id).update(up_num = F('up_num') + 1) |
| back_dic['msg'] = '点赞成功' |
| else: |
| |
| models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1) |
| back_dic['msg'] = '点踩成功' |
| |
| models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up) |
| else: |
| back_dic['code'] = 1001 |
| back_dic['msg'] = '你已经点过了,不能再点了' |
| else: |
| back_dic['code'] = 1002 |
| back_dic['msg'] = '你个臭不要脸的!' |
| else: |
| back_dic['code'] = 1003 |
| back_dic['msg'] = '请先<a href="/login/">登陆</a>' |
| return JsonResponse(back_dic) |
| |
| |
| from django.db import transaction |
| def comment(request): |
| |
| if request.is_ajax(): |
| back_dic = {'code': 1000, 'msg': ""} |
| if request.method == 'POST': |
| if request.user.is_authenticated(): |
| article_id = request.POST.get('article_id') |
| content = request.POST.get("content") |
| parent_id = request.POST.get('parent_id') |
| |
| with transaction.atomic(): |
| models.Article.objects.filter(pk=article_id).update(comment_num = F('comment_num') + 1) |
| models.Comment.objects.create(user=request.user,article_id=article_id,content=content,parent_id=parent_id) |
| back_dic['msg'] = '评论成功' |
| else: |
| back_dic['code'] = 1001 |
| back_dic['msg'] = '用户未登陆' |
| return JsonResponse(back_dic) |
| |
| from app01.utils.mypage import Pagination |
| @login_required |
| def backend(request): |
| |
| article_list = models.Article.objects.filter(blog=request.user.blog) |
| |
| page_obj = Pagination(current_page=request.GET.get('page',1),all_count=article_list.count()) |
| page_queryset = article_list[page_obj.start:page_obj.end] |
| return render(request,'backend/backend.html',locals()) |
| |
| |
修改头像
save.avatar.html
| {% extends 'base.html' %} |
| |
| |
| {% block content %} |
| <h3 class="text-center">修改头像</h3> |
| <form action="" method="post" enctype="multipart/form-data"> |
| {% csrf_token %} |
| <p> |
| 原头像: |
| <img src="/media/{{ request.user.avatar }}" alt=""> |
| </p> |
| <p> |
| |
| <label for="myfile">新头像: |
| {% load static %} |
| <img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px"> |
| </label> |
| <input type="file" id="myfile" name="avatar" style="display: none" > |
| |
| </p> |
| <input type="submit" class="btn btn-info"> |
| </form> |
| {% endblock %} |
| |
| {% block js %} |
| <script> |
| $("#myfile").change(function () { |
| |
| |
| let myFileReaderObj = new FileReader(); |
| |
| let fileObj = $(this)[0].files[0]; |
| |
| myFileReaderObj.readAsDataURL(fileObj) |
| |
| |
| myFileReaderObj.onload = function(){ |
| $('#myimg').attr('src',myFileReaderObj.result) |
| } |
| }) |
| </script> |
| {% endblock %} |
修改头像视图
| @login_required |
| def set_avatar(request): |
| if request.method == 'POST': |
| file_obj = request.FILES.get('avatar') |
| |
| |
| |
| user_obj = request.user |
| user_obj.avatar = file_obj |
| user_obj.save() |
| return redirect('/home/') |
| blog = request.user.blog |
| username = request.user.username |
| return render(request,'set_avatar.html',locals()) |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY