A little Hacker News in Django (part 8)

Daniel Dương
3 min readJun 12, 2018

--

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!

--

--