在本教程中,我们将使用Django Web 框架构建一个完整的 Web 应用程序– 类似于 Hacker News 网站。如果您想创建一个完整的网站,这是一个很棒的 Django 项目想法。没有什么比自己开发框架更好的学习框架的方法了。
关于黑客新闻
Hacker News 是一个社交新闻网站,由投资基金和初创企业孵化器 Y-Combinator 运营和管理。该网站主要关注计算机科学和创业学。
该网站将自己定义为一个平台,人们可以在其中分享任何“满足个人好奇心”的东西。
看看这里的网站 –黑客新闻
我们将制作一个包含网站所有主要功能的网络应用程序。话已经说够了;现在让我们深入探讨一下!
该网站的一些有趣的功能
现在我们将看到我们将使用 Django 创建的网站的有趣功能
1.顶部导航栏
- “黑客新闻”按钮可带您返回主页。
- 新建按钮显示所有最新提交的内容。
- 过去按钮显示30 分钟前提交的内容等。
- 同样,还有询问、表演和工作等并不是那么重要。
- 然后有一个提交选项和一个注销/登录选项
我们将在我们的应用程序中对所有这些进行编码。
2. 个人及帖子列表
然后我们在主页上显示了一个帖子列表。
- 每个帖子都有一个投票选项来投票
- 每个帖子都会显示总投票数和总评论数
- 显示创建者的用户名
- 显示提交时间
此外,当您单击评论时,该网站会将您重定向到评论页面:
在这里我们可以对帖子发表评论,也可以回复其他人。
这里还有一个有趣的功能是形成线索评论。
那是我们回复评论的时候;我们的回复应该显示在它的正下方。这并不像听起来那么容易,别担心,我们将在接下来的部分中讨论这一点。
3. 用户认证
一项更重要的功能是用户身份验证。在网站上,只有拥有帐户后,我们才能发布、评论或回复。
和注册视图
我们将再次将这两个视图包含在我们的代码中!
4. 发布提交视图
该网站有一个提交视图:
我们可以在这里提交帖子的标题、URL和描述。就是这样,伙计们!这是我们必须要做的。那么让我们开始吧!
在 Django Web 应用程序中编写黑客新闻克隆代码
首先,我们必须创建一个新的 Django 项目。那么让我们这样做:
django-admin startproject HackerNews |
另外,创建 Django 应用程序。我将我的命名为 – hnapp。所以代码是:
django-admin startapp hnapp |
好的。不要忘记在 settings.py 文件中添加应用程序名称。此外,我们将使用模板继承来编码我们的模板。
在 Django HackerNews 项目目录中创建一个templates文件夹,并在 settings.py 文件中提及它的路径。
我们通过在 TEMPLATES/settings.py 中添加以下行来完成此操作
'DIRS' : [os.path.join(BASE_DIR, 'HackerNews/templates' )], |
好的,酷,现在在创建的模板文件夹中添加 base.html –我们的网站基本模板:
在文件中添加代码:
<! DOCTYPE html> < html > < head > < meta name = "viewport" content = "width=device-width, initial-scale=1" > < link rel = "stylesheet" href = "https://www.w3schools.com/w3css/4/w3.css" > < style > body { margin: 0; font-family: Arial, Helvetica, sans-serif; } .topnav { overflow: hidden; background-color: #333; } .topnav a { float: left; color: #f2f2f2; text-align: center; padding: 14px 16px; text-decoration: none; font-size: 17px; } .topnav a:hover { background-color: #ddd; color: black; } .topnav a.active { background-color: #FF0000; color: white; } .topnav-right { float: right; } p.ex1 { padding-left: 40px; } </ style > </ head > < body > {% block content %} {% endblock %} </ body > </ html > |
这段代码是为了我们的网络应用程序的美观。我尝试添加 CSS 使其看起来比默认布局更好。
另外,在Project/urls.py中添加以下行:
from django.contrib import admin from django.urls import path,include urlpatterns = [ path( 'admin/' , admin.site.urls), path(' ',include(' hnapp.urls')), ] |
一切就绪后,现在让我们继续讨论真正的 Django 部分。
1. 编写所需模型的代码
对于网站,我们需要:
- 帖子模型:存储帖子信息
- 投票模型:存储每个帖子的点赞数
- 评论模型:存储每个帖子的评论。
以及预先构建的用户模型来存储用户帐户信息。因此在models.py中添加以下模型:
帖子型号:
class Post(models.Model): title = models.CharField( "HeadLine" , max_length = 256 , unique = True ) creator = models.ForeignKey(User, on_delete = models.SET_NULL, null = True ) created_on = models.DateTimeField(auto_now_add = True ) url = models.URLField( "URL" , max_length = 256 ,blank = True ) description = models.TextField( "Description" , blank = True ) votes = models.IntegerField(null = True ) comments = models.IntegerField(null = True ) def __unicode__( self ): return self .title def count_votes( self ): self .votes = Vote.objects. filter (post = self ).count() def count_comments( self ): self .comments = Comment.objects. filter (post = self ).count() |
在这里,我们有两个函数来计算每个帖子的总投票数和总评论数。请注意,当创建者删除其帐户时,帖子不应被删除,因此将on_delete设置为models.SET_NULL。
投票模型:
class Vote(models.Model): voter = models.ForeignKey(User, on_delete = models.CASCADE) post = models.ForeignKey(Post, on_delete = models.CASCADE) def __unicode__( self ): return f "{self.user.username} upvoted {self.link.title}" |
该模型将存储有关哪个用户对哪个帖子点赞的信息。
最终的评论模型:
class Comment(models.Model): creator = models.ForeignKey(User, on_delete = models.SET_NULL, null = True ) post = models.ForeignKey(Post, on_delete = models.CASCADE) content = models.TextField() identifier = models.IntegerField() parent = models.ForeignKey( 'self' , on_delete = models.SET_NULL, null = True ) def __unicode__( self ): return f "Comment by {self.user.username}" |
每个评论都会有一个创建者、创建者评论的帖子以及评论内容本身。
现在,每个回复评论还将有一个父评论,即给出回复的评论。因此我们需要一个父字段,它是评论模型本身的外键
我们还需要另一个字段,一个标识符字段,用于标识不同级别的回复评论。要理解这一点,请考虑下图:
因此,
- 对帖子本身的评论将具有标识符 = 0 和父 = 无,因为它们是最上面的评论。
- 第一级回复评论的标识符 = 1,并且回复的评论作为父评论(标识符 = 0)
- 同样,第二级回复评论的标识符 = 2,父评论的标识符 = 1。
稍后我们将看到如何使用这两个字段以线程方式显示评论。
模型就这样了,现在使用admin.site.register(model_name)行在admin.py中注册三个模型。
另外,不要忘记使用以下代码运行迁移:
python manage.py migrate python manage.py makemigrations python manage.py migrate |
2. 编码视图和相应的模板
模型就位后,现在让我们对视图进行编码。现在,对于完整的网站,我们需要以下视图:
- 主页视图:显示帖子列表
- 新帖子视图:显示所有最新帖子
- 过去的帖子视图:显示 30 分钟或更长时间的帖子
- 单个帖子视图:显示评论表单和现有评论
- 回复评论视图:显示回复现有评论的表单
- 用户信息视图:显示有关用户的信息
- 用户帖子视图:显示特定用户的帖子
- 提交视图:显示提交表单
- 编辑视图:编辑提交的表单
- 登录视图:显示登录页面
- 注册视图:显示注册页面
- 注销视图:注销用户
除此之外,我们还需要两个视图来处理帖子的赞成票和反对票。
哇很多观点!那么让我们开始吧,不要浪费太多时间。
1. 主页视图
因此,在Views.py中,添加PostListView(主页)功能视图:
def PostListView(request): posts = Post.objects. all () for post in posts: post.count_votes() post.count_comments() context = { 'posts' : posts, } return render(request, 'postlist.html' ,context) |
对于每篇帖子,我们都会计算总投票数和评论数,然后再将其显示在网页上。
urls.py中的 url 端点:
path(' ',PostListView, name=' home'), |
将postlist.html模板添加到Django App文件夹本身的templates文件夹中。
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > < a href = "{% url 'new_home'%}" >New</ a > < a href = "{% url 'past_home'%}" >Past</ a > < a href = "{% url 'submit'%}" >Submit</ a > {% if request.user.is_authenticated %} < div class = "topnav-right" > < a href = "{% url 'signout' %}" >Sign Out </ a > </ div > {% else %} < div class = "topnav-right" > < a href = "{% url 'signin' %}" >Sign In </ a > </ div > {% endif %} </ div > < div class = "w3-panel w3-light-grey w3-leftbar w3-border-grey" > < ol > {% for post in posts %} < li >< p >< a href = "{{post.url}}" >< strong >{{post.title}}</ strong ></ a > - < a href = "{% url 'vote' post.id %}" >Upvote</ a > - < a href = "{% url 'dvote' post.id %}" >Downvote</ a ></ p > {% if post.creator == request.user%} < p >{{post.votes}} votes | Created {{post.created_on}}| < a href = "{% url 'user_info' post.creator.username %}" >{{post.creator.username}}</ a > | < a href = "{% url 'post' post.id %}" > {{post.comments}} Comments</ a > | < a href = "{% url 'edit' post.id %}" > Edit</ a ></ p ></ li > {%else %} < p >{{post.votes}} votes | Created {{post.created_on}}| < a href = "{% url 'user_info' post.creator.username %}" >{{post.creator.username}}</ a > | < a href = "{% url 'post' post.id %}" > {{post.comments}} Comments</ a ></ p ></ li > {%endif%} {% endfor %} </ ol > </ div > {% endblock %} |
在这里,如果用户已登录,我们将在导航栏中显示注销;如果用户未登录,我们将在导航栏中显示登录。
对于每个帖子,我们将显示帖子的标题、创建者、创建日期和时间、总投票数和评论。
此外,如果帖子的创建者是用户本身,那么我们也会显示一个编辑选项。
2. 新帖子和过去帖子查看
新视图将首先显示所有最新帖子。因此,执行此操作的代码将是:
def NewPostListView(request): posts = Post.objects. all ().order_by( '-created_on' ) for post in posts: post.count_votes() post.count_comments() context = { 'posts' : posts, } return render(request, 'hnapp/postlist.html' , context) |
同样,为了显示 30 分钟或更长时间之前创建的帖子,我们使用Python 的DateTime库。因此代码将是:
from datetime import datetime,timedelta from django.utils import timezone def PastPostListView(request): time = str ((datetime.now(tz = timezone.utc) - timedelta(minutes = 30 ))) posts = Post.objects. filter (created_on__lte = time) for post in posts: post.count_votes() post.count_comments() context = { 'posts' : posts, } return render(request, 'hnapp/postlist.html' ,context) |
这里__lte函数代表小于或等于。因此,它会过滤掉所有created_on时间小于半小时前的帖子。
视图的 url 端点:
path( 'new' ,NewPostListView, name = 'new_home' ), path( 'past' ,PastPostListView, name = 'past_home' ), |
这两个模板与主页视图相同。
3. 用户信息和用户帖子查看
当客户点击帖子的创建者姓名时,他应该到达用户信息页面。
用户信息页面应显示用户名、创建日期以及显示用户帖子页面的链接。
因此,让我们在此处编写用户信息和用户帖子视图的代码:
def UserInfoView(request,username): user = User.objects.get(username = username) context = { 'user' :user,} return render(request, 'user_info.html' ,context) def UserSubmissions(request,username): user = User.objects.get(username = username) posts = Post.objects. filter (creator = user) for post in posts: post.count_votes() post.count_comments() return render(request, 'user_posts.html' ,{ 'posts' : posts}) |
在 UserSubmissions 视图中,在显示帖子之前,我们使用for 循环计算总投票数和评论数。
视图的 URL 端点是:
path( 'user/<username>' , UserInfoView, name = 'user_info' ), path( 'posts/<username>' ,UserSubmissions, name = 'user_posts' ), |
相应的模板将是:
用户信息.html:
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > < a href = "{% url 'new_home'%}" >New</ a > < a href = "{% url 'past_home'%}" >Past</ a > < a href = "{% url 'submit'%}" >Submit</ a > {% if request.user.is_authenticated %} < div class = "topnav-right" > < a href = "{% url 'signout' %}" >Sign Out </ a > </ div > {% else %} < div class = "topnav-right" > < a href = "{% url 'signin' %}" >Sign In </ a > </ div > {% endif %} </ div > < div class = "w3-panel w3-light-grey w3-leftbar w3-border-grey" > < p >< strong >User: </ strong >{{user.username}}</ p > < p >< strong >Created: </ strong >{{user.date_joined}}</ p > </ div > < a href = "{% url 'user_posts' user.username %}" >Submissions</ a > {% endblock %} |
用户帖子.html:
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > < a href = "{% url 'new_home'%}" >New</ a > < a href = "{% url 'past_home'%}" >Past</ a > < a href = "{% url 'submit'%}" >Submit</ a > {% if request.user.is_authenticated %} < div class = "topnav-right" > < a href = "{% url 'signout' %}" >Sign Out </ a > </ div > {% else %} < div class = "topnav-right" > < a href = "{% url 'signin' %}" >Sign In </ a > </ div > {% endif %} </ div > < ol > {%for post in posts%} < div class = "w3-panel w3-light-grey w3-leftbar w3-border-grey" > < li >< p >< a href = "{{post.url}}" >{{post.title}}</ a ></ p > < p >{{post.votes}} | Created {{post.created_on}}| < a href = "{% url 'user_info' post.creator.username %}" >{{post.creator.username}}</ a > | < a href = "{% url 'post' post.id %}" > {{post.comments}} Comments</ a ></ p ></ li > </ div > {% endfor %} </ ol > {% endblock %} |
4. 提交和编辑视图
好的,现在我们来编写提交视图和编辑视图的代码。如果用户已登录,提交页面应显示提交表单。
编辑页面也可以执行相同的工作,但它将更新现有帖子而不是创建新帖子。
所以这两个函数视图将是:
from datetime import datetime def SubmitPostView(request): if request.user.is_authenticated: form = PostForm() if request.method = = "POST" : form = PostForm(request.POST) if form.is_valid(): title = form.cleaned_data[ 'title' ] url = form.cleaned_data[ 'url' ] description = form.cleaned_data[ 'description' ] creator = request.user created_on = datetime.now() post = Post(title = title, url = url, description = description, creator = creator, created_on = created_on) post.save() return redirect( '/' ) return render(request, 'submit.html' ,{ 'form' :form}) return redirect( '/signin' ) def EditPostView(request, id ): post = get_object_or_404(Post, id = id ) if request.method = = 'POST' : form = PostForm(request.POST, instance = post) if form.is_valid(): form.save() return redirect( '/' ) form = PostForm(instance = post) return render(request, 'submit.html' ,{ 'form' :form}) |
在 SubmitPostView 中,我们正在创建一个全新的 Post 对象,而在 EditPostView 中,我们只是更新现有的 Post 对象。
两个视图的 URL 指向是:
path( 'submit' ,SubmitPostView, name = 'submit' ), path( 'edit/<int:id>' ,EditListView, name = 'edit' ) |
还要在forms.py文件中添加 PostForm :
from django import forms from .models import Comment,Post class PostForm(forms.ModelForm): class Meta: model = Post fields = ( 'title' , 'url' , 'description' ) |
此外,它们的模板是相同的,因为两者都显示相同的表单
因此,submit.html:
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > {% if request.user.is_authenticated %} < div class = "topnav-right" > < a href = "{% url 'signout' %}" >Sign Out </ a > </ div > {% else %} < div class = "topnav-right" > < a href = "{% url 'signin' %}" >Sign In</ a > </ div > {% endif %} </ div > < div class = "w3-panel w3-light-grey w3-leftbar w3-border-grey" > < form method = 'post' > {% csrf_token %} {{form.as_p}} < input type = "submit" value = "Submit" > </ form > </ div > {% endblock %} |
5. 注册、登录和注销视图
这里我们将使用django.contrib.auth库来验证、登录和注销用户。
此外,我们将使用内置的 Django 用户模型、AuthenticationForm 和UserCreationForm。
所以视图将是:
from django.contrib.auth import authenticate,login,logout from django.contrib.auth.forms import AuthenticationForm,UserCreationForm def signup(request): if request.user.is_authenticated: return redirect( '/' ) if request.method = = 'POST' : form = UserCreationForm(request.POST) if form.is_valid(): form.save() username = form.cleaned_data[ 'username' ] password = form.cleaned_data[ 'password1' ] user = authenticate(username = username,password = password) login(request, user) return redirect( '/' ) else : return render(request, 'auth_signup.html' ,{ 'form' :form}) else : form = UserCreationForm() return render(request, 'auth_signup.html' ,{ 'form' :form}) def signin(request): if request.user.is_authenticated: return redirect( '/' ) if request.method = = 'POST' : username = request.POST[ 'username' ] password = request.POST[ 'password' ] user = authenticate(request, username = username, password = password) if user is not None : login(request,user) return redirect( '/' ) else : form = AuthenticationForm() return render(request, 'auth_signin.html' ,{ 'form' :form}) else : form = AuthenticationForm() return render(request, 'auth_signin.html' , { 'form' :form}) def signout(request): logout(request) return redirect( '/' ) |
视图的 URL 端点:
path( 'signin' ,signin, name = 'signin' ), path( 'signup' ,signup, name = 'signup' ), path( 'signout' ,signout, name = 'signout' ), |
auth_signup.html和auth_signin.html都将显示表单以获取用户凭据。
因此,auth_signup.html将是:
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > < a href = "{% url 'new_home'%}" >New</ a > < a href = "{% url 'past_home'%}" >Past</ a > < a href = "{% url 'submit'%}" >Submit</ a > </ div > < form method = 'post' > {% csrf_token %} {{form.as_p}} < input type = "submit" value = "Submit" > </ form > < br > < h3 >Already Have an Account??</ h3 > < a href = "{% url 'signin' %}" >Sign In Here</ a > {% endblock %} |
和auth_signin.html
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > < a href = "{% url 'new_home'%}" >New</ a > < a href = "{% url 'past_home'%}" >Past</ a > < a href = "{% url 'submit'%}" >Submit</ a > </ div > < form method = 'post' > {% csrf_token %} {{form.as_p}} < input type = "submit" value = "Submit" > </ form > < br > < h3 >Dont have an account??</ h3 > < a href = "{% url 'signup' %}" >SignUp Here</ a > {% endblock %} |
6. 编码赞成票和反对票逻辑
每当用户单击“Upvote”按钮时,该帖子的投票数应增加一票,对于“Downvote”,反之亦然。
另请注意,用户不能对特定帖子多次投赞成票/反对票。现在让我们对赞成票和反对票的视图进行编码
def UpVoteView(request, id ): if request.user.is_authenticated: post = Post.objects.get( id = id ) votes = Vote.objects. filter (post = post) v = votes. filter (voter = request.user) if len (v) = = 0 : upvote = Vote(voter = request.user,post = post) upvote.save() return redirect( '/' ) return redirect( '/signin' ) def DownVoteView(request, id ): if request.user.is_authenticated: post = Post.objects.get( id = id ) votes = Vote.objects. filter (post = post) v = votes. filter (voter = request.user) if len (v) ! = 0 : v.delete() return redirect( '/' ) return redirect( '/signin' ) |
这里的逻辑很简单:
- UpVoteView:如果对于特定帖子,特定用户的投票数等于零,则在投票模型中创建并保存该用户的新投票。
- DownVoteView:如果对于特定帖子,特定用户的投票数不等于,即大于零,则从投票模型中删除该用户的赞成票
两者的 URL 端点:
path( 'vote/<int:id>' ,UpVoteView,name = 'vote' ), path( 'downvote/<int:id>' ,DownVoteView,name = 'dvote' ), |
好的 !!
7. 编写评论页面视图
现在是该项目最激动人心的部分。评论视图应显示评论表单。此外,它应该以正确的线程顺序显示评论和相应的回复。
也就是说,注释只能按以下顺序显示:C1、C1-Child、C1-Child’s Child、C2、C2-child,依此类推。
为此,我们将使用一个递归函数,以标识符和父实例作为参数。因此对于特定的帖子 = post。
递归函数如下所示:
def func(i,parent): children = Comment.objects. filter (post = post). filter (identifier = i). filter (parent = parent) for child in children: gchildren = Comment.objects. filter (post = post). filter (identifier = i + 1 ). filter (parent = child) if len (gchildren) = = 0 : comments.append(child) else : func(i + 1 ,child) comments.append(child) |
我们首先获取所有孩子对特定家长评论的评论。然后我们找到每个子实例有多少个子实例(即 gchildren)。
如果孩子没有任何孙子(gchild),即。这是该家长评论的最底部回复。因此,我们将孩子保存到一个空列表中。
如果孩子有“ gchildren”,那么我们再次使用该函数,并将孩子作为父参数。我们这样做直到到达线程的底部。到达那里后,我们将该评论实例添加到列表中。
因此,每个线程都将以相反的顺序添加到列表中,最底部的线程注释首先保存,最顶部的最后保存。
但是,我们需要以正确的顺序显示评论线程,其中评论(标识符=0)位于顶部,后续回复位于其下方。因此在显示它们之前,我们使用Python列表的reverse(list)属性。
因此,完整的CommentView将是:
def CommentListView(request, id ): form = CommentForm() post = Post.objects.get( id = id ) post.count_votes() post.count_comments() comments = [] def func(i,parent): children = Comment.objects. filter (post = post). filter (identifier = i). filter (parent = parent) for child in children: gchildren = Comment.objects. filter (post = post). filter (identifier = i + 1 ). filter (parent = child) if len (gchildren) = = 0 : comments.append(child) else : func(i + 1 ,child) comments.append(child) func( 0 , None ) if request.method = = "POST" : if request.user.is_authenticated: form = CommentForm(request.POST) if form.is_valid(): content = form.cleaned_data[ 'content' ] comment = Comment(creator = request.user,post = post,content = content,identifier = 0 ) comment.save() return redirect(f '/post/{id}' ) return redirect( '/signin' ) context = { 'form' : form, 'post' : post, 'comments' : list ( reversed (comments)), } return render(request, 'commentpost.html' , context) |
我们调用func(0,None)因为我们想要完整的评论线程。
视图的 URL 端点:
path( 'post/<int:id>' ,CommentListView, name = 'post' ) |
我们还需要一个评论表来提交评论。因此在forms.py中,添加 CommentForm:
class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ( 'content' ,) |
commentpost.html应该显示表单和线程评论。
所以commentpost.html:
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > < a href = "{% url 'new_home'%}" >New</ a > < a href = "{% url 'past_home'%}" >Past</ a > < a href = "{% url 'submit'%}" >Submit</ a > {% if request.user.is_authenticated %} < div class = "topnav-right" > < a href = "{% url 'signout' %}" >Sign Out </ a > </ div > {% else %} < div class = "topnav-right" > < a href = "{% url 'signin' %}" >Sign In </ a > </ div > {% endif %} </ div > < div class = "w3-panel w3-light-grey w3-leftbar w3-border-grey" > < p >< a href = "{{post.url}}" >< strong >Title: {{post.title}}</ strong ></ a ></ p > {% if post.creator == request.user%} < p >{{post.votes}} votes | Created {{post.created_on}}| < a href = "{% url 'user_info' post.creator.username %}" >{{post.creator.username}}</ a > | {{post.comments}} Comments | < a href = "{% url 'edit' post.id %}" > Edit</ a ></ p > {%else %} < p >{{post.votes}} votes | Created {{post.created_on}}| < a href = "{% url 'user_info' post.creator.username %}" >{{post.creator.username}}</ a > | {{post.comments}} Comments</ p > {% endif %} < p >< strong >Description: </ strong >{{post.description}}</ p > < form method = 'post' > {% csrf_token %} {{form.as_p}} < input type = "submit" value = "Submit" > </ form > < br > </ div > {% for comment in comments %} {% if comment.identifier %} < div class = "w3-panel w3-orange w3-leftbar w3-border-red" > < p class = "ex1" style = "font-family:helvetica;" style = "color:black" >< a href = "{% url 'user_info' comment.creator.username %}" >Comment by: {{comment.creator.username}}</ a > | Thread Level: {{comment.identifier}}</ p > < p class = "ex1" style = "font-family:helvetica;" style = "color:black" >< strong >{{comment.content}}</ strong ></ p > < p class = "ex1" style = "font-family:helvetica;" style = "color:black" >< a href = "{% url 'reply' id1=post.id id2=comment.id %}" >reply</ a ></ p > </ div > {% else %} < div class = "w3-panel w3-red w3-leftbar w3-border-orange" > < p style = "font-family:helvetica;" style = "color:black" >< a href = "{% url 'user_info' comment.creator.username %}" >Comment by: {{comment.creator.username}}</ a > | Thread Level: {{comment.identifier}}</ p > < p style = "font-family:helvetica;" style = "color:black" >< strong >{{comment.content}}</ strong ></ p > < p style = "font-family:helvetica;" style = "color:black" >< a href = "{% url 'reply' id1=post.id id2=comment.id %}" >reply</ a ></ p > </ div > {% endif %} {% endfor %} |
8. 编写回复评论视图
现在,当我们单击评论下方的回复按钮时,我们应该得到一个表单来提交回复。
因此CommentReplyView将是:
def CommentReplyView(request,id1,id2): form = CommentForm() comment = Comment.objects.get( id = id2) post = Post.objects.get( id = id1) if request.method = = "POST" : if request.user.is_authenticated: form = CommentForm(request.POST) if form.is_valid(): reply_comment_content = form.cleaned_data[ 'content' ] identifier = int (comment.identifier + 1 ) reply_comment = Comment(creator = request.user, post = post, content = reply_comment_content, parent = comment, identifier = identifier) reply_comment.save() return redirect(f '/post/{id1}' ) return redirect( '/signin' ) context = { 'form' : form, 'post' : post, 'comment' : comment, } return render(request, 'reply_post.html' , context) |
回复评论将有一个父实例,这与常规的帖子评论不同,后者的父实例 = None。
视图的 URL 端点:
path( 'post/<int:id1>/comment/<int:id2>' ,CommentReplyView,name = 'reply' ), |
reply_post.html应显示父评论实例和回复表单。
因此模板reply_post.html将是:
{% extends 'base.html' %} {% block content %} < div class = "topnav" > < a class = "active" href = "{% url 'home'%}" >Hacker News</ a > < a href = "{% url 'new_home'%}" >New</ a > < a href = "{% url 'past_home'%}" >Past</ a > < a href = "{% url 'submit'%}" >Submit</ a > {% if request.user.is_authenticated %} < div class = "topnav-right" > < a href = "{% url 'signout' %}" >Sign Out </ a > </ div > {% else %} < div class = "topnav-right" > < a href = "{% url 'signin' %}" >Sign In </ a > </ div > {% endif %} </ div > < div class = "w3-panel w3-light-grey w3-leftbar w3-border-grey" > < p > < h5 >< a href = "{% url 'user_info' comment.creator.username %}" >{{comment.creator.username}}</ a > | On : < a href = "{% url 'post' post.id %}" >{{post.title}}</ a ></ h5 ></ p > < p >{{comment.content}}</ p > < form method = 'post' > {% csrf_token %} {{form.as_p}} < input type = "submit" value = "Submit" > </ form > </ div > {% endblock %} |
伟大的 !!就是这样,伙计们!
Django 项目的最终代码
整个项目可以在我的Github 个人资料中找到。请随意在您的系统中克隆存储库并使用代码。为了您的方便,我还发布了下面每个文件的完整代码。
1.模型.py
from django.db import models from django.contrib.auth.models import User # Create your models here. class Post(models.Model): title = models.CharField( "HeadLine" , max_length = 256 , unique = True ) creator = models.ForeignKey(User, on_delete = models.SET_NULL, null = True ) created_on = models.DateTimeField(auto_now_add = True ) url = models.URLField( "URL" , max_length = 256 ,blank = True ) description = models.TextField( "Description" , blank = True ) votes = models.IntegerField(null = True ) comments = models.IntegerField(null = True ) def __unicode__( self ): return self .title def count_votes( self ): self .votes = Vote.objects. filter (post = self ).count() def count_comments( self ): self .comments = Comment.objects. filter (post = self ).count() class Vote(models.Model): voter = models.ForeignKey(User, on_delete = models.CASCADE) post = models.ForeignKey(Post, on_delete = models.CASCADE) def __unicode__( self ): return f "{self.user.username} upvoted {self.link.title}" class Comment(models.Model): creator = models.ForeignKey(User, on_delete = models.SET_NULL, null = True ) post = models.ForeignKey(Post, on_delete = models.CASCADE) content = models.TextField() identifier = models.IntegerField() parent = models.ForeignKey( 'self' , on_delete = models.SET_NULL, null = True ) def __unicode__( self ): return f "Comment by {self.user.username}" |
2. 视图.py
from django.shortcuts import render,redirect,get_object_or_404 from django.views.generic import ListView from .models import Post,Vote,Comment from .forms import CommentForm,PostForm from django.contrib.auth.models import User from django.contrib.auth import authenticate,login,logout from django.contrib.auth.forms import AuthenticationForm,UserCreationForm from datetime import datetime,timedelta from django.utils import timezone from django.contrib.auth import authenticate,login,logout from django.contrib.auth.forms import AuthenticationForm,UserCreationForm # Create your views here. def PostListView(request): posts = Post.objects. all () for post in posts: post.count_votes() post.count_comments() context = { 'posts' : posts, } return render(request, 'hnapp/postlist.html' ,context) def NewPostListView(request): posts = Post.objects. all ().order_by( '-created_on' ) for post in posts: post.count_votes() post.count_comments() context = { 'posts' : posts, } return render(request, 'hnapp/postlist.html' , context) def PastPostListView(request): time = str ((datetime.now(tz = timezone.utc) - timedelta(minutes = 30 ))) posts = Post.objects. filter (created_on__lte = time) for post in posts: post.count_votes() post.count_comments() context = { 'posts' : posts, } return render(request, 'hnapp/postlist.html' ,context) def UpVoteView(request, id ): if request.user.is_authenticated: post = Post.objects.get( id = id ) votes = Vote.objects. filter (post = post) v = votes. filter (voter = request.user) if len (v) = = 0 : upvote = Vote(voter = request.user,post = post) upvote.save() return redirect( '/' ) return redirect( '/signin' ) def DownVoteView(request, id ): if request.user.is_authenticated: post = Post.objects.get( id = id ) votes = Vote.objects. filter (post = post) v = votes. filter (voter = request.user) if len (v) ! = 0 : v.delete() return redirect( '/' ) return redirect( '/signin' ) def UserInfoView(request,username): user = User.objects.get(username = username) context = { 'user' :user,} return render(request, 'hnapp/userinfo.html' ,context) def UserSubmissions(request,username): user = User.objects.get(username = username) posts = Post.objects. filter (creator = user) print ( len (posts)) for post in posts: post.count_votes() post.count_comments() return render(request, 'hnapp/user_posts.html' ,{ 'posts' : posts}) def EditListView(request, id ): post = get_object_or_404(Post, id = id ) if request.method = = 'POST' : form = PostForm(request.POST, instance = post) if form.is_valid(): form.save() return redirect( '/' ) form = PostForm(instance = post) return render(request, 'hnapp/submit.html' ,{ 'form' :form}) def CommentListView(request, id ): form = CommentForm() post = Post.objects.get( id = id ) post.count_votes() post.count_comments() comments = [] def func(i,parent): children = Comment.objects. filter (post = post). filter (identifier = i). filter (parent = parent) for child in children: gchildren = Comment.objects. filter (post = post). filter (identifier = i + 1 ). filter (parent = child) if len (gchildren) = = 0 : comments.append(child) else : func(i + 1 ,child) comments.append(child) func( 0 , None ) if request.method = = "POST" : if request.user.is_authenticated: form = CommentForm(request.POST) if form.is_valid(): content = form.cleaned_data[ 'content' ] comment = Comment(creator = request.user,post = post,content = content,identifier = 0 ) comment.save() return redirect(f '/post/{id}' ) return redirect( '/signin' ) context = { 'form' : form, 'post' : post, 'comments' : list ( reversed (comments)), } return render(request, 'hnapp/post.html' , context) def CommentReplyView(request,id1,id2): form = CommentForm() comment = Comment.objects.get( id = id2) post = Post.objects.get( id = id1) if request.method = = "POST" : if request.user.is_authenticated: form = CommentForm(request.POST) if form.is_valid(): reply_comment_content = form.cleaned_data[ 'content' ] identifier = int (comment.identifier + 1 ) reply_comment = Comment(creator = request.user, post = post, content = reply_comment_content, parent = comment, identifier = identifier) reply_comment.save() return redirect(f '/post/{id1}' ) return redirect( '/signin' ) context = { 'form' : form, 'post' : post, 'comment' : comment, } return render(request, 'hnapp/reply_post.html' , context) def SubmitPostView(request): if request.user.is_authenticated: form = PostForm() if request.method = = "POST" : form = PostForm(request.POST) if form.is_valid(): title = form.cleaned_data[ 'title' ] url = form.cleaned_data[ 'url' ] description = form.cleaned_data[ 'description' ] creator = request.user created_on = datetime.now() post = Post(title = title, url = url, description = description, creator = creator, created_on = created_on) post.save() return redirect( '/' ) return render(request, 'hnapp/submit.html' ,{ 'form' :form}) return redirect( '/signin' ) def signup(request): if request.user.is_authenticated: return redirect( '/' ) if request.method = = 'POST' : form = UserCreationForm(request.POST) if form.is_valid(): form.save() username = form.cleaned_data[ 'username' ] password = form.cleaned_data[ 'password1' ] user = authenticate(username = username,password = password) login(request, user) return redirect( '/' ) else : return render(request, 'hnapp/auth_signup.html' ,{ 'form' :form}) else : form = UserCreationForm() return render(request, 'hnapp/auth_signup.html' ,{ 'form' :form}) def signin(request): if request.user.is_authenticated: return redirect( '/' ) if request.method = = 'POST' : username = request.POST[ 'username' ] password = request.POST[ 'password' ] user = authenticate(request, username = username, password = password) if user is not None : login(request,user) return redirect( '/' ) else : form = AuthenticationForm() return render(request, 'hnapp/auth_signin.html' ,{ 'form' :form}) else : form = AuthenticationForm() return render(request, 'hnapp/auth_signin.html' , { 'form' :form}) def signout(request): logout(request) return redirect( '/' ) |
3. 网址.py
from django.contrib import admin from django.urls import path from .views import * urlpatterns = [ path(' ',PostListView, name=' home'), path( 'new' ,NewPostListView, name = 'new_home' ), path( 'past' ,PastPostListView, name = 'past_home' ), path( 'user/<username>' , UserInfoView, name = 'user_info' ), path( 'posts/<username>' ,UserSubmissions, name = 'user_posts' ), path( 'post/<int:id>' ,CommentListView, name = 'post' ), path( 'submit' ,SubmitPostView, name = 'submit' ), path( 'signin' ,signin, name = 'signin' ), path( 'signup' ,signup, name = 'signup' ), path( 'signout' ,signout, name = 'signout' ), path( 'vote/<int:id>' ,UpVoteView,name = 'vote' ), path( 'downvote/<int:id>' ,DownVoteView,name = 'dvote' ), path( 'edit/<int:id>' ,EditListView, name = 'edit' ), path( 'post/<int:id1>/comment/<int:id2>' ,CommentReplyView,name = 'reply' ), ] |
4. 表格.py
from django import forms from .models import Comment,Post class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ( 'content' ,) class PostForm(forms.ModelForm): class Meta: model = Post fields = ( 'title' , 'url' , 'description' ) |
6. 管理.py
from django.contrib import admin from .models import * # Register your models here. admin.site.register(Post) admin.site.register(Vote) admin.site.register(Comment) #admin.site.register(UserInfo) |
准则的实施
这就是编码部分!现在运行服务器
python manage.py runserver |
并转到主页:“ www.localhost.com ”
那里没有帖子,所以点击登录:
单击此处注册并注册您的帐户
注册完成后,去提交并添加一些帖子
我已经在那里提交了一些帖子,所以现在单击导航栏上的“黑客新闻”按钮即可到达主页:
您现在可以对帖子投赞成票和反对票。同样,单击“新建”和“过去”按钮。现在点击帖子下方的用户名 –在我的例子中是 Nishant:
您将看到用户信息以及提交按钮。好的,现在返回并点击评论;您将到达评论页面
提交评论
这就是我们最热门的评论。现在当点击回复时:
输入随机回复并单击“提交”。
请参阅线程级别 = 1,已重新排列自身,使父评论位于顶部。这就是递归函数正在做的事情。尝试添加更多回复,看看它是如何安排的。
伟大的 !!我们自己的 Django 项目想法正在变成现实。
参考
结论
就是这样,伙计们!您自己的黑客新闻 Web 应用程序已准备就绪。请尝试自己实现所有逻辑代码,以便更好地理解!