Skip to content

Latest commit

 

History

History
849 lines (735 loc) · 29.5 KB

README.md

File metadata and controls

849 lines (735 loc) · 29.5 KB

django-blog-app

Learning django and python to build a blog app.

Note: A single project can contain multiple apps, which is good for separating out different part of project. You can take a single app and add it to multiple project.

KEY POINTS

create virtual environment

  • pip list
  • cd Desktop
  • mkdir Djangoenv
  • cd Djangoenv
  • virtualenv project1_env
  • project1_env\Scripts\activate (linux: source project1_env/bin/activate)
  • where python (linux: which python)
  • where pip

install django

  • pip install django
  • python -m django --version
  • django-admin startproject django_project
  • cd django_project
  • py manage.py runserver

basic routing in django

  • create blog app within the project
  • py manage.py startapp blog
|-- blog
|    |-- __init__.py
|    |-- admin.py
|    |-- apps.py
|    |-- migration
|    |    |-- __init__.py 
|    |-- models.py
|    |-- tests.py
|    |-- views.py
|-- db.sqlite3
|-- django_project
|    |-- __init__.py
|    |-- settings.py
|    |-- urls.py
|    |-- wsgi.py
|-- manage.py
  • add home & about page

use templates to return home and about page

  • do not forget to add the blog application to the list of installed apps in project's settings.py
  • blog.apps.BlogConfig
  • by adding apps to this list, django will correctly search the template
  • from django.shortcuts import render (render function has the third optional arg, which will pass the data to the template
  • this third arg is a dict, the keys of the dict will be accessible in the template

template inheritance

  • base.html contains repeated section of home and about templates.
  • add bootstrap to the website
  • add navigation bar, beautify content, custom styles in main.css
  • Starter template
  • in Django, static files (eg. css and javascripte) need to be located in a static directory within the blog app
  • use Django url tag with the name of the route in urlpattern instead of hard-coding it for better maintainess

create database & migrations & query data

  • Django ORM, Object-Relational Mapping, is a technique that lets you query and manipulate data from a database using an object-oriented paradigm
  • Django ORM represent database structure as classes(models)
  • each class is its own table in the database, each attribute of the class is a field in the database
  • author is user, which is a separate table
  • from django.contrib.auth.models import User
  • author = models.ForeignKey(User, on_delete=models.CASCADE) # if user is deleted, post also
  • we need to run migrations to update the database with any change
    • py manage.py makemigrations
    • py manage.py sqlmigrate blog 0001
    • py manage.py migrate
  • Django python shell allow us to work with the models interactively line by line:
    • py manage.py shell
(PROJEC~1) C:\Users\Jiayi Su\Desktop\Djangoenv\django_project>py manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0001_initial.py
    - Create model Post

(PROJEC~1) C:\Users\Jiayi Su\Desktop\Djangoenv\django_project>py manage.py sqlmigrate blog 0001
BEGIN;
--
-- Create model Post
--
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(100) NOT N
ULL, "content" text NOT NULL, "date_posted" datetime NOT NULL, "author_id" integer NOT NULL REFERENCE
S "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "blog_post_author_id_dd7a8485" ON "blog_post" ("author_id");
COMMIT;

(PROJEC~1) C:\Users\Jiayi Su\Desktop\Djangoenv\django_project>py manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0001_initial... OK



(PROJEC~1) C:\Users\Jiayi Su\Desktop\Djangoenv\django_project>py manage.py shell
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from blog.models import Post
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: jiayisu>, <User: TestUser>]>
>>> User.objects.first()
<User: jiayisu>
>>> User.objects.filter(username='jiayisu')
<QuerySet [<User: jiayisu>]>
>>> User.objects.filter(username='jiayisu').first()
<User: jiayisu>
>>> user = User.objects.filter(username='jiayisu').first()
>>> user
<User: jiayisu>
>>> user.id
1
>>> user.pk
1
>>> user = User.objects.get(id=1)
>>> user
<User: jiayisu>
>>> Post.objects.all()
<QuerySet []>
>>> post_1=Post(title='Blog 1', content='First Post Content!', author=user)
>>> Post.objects.all()
<QuerySet []>
>>> post_1.save()
>>> Post.objects.all()
<QuerySet [<Post: Post object (1)>]>
>>> exit()


    def __str__(self):
        return self.title

(PROJEC~1) C:\Users\Jiayi Su\Desktop\Djangoenv\django_project>py manage.py shell
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from blog.models import Post
>>> from django.contrib.auth.models import User
>>> Post.objects.all()
<QuerySet [<Post: Blog 1>]>
>>> User.objects.filter(username='jiayisu').first()
<User: jiayisu>
>>> user = User.objects.filter(username='jiayisu').first()
>>> user
<User: jiayisu>
>>> post_2 = Post(title='Blog 2', content='Second Post Content!', author_id=user.id)
>>> post_2.save()
>>> Post.objects.all()
<QuerySet [<Post: Blog 1>, <Post: Blog 2>]>
>>> post = Post.objects.first()
>>> post.content
'First Post Content!'
>>> post.date_posted
datetime.datetime(2019, 3, 7, 22, 23, 49, 609491, tzinfo=<UTC>)
>>> post.author
<User: jiayisu>
>>> post.author.email
'[email protected]'

>>> #--------------------------get all the post by this user-------------------------------------------
>>> user.post_set
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManag
er object at 0x000001A0F67EAB00>
>>> user.post_set.all()
<QuerySet [<Post: Blog 1>, <Post: Blog 2>]>
>>> #--------------------------create a post directly (no need to specify user & save)------------------
>>> user.post_set.create(title='Blog 3', content='Third Post Content!')
<Post: Blog 3>
>>> Post.objects.all()
<QuerySet [<Post: Blog 1>, <Post: Blog 2>, <Post: Blog 3>]>
>>>

query the data and pass it to view.py

  • from .models import Post # use the post from database
  • 'post':Post.objects.all() # query the posts
  • py manage.py runserver # now, the posts are updated
  • change the formatting of the date_posted: in home.html, post.date_posted(date:"F d, Y")
  • to see Post model in admin page, we have to register Post in admin.py
    • from .models import Post
    • admin.site.register(Post)
  • now we can update, delete, change author of any post

user registration

  • py manage.py startapp users
  • in settings.py, add "'users.apps.UsersConfig'," to INSTALLED_APPS list
  • in users/views.py
def register(request):
    form = UserCreationForm()
    return render(request, 'users/register.html', {'form': form})
  • create users/templates/users/register.html, which extends blog/base.html
  • in the form, we need to add "{% csrf_token %}", which is Cross Site Request Forgery protection
  • in the project's url.py, create an url pattern that uses the register/views.py, so we can navigate to this page in the browser
    • from users import views as user_views
    • path('register/', user_views.register, name='register'),
  • if we get a POST request, it instantiates the UserCreationForm with that POST data, else eg. it's a GET request, we just desplay a blank form.
  • if the form is valid, create the user, save the data, grab the username, and redirect the user to the home page.
  • from django.contrib import messages, which has debug, info, success, warning and error tags.
def register(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            messages.success(request, f'Account created for {username}!')
            return redirect('blog-home')
    else:
        form = UserCreationForm()
    return render(request, 'users/register.html', {'form': form})
  • put flush messages in base template so any massages pop up on any page.
        <div class="col-md-8">
            {% if messages %}
                {% for message in messages %}
                    <div class="alert alert-{{ message.tags }}">
                        {{ message }}
                    </div>
                {% endfor%}
            {% endif %}
            {% block content %}{% endblock %}
        </div>

add email field to the form and use django-crispy-forms

  • users/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm


class UserRegisterForm(UserCreationForm):   # inheritsUserCreationForm
    email = forms.EmailField()              # default: required=True

    class Meta:                             # keep the configuration in one place
        model = User                        # the model that'll be affected is User model
        fields = ['username', 'email', 'password1', 'password2']  # fields will be shown in the model in this order
  • crispy forms allow us to put some simple tags in the template that'll style the form in a bootstrap fashion
  • pip install django-crispy-forms
  • in project/settings.py INSTALLED_APPS, add 'crispy_forms', CRISPY_TEMPLATE_PACK = 'bootstrap4'
  • in register.html
{% load crispy_forms_tags %}
{% block content %}
...
	{{ form|crispy }}

login/logout systems & users are forced to login before they can see the /profile page

  • in project/urls.py from django.contrib.auth import views as auth_views
  • create path: path('login/', auth_views.LoginViews.as_view(template_name='users/login.html'), name='login'),
  • LOGIN_REDIRECT_URL = 'blog-home'
  • modify register route to redirect users to login page:
    • in users/views.py f'Your account has been created! You are now able to log in'
    • return redirect('login')
  • django provides a user variable that contains the current user and has an attribute called .is_authenticated to check if the user is currently logged in

create user's profile page that users can access after logged in

  • create the view: in users/views.py
def profile(request):
    return render(request, 'users/profile.html')
  • create the template: users/templates/users/profile.html
  • create the routes in url_patterns that will use this view: in urls.py urlpatterns list, add path('profile/', user_views.profile, name='profile'),
  • add profile button in navigation bar in base.html, display a link to the profile page when logged in
  • put a check to see if the user is logged in before accessing the profile page:
    • in views.py, from django.contrib.auth.decorators import login_required
@login_required
def profile(request):
    return render(request, 'users/profile.html')
  • .../accounts/login/?next=/profile/ is the default location that django looks for login routes, but we have simply put our login page at /login, so we need to tell django where it can find the login route:
    • in settings.py LOGIN_URL = 'login'
  • ?next=/profile/ it's keeping track of the page that we were trying to access and it will direct us to that page after we log in
    • note: the default redirect url is to the 'home-page'
    • so if we log in here, we will be redirected to /profile page now

user's profile and picture

  • extend the existing User model that django provides
  • in users/models.py
from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(default='default.jpg', upload_to='profile_pics')

    def __str__(self):
        return f'{self.user.username} Profile'
  • Anytime after changing the models, be sure to makemigrations to update the database
  • pip install Pillow
  • py manages.py makemigrations
  • py manages.py migrate
  • in order to be able to view the profile on /admin page, we need to register this model within admin.py
  • in users/admin.py:
from django.contrib import admin
from .models import Profile

admin.site.register(Profile)
  • add profile manually through admin page
(PROJEC~1) C:\Users\Jiayi Su\Desktop\Djangoenv\django_project>py manage.py shell
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user = User.objects.filter(username='jiayisu').first()
>>> user
<User: jiayisu>
>>> user.profile
<Profile: jiayisu Profile>
>>> user.profile.image
<ImageFieldFile: profile_pics/girl.jpg>
>>> user.profile.image.width
564
>>> user.profile.image.url
'profile_pics/girl.jpg'
>>> user = User.objects.filter(username='TestUser').first()
>>> user
<User: TestUser>
>>> user.profile.image
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Users\JIAYIS~1\Desktop\DJANGO~1\PROJEC~1\lib\site-packages\django\db\models\fields\related
_descriptors.py", line 414, in __get__
    self.related.get_accessor_name()
django.contrib.auth.models.User.profile.RelatedObjectDoesNotExist: User has no profile.
>>>
  • change the dir where the profile pictures will be saved
    • in settings.py: MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/'
  • profile.html
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
      <div class="media">
        <img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
        <div class="media-body">
          <h2 class="account-heading">{{ user.username }}</h2>
          <p class="text-secondary">{{ user.email }}</p>
        </div>
      </div>
      <!-- FORM HERE -->
    </div>
{% endblock content %}
from django.conf import settings
from django.conf.urls.static import static

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  • auto-set default pic for newly-created users
    • in users/signals.py
from django.db.models.signals import post_save        # signal
from django.contrib.auth.models import User           # sender
from django.dispatch import receiver
from .models import Profile


@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
    instance.profile.save()
  • import the signal inside of the ready function of users/apps.py
from django.apps import AppConfig


class UsersConfig(AppConfig):
    name = 'users'

    def ready(self):
        import users.signals

update user's profile (information and picture) and auto-resize uploaded picture

  • users/forms.py create a model form, which allows us to create a form that'll work with a specific database model.
  • here, we want our form to update the user model
class UserUpdateForm(forms.ModelForm):
    email = forms.EmailField()

    class Meta:
        model = User
        fields = ['username', 'email']


class ProfileUpdateForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['image']
  • add these forms to profile view
@login_required
def profile(request):
    u_form = UserUpdateForm()
    p_form = ProfileUpdateForm()
    
    context = {
        'u_form': u_form,
        'p_form': p_form,
    }

    return render(request, 'users/profile.html', context)
  • put two fields for these forms into a single form, so the user see it as one
  • in users/profile.html
<!-- FORM HERE IS USERUPDATE & PROFILEUPDATE Form-->
      <form method="POST" enctype="multipart/form-data">
        {% csrf_token %}
        <fieldset class="form-group">
            <legend class="border-bottom mb-4">Profile Info</legend>
            {{ u_form|crispy }}
            {{ p_form|crispy }}
        </fieldset>
        <div class="form-group">
            <button class="btn btn-outline-info" type="submit">Update</button>
        </div>
      </form>
  • populate the fields with current user info: in views.py
@login_required
def profile(request):
    if request.method == 'POST':
        u_form = UserUpdateForm(request.POST, instance=request.user)
        p_form = ProfileUpdateForm(request.POST, request.FILES,
                                   instance=request.user.profile)
        if u_form.is_valid() and p_form.is_valid():
            u_form.save()
            p_form.save()
            messages.success(request,
                             f'Your account has been updated!')
            return redirect('profile')
    else:
        u_form = UserUpdateForm(instance=request.user)
        p_form = ProfileUpdateForm(instance=request.user.profile)

    context = {
        'u_form': u_form,
        'p_form': p_form,
    }

    return render(request, 'users/profile.html', context)
  • auto-resize image when uploading picture: in models.py, override the save method
    def save(self):
        super().save()

        img = Image.open(self.image.path)

        if img.height > 300 or img.width > 300:
            output_size = (300, 300)
            img.thumbnail(output_size)
            img.save(self.image.path)
  • display the image of the author beside each post: blog/home.html
   <img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">

function view VS class-based view

  • ListView, DetailView, CreateView, UpdateView, DeleteView
  • in blog/views.py, create a ListView for model Post. We can change the template django was looking for with template_name
  • in blog/urls.py:
from .views import PostListView

urlpatterns = [
    # path('', views.home, name='blog-home'),
    path('', PostListView.as_view(), name='blog-home'),
    path('about/', views.about, name='blog-about'),
]
  • by default, ListView will call the variable object_list instead of posts, so we can either go into the template and change it so it's looping over object_list
  • or we can set one more variable in our ListView and let the class know that we want that variable to be called "posts" instead
  • to see the latest post at the top, we need to change the order our query is making to the database:
    • in our ListView, add an ordering attribute with the field we want to order on, the minus sign will reverse the order
from django.views.generic import ListView

# ------------function view
def home(request):
    context = {
        'posts': Post.objects.all()
    }
    # return HttpResponse('<h1>Blog Home</h1>')
    return render(request, 'blog/home.html', context)

# ------------class-based view
class PostListView(ListView):
    model = Post
    template_name = 'blog/home.html'    # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    ordering = ['-date_posted']

a detail view: a view for individual post

  • views.py
class PostDetailView(DetailView):
    model = Post
  • urls.py: pk is the primary key of the post we want to view, pk will be passed to the class-based view
    path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
  • create a template that'll desplay our post details
    • in views, by default, our generic class-based view will be looking for a template with the naming convention: /_.html
    • so we need to create a template: blog/templates/blog/post_detail.html
    • within this html, change "post" to "object"
  • now we can add links to the routes for the individual post on the home page:
    • home.html
<h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>

CreateView UpdateView DeleteView

  • for CreateView, it'll share a template with the UpdateView, named _form.html
  • we want the author to be the current logged-in user, so we can override the form_valid method for our CreateView
    • this will allow us to add the author before the form is submitted
class PostCreateView(CreateView):
    model = Post
    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)
  • in Post model, we need to create the get_absolute_url method so that django knows how to find the location to a specific post
    • the reverse function will simply return the full url to a route as a string
    • whereas the redirect function will redirect you to a specific route
  • in blog/models.py reverse() need two args: the full path of the route we want to get, and a specific post with a primary key
from django.urls import reverse

class Post(models.Model):
    # ...
    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk':self.pk})
  • if you just want to go to the home page, you can set an attribute in CreateView called success_url and set that to the home page
  • We shouldn't be able to create a post unless we're logged in, if not, we'll be directed to the login page
    • for function-based views (user profile page we created) login_required decorator was used
    • for class-based views, we use login mixin, which is a class we inherit from that'll add that login functionality to the view
from django.contrib.auth.mixins import LoginRequiredMixin

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)
  • UpdateView in urls.py, we provide the primary key in the url to the post that we wanna update
  • the template for UpdateView just uses the same post_form template that we created for the CreateView
    path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
  • we only want the people who wrote the post to be able to edit it, so add another mixin
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Post
    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:
            return True
        return False
  • DeleteView, we want users to be logged in and is the author of the post in order to see the DeleteView
  • template is a form that just ask us if we're sure to wanna delete a post, if we submit the form, the post will be deleted
    • blog/post_confirm_delete.html
{% extends "blog/base.html" %}
{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Delete Post</legend>
                <h2>Are you sure you want to delete the post "{{ object.title }}"</h2>
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-danger" type="submit">Yes, Delete</button>
                <a class="btn btn-outline-secondary" href="{% url 'post-detail' object.id %}">Cancel</a>
            </div>
        </form>
    </div>
{% endblock content %}
  • add links to these routes
    • put a link to navigation bar to create a new post
    • blog/base.html
<a class="nav-item nav-link" href="{% url 'post-create' %}">New Post</a>
  • links to update and delete the post
    • blog/post_detail.html
        {% if object.author == user %}
          <div>
            <a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'post-update' object.id %}">Update</a>
            <a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'post-delete' object.id %}">Delete</a>
          </div>
        {% endif %}

Pagination

  • use json file data to create posts and work with paginator object
(PROJEC~1) C:\Users\Jiayi Su\Desktop\Djangoenv\django_project>py manage.py shell
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import json
>>> from blog.models import Post

>>> with open('posts.json') as f:
...     post_json = json.load(f)
...
>>> for post in post_json:
...     post = Post(title=post['title'], content=post['content'], author_id=post['user_id'])
...     post.save()
...

>>> from django.core.paginator import Paginator
>>> posts = ['1', '2', '3', '4', '5']
>>> p = Paginator(posts, 2)
>>> p.num_pages
3
>>> for page in p.page_range:
...     print(page)
...
1
2
3
>>> p1 = p.page(1)         # to look at a specific page (page 1)
>>>
>>> p1
<Page 1 of 3>
>>> p1.number             # the number of that page
1
>>> p1.object_list
['1', '2']
>>> p1.has_previous()
False
>>> p1.has_next()
True
>>> p1.next_page_number()
2
>>>
  • pagination of home page
    {% if is_paginated %}
      {% if page_obj.has_previous %}
        <a class="btn btn-outline-info mb-4" href="?page=1">First</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
      {% endif %}

      {% for num in page_obj.paginator.page_range %}
        {% if page_obj.number == num %}
          <a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
          <a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% endif %}
      {% endfor %}

      {% if page_obj.has_next %}
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
      {% endif %}

    {% endif %}
  • posts of a specific author
class PostListView(ListView):
    model = Post
    template_name = 'blog/home.html'    # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    ordering = ['-date_posted']
    paginate_by = 5


class UserPostListView(ListView):
    model = Post
    template_name = 'blog/user_posts.html'    # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    paginate_by = 5

    def get_queryset(self):
        user = get_object_or_404(User, username=self.kwargs.get('username'))
        return Post.objects.filter(author=user).order_by('-date_posted')

use email to let users reset password

  • email info

[email protected] to me

You're receiving this email because you requested a password reset for your user account at 127.0.0.1:8000.

Please go to the following page and choose a new password:

http://127.0.0.1:8000/password-reset-confirm/MQ/558-5f8a6d2c1592b17b0f87/

Your username, in case you've forgotten: jiayisu

Thanks for using our site!

The 127.0.0.1:8000 team

  • django_project/urls.py
    path('password-reset/', auth_views.PasswordResetView.as_view(template_name='users/password_reset.html'),
         name='password_reset'),
    path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(template_name='users/password_reset_done.html'),
         name='password_reset_done'),
    path('password-reset-confirm/<uidb64>/<token>/',
         auth_views.PasswordResetConfirmView.as_view(template_name='users/password_reset_confirm.html'),
         name='password_reset_confirm'),
    path('password-reset-complete/', auth_views.PasswordResetCompleteView.as_view(template_name='users/password_reset_complete.html'),
         name='password_reset_complete'),
  • django_project/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get('EMAIL_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_PASS')
  • password_reset_comfirm.html
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Reset Password</legend>
                {{ form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Reset Password</button>
            </div>
        </form>
    </div>
{% endblock content %}
  • create a link for users to reset password on login page
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Log In</button>
                <small class="text-muted ml-2">
                    <a href="{% url 'password_reset' %}">Forgot Password?</a>
                </small>
            </div>
  • room for improvements:
    • unitest
    • deploy on different platforms
    • send longer running request up to a message queue and make it a synchronous
    • add a commenting system, a search feature