django大作业2-话题APP
要求
编写一个话题APP。
需求:
1.用户登录、注册功能:输入用户名,密码
2.发布话题功能:用户登录后方可发布话题
3.话题评论功能:登录用户可对任意能查看到的话题进行评论(包括自己发布的话题);未登录用户不可发布评论
4.查看话题功能:登录用户可查看话题及评论内容,未登录用户仅能查看话题,话题及评论需要根据时间倒序进行展示(最新的话题及评论在最上)
技术要求:
1.数据库表:用户表、话题-评论表
2.用户注册后跳转至登录页面
3.登录后跳转至话题list页面,话题list页面要包含发布话题按钮及话题输入框
4.评论逻辑:只有话题可以评论(评论不必再有评论),不需要考虑评论嵌套
5.使用中间键判断登录状态
示意图:
话题1:xxxxxx
评论1:xxxxxx
评论2:xxxxxx
评论3:xxxxxx
话题2:xxxxxx
评论1:xxxxxx
评论2:xxxxxx
评论3:xxxxxx
话题3:xxxxxx
评论1:xxxxxx
评论2:xxxxxx
评论3:xxxxxx
注册登录部分
根据需求一,需要实现用户登录,注册功能,并且通过输入用户名密码进行登录,所以这里首先第一步需要创建用户数据库,而后创建视图函数,完善路由,以及模板渲染,大概步骤是这样。
用户数据库
首先第一步是创建一个用户数据库,这里我取名为Users(数据库中看到的是info_users),其中的元素包括用户姓名,用户密码,以及用于保存登录状态的token,底下写了几个类方法,分别是用于获取全部user实体、获取单个实体、筛选一个实体、创建一个实体。
代码如下:
from django.db import models
# Create your models here.
class Users(models.Model):
objects = models.Manager()
user_name = models.CharField(max_length=32, verbose_name="用户名", unique=True)
user_pwd = models.CharField(max_length=32, verbose_name="密码")
token = models.CharField(max_length=256, verbose_name="token", null=True)
class Meta:
db_table = "info_users"
verbose_name = "用户表"
verbose_name_plural = verbose_name
def __str__(self):
return self.user_name
@classmethod
def get_all(cls):
return Users.objects.all()
@classmethod
def get_one(cls, pk):
return Users.objects.get(pk=pk)
@classmethod
def get_list(cls,**kwargs):
filters = {}
if kwargs.get("name"):
filters["name"] = kwargs.get("name")
if kwargs.get("pwd"):
filters["pwd"] = kwargs.get("pwd")
return cls.objects.filter(**filters)
@classmethod
def creat_one(cls, user_name, pwd):
return cls.objects.create(user_name=user_name,
user_pwd=pwd
)
模板及视图函数
注册部分:
首先需要进行用户注册,这里视图函数取名为RegisterView,如果只是显示页面(method=get)只需要返回相应的html即可;如果是需要将注册的数据保存进数据库,此时的method=post,因此需要通过request.POST.get来获取注册的信息,这里get到的变量名称来源于html中的<input name=“变量名">,即通过前端传参至后端,如果这个用户名已注册,则返回”用户名已注册“,否则通过models.py的creat_one模型类方法创建一条数据保存进数据库,实现注册功能。代码如下:
#要导入的包
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views import View
# Create your views here.
from std_proj_temp.settings import PAGE_SIZE
from test2.models import Users, News, comments
from utils.utils import gen_md5
#注册
def RegisterView(request):
if request.method == 'POST':
name = request.POST.get("name")
user = Users.get_list(user_name=name)
pwd = request.POST.get("pwd")
# pwd = gen_md5(request.POST.get("pwd"))
if user:
return HttpResponse("用户名已存在")
Users.creat_one(user_name=name,pwd=pwd)
return redirect(reverse("test2:login"))
elif request.method == "GET":
return render(request, "reg.html")
相应的模板如下:
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册界面</title>
</head>
<body>
<h1 align="center">论坛注册</h1><br>
<form action="{% url 'test2:register' %}" method="POST">
{% csrf_token %}
<h3 align="center">用户:<input type="text" name="name"></h3>
<h3 align="center">密码:<input type="password" name="pwd"></h3>
<h3 align="center"><input type="submit" value="注册"></h3>
</form>
<h3 align="center">已有账号请<a href="{% url 'test2:login' %}"> 登录 </a></h3>
<h3 align="center"><a href="{% url 'test2:critic' %}"> 游客模式 </a></h3>
</body>
</html>
这里如果说不想注册仅浏览,设置了游客模式,直接转到要求的list页面,页面如下:
最后别忘了完善路由配置。
登录部分:
登录的思路和注册很像,如果method=get那么直接返回页面即可,如果为post,这里需要提取前端input标签中的name和pwd,通过get_list类方法获取对应User数据表中的内容是否一致,如果一致,则能提取到内容,bool(users)就为TRUE,及此时这个用户登录状态正确,此时我们需要做的是对用户的密码进行加密,并且添加token,而后保存数据,而后添加session,session包含name和token,以字典形式保存,而后重定向至论坛主页。
代码如下:
def LoginView(request):
if request.method == "POST":
name = request.POST.get("name")
# pwd = gen_md5(request.POST.get("pwd"))
pwd = request.POST.get("pwd")#这里如果采用gen_md5后面传入密码会找不到
filters = {
"user_name" : name,
"user_pwd" : pwd,
}
users = Users.get_list(**filters)
if users:
# print("found!")
user = users.first()
user.token = gen_md5(user.user_name) + str(time.time())
user.save()
request.session["user_name"] = user.user_name
request.session["user_token"] = user.token
return redirect(reverse("test2:critic"))
else:
# print(filters)
return HttpResponse("用户名或密码错误!")
elif request.method == "GET":
return render(request, "log_in.html",locals())
登录界面的html同注册界面大同小异,如下:
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录界面</title>
</head>
<body>
<h1 align="center">论坛登录</h1><br>
<form action="{% url 'test2:login' %}" method="POST">
{% csrf_token %}
<h3 align="center">用户:<input type="text" name="name"></h3>
<h3 align="center">密码:<input type="password" name="pwd"></h3>
<h3 align="center"><input type="submit" value="登录"></h3>
</form>
<h3 align="center">没有账号请<a href="{% url 'test2:register' %}"> 注册 </a></h3>
<h3 align="center"><a href="{% url 'test2:critic' %}"> 游客模式 </a></h3>
</body>
</html>
界面显示如下:
别忘了更新路由配置。
话题发布部分
在实现了登录注册功能后下一步就是展示论坛界面了,同样,这里首先需要创建数据表,而后编写视图函数,并且需要加入分页的功能,同时,游客的登录状态不同,页面展示的功能不同,关于分页,登录状态判断,以及如何关联页面的评论功能如下。
话题-评论表数据库
首先需要创建话题数据库,以及相应的评论数据库,一个topic可以有多个评论,这是典型的一对多模式,话题是一,评论是多,代码如下
#models.py
class News(models.Model):
# 话题数据
objects = models.Manager()
topic_name = models.CharField(max_length=400, verbose_name="新闻名称")
detail_name = models.CharField(max_length=600, verbose_name="新闻内容")
class Meta:
db_table = "news"
verbose_name = "新闻"
verbose_name_plural = verbose_name
def __str__(self):
return self.topic_name
@classmethod
def get_all(cls):
return cls.objects.all()
@classmethod
def creat_one(cls, topic_name, detail_name):
return cls.objects.create(topic_name=topic_name,
detail_name=detail_name
)
@classmethod
def get_one(cls,pk):
try:
return cls.objects.get(pk=pk)
except Exception:
return None
class comments(models.Model):
# 评论数据
objects = models.Manager()
comment_name = models.CharField(max_length=1000, null=True, verbose_name="评论内容")
topic = models.ForeignKey(News, on_delete=models.CASCADE)
class Meta:
db_table = "comments"
verbose_name = "评论"
verbose_name_plural = verbose_name
def __str__(self):
return self.comment_name
@classmethod
def get_all(cls):
return cls.objects.all()
这里关于话题,表的名称起名为News,添加了话题标题(topic_name)与内容(detail_name)两个部分。关于评论,分为了评论内容(comment_name)以及关联的话题(news)的外键,在一对多模式下,只需要多方添加一方的外键即可,用法为models.foreignkey(一方模型名称,级联删除)。
模板及视图函数
# views.py
def critic_show(request):
if request.method == "GET":
page_num = request.GET.get("page", 1)
print("执行get")
# if request.method == "GET":
news = News.get_all() # 所有News的实例
paginator = Paginator(news, PAGE_SIZE) # 实例化paginator
dics = {}
try:
data = paginator.page(page_num)
except InvalidPage: # 如果页数out of range
data = paginator.page(1) # 返回第一页
# 以上是分页
# for new in news:
for new in data:
critics = list(new.comments_set.all()) # 获取话题下的所有评论
critics.reverse() # 倒序
dics[new] = critics # 以列表的形式保存
print(news)
# comment = comments.get_all()
# topics = news.topic_name,#话题标题,传入模板
# news[1].comments_set.all()#获取话题一下所有的评论
user_token = bool(Users.objects.filter(user_name=request.session.get("user_name")).first().token)
print(f"!!!!!!!!!!!{user_token}")
return render(request, "topics.html", locals())
if request.method == 'POST':
# 登录用户发表topic部分
topic_name = request.POST.get("new_topic")
detail_name = request.POST.get("topic_comment")
datas = News()
datas.topic_name = topic_name
datas.detail_name = detail_name
datas.save()
print("已执行post")
return redirect(reverse("test2:critic"))
这里视图函数取名为critic_show,在此之前我添加了四条话题及评论,分页的限制是两条,在method=get下,首先获取所有的话题实体,而后通过实例化paginator,这里paginator传入的参数PAGE_SIZE在settings.py中,而后try获取页面数,如果超范围则返回第一页。
之前已经通过实例化paginator实例化了news数据对象,因此为了获取话题下的评论,我们只需做一个遍历,这里通过一对多的外键获取话题下所有的评论,即一方数据对象实体.多方数据表名称小写_set.all()来获取,后发表的在上面,因此还需要做一个逆序(因为获取到的数据实体是以queryset的数据类型,这里需要先转化为list而后调用reverse)。
为了传给前端,做了一个字典,key是topic实体,value是话题列表。
为了判断登录状态,还需要加入一个token变量,如果登陆了token不为空,可以发表评论。
否则发表不了:
对应的模板如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>话题</title>
</head>
<body>
<h1 align="center">欢迎来到风语论坛</h1><br>
<!--以下展示内容部分-->
{% for key,value in dics.items %}
<h3 align="center">话题:{{key}}</h3><h4 align="center"><a href="http://127.0.0.1:8000/test2/detail/{{ key.id }}/">详情</a></h4>
{% for comment in value %}
<h5 align="center">评论:{{comment}}</h5>
{% endfor %}
<h3 align="center"><-------------------------------------------------------------------------------------------------------------------><br></h3>
{% endfor %}<br>
<!--判断是否登录部分-->
<h2 align="center">欢迎您发表话题!(只对注册用户开放此功能)</h2>
{% if user_token == False %}
<h3 align="center"><a href="{% url 'test2:login' %}">请登录</a></h3>
{% else %}
<form action="{% url 'test2:critic' %}" method="POST">
{% csrf_token %}
<h2 align="center"><input type="text" name="new_topic" value="这里发布您的话题名称" style="width:1000px;height: 30px;border-radius:10px" align="center"><br><br>
<input type="text" name="topic_comment" value="这里发布您的话题内容" style="width:1000px;height: 100px;border-radius:10px" align="center"><br><br>
<input type="submit" value="点击发表" align="center"><br></h2>
</form>
<h3 align="center"><a href="{% url 'test2:logout' %}">退出登录</a></h3>
{% endif %}
<!--以下是分页部分-->
<h3 align="center"><a href="{% url 'test2:critic' %}">首页</a>
{% if data.has_previous %}
<a href="{% url 'test2:critic' %}?page={{ data.previous_page_number }}">上一页</a>
{% else %}
<a href="javascript:alert('没有上一页了')">上一页</a>
{% endif %}
{% if data.has_next %}
<a href="{% url 'test2:critic' %}?page={{ data.next_page_number }}">下一页</a>
{% else %}
<a href="javascript:alert('没有下一页了')">下一页</a>
{% endif %}
<a href="{% url 'test2:critic' %}?page={{ paginator.num_pages }}">末页</a></h3>
</body>
</html>
html中对字典的循环,可直接提取key与value,以及forloop遍历次数,还是不一样的。
单个话题评论部分
在主页查看标题点击链接可以查看内容,并且添加评论:
具体思路为通过在主页html中传递topic_id,获取话题id,而后调用数据库,查找相应数据,展示到界面,通过外键获取关联评论,post下再获取相应的发表评论数据,存进数据库。
模板及视图函数
代码比较简单就不展示了。
联合调试(中间键判断登录)、
在app目录下创建middleware.py
from django.utils.deprecation import MiddlewareMixin
from test2.models import Users
class LoginMiddleWare(MiddlewareMixin):
def process_request(self,request):
if "/test2/detail/" in request.path:
user_name = request.session.get("user_name") # session中获取用户姓名
user = Users.objects.filter(user_name=user_name).first() # 获取对象用户实例
if not user_name:
user.token = "" # 将User数据表中的token置0
user.save()
request.session["user_token"] = ""
request.session["user_id"] = "" # 清除session
return None
所要做的工作是判断缓存中还有没有,如果没有删除token,清除缓存,否则什么都不做,返回none。
写完后需要在settings.py中添加中间件类名称:
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',
'test2.middleware.LoginMiddleWare',#添加的类
]
为了更自洽,补充写了注销登录的功能:
def logout(request):
user_name = request.session.get("user_name")#session中获取用户姓名
user = Users.objects.filter(user_name=user_name).first()# 获取对象用户实例
if user:
user.token = ""# 将User数据表中的token置0
user.save()
request.session["user_token"] = ""
request.session["user_id"] = ""# 清除session
return HttpResponse("您已退出登录~")
这个视图函数没有具体返回的页面,我们只需要添加他的url,在html中具体的input标签或是a标签中通过“test2:logout”的形式引入即可。
同样,小白不懂JavaScript以及css,所以页面比较朴素