How I Added User Authentication to My Hacker News Clone Django Website

In this tutorial, we will continue designing the Hacker News clone website by adding a user authentication feature. Anyone interested in using our application to share the latest news must be authenticated. This means the person has to register on the platform and be able to log in and log out.

Once authenticated, the creator can be able to submit or edit posts. I have already demonstrated how to authenticate users in previous Django project tutorials. Since we are replicating an existing application, we have to show how the particular feature was designed.

Therefore, in this series, we will:

  1. Create a user authentication feature for registering, sign-in and sign-out functionalities.
  2. Add more views to allow authenticated users to submit or edit posts.
  3. Give readers the option to show their appreciation for the news read by upvoting or downvoting the post.

User Authentication

Signup View

from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login

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,'signup.html',{'form':form})
    else:
        form = UserCreationForm()
        return render(request, 'signup.html', {'form':form})

This code is a Django view function that handles user registration. It imports the UserCreationForm from Django’s built-in authentication forms, as well as the authenticate, login, and logout functions from Django’s authentication system.

The signup function first checks if the user is already authenticated. If they are, it redirects them to the homepage. If the user is not authenticated, the function checks if the request method is POST, meaning that the form has been submitted.

If the form has been submitted, it creates a new instance of the UserCreationForm with the data from the request. It then checks if the form is valid. If it is, it saves the new user, authenticates them using their username and password, logs them in, and redirects them to the homepage.

If the form is not valid, it returns a response that renders a template called signup.html and passes in the invalid form to be displayed to the user.

If the request method is not POST, meaning that the form has not been submitted, it creates a new instance of the UserCreationForm and returns a response that renders the signup.html template and passes in the empty form to be displayed to the user.

Sign-in View

from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth import  login

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,'signin.html',{'form':form})
    else:
        form = AuthenticationForm()
        return render(request, 'signin.html', {'form':form})

This code checks if the user is already authenticated, and if they are, it redirects them to the homepage.

If the user is not authenticated, the function checks if the request method is POST, which means the form has been submitted. If it has, it retrieves the username and password from the request data and uses the authenticate function to check if the provided credentials are valid.

If the credentials are valid, it logs in the user using the login function and redirects them to the homepage. If the credentials are not valid, it creates a new instance of Django’s built-in AuthenticationForm and returns a response that renders a template called signin.html and passes in the form to be displayed to the user.

If the request method is not POST, that means the form has not been submitted, it creates a new instance of the AuthenticationForm and returns a response that renders the signin.html template and passes in the empty form to be displayed to the user.

Logout View

from django.contrib.auth import logout
        
def signout(request):
    logout(request)
    return redirect('/')

This view function uses the logout function from Django’s authentication system to log out the user associated with the given request. After logging out the user, it redirects them to the homepage.

And here are the urlpatterns:

path('signin', signin, name='signin'),
path('signup', signup, name='signup'),
path('signout', signout, name='signout'),

Adding More Views

Submit View

from .forms import PostForm

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')

This code is a Django view function that handles the submission of a new post. It first checks if the user is authenticated, and if they are not, it redirects them to the signin URL.

If the user is authenticated, it creates a new instance of a form called PostForm. It then checks if the request method is POST. If the form has been submitted, it creates a new instance of the PostForm with the data from the request.

It then checks if the form is valid. If it is, it retrieves the title, URL, and description from the form’s cleaned data and uses them to create a new Post object. It also sets the creator attribute of the post to the current user and sets the created_on attribute to the current date and time. It then saves the new post to the database and redirects the user to the homepage.

If the form is not valid or if the request method is not POST, meaning that the form has not been submitted, it returns a response that renders a template called submit.html and passes in the form to be displayed to the user.

Here is the PostForm class. Create a forms.py file and add it there.

from django import forms
from .models import Post


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ('title','url','description')

The PostForm class inherits from forms.ModelForm and defines a nested Meta class that specifies the model and fields to be used by the form.Β  A ModelForm allows you to create and update instances of a specific model.

In this case, the model attribute is set to Post, and the fields attribute is set to a tuple containing the fields title, URL, and description. This means that the form will include fields for these three attributes of the Post model.

When an instance of this form is created and validated, it can be used to create or update a Post object with the data entered by the user.

Edit View

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})

This view function handles the editing of an existing post. It takes two arguments: request and id. The id argument is used to retrieve the Post object that should be edited.

The function uses the get_object_or_404 function to retrieve the Post object with the given id. If no such object exists, it raises a 404 error.

If the request method is POST, meaning that the form has been submitted, it creates a new instance of the PostForm with the data from the request and the instance parameter set to the retrieved Post object. It then checks if the form is valid. If it is, it saves the changes to the post and redirects the user to the homepage.

If the request method is not POST, meaning that the form has not been submitted, it creates a new instance of the PostForm with the instance parameter set to the retrieved Post object. This pre-populates the form fields with the current values of the post. It then returns a response that renders a template called submit.html and passes in the form to be displayed to the user.

Here are the urlpatterns:

path('submit', SubmitPostView, name='submit'),
path('edit/<int:id>', EditPostView, name='edit'),

Upvote and Downvote Views

from .models import Vote

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')

The function is used to handle upvoting a post by a user. Here’s what the code does:

  1. It checks if the user making the request is authenticated (i.e., logged in).
  2. If the user is authenticated, it retrieves the post with the given id from the database.
  3. It then retrieves all votes for that post from the database.
  4. It checks if the user has already voted for that post by filtering the votes for that post by the current user.
  5. If the user has not already voted for that post, it creates a new Vote object with the current user as the voter and the post as the post being voted for.
  6. It then saves this new vote to the database.
  7. Finally, it redirects the user to the homepage.

If the user is not authenticated, it redirects them to the signin page.

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')

In this function, it checks if the user has already voted. If so, it deletes it. And then redirects them to the home page.

Finally, the urlpatterns:

path('vote/<int:id>', UpVoteView, name='vote'),
path('downvote/<int:id>', DownVoteView, name='dvote'),

The Templates

Please check the source code for the templates. They all go by the same explanation from the previous series. They all check if the user is authenticated before allowing them to benefit from the app’s functionalities.

From the templates, notice how the names given in the urlpatterns were used to refer to the URLs. All these were made possible using Django templating engine.

Conclusion

We have come a long way in this Hacker News project created using the Django web framework. In the final part of this project, we will create views for the comment functionality. Thereafter, we test every feature of this app using information from the Hacker News website. If some features do not function as intended, we make the needed adjustments. Stay tuned!

πŸ’‘ Recommended: How I Coded a Hacker News Clone in Django