一、点赞和踩灭

样式

先来做样式,修改article_detail.html,增加div_digg的div

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div></div>{% endblock %}

View Code

在static-->css目录下,创建文件article_detail.css

#div_digg {    float: right;margin-bottom: 10px;margin-right: 30px;font-size: 12px;width: 125px;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;
}

View Code

从博客园拷贝2个图片到static-->img目录下

修改base.html,引入article_detail.css

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="shortcut icon" href="https://common.cnblogs.com/favicon.ico" type="image/x-icon"/>{#公共样式#}<link rel="stylesheet" href="/static/css/theme/common.css">{#个人站点主题样式#}<link rel="stylesheet" href="/static/css/theme/{{ blog.theme }}">{#bootstrap#}<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"><script src="/static/js/jquery.js"></script><script src="/static/bootstrap/js/bootstrap.js"></script>{#文章详情#}<link rel="stylesheet" href="/static/css/article_detail.css"></head>
<body>
<div class="header"><p class="title">{{ blog.title }}</p>
</div><div class="container-fluid"><div class="row"><div class="col-md-3">{#加载自定义标签模块#}{% load my_tags %}{#调用get_query_data标签,它返回left_region.html,是已经被渲染过的文件#}{% get_query_data username %}</div><div class="col-md-9">{% block content %}{% endblock %}</div></div>
</div></body>
</html>

View Code

点击一篇文章:http://127.0.0.1:8000/xiao/articles/5/

拉到最下面,右侧效果如下:

绑定事件

推荐和反对有2个按钮,要绑定2个事件吗?答案是可以,但是不推荐使用!为什么呢?

因为会造成大量代码重复!这2个按钮需要发送的文章id和用户id是一样的,唯一不同的就是:一个是推荐,一个是反对。

修改article_detail.html增加div,测试js代码

注意:在base.html中已经导入了jquery

hasClass() 方法检查被选元素是否包含指定的 class。它返回True和False

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div></div><script>$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");alert(is_up);})</script>{% endblock %}

View Code

刷新网页,点击文章右下角的推荐,提示true

点击反对,提示false

发送ajax请求

数据分析

到底是用get还是post呢?有一个规范:查询用get,更改用post

查看blog_articleupdown表,这个是点赞表。插入一条记录,需要3个参数,分别是是否点赞、文章id、用户id

注意:这个用户id是当前登录用户,不是文章作者!

由于在视图函数中,有一个全局变量request.user。它是用户登录之后,才有的!所以这个用户id,不需要传。

只需要传2个值就可以了,分别是是否点赞和文章id。

在article_detail.html中,已经有一个article_obj,它是一个model对象,那么就可以得到文章id。

是否点赞,通过js代码中的is_up变量,也可以知道。

所以发送的数据,不需要使用标签选择器来获取。直接使用即可!

操作流程

  1. 用户点击推荐或者反对,触发点击事件
  2. 发送ajax请求给服务器
  3. 服务器接收参数,生成一条点赞或者踩灭的记录
  4. 服务器将返回结果响应给浏览器,浏览器中的ajax代码的success中的data接收响应体
  5. ajax对响应的数据做判断,操作DOM,显示给用户看!

服务器处理

修改urls.py,增加一个路径digg

urlpatterns = [path('admin/', admin.site.urls),path('login/', views.login),path('index/', views.index),path('logout/', views.logout),path('', views.index),#点赞或者踩灭path('digg/', views.digg),#文章详情re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail),# 跳转re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite),# 个人站点re_path('(?P<username>\w+)/$', views.homesite),
]

View Code

注意:digg要放到文章详情的上面

修改article_detail.html

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div></div>{% csrf_token %}<script>$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);}})} else {location.href = "/login/";}})</script>{% endblock %}

View Code

修改views.py,增加视图函数digg

注意:导入2个模块

import json
from django.http import JsonResponse

视图函数如下:

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown
from django.db.models import Sum,Avg,Max,Min,Count
from django.db.models import F
import json
from django.http import JsonResponse# Create your views here.
def login(request):if request.method=="POST":user=request.POST.get("user")pwd=request.POST.get("pwd")# 用户验证成功,返回user对象,否则返回Noneuser=auth.authenticate(username=user,password=pwd)if user:# 登录,注册session# 全局变量 request.user=当前登陆对象(session中)
            auth.login(request,user)return redirect("/index/")return render(request,"login.html")def index(request):article_list=Article.objects.all()return render(request,"index.html",{"article_list":article_list})def logout(request):  # 注销
    auth.logout(request)return redirect("/index/")def query_current_site(request,username):  # 查询当前站点的博客标题# 查询当前站点的用户对象user = UserInfo.objects.filter(username=username).first()if not user:return render(request, "not_found.html")# 查询当前站点对象blog = user.blogreturn blogdef homesite(request,username,**kwargs):  # 个人站点主页print("kwargs", kwargs)blog = query_current_site(request,username)# 查询当前用户发布的所有文章if not kwargs:article_list = Article.objects.filter(user__username=username)else:condition = kwargs.get("condition")params = kwargs.get("params")#判断分类、随笔、归档if condition == "category":article_list = Article.objects.filter(user__username=username).filter(category__title=params)elif condition == "tag":article_list = Article.objects.filter(user__username=username).filter(tags__title=params)else:year, month = params.split("/")article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,create_time__month=month)return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list})def article_detail(request,username,article_id):blog = query_current_site(request,username)#查询指定id的文章article_obj = Article.objects.filter(pk=article_id).first()user_id = UserInfo.objects.filter(username=username).first().nidreturn render(request,'article_detail.html',{"blog":blog,"username":username,'article_obj':article_obj,"user_id":user_id})def digg(request):print(request.POST)if request.method == "POST":#ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值is_up = json.loads(request.POST.get("is_up"))article_id = request.POST.get("article_id")user_id = request.user.pkresponse = {"state": True, "msg": None}  # 初始状态#判断当前登录用户是否对这篇文章做过点赞或者踩灭操作obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()if obj:response["state"] = False  # 更改状态else:#插入一条记录new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)return JsonResponse(response)else:return HttpResponse("非法请求")

View Code

json处理问题

ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值

json支持7种数据格式,其中true和false是其中2种。那么反序列化之后,会得到一个布尔值。

为什么一定要反序列化呢?因为...create(...is_up=is_up) 等式右边的值,非空。那么ORM会认为是True!

所以不管ajax传过来的是true和falase,它都是字符串,那么ORM执行时,始终都是True

JsonResponse

JsonResponse对象是HttpRespon的子类,它主要和父类的区别在于:

1.它的默认Content-Type 被设置为: application/json

2.第一个参数,data应该是一个字典类型,如果不是字典,抛出 TypeError的异常

它返回json数据,那么ajax接收时,不需要反序列化,可以直接使用!

刷新网页,点击右侧的推荐,查看浏览器控制台

查看blog_articleupdown表记录,发现多了一条记录

查看Pycharm控制台输出:

<QueryDict: {'article_id': ['6'], 'csrfmiddlewaretoken': ['JgLyFpVgp92Rs8ppPCd2pm9jVj6z8bo9KSsMwKnakpB6CwTCT1K58v2JHLeR5ejN'], 'is_up': ['true']}>

判断用户之前的操作

当用户第一次点击推荐时,直接将数字加1。再次点击时,提示您已经推荐过!再次点击返回时,也提示您已经推荐过。

同理,当用户对一篇点击返回后。之后不论是点击推荐还是反对,都会提示您已经反对过!

不可以取消推荐或者反对!

修改views.py,获取上一次操作的记录

def digg(request):print(request.POST)if request.method == "POST":#ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值is_up = json.loads(request.POST.get("is_up"))article_id = request.POST.get("article_id")user_id = request.user.pkresponse = {"state": True, "msg": None}  # 初始状态#判断当前登录用户是否对这篇文章做过点赞或者踩灭操作obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()if obj:response["state"] = False  # 更改状态response["handled"] = obj.is_up  # 获取之前的操作,返回true或者falseprint(obj.is_up)else:#插入一条记录new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)return JsonResponse(response)else:return HttpResponse("非法请求")

View Code

修改article_detail.html的js代码

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div></div>{% csrf_token %}<script>$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功} else {if (data.handled) {  //判断之前的操作记录$("#digg_tips").html("您已经推荐过!")} else {$("#digg_tips").html("您已经反对过!")}}}})} else {location.href = "/login/";}})</script>{% endblock %}

View Code

修改static-->css目录下的article_detail.css,增加样式

#div_digg {    float: right;margin-bottom: 10px;margin-right: 30px;font-size: 12px;width: 125px;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;
}#digg_tips{    color: red;
}

View Code

重启django项目,页面强制刷新几次!

再次点击,提示已经点过了

修改article_detail.html的js代码,增加动态效果,1秒后,清空红色文字

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div></div>{% csrf_token %}<script>$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功} else {if (data.handled) {  //判断之前的操作记录$("#digg_tips").html("您已经推荐过!")} else {$("#digg_tips").html("您已经反对过!")}setTimeout(function () {$("#digg_tips").html("")  //清空提示文字}, 1000)}}})} else {location.href = "/login/";}})</script>{% endblock %}

View Code

效果如下:

更新文章表

blog_articleupdown表有一个联合唯一索引,即使重复点击,也不会增加记录!

blog_article表有一个up_count和down_count字段,分别表示推荐和反对的计数。

那么这2个字段,也需要同时更新。在原有数值的基础上加1,需要使用F查询!

导入F模块,完整代码如下:

def digg(request):print(request.POST)if request.method == "POST":#ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值is_up = json.loads(request.POST.get("is_up"))article_id = request.POST.get("article_id")user_id = request.user.pkresponse = {"state": True, "msg": None}  # 初始状态#判断当前登录用户是否对这篇文章做过点赞或者踩灭操作obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()if obj:response["state"] = False  # 更改状态response["handled"] = obj.is_up  # 获取之前的操作,返回true或者falseprint(obj.is_up)else:#插入一条记录new_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)return JsonResponse(response)else:return HttpResponse("非法请求")

View Code

清空blog_articleupdown表记录

访问网页,重新点击。再次刷新页面,那么值就会更新了!

网页实时展示

上面已经实现了,点击之后,更新数据库。但是需要刷新网页,才会有效果。想要用户能实时看到数字,需要进行DOM操作。

获取网页原有的值,进行加1。最后进行DOM操作,展示给用户看!

修改article_detail.html的js代码

注意:js是弱类型语言,获取到值时,不能直接进行加法,需要强制转换才行!

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div></div>{% csrf_token %}<script>$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#获取提示的span标签#}var _this= $(this).children("span");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功var val=_this.text();  //获取text值//在原有的基础上加1。注意:一定要进行类型转换_this.text(parseInt(val)+1)} else {if (data.handled) {  //判断之前的操作记录$("#digg_tips").html("您已经推荐过!")} else {$("#digg_tips").html("您已经反对过!")}setTimeout(function () {$("#digg_tips").html("")  //清空提示文字}, 1000)}}})} else {location.href = "/login/";}})</script>{% endblock %}

View Code

清空blog_articleupdown表记录

将blog_article表的相关字段设置为0

访问网页,重新点击,效果如下:

优化js代码

将if判断部分,改成三元运算

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div></div>{% csrf_token %}<script>$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#获取提示的span标签#}var _this = $(this).children("span");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功var val = _this.text();  //获取text值//在原有的基础上加1。注意:一定要进行类型转换_this.text(parseInt(val) + 1)} else {// 重复提交var val = data.handled ? "您已经推荐过!" : "您已经反对过!";$("#digg_tips").html(val);setTimeout(function () {$("#digg_tips").html("")  //清空提示文字}, 1000)}}})} else {location.href = "/login/";}})</script>{% endblock %}

View Code

事务

是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。

在上面的例子,点赞时,需要更新2个表。一个是blog_articleupdown表,一个是blog_article表。

如果有一个表没有更新,那么就会产生脏数据!为了避免这个问题,需要使用事务!

举例:模拟异常情况

修改digg视图函数,模拟异常

def digg(request):print(request.POST)if request.method == "POST":#ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值is_up = json.loads(request.POST.get("is_up"))article_id = request.POST.get("article_id")user_id = request.user.pkresponse = {"state": True, "msg": None}  # 初始状态#判断当前登录用户是否对这篇文章做过点赞或者踩灭操作obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()if obj:response["state"] = False  # 更改状态response["handled"] = obj.is_up  # 获取之前的操作,返回true或者falseprint(obj.is_up)else:#插入一条记录new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)ask  # 这一行故意出错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)return JsonResponse(response)else:return HttpResponse("非法请求")

View Code

清空blog_articleupdown表,将blog_article表的up_count和down_count字段设置为0

刷新页面,重新点击,打开浏览器工具-->network

出行了500错误

查看blog_articleupdown表,多了一条记录

查看blog_article表,对应文章的up_count字段,发现还是为0

使用事务

修改digg视图函数,导入模块transaction。

使用语法很简单,将相关原子操作的代码直接tab一下,就可以了

完整代码如下:

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown
from django.db.models import Sum,Avg,Max,Min,Count
from django.db.models import F
import json
from django.http import JsonResponse
from django.db import transaction# Create your views here.
def login(request):if request.method=="POST":user=request.POST.get("user")pwd=request.POST.get("pwd")# 用户验证成功,返回user对象,否则返回Noneuser=auth.authenticate(username=user,password=pwd)if user:# 登录,注册session# 全局变量 request.user=当前登陆对象(session中)
            auth.login(request,user)return redirect("/index/")return render(request,"login.html")def index(request):article_list=Article.objects.all()return render(request,"index.html",{"article_list":article_list})def logout(request):  # 注销
    auth.logout(request)return redirect("/index/")def query_current_site(request,username):  # 查询当前站点的博客标题# 查询当前站点的用户对象user = UserInfo.objects.filter(username=username).first()if not user:return render(request, "not_found.html")# 查询当前站点对象blog = user.blogreturn blogdef homesite(request,username,**kwargs):  # 个人站点主页print("kwargs", kwargs)blog = query_current_site(request,username)# 查询当前用户发布的所有文章if not kwargs:article_list = Article.objects.filter(user__username=username)else:condition = kwargs.get("condition")params = kwargs.get("params")#判断分类、随笔、归档if condition == "category":article_list = Article.objects.filter(user__username=username).filter(category__title=params)elif condition == "tag":article_list = Article.objects.filter(user__username=username).filter(tags__title=params)else:year, month = params.split("/")article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,create_time__month=month)return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list})def article_detail(request,username,article_id):blog = query_current_site(request,username)#查询指定id的文章article_obj = Article.objects.filter(pk=article_id).first()user_id = UserInfo.objects.filter(username=username).first().nidreturn render(request,'article_detail.html',{"blog":blog,"username":username,'article_obj':article_obj,"user_id":user_id})def digg(request):print(request.POST)if request.method == "POST":#ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值is_up = json.loads(request.POST.get("is_up"))article_id = request.POST.get("article_id")user_id = request.user.pkresponse = {"state": True, "msg": None}  # 初始状态#判断当前登录用户是否对这篇文章做过点赞或者踩灭操作obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()if obj:response["state"] = False  # 更改状态response["handled"] = obj.is_up  # 获取之前的操作,返回true或者falseprint(obj.is_up)else:with transaction.atomic():#插入一条记录new_obj = ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)ask  # 模拟异常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)return JsonResponse(response)else:return HttpResponse("非法请求")

View Code

清空blog_articleupdown表

刷新页面,重新点击,再一次返回500错误

查看blog_articleupdown表,发现并没增加一条记录。

这样就比较合理了!删除异常代码

def digg(request):print(request.POST)if request.method == "POST":#ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值is_up = json.loads(request.POST.get("is_up"))article_id = request.POST.get("article_id")user_id = request.user.pkresponse = {"state": True, "msg": None}  # 初始状态#判断当前登录用户是否对这篇文章做过点赞或者踩灭操作obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()if obj:response["state"] = False  # 更改状态response["handled"] = obj.is_up  # 获取之前的操作,返回true或者falseprint(obj.is_up)else:with transaction.atomic():#插入一条记录new_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)return JsonResponse(response)else:return HttpResponse("非法请求")

View Code

刷新页面,重新点击

查看blog_articleupdown表,发现增加了一条记录。

查看blog_article表,对应文章的up_count字段,发现为1了!

事务用起来很简单,是因为django把复杂的逻辑给封装好了!
有时间的小伙伴,还可以加一个需求,自己不能给自己点赞!

二、用户评论

评论样式

先写html的样式代码

修改article_detail.html

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div><div class="clearfix"></div><div class="comment"><p>评论列表</p><ul class="comment_list list-group">{% for comment in comment_list %}<li class="list-group-item"><div><a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;<span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;<a href="">{{ comment.user.username }}</a><a href="" class="pull-right"><span>回复</span></a></div><div><p>{{ comment.content }}</p></div></li>{% endfor %}</ul><p>发表评论</p><p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p><div><textarea name="" id="comment_content" cols="60" rows="10"></textarea></div><input type="button" value="submit" class="btn btn-default comment_btn"></div></div>{% csrf_token %}<script>$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#获取提示的span标签#}var _this = $(this).children("span");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功var val = _this.text();  //获取text值//在原有的基础上加1。注意:一定要进行类型转换_this.text(parseInt(val) + 1)} else {// 重复提交var val = data.handled ? "您已经推荐过!" : "您已经反对过!";$("#digg_tips").html(val);setTimeout(function () {$("#digg_tips").html("")  //清空提示文字}, 1000)}}})} else {location.href = "/login/";}})</script>{% endblock %}

View Code

修改css目录下的article_detail.css

注意放一个gif图片到img目录下

/*推荐和反对*/
#div_digg {    float: right;margin-bottom: 10px;margin-right: 30px;font-size: 12px;width: 125px;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;
}#digg_tips{    color: red;
}/*评论*/input.author{background-image: url("/static/img/icon_form.gif");background-repeat: no-repeat;border: 1px solid #ccc;
    padding: 4px 4px 4px 30px;width: 300px;font-size: 13px;
}input.author {background-position: 3px -3px;
}

View Code

刷新网页,拉到最下面,效果如下:

评论步骤

  • 提交根评论请求
  • 显示根评论
  1. render
  2. Ajax显示
  • 提交子评论
  • 显示子评论
  • 评论树

发送ajax请求

查看评论表模型

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

View Code

它有6个字段,其中nid、user、parent_comment、create_time是可选的。

user 在视图函数中,可以直接获取,不需要ajax传此参数。

parent_comment字段的值为空,表示这是一条根评论。否则为一条子评论!

那么ajax如果传一个空值,表示为根评论,否则为子评论。

create_time字段有一个属性:auto_now_add=True。

字段在实例第一次保存的时候会保存当前时间,不管你在这里是否对其赋值。但是之后的save()是可以手动赋值的。

也就是新实例化一个model,想手动存其他时间,就需要对该实例save()之后赋值然后再save()

根评论

修改article_detail.html的js代码,只需要发送3个参数即可。分别是content,atricle_id,parent_comment

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div><div class="clearfix"></div><div class="comment"><p>评论列表</p><ul class="comment_list list-group">{% for comment in comment_list %}<li class="list-group-item"><div><a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;<span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;<a href="">{{ comment.user.username }}</a><a href="" class="pull-right"><span>回复</span></a></div><div><p>{{ comment.content }}</p></div></li>{% endfor %}</ul><p>发表评论</p><p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"value="{{ request.user.username }}"></p><div><textarea name="" id="comment_content" cols="60" rows="10"></textarea></div><input type="button" value="submit" class="btn btn-default comment_btn"></div></div>{% csrf_token %}<script>// 点赞和踩灭$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#获取提示的span标签#}var _this = $(this).children("span");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功var val = _this.text();  //获取text值//在原有的基础上加1。注意:一定要进行类型转换_this.text(parseInt(val) + 1)} else {// 重复提交var val = data.handled ? "您已经推荐过!" : "您已经反对过!";$("#digg_tips").html(val);setTimeout(function () {$("#digg_tips").html("")  //清空提示文字}, 1000)}}})} else {location.href = "/login/";}})// 提交评论$(".comment_btn").click(function () {{#评论内容#}var content = $("#comment_content").val();{#默认为空#}var pid = "";$.ajax({url: "/comment/",type: "post",data: {content: content,article_id: "{{ article_obj.pk }}",pid: pid,csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);// 清空输入框的内容$("#comment_content").val("")}})})</script>{% endblock %}

View Code

修改urls.py,增加路径comment

urlpatterns = [path('admin/', admin.site.urls),path('login/', views.login),path('index/', views.index),path('logout/', views.logout),path('', views.index),#点赞或者踩灭path('digg/', views.digg),# 评论path('comment/', views.comment),#文章详情re_path('(?P<username>\w+)/articles/(?P<article_id>\d+)/$', views.article_detail),# 跳转re_path('(?P<username>\w+)/(?P<condition>category|tag|achrive)/(?P<params>.*)/$', views.homesite),# 个人站点re_path('(?P<username>\w+)/$', views.homesite),
]

View Code

修改views.py,增加视图函数comment,完整代码如下:

from django.shortcuts import render,HttpResponse,redirect
from django.contrib import auth
from blog.models import Article,UserInfo,Blog,Category,Tag,ArticleUpDown,Comment
from django.db.models import Sum,Avg,Max,Min,Count
from django.db.models import F
import json
from django.http import JsonResponse
from django.db import transaction# Create your views here.
def login(request):if request.method=="POST":user=request.POST.get("user")pwd=request.POST.get("pwd")# 用户验证成功,返回user对象,否则返回Noneuser=auth.authenticate(username=user,password=pwd)if user:# 登录,注册session# 全局变量 request.user=当前登陆对象(session中)
            auth.login(request,user)return redirect("/index/")return render(request,"login.html")def index(request):article_list=Article.objects.all()return render(request,"index.html",{"article_list":article_list})def logout(request):  # 注销
    auth.logout(request)return redirect("/index/")def query_current_site(request,username):  # 查询当前站点的博客标题# 查询当前站点的用户对象user = UserInfo.objects.filter(username=username).first()if not user:return render(request, "not_found.html")# 查询当前站点对象blog = user.blogreturn blogdef homesite(request,username,**kwargs):  # 个人站点主页print("kwargs", kwargs)blog = query_current_site(request,username)# 查询当前用户发布的所有文章if not kwargs:article_list = Article.objects.filter(user__username=username)else:condition = kwargs.get("condition")params = kwargs.get("params")#判断分类、随笔、归档if condition == "category":article_list = Article.objects.filter(user__username=username).filter(category__title=params)elif condition == "tag":article_list = Article.objects.filter(user__username=username).filter(tags__title=params)else:year, month = params.split("/")article_list = Article.objects.filter(user__username=username).filter(create_time__year=year,create_time__month=month)return render(request,"homesite.html",{"blog":blog,"username":username,"article_list":article_list})def article_detail(request,username,article_id):blog = query_current_site(request,username)#查询指定id的文章article_obj = Article.objects.filter(pk=article_id).first()user_id = UserInfo.objects.filter(username=username).first().nidreturn render(request,'article_detail.html',{"blog":blog,"username":username,'article_obj':article_obj,"user_id":user_id})def digg(request):print(request.POST)if request.method == "POST":#ajax发送的过来的true和false是字符串,使用json反序列化得到布尔值is_up = json.loads(request.POST.get("is_up"))article_id = request.POST.get("article_id")user_id = request.user.pkresponse = {"state": True, "msg": None}  # 初始状态#判断当前登录用户是否对这篇文章做过点赞或者踩灭操作obj = ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()if obj:response["state"] = False  # 更改状态response["handled"] = obj.is_up  # 获取之前的操作,返回true或者falseprint(obj.is_up)else:with transaction.atomic():#插入一条记录new_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)return JsonResponse(response)else:return HttpResponse("非法请求")def comment(request):print(request.POST)if request.method == "POST":# 获取数据user_id = request.user.pkarticle_id = request.POST.get("article_id")content = request.POST.get("content")pid = request.POST.get("pid")# 生成评论对象with transaction.atomic():  # 增加事务# 评论表增加一条记录comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)# 当前文章的评论数加1Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)response = {"state": False}  # 初始状态if comment.user_id:  # 判断返回值response = {"state": True}return JsonResponse(response)  # 返回json对象else:return HttpResponse("非法请求")

View Code

刷新网页,发表一条评论

查看表blog_comment,多了一条记录

查看blog_article表,对应的comment_count值,数值加1了。

渲染评论

修改article_detail视图函数,将评论列表传给模板

def article_detail(request,username,article_id):blog = query_current_site(request,username)#查询指定id的文章article_obj = Article.objects.filter(pk=article_id).first()user_id = UserInfo.objects.filter(username=username).first().nidcomment_list = Comment.objects.filter(article_id=article_id)dict = {"blog":blog,"username":username,'article_obj':article_obj,"user_id":user_id,"comment_list":comment_list,}return render(request,'article_detail.html',dict)

View Code

修改article_detail.html,使用for循环,遍历列表

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div><div class="clearfix"></div><div class="comment"><p>评论列表</p><ul class="comment_list list-group">{% for comment in comment_list %}<li class="list-group-item"><div><a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;<span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;<a href="">{{ comment.user.username }}</a><a href="" class="pull-right"><span>回复</span></a></div><div><p>{{ comment.content }}</p></div></li>{% endfor %}</ul><p>发表评论</p><p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"value="{{ request.user.username }}"></p><div><textarea name="" id="comment_content" cols="60" rows="10"></textarea></div><input type="button" value="submit" class="btn btn-default comment_btn"></div></div>{% csrf_token %}<script>// 点赞和踩灭$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#获取提示的span标签#}var _this = $(this).children("span");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功var val = _this.text();  //获取text值//在原有的基础上加1。注意:一定要进行类型转换_this.text(parseInt(val) + 1)} else {// 重复提交var val = data.handled ? "您已经推荐过!" : "您已经反对过!";$("#digg_tips").html(val);setTimeout(function () {$("#digg_tips").html("")  //清空提示文字}, 1000)}}})} else {location.href = "/login/";}})// 提交评论$(".comment_btn").click(function () {{#评论内容#}var content = $("#comment_content").val();{#默认为空#}var pid = "";$.ajax({url: "/comment/",type: "post",data: {content: content,article_id: "{{ article_obj.pk }}",pid: pid,csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);// 清空输入框的内容$("#comment_content").val("")}})})</script>{% endblock %}

View Code

刷新网页,效果如下:

评论实时展示

上面提交评论后,网页不能立即看到,需要刷新之后,才能看到。这样体验不好!

查看博客园的评论,提交之后,会立即看到评论。此时不会显示楼层,只会显示评论内容!

那么只需要提交成功之后,操作DOM,在评论列表中,追加一段li标签,展示一下,就可以了!

数据获取问题

那么内容从何而来呢?

1.直接从html中获取相关数据

2.让服务器返回相关数据,从响应体中取数据。

针对这2种方式,我们选择第2种!

为什么不选择第一种呢?因为第一种是原始输入框中的值,那么存储到数据后之后。就不一定还是输入框的值!

服务器存储到数据库之前,会将提交的数据,做一次处理!

我们想要的效果,就是不论是DOM操作,追加一段html代码。还是刷新网页,加载评论。这2种方式,评论内容是一摸一样的!

所以,我们必须选择第二种方案,让服务器返回存储的值给ajax,ajax操作DOM,最加一段html代码,给用户展示!

数据展示

修改comment视图函数,返回3个变量给ajax

def comment(request):print(request.POST)if request.method == "POST":# 获取数据user_id = request.user.pkarticle_id = request.POST.get("article_id")content = request.POST.get("content")pid = request.POST.get("pid")# 生成评论对象with transaction.atomic():  # 增加事务# 评论表增加一条记录comment = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid)# 当前文章的评论数加1Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1)response = {"state": False}  # 初始状态if comment.user_id:  # 判断返回值response = {"state": True}#响应体增加3个变量response["timer"] = comment.create_time.strftime("%Y-%m-%d %X")response["content"] = comment.contentresponse["user"] = request.user.usernamereturn JsonResponse(response)  # 返回json对象else:return HttpResponse("非法请求")

View Code

修改article_detail.html中的js代码,使用append最加一段li标签

{% extends "base.html" %}{% block content %}<div class="article_info"><h4 class="text-center">{{ article_obj.title }}</h4><div class="content">{{ article_obj.content|safe }}</div><div id="div_digg"><div class="diggit action"><span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span></div><div class="buryit action"><span class="burynum" id="bury_count">{{ article_obj.down_count }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"></div></div><div class="clearfix"></div><div class="comment"><p>评论列表</p><ul class="comment_list list-group">{% for comment in comment_list %}<li class="list-group-item"><div><a href="">#{{ forloop.counter }}楼</a>&nbsp;&nbsp;<span class="small">{{ comment.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;<a href="">{{ comment.user.username }}</a><a href="" class="pull-right"><span>回复</span></a></div><div><p>{{ comment.content }}</p></div></li>{% endfor %}</ul><p>发表评论</p><p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"value="{{ request.user.username }}"></p><div><textarea name="" id="comment_content" cols="60" rows="10"></textarea></div><input type="button" value="submit" class="btn btn-default comment_btn"></div></div>{% csrf_token %}<script>// 点赞和踩灭$(".action").click(function () {{#hasClass() 方法检查被选元素是否包含指定的 class#}var is_up = $(this).hasClass("diggit");{#获取提示的span标签#}var _this = $(this).children("span");{#判断是否登录#}if ("{{ request.user.username }}") {$.ajax({url: "/digg/",type: "post",data: {is_up: is_up,article_id: "{{ article_obj.pk }}",csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);console.log(typeof data);if (data.state) {//提交成功var val = _this.text();  //获取text值//在原有的基础上加1。注意:一定要进行类型转换_this.text(parseInt(val) + 1)} else {// 重复提交var val = data.handled ? "您已经推荐过!" : "您已经反对过!";$("#digg_tips").html(val);setTimeout(function () {$("#digg_tips").html("")  //清空提示文字}, 1000)}}})} else {location.href = "/login/";}})// 提交评论$(".comment_btn").click(function () {{#评论内容#}var content = $("#comment_content").val();{#默认为空#}var pid = "";$.ajax({url: "/comment/",type: "post",data: {content: content,article_id: "{{ article_obj.pk }}",pid: pid,csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()},success: function (data) {console.log(data);{#获取3个值#}var comment_time = data.timer;var comment_content = data.content;var comment_user = data.user;{#组织li标签#}var $li = ` <li class="list-group-item"><div><span class="small">${comment_time}</span>&nbsp;&nbsp;<a href="">${comment_user}</a></div><div><p>${comment_content}</p></div></li>`;{#追加到评论列表中#}$(".comment_list").append($li);// 清空输入框的内容$("#comment_content").val("")}})})</script>{% endblock %}

View Code

刷新网页,重新评论,效果如下:

评论可以实时展示了

刷新网页,效果如下:

显示出楼层


参考资料:

Django 基础教程

转载声明:
作者:肖祥
出处: https://www.cnblogs.com/xiao987334176/

转载于:https://www.cnblogs.com/bqwzx/p/10198770.html

python 全栈开发,Day82(点赞和踩灭,用户评论)相关推荐

  1. python全栈开发工程师_【2018重磅巨献】老男孩Python高级全栈开发工程师第三期...

    2018年老男孩python全栈第三期老男孩python全栈3期 老男孩教育Python全栈开发为国内首家专注python全方面开发的教育机构 老男孩Python全栈第三期课程详细目录,高清不加密! ...

  2. python 全栈开发,Day103(微信消息推送,结算中心业务流程)

    python 全栈开发,Day103(微信消息推送,结算中心业务流程) 昨日内容回顾 第一部分:考试题(Python基础)第二部分:路飞相关 1. 是否遇到bug?难解决的技术点?印象深刻的事?- o ...

  3. 路飞学城python全栈开发_[Python] 老男孩路飞学城Python全栈开发重点班 骑士计划最新100G...

    简介 老男孩&路飞学城Python全栈开发重点班课程,作为Python全栈教学系列的重头戏,投入了全新的课程研发和教学精力,也是Python骑士计划的核心教学,由ALEX老师开班镇守,一线技术 ...

  4. python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)

    python 全栈开发,Day104(DRF用户认证,结算中心,django-redis) 考试第二部分:MySQL数据库 6.  MySQL中char和varchar的区别(1分) char是定长, ...

  5. python全栈开发要学些什么_战争热诚的python全栈开发之路

    从学习python开始,一直是自己摸索,但是时间不等人啊,所以自己为了节省时间,决定报个班系统学习,下面整理的文章都是自己学习后,认为重要的需要弄懂的知识点,做出链接,一方面是为了自己找的话方便,一方 ...

  6. 收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!

    Python Web全栈开发入门实战教程教程    大家好,我叫亓官劼(qí guān jié ),这个<Python Web全栈开发入门实战教程教程>是一个零基础的实战教程,手把手带你开 ...

  7. python利器手机版-将安卓手机打造成你的python全栈开发利器

    超神利器- 相信多数安卓用户都使用过Qpython这款移动端的Python编辑器吧?之前我也研究过一阵子这个工具,但因为一次简单的爬虫让我对它失望之极.Qpython不支持lxml这个模块,然而pyt ...

  8. termux pythonlxml安装_将安卓手机打造成你的python全栈开发利器

    原标题:将安卓手机打造成你的python全栈开发利器 超神利器 相信多数安卓用户都使用过Qpython这款移动端的Python编辑器吧?之前我也研究过一阵子这个工具,但因为一次简单的爬虫让我对它失望之 ...

  9. python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)

    python全栈开发笔记第二模块 第四章 :常用模块(第二部分)     一.os 模块的 详解 1.os.getcwd()    :得到当前工作目录,即当前python解释器所在目录路径 impor ...

最新文章

  1. 我使用过的Linux命令之file - 检测并显示文件类型
  2. Request转发---应用
  3. html兄弟选择器怎么用,CSS的相邻兄弟选择器用法示例讲解
  4. 【⌛工欲善其事,必先利其器⏳】葵花宝典の费曼学习法
  5. MyBatis Plus Generator——MyBatis Plus代码生成器DEMO
  6. (3) Hibernate的查询 标准(Criteria)查询
  7. vue如何使浏览器url固定_怎么给 vue的 vue-router url地址最后加上 / 斜杠
  8. 华为交换机默认vlan都是通的吗_【思唯网络学院】华为交换机常用的三种vlan划分方法...
  9. localStorage sessionStorage
  10. 11gR2 Grid Infrastructure Installation prerequisites On Windows
  11. 3dmax教程技术:3dmax 怎么去掉贴图_3dmax如何去掉贴图
  12. 晶体三极管共射放大电路(共射基本放大电路)的研究与设计
  13. Steam游戏导入vrPlus中运行
  14. socketException之broken pipe
  15. 插件化开发之发展历史及特性区别总结
  16. Android判断用户是否已完成设置向导(开机向导)
  17. 信号满格怎么显示无法连接服务器,Win7 32系统网络信号满格却无法连接上网怎么处理...
  18. python 列表排序_python列表排序有哪些
  19. 教育行业疫情分析研判报告撰写格式与模板详解
  20. 3a2b递归排列java,写点关于递归的话题(一)

热门文章

  1. 后台获取html控件的值
  2. 用javascript判断当前是安卓平台还是ios平台
  3. 工作之本地存储RAID5一硬盘离线恢复简要说明
  4. 初识 ASP.NET 3.5 MVC 开发
  5. 怎么理解马氏距离呢?简单的解释
  6. 来自艾斯维尔的计算机科学系的期刊排行,研究生必备!
  7. SHADER 用 step替代 if-else
  8. 领域驱动设计战术模式--值对象
  9. AWS_EC2_Oracle安装教程
  10. JS 原型链 prototypt 和隐式原型 _proto_