A little Hacker News in Django (part 8)
Let’s start by creating a view to upvote a comment. We can reuse our set_upvoted_post
view.
def create_upvote_view(model):
@require_POST
@login_required
def view(request, id):
obj = get_object_or_404(model, id=id)
try:
upvoted = json.loads(request.body.decode('utf-8'))['upvoted']
except (json.JSONDecodeError, KeyError):
return HttpResponseBadRequest()
obj.set_upvoted(request.user, upvoted=upvoted)
# 204: No content
return HttpResponse(status=204)
return view
Now I can do this:
set_upvoted_post = create_upvote_view(Post)
set_upvoted_comment = create_upvote_view(Comment)
Let’s try to upvote here: http://localhost:8000/posts/.
TypeError: view() got an unexpected keyword argument 'post_id'
It doesn’t work very well. Let’s try to change the interface.
@upvote_view
def set_upvoted_post(request, post_id):
return get_object_or_404(Post, id=post_id)
@upvote_view
def set_upvoted_comment(request, comment_id):
return get_object_or_404(Comment, id=comment_id)
Yeah, it might look a little bit magic, I wouldn’t necessarily advice you to do the same type of things. But it’s cool, so let’s do it.
def upvote_view(view):
@wraps(view)
@require_POST
@login_required
def new_view(request, *args, **kwargs):
obj = view(request, *args, **kwargs)
try:
upvoted = json.loads(request.body.decode('utf-8'))['upvoted']
except (json.JSONDecodeError, KeyError):
return HttpResponseBadRequest()
obj.set_upvoted(request.user, upvoted=upvoted)
# 204: No content
return HttpResponse(status=204)
return new_view
Now let’s add set_upvoted_comment
to the routes. Now, comes a problem. Right now, everything is in the posts
model. The route to set upvoted for a post is /posts/<post_id>/set_upvoted/
. Now, if we follow the logic, the route for comments should be /comments/<comment_id>/set_upvoted/
. Now if I have that route, it would be logic to have a comments
app. If I wanna play the refactoring game, this is the time. But I feel lazy so I’m going to do ugly stuff, put the route in the global urls.py
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import include, path
from hnews.posts import views as posts_views
urlpatterns = [
path('admin/', admin.site.urls),
path('posts/', include('hnews.posts.urls')),
path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
path('comments/<int:comment_id>/set_upvoted/', posts_views.set_upvoted_comment, name='set_upvoted_comment')
]
Let’s add a to_dict
to our Comment
model.
def to_dict(self, user):
return {
'content': self.content,
'how_long_ago': self.how_long_ago(),
'creator': self.creator.username,
'upvoted': self.upvotes.filter(user=user).count() > 0,
'upvote_url': reverse('set_upvoted_comment', kwargs={'comment_id': self.id}),
}
Now let’s update Post.to_dict
def to_dict(self, user):
return {
'title': self.title,
'how_long_ago': self.how_long_ago(),
'domain_name': self.get_domain_name(),
'creator': self.creator.username,
'upvoted': self.upvotes.filter(user=user).count() > 0,
'upvote_url': reverse('posts:set_upvoted_post', kwargs={'post_id': self.id}),
'comments': [comment.to_dict(user) for comment in self.comments.all()]
}
Let’s update our template now:
<div id="app">
[[ post.title ]] - [[ post.how_long_ago ]] - [[ post.domain_name ]]
{% if user.is_authenticated %}
- <span v-if="post.upvoted">Upvoted</span><span v-else>Not Upvoted</span>
- <button v-on:click="upvote(post)">Toggle upvoted</button>
{% endif %}
{% if user.is_authenticated %}
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
{% endif %}
<ul v-for="comment in post.comments">
<li>[[ comment.content ]] - [[ comment.how_long_ago ]]</li>
</ul>
</div>
Let’s try now: http://localhost:8000/posts/1/
I messed up some stuff in my database so I have this error: ’NoneType’ object has no attribute ‘username’
. Let’s fix that.
In [2]: user = User.objects.first()In [3]: user
Out[3]: <User: jack>In [6]: Post.objects.filter(creator=None).update(creator=user)
Out[6]: 2
Ok it works now. Let’s add something to upvote the comment.
<div id="app">
[[ post.title ]] - [[ post.how_long_ago ]] - [[ post.domain_name ]]
{% if user.is_authenticated %}
- <span v-if="post.upvoted">Upvoted</span><span v-else>Not Upvoted</span>
- <button v-on:click="upvote(post)">Toggle upvoted</button>
{% endif %}
{% if user.is_authenticated %}
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
{% endif %}
<ul v-for="comment in post.comments">
<li>[[ comment.content ]] - [[ comment.how_long_ago ]]</li>
{% if user.is_authenticated %}
- <span v-if="comment.upvoted">Upvoted</span><span v-else>Not Upvoted</span>
- <button v-on:click="upvote(comment)">Toggle upvoted</button>
{% endif %}
</ul>
</div>
Ok it’s good for today. Not a very productive day but it’s better than nothing :).
See ya!
- A little Hacker News in Django (Part 1)
- A little Hacker News in Django (Part 2)
- A little Hacker News in Django (Part 3)
- A little Hacker News in Django (Part 4)
- A little Hacker News in Django (Part 5)
- A little Hacker News in Django (Part 6)
- A little Hacker News in Django (Part 7)
- A little Hacker News in Django (Part 9)
- Github repository: hnews