Never make a Django Repository public. The secret key will be made public which is serious security risk.
I have learnt the DJANGO
by watching the sentdex youtube tutorial and from the original django documentation.
A BIG THANKS to sentdex
for providing such awesome content and Django team for giving an beginner friendly documentation.
(In windows powershell is recommended)
-
Create virtual environment and activate it.
-
(Optional)For windows: If the prompt if too long shorten it by writing the function. (This is temporary. If you close and open the powershell the original prompt will be shown):
Function prompt {"$"}
*Add space at the end and hit enter. In place of
$
you can keep anything. -
Install django
python -m pip intall django
-
Requirements file
pip freeze > requirements.txt
-
Start a django project with the following command (Only
_
underscore is allowed in the project name.):Syntax: django-admin startproject <website_name> django-admin startproject Demo_Website
-
A directory is created with the name
Demo_Website
. Navigate to that directory. Inside that directory initially we will have a python file namedmanage.py
and a directory with the same name.6-a. The python file
manage.py
is the main file that is used for running the django applicaton. It is used for interacting with the django project (The whole project).6-b. The directory's primary use is to connect the other applications that you are going to create for the web application. We can develop applications (example a blog, a chatroom) and connect all of them to the main web application by modifying the files and contents here. This is like the main directory that points to all different web applications inside a website. It consists of 5 files:
__init__.py ==> To determine the project as a python package. asgi.py ==> For ASGI-compatible web servers to serve the django project. settings.py ==> Configurations for the django project. urls.py ==> Contains urls patterns to specify what view should be displayed based on the given url. wsgi.py ==> For WSGI-compatible web servers to serve the django project.
-
Create an application (like a blog or main page for the website). An application in django is like a piece of logic. For example, for an e-commerce website we create it as:
django-admin startproject the_site
*Any e-commerce site has basically 3 important logics(functionalities) ==> cart page, products page, profile page. *Each page has its own logic. We can create three applications now to divide the logic instead of having all of them in a single application. Example:
Syntax: python manage.py startapp <application_name> python manage.py startapp products_page python manage.py startapp cart_page python manage.py startapp profile_page
*Now let's create the application for
Demo_Website
py manage.py startapp home
-
A new directory is created again with the name
home
. It contains one directory and 6 files. These are for configuring the applicationhome
.__init__.py ==> To determine the project as a python package. admin.py ==> This is used to register the models that were created for the application. Also we can modify the admin view for the current application. apps.py ==> To register the created pages in the current application. models.py ==> Every application is based on the models in the Django. It is like creating and mapping to a database table. tests.py ==> To create our own tests for the project and applications. views.py ==> To create our own views that are to be shown to the user. /migrations ==> All the created models for the application must be migrated and these migrations are stored in this directory.
-
Run the server to check if the web application is working fine or not.
py manage.py runserver
-
If running successfully then continue. Otherwise, start from beginning by deleting everything. Make sure that you have two terminals opened. One for server and other for executing the scripts that are being modified. Keep the server running always.
-
Inside the newly created
home
directory and create a file namedurls.py
(OR) copy and paste that file from theDemo_Website
directory. -
Open the
urls.py
file located in theDemo_Website
directory and edit like this:from django.contrib import admin # importing include from django.urls import path, include # Adding path to the urls that will be created in the # "urls.py" file located in the "home" directory. urlpatterns = [ path('', include('home.urls')), path('admin/', admin.site.urls), ] # when you visit home page the urls written in the # "home" directory file will be executed
*Adding all url paths of the
home
application in the currenturls.py
file. -
Open the
urls.py
file located in thehome
directory and edit like this:from django.urls import path # from current directory importing view # This type of importing (using dot(.)) is called Relative import from . import views # useful when creating custom URLS (in future) app_name = "home" urlpatterns = [ path("", views.homepage, name="homepage"), ]
*Adding url paths of the
home
application views in the currenturls.py
file. -
Open
views.py
fromhome
directory to createhomepage
function. The file looks like this:from django.shortcuts import render from django.http import HttpResponse # Create your views here. # passing a request to the view def homepage(request): return HttpResponse("Hello World!")
*Django gives response in
HttpResponse
object. -
Refresh the homepage and check the results. If it is okay continue. Otherwise go to step 11 and do it again.
-
Open
models.py
to create a model. We use a class to create models. It look like this:from django.db import models # Create your models here. # Creating a database table with name "Product" and adding columns class Product(models.Model): product_title = models.CharField(max_length=200) product_content = models.TextField() product_added = models.DateTimeField("date added") # overriding the __str__ method to return the product title instead of an object def __str__(self): return self.product_title
-
The changes that are done will not be effected in the applications. Because, when a new application is created we are giving the urls to go to it. But, we did not actually install it. Every time we create a new application we have to add it to the
INSTALLED APPS
list in thesettings.py
in theDemo_Website
directory. After, adding it to the list the list should like this:INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'home.apps.HomeConfig', ]
17-a: The last line that is added will be found when we navigate to the
home
directory and openapps.py
. We have to add theHomeConfig
function there. It looks like this:from django.apps import AppConfig class HomeConfig(AppConfig): name = 'home'
-
Everytime you modify models file (create a table or modify its columns-add or delete or update columns) you have to migrate them in Django. It is a two step process. Navigate to the
Demo_Website
directory (wheremanage.py
file is located) and do run these commands:18-a: To see the migrations that are to be done if any. All the boxes should be checked (like ==> [X]). If at least one is without that mark you should do the '18-b' and '18-c' steps.
python manage.py showmigrations
18-b: You have to make the migrations.
python manage.py makemigrations
*When you navigate to the
migratios
directory you can see the generated migrations and their configurations with a number for each generation. To see what and how the table is created in the database (to see the SQL query) you can see it like this:python manage.py sqlmigrate home 0001
*Since, it is out first generation we get only one generation and it will have '0001'.
18-c: You have to migrate the migrations.
python manage.py migrate
-
This step is optional. Instead of using a template to add the data into the database we can use the django's interactive shell. It is easy and fast. To open the shell run command
python manage.py shell
19-a. To add data into the table using shell:
# import the Model from home.models import Product # Retrieve all the data from the table Product.objects.all() # importing timezone to add date time from django.utils import timezone # the parameters names must be same as in the product model new_product = Product(product_title='Product 1', product_content='Product 1 Content', product_added=timezone.now()) # now we need to save the insertion in the database new_product.save() # Retrieve all the data from the table Product.objects.all() # exit the shell exit()
-
Now we have to create a super user to use the django's admin page. The steps are as follows:
python manage.py createsuperuser
*Username and email address can be edited later on. For practice use simple passwords. But, for production use a strong password since it is a password for the ADMIN page where you can edit the whole database.
-
Now go to
localhost:8000/admin
(OR)127.0.0.1:8000/admin
in the web browser to open the Admin page and login with the super user credentials. -
You can find your credentials under the
Users
section. You can see your details there (except for password-it will be hashed). You can edit details in that page. -
But the models or the database is not shown there because we haven't registered it. So to register the models open the
admin.py
file located inside thehome
directory. Register the newly created model. It should look like this:from django.contrib import admin # Relative importing the model here from .models import Product # Register your models here. admin.site.register(Product)
*Refresh the admin page now. You should see the model.
-
Do not mind the name in the webpage which is plural (Products) as you have registed singular (Product). There is an in-built function that does this. We can override it if we want later. But it is not mandatory.
-
We can add or update or delete the products in the database using this in-built template. We can modify this template by adding a custom class during the registration of the models. We can also change the order of displaying the database items. After modifying the "admin.py" it looks like this:
from django.contrib import admin from .models import Product # Register your models here. class ProductDisplay(admin.ModelAdmin): # The order of displaying the items can be changed here # We can also change what items to show and not to show fields = [ "product_title", "product_content", "product_added" ] admin.site.register(Product, ProductDisplay)
*Without registering the models the admin cannot add new data into the database.
-
We can also set fieldsets (like showing items in sections - title and data one set, content another set) by using fieldsets variable instead of fields variable. Replace fields variable with following variable:
fieldsets = [ ("Title/Date", {"fields": ["product_title", "product_added"]}), ("Content", {"fields": ["product_content"]}) ]
*This feature gives the admin to view the fields in set of groups
-
We can have default values for the fields like getting the current time for adding a product by default instead of giving it manually in the template. This can be done by adding a paramenter called
default
to theDateTimeField
in theproduct_added
in the models of the home page. Remember step-18. On modifying the model we have to start the migration again. Sometimes it might not be necessary. But doing migrations is always a good practice to avoid unapplied migrations erros. The table will be altered only on migrating. On modifying the model it looks like this:from django.db import models from datetime import datetime # Create your models here. class Product(models.Model): product_title = models.CharField(max_length=200) product_content = models.TextField() product_added = models.DateTimeField("date added", default=datetime.now()) def __str__(self): return self.product_title
*The
CharField()
takes the (ascii) characters as the input with a maximum length of 200 (this can be changed). TheTextField()
takes any length of characters. TheDateTImeField()
accepts only date as the input. Now, do the migration process. -
In the
home
directory create a directory namedtemplates
to store the html templates. Inside that directory create another directory calledhome
to store the html pages that are to be rendered for the home page. Navigate to the directory and create a html page named "home.html" and add the following line.<h2>Hello! This is Home Page</h2>
*The creation of extra directories inside the
templates
is recommended because Django looks in all of the templates directories available in theDemo_Website
. It means when we call a template for rendering the Django looks for that template in all the createdapplications
. We may have few files with same names but in different applications. To avoid such complexity (for Django) in choosing the template it is highly recommended to create extra directories inside templated for each application.*The same principle applies to the files css and javascript files.
-
Now we to update the
views.py
insidehome
application directory
to display the created html page. It looks like this:from django.shortcuts import render from django.http import HttpResponse from .models import Product # Create your views here. # passing a request to the view def homepage(request): return render(request=request, template_name="home/home.html", context={"products": Product.objects.all}) ''' 1. The "request" parameter is necessary to catch the request objects that are passed to the html page. This will be used later. But, keep it for now. 2. The "template_name" parameter gets the template name that has to be rendered. It searches for the template inside the "templates" directory of the current application. 3. The "context" parameter is used to pass data to the rendered template. Here we are passing all the items in the products table. '''
-
Now for editing the
home.html
template to display the products on the home page we use Django Template Language (DTL) which is alternative to the Jinja2 Template (which is used in Flask). It looks like this:{% for pd in products %} <p>{{ pd.product_title }}</p> <p>{{ pd.product_added }}</p> <p>{{ pd.product_content }}</p> <br/> {% endfor %}
-
Alongside
templates
directory create a directory namedstatic
and then createhome
directory inside it and then createcss
directory inside it and create the filestyles.css
-
Before importing css in the template add the following lines in the created css file:
*{ margin: 0; padding: 0; } p{ color: blue; }
-
Modify the
home.html
like this:<head> {% load static %} <link rel="stylesheet" href="{% static 'home/css/styles.css' %}"/> </head> <body> {% for pd in products %} <p>{{ pd.product_title }}</p> <p>{{ pd.product_added }}</p> <p>{{ pd.product_content }}</p> <br/> {% endfor %} </body>
*The static folder must be loaded before using it. After modifying the html file restart the server (stop and start). If it is not loaded then the server throws an error saying that the static directory is not loaded.
-
The css or javascript files and some headings (like title) or navigation bar are common for most of the html pages in an application. So rather than copy and pasting the
importing
lines for every html file we can write those lines in a separate html file and import that one file in every html file in that application with one or few lines of code. -
Alongside
home.html
create a directory namedbases
and create a html file namedheader.html
and add the lines:<head> {% load static %} <link rel="stylesheet" href="{% static 'home/css/styles.css' %}"/> </head> <body> {% block content %} {% endblock %} </body>
*The
block content
is the block where the code is dynamically added and executed from the file in which theheader.html
is imported. -
Modify the
home.html
like this:{% extends 'home/bases/header.html' %} {% block content %} {% for pd in products %} <p>{{ pd.product_title }}</p> <p>{{ pd.product_added }}</p> <p>{{ pd.product_content }}</p> <br/> {% endfor %} {% endblock %}
*The
block content
in theheader.html
file is replaced by the body tag and it's content in thehome.html
file. Refresh the home page. It should work fine. If not restart the server (OR) do the steps again carefully. -
We use bootstrap to give styling for the pages. Now the
header.html
looks like this.<head> {% load static %} <link rel="stylesheet" href="{% static 'home/css/styles.css' %}"/> <!-- Bootstrap CDN --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="/">Home</a> </li> <li class="nav-item"> <a class="nav-link" href="/register">Register</a> </li> <li class="nav-item"> <a class="nav-link" href="/login">Login</a> </li> </ul> </div> </nav> <br/> {% block content %} {% endblock %} </body>
*Refresh the page.
-
Now we need to add the views for registering the users, login and others. Create
register.html
alongsidehome.html
and add the lines here:{% extends 'home/bases/header.html' %} {% block content %} <form method='POST'> {% csrf_token %} {{ form.as_p }} </form> {% endblock %}
*The
csrf_token
is essential for providing security. Do google to know more about it. Django provides a in-built or pre-created user registration form. We will use it in the views. *Theform.ap_p
shows every element with a paragraph tag. -
Now add a function named register in the "views.py" in the "home" directory and also import the "UserCreationForm" from "django.contrib.auth.forms". Add the following lines at appropriate places:
# importing from django.contrib.auth.forms import UserCreationForm # register function def register(request): form = UserCreationForm return render(request, template_name="home/register.html", context={"form": form} )
*The
UserCreationForm()
is an in-built template that the django provides. With this form the registeration of new users is very simple and is also modifiable. -
Now add the following list item in
urlpatterns
in theurls.py
insidehome
directory.path("register/", views.register, name="register"),
*Always keep forward slash '/' at the end of the name in the url
-
Refresh and go to register. The form doesn't look nice because we have used Bootstrap and it rewrites all the css. If you want to modify you can. We still need to add button and the register behaviour.
-
Add a button after the
{{ form }}
inregister.html
like this:<button class="btn btn-primary" values="submit">Regsiter</button>
*The styling of the button is done by bootstrap
-
Now modifying the view to get the data by clicking the register button. It looks like this:
# imported redirect along with render from django.shortcuts import render, redirect # new import from django.contrib.auth import login, logout, authenticate # modified register def register(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect("home:homepage") else: for msg in form.error_messages: print(form.error_messages[msg]) form = UserCreationForm return render(request, template_name="home/register.html", context={"form": form})
*Same usernames must not be given. Passwords can be duplicated. To check your entered passwords if you are stuck use the following statements while getting the input from the form in the
POST
method:print(request.POST['password1']) print(request.POST['password2'])
*Passwords can be printed to the server console and also username. Also check users in admin page so that you find a way to register.
-
Adding messages to show the register to the user. We use bootstrap template. Before the block content and after the navigation bar paste the following code:
{% if messages %} {% for msg in messages %} <div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay=4000> <div class="toast-header"> <strong class="mr-auto">{{ msg.tags|upper }}</strong> </div> <div class="toast-body"> {{ msg }} </div> </div> <script> $('.toast').toast('show'); </script> {% endfor %} {% endif %}
*The
script
tag must be written inside thefor
loop because for every message (if there are multiple) the script must be executed to show the message. -
Update the
views.py
as follows:# Add the import statement from django.contrib import messages # Update the request function def register(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() username = form.cleaned_data.get('username') messages.success(request, f"New account created {username}") login(request, user) return redirect("home:homepage") else: for msg in form.error_messages: messages.error(request, f"{msg}: {form.error_messages[msg]") form = UserCreationForm return render(request, template_name="home/register.html", context={"form": form})
*The
messages
module provides in-built methods to generate success or error messages. -
Add user authentication logic in the navigation bar. Update the navigation bar as below:
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> {% if user.is_authenticated %} <li class="nav-item"> <a class="nav-link" href="#">{{ user.username }}</a> </li> <li class="nav-item"> <a class="nav-link" href="/logout">Logout</a> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="/register">Register</a> </li> <li class="nav-item"> <a class="nav-link" href="/login">Login</a> </li> {% endif %} </ul> </div>
*No need to update the
views.py
. It is already modified previously. -
Since header file became large lets split that. Making
navbar.html
andmessage.html
for navigation bar and messages respectively. Cut and paste from<nav>
tag to</nav>
tag fromheader.html
intonavbar.html
. In theheader.html
in place of the removed code write the following line:{% include "home/bases/navbar.html" %}
*Likewise, cut and paste the toast code from the header file into the message file.
-
Now adding the logout. Open
urls.py
in thehome
directory and add the url pattern as below:path("logout/", views.logout_the_user, name="logout"),
*Add the logout function in the
views.py
in the same directory as below:def logout_the_user(request): logout(request) messages.info(request, "You logged out!!!") return redirect("home:homepage")
-
Now adding the login functionality. Add the url pattern in
urls.py
file.# new url pattern path("login/", views.login_the_user, name="login"), # Modified import. Import the Django user Login template from django.contrib.auth.forms import UserCreationForm, AuthenticationForm # login view def login_the_user(request): if request.method == 'POST': form = AuthenticationForm(request, data=request.POST) if form.is_valid(): username = form.cleaned_data('username') password = form.cleaned_data('password') user = authenticate(request, username=username, password=password) if user is not None: login(request, user) messages.success(request, f"You are logged in as {username}") return redirect("home:homepage") else: for msg in form.error_messages: messages.error(request, f"{msg}: {form.error_messages[msg]}") else: for msg in form.error_messages: messages.error(request, f"{msg}: {form.error_messages[msg]}") form = AuthenticationForm() return render(request, "home/login.html", {"form": form} )
*The
AuthenticationForm()
is an in-built login template that is provided by the Django. It is also customizable. *Now thelogin.html
:{% extends 'home/bases/header.html' %} {% block content %} <form method='POST'> {% csrf_token %} {{ form.as_p }} <button class="btn btn-primary" values="submit">Login</button> </form> {% endblock %}
-
Let's start building some real stuff. Using the bootstrap to get the pre-built templates to change the home page. Modifying css to this:
*{ margin: 0; padding: 0; } .bd-placeholder-img { font-size: 1.125rem; text-anchor: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } }
*Modifying the homepage to this:
{% extends 'home/bases/header.html' %} {% block content %} <div class="album py-5 bg-light"> <div class="container"> <div class="row"> {% if products|length > 0 %} {% for pd in products %} <div class="col-md-4"> <div class="card mb-4 shadow-sm"> <svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: Thumbnail"> <title>{{ pd.product_title }}</title> <rect width="100%" height="100%" fill="#55595c"/> <text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text> </svg> <div class="card-body"> <p class="card-text"> <strong>{{ pd.product_title }}</strong> </p> <p class="card-text">{{ pd.product_content }}</p> <p class="card-text">(INR) <strong>Rs. Price</strong></p> <div class="d-flex justify-content-between align-items-center"> <div class="btn-group"> <button type="button" class="btn btn-sm btn-outline-secondary">View</button> </div> <small class="text-muted">{{ pd.product_added }}</small> </div> </div> </div> </div> {% endfor %} {% else %} <h2>Sorry! No products to show</h2> {% endif %} </div> </div> </div> {% endblock %}
-
The products can only be added by the admin. Now, let's update the product model (table) so that the admin can add thumbnail, title, content, price and a link. Before updating, delete the products and refresh the homepage. Also, install
Pillow
module to work with Images (Required). The updatedmodels.py
in the home directory looks like this:# updated product view class Product(models.Model): product_thumbnail = models.ImageField(blank=True, upload_to='products_images/', max_length=500) product_title = models.CharField(max_length=200) product_content = models.TextField() product_price = models.IntegerField(default=0) product_url = models.URLField(blank=True, max_length=250) product_added = models.DateTimeField("date added", default=datetime.now()) # updated fieldsets variable in admin view fieldsets = [ ("Title/Date", {"fields": ["product_title", "product_added"]}), ("Content", {"fields": ["product_content", "product_thumbnail", "product_url", "product_price"] }) ]
*Repeat step-18. Go to admin page and add the products.
-
Update the
settings.py
to work with images like this:# new import statement import os # new lines MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'home/media')
*Update the "urls.py" from the "home" directory as below:
#new import statements from django.conf import settings from django.conf.urls.static import static # Add this to the urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
*Replace the homepage tag with the following code:
{% if not pd.product_thumbnail %} <svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: Thumbnail"> <title>{{ pd.product_title }}</title> <rect width="100%" height="100%" fill="#55595c"/> <text x="50%" y="50%" fill="#eceeef" dy=".3em">Picture not available!</text> </svg> {% else %} <center> <img src="{{ pd.product_thumbnail.url }}" alt="{{ pd.product_title }} Picture" width="50%"/> </center> {% endif %}
-
Update the
home.html
to work with the urls as below:*Replace the view button with this code:
{% if pd.product_url %} <button type="button" class="btn btn-sm btn-outline-secondary" onclick="urlFun('{{ pd.product_url }}')">View</button> {% else %} <button type="button" class="btn btn-sm btn-outline-secondary" onclick="urlFun('empty')">View</button> {% endif %}
*Add the following script in the bottom after the last div tag and before "{% endblock %}":
<script> function urlFun(url) { if (!("empty".localeCompare(url))) { alert("Sorry! no URL is found!"); } else { var element = document.createElement('a'); element.style.display = 'none'; element.setAttribute('href', url); element.setAttribute('target', '_blank'); document.body.appendChild(element); element.click(); } } </script>