Making of Django Blog

  1. Create environment and activate it python3 -m venv benv and source benv/bin/activate
  2. Install django pip3 install django
  3. Create requirements.txt pip freeze > requirements.txt, .gitignore and .env files.
  4. Install decouple python3 -m pip install python-decouple
  5. Move SECRET_KEY to .env and add the following to settings.py
from decouple import config

and

SECRET_KEY = config('SECRET_KEY')
  1. Change folder name to src and Start project django-admin startproject cblog
  2. Create an app python3 manage.py startapp blog and add this line to settings>INSTALLED_APPS 'blog.apps.BlogConfig'
  3. Migrate python3 manage.py migrate
  4. Create superuser python3 manage.py createsuperuser
  5. run server and check from browser python3 manage.py runserver
  6. Create model (Category and Post)
  7. Register the modelsin admin.py
  8. Install pillow and edit requirement.txt
  9. Make migrations + migrate
  10. Run server and check, add items to models manually from admin panel
  11. Add str function to models to make them seen other than an object
  12. Add MEDIA_URl and MEDIA_ROOT to seetings.py and edit urls.py. Create media_root folder
  13. Edit models.py for uploading images (paths)
  14. For generating slug create signals.py file, and add a function to apps.py
  15. Edit signals.py
  16. Create Comment, Like and View classes in models. Make migrations + migrate
  17. Create forms.py. Add PostForm and CommentForm
  18. Edit views -> post_list
  19. Edit settings.py ->TEMPLATES
'DIRS': [BASE_DIR, "templates"]
  1. Create folder templates under src folder and create file base.html
  2. Create folders templates/blog under blog folder and create post_list.html
  3. Create post_list function in views, edit both urls.py files
  4. Create post_create function in views.(❗️ adding files, getting author info )
  5. Create post_create.html and edit urls.py
  6. uuid -> create file uyils.py. Inside create a 10 char-long unique id to improve slugify
  7. Create post_details function in views, edit urls.py, create post_details.html file
  8. Pace post titles in post_list page anchor to link user to post_detail page
  9. Add update and delete posts as above
  10. Apply bootstrap starter template to base.html
  11. Create navbar.html under src/templates
  12. Include navbar in base.html
  13. Create folders static/blog under application (blog) and create main.css file. (This is sth django wants)
  14. Add main.css file's link to head section of base.html and add {% load static %} on top
  15. Edit settings.py as
STATICFILES_DIRS = [BASE_DIR / "static"]
  1. Apply a card style to post_list page with bootstrap.
  2. Get a kit script from fontawesome and apply to base.html to icons for comment, like and view
  3. Add functions to Post class in models.py to retrieve count of comments, likes and views
  4. Import CommentForm to views and add comment to post_detail function
  5. Add a method to Post class to retrieve all the comments of a post
  6. Display all the comments of a post using a for loop in post_detail page, and add a form to the same page to get comments from the user
  7. Import Like to views + add a like function + add like func to urls + create a form in post_detail.html (❗️ Note the action url)
  8. Add an if statement to post_detail.html do display delete and update buttons. If the user is the author, he should see update & delete buttons for his own post
  9. Now the problem is that a user can reach delete & update pages to any post by typing the url path to address bar. A temporary solution below. After using authentication we will change the way we do it.
  10. Install HttpResponse from django.http in views and add an if statement to post_update and post_delete as
if request.user.id != obj.author.id
    return HttpResponse("You are not authorized")

We will change this to a more advanced method soon.

  1. Install django-crispy-forms with pip and add crispy_forms to INSTALLED_APPS in settings.py. This third party app is for styling forms w bootstrap.
  2. Edit the detail, create, update and delete pages as follows:
    • add {% load crispy_forms_tags %}
    • and change previously form.as_p to form|crispy
  3. Style these pages with bootstrap
  4. 🚩 Create users app + add the new app to INSTALLED_APPS in settings.py
  5. Create a Profile class in users > models add fields. Add a function to point the path to save the uploaded profile pictures of the user
  6. pip freeze > requiremets.txt
  7. makemigrations & migrate
  8. Register Profile in users > admin.py
  9. In order to automatically create a userf profile just after adding a user create a signals.py file (see the code)
  10. Add a ready method in users > apps.py to make signals.py take effect
  11. Add users path to main urls
  12. Create users > urls + import views from django.contrib.auth to use built-in views of django
  13. Create users > forms.py + create a registration form + add a method to prevent email repetation
  14. Import registration to views + create a registration func
  15. Create users > templates/users/register.html + import register func and add path to urls
  16. Give a link to register.html from navbar.html
  17. To make email a required field add the following code to registration class in forms.py
email = forms.EmailField()
#default value is True, otherwise EmailField(required = False)
# models => blank=True
# forms => required=False
  1. Import Profile to forms and create ProfileUpdateForm and UserUpdateForm. We will update two forms in one page.
  2. Import ProfileUpdateForm and UserUpdateForm to views + create a profile function to handle both forms.
  3. Create profil.html + arrahne urls + give a link in navbar.html
  4. By default django structures login page is created at "registration/login.html". To change this path we overwrite a new template name as
path("login/", auth_views.LoginView.as_view(template_name="users/login.html"), name="login")
  1. Add a link from navbar to login and add another link to register page.
  2. Do the same for logout just like login
  3. By default after logging in Django redirects to "accounts/profile". To change the path add LOGIN_REDIRECT_URL = "blog:list" to settings.py
  4. In blog > views import login_required from django.contrib.auth.decorators and add @login_required() on top of post_create func. The purpose is to prevent getting an error when a user writes /create path to address bar. But first we need to add LOGIN_URL="login" because the link is diverted by default to another path which we do not want.
  5. Add @login_required() on top of post_delete, post_update and like functions accordingly.
  6. Import messages from django.contrib in blog > views and add messages.success(request, "Post successfully created") after post.save() and before redirect in post_create func.
  7. Do the same for post_update and post delete
  8. Add also a warning message to post_delete and post_update like the one below
if request.user.id != obj.author.id:
    messages.warning(request, "You are not the author")
    return redirect("blog:list")
  1. Add an if statement and a for loop to base.html to display warning messages on the pages.
 <div class="container">
    {% if messages %}
        {% for message in messages %}
            <div class="alert alert-{{ message.tags }}">
                {{ message }}
            </div>
        {% endfor %}
    {% endif %}
</div>
  1. User can be redirected to register page by writing users/register path to address bar even if the user is already logged in. To fix this add the folowing to register func in users > views
if request.user.is_authenticated:
    messages.warning(request, "You already have an account")
    return redirect("blog:list")
  1. Add success messages for register and profile functions
  2. For handling the issues regarding password forgetting,resetting etc we use django's defaults.
  3. For password resetting add path('password-reset/', auth_views.PasswordResetView.as_view(template_name='users/password_reset_email.html', name='password_reset'), to urls + create password_reset_email.html page and add link from forgot passsword in login page to password_reset_email.html
  4. Add path and create password_reset_done. This is for giving information that a recovery email has been sent. We will handle email issue soon.
  5. Add path and create password_reset_confirmation. This is for confirmation of the new password.
  6. For sending email for password reset add the following to settings.py. Note that these settings are for gmail. For other services find relevant information.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'  # this mail will send emails to users
EMAIL_PORT = 587  # post for gmail
EMAIL_USE_TLS = True
EMAIL_HOST_USER = config("EMAIL_USER")
EMAIL_HOST_PASSWORD = config("EMAIL_PASSWORD")

for email and the password we use .env file like

EMAIL_USER = example@gmail.com
EMAIL_PASSWORD = examplepassword
  1. To check if this system for gmail works => go to google account settings > security. Switch the security level to "Less secure app access". This method is not the best practice, only for trying.
  2. When the user clicks Forgot password he is redirected to password reset request page. Here the user writes his own email. But the app does not control if the email is registered before. To fix this import PasswordResetForm to users > forms and add the following class
class PasswordResetEmailCheck(PasswordResetForm):
    def clean_email(self):
        email = self.cleaned_data["email"]
        if not User.objects.filter(email=email).exists():
            raise forms.ValidationError("There is no such email")
        return email

Then import PasswordResetEmailCheck to urls and overwrite the form class in the password_reset path to change the default form django uses:

path(
    'password-reset/', 
    auth_views.PasswordResetView.as_view(
        template_name='users/password_reset_email.html', 
        form_class=PasswordResetEmailCheck
    ), 
    name='password_reset'
)