Skip to content

Instantly share code, notes, and snippets.

@sivaa
Last active June 1, 2017 08:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sivaa/883dfe47df739070b4a4a1b570c3a181 to your computer and use it in GitHub Desktop.
Save sivaa/883dfe47df739070b4a4a1b570c3a181 to your computer and use it in GitHub Desktop.
Django Workshop - Beginners
  1. Installation/Setup ======================

Required Tools

Mandatory

Optional

Step by Step Instructions

Operating System Guide Link
Microsoft Windows 7+ http://bit.ly/pycon-gswd-windows-setup
GNU/Linux Ubuntu http://bit.ly/pycon-gswd-linux-setup
GNU/Linux CentOS http://bit.ly/pycon-gswd-centos-setup
  1. New Django Project & App Creation ==================================== 2.1 Virtual Environment

  • Create a new virutal environment using the command: mkvirtualenv dj
  • This new enviroment will be initially empty (No dependencies or packages installed). Verify it using pip freeze
  • Install Django using pip: pip install django and verify it using pip freeze

2.2 New Django Project

  • Create a new django project using the command:
    • Windows : python "C:\Users\<User name>\Envs\dj\Scripts\django-admin.py" startproject fav
    • GNU/Linux: python ~/.virtualenvs/pycon-dj-workshop/bin/django-admin.py startproject fav
  • List and verify the project files created using the command:
    • Windows: tree /f
    • Gnu/Linux: tree
  • Navigate to the project directory: cd fav
  • Run the Django Development Server: python manage.py runserver
    • It can be stopped with CTRL + C
  • Check the project's home page using the link: http://127.0.0.1:8000/

2.3 Default Apps and DB Tables Creation

  • Open these files in your favorite editor.
  • Refer the settings.py(fav/fav/settings.py) for the INSTALLED_APPS and DATABASES settings.
  • Verify if the SQLite3 DB file(fav/db.sqlite3) is created
    • Open the file in DB Browser for SQLite tool and verify if there are any tables.
  • Create the DB tables for the default Django apps configured in INSTALLED_APPS with python manage.py migrate
    • Open the file in DB Browser for SQLite tool and verify the newly created tables

2.4 New Custom Django App

  • Create a new Django App under this project called demo using: python manage.py startapp demo
  • List and verify the project files created using the command:
    • Windows: tree /f
    • Gnu/Linux: tree
  1. Hello World ============== 3.1 Simple Hello World

  • Open fav/demo/views.py and create a view for returning "Hello World!" as a HTTP response
from django.http import HttpResponse

def hello1(request):
    return HttpResponse("Hello World!")
```
* Open ```fav/fav/urls.py``` import the views module to configure the routing.
```
from demo import views
```
* Add a new routing for the above view in the ```urlpatterns```
```
url(r'^hello1/$', views.hello1),
```
* Make sure that the dev server is running and access the following URL in the browser: http://127.0.0.1:8000/hello1/

3.2 Hello World with HTML Tags
------------------------------
* Write an another view in the same ```views.py``` to return the HTML string as a HTTP response
```
def hello2(request):
    return HttpResponse("<h1>Hello World!</h1>")
```
* Create a routing for the above view in ```urls.py```
```
url(r'^hello2/$', views.hello2),
```
* Access the following URL in the browser: http://127.0.0.1:8000/hello2/

3.3 Hello World with Dynamic Data
---------------------------------
* Write a another view to return the currnet timestamp
```
from datetime import datetime

def hello3(request):
    return HttpResponse("<h1>Hello World! at {} </h1>".format(datetime.now()))
```
* Create a routing for the above view in ```urls.py```
```        
url(r'^hello3/$', views.hello3),
```
* Access the following URL in the browser: http://127.0.0.1:8000/hello3/

3.4 Hello World with Request Data
--------------------------------
* Write a another view to fetch and return the query string
```
def hello4(request):
    name = request.GET.get("name", "Django")
    return HttpResponse("Hello <strong>{}</strong>!".format(name)))
```
* Create a routing for the above view in ```urls.py```
```
url(r'^hello4/$', views.hello4),
```
* Access the following URLs in the browser: 
  * http://127.0.0.1:8000/hello4/
  * http://127.0.0.1:8000/hello4/?name=Siva


4. Hello World with Templates
=============================
4.1 Simple Hello World
----------------------
* Create ```templates``` directory under ```fav/demo/``` folder: ```mkdir demo/templates```
* Create a HTML document called ```hello5.html``` with the following content
```
<!DOCTYPE html>
<html>
    <head>
        <title>Introduction to Django Workshop</title>
    </head>
    <body>
        Hello World!
    </body>
</html>
```
* Write an another view to return the above template as a HTTP response
```    
from django.shortcuts import render
        
def hello5(request):
    return render(request, "hello5.html")
```
* Create a routing for the above view in urls.py
```
url(r'^hello5/$', views.hello5),
```
* Access the following URL in the browser: http://127.0.0.1:8000/hello5/
  * It will throw ```TemplateDoesNotExist``` exception since this app is not registred in the ```INSTALLED_APPS```
  * Only the registered apps will be scanned for ```templates``` folder
  * Register this app in the ```INSTALLED_APPS``` tuple in ```fav/fav/settings.py```
```
        'demo',
```
* Access the same url again in the browser

4.2 Hello World with Dynamic Data
---------------------------------
* Copy ```hello5.html``` and create ```hello6.html``` and update the body with ```current_time``` template variable
```
<body>
    Hello World! at {{ current_time }}
</body>
```
* Write an another view to return the currnet timestamp
```
def hello6(request):
    return render(request, 
                  "hello6.html",
                  {"current_time" : datetime.now() })
```
* Create a routing for the above view in urls.py
```
url(r'^hello6/$', views.hello6),
```
* Access the following URL in the browser: http://127.0.0.1:8000/hello6/

4.3 Hello World with Request Data
---------------------------------
* Copy ```hello6.html``` and create ```hello7.html``` and update the body with ```name``` template variable
```
<body>
    Hello <strong> {{ name }} <strong>!
</body>
```
* Write a another view to fetch and retrun the query string
```
def hello7(request):
    name = request.GET.get("name", "Django")
    return render(request, 
                  "hello7.html",
                  {'name' : name })
```
* Create a routing for the above view in ```urls.py```
```
url(r'^hello7/$', 'demo.views.hello7'),
```
* Access the below URLs in the browser
  * http://127.0.0.1:8000/hello7/
  * http://127.0.0.1:8000/hello7/?name=Siva

5. Favourite Movie App
=====================


5.1 Movie app Creation & Registration
-------------------------------------
* Create a new app called movie with the command: ```python manage.py startapp movie```
* Create ```templates``` directory under ```fav/movie/``` folder: ```mkdir movie/templates```
* Register this app in the ```INSTALLED_APPS``` in ```fav/fav/settings.py```
```
    'movie',
```

5.2 Create Movie Add HTML Form
-----------------------------
* Create ```fav/movie/templates/movies.html``` file with the following content
```
<!DOCTYPE html>
<html>
    <head>
        <title>My Favourite Movie List</title>
    </head>
    <body>
        <form action="/movies/" method="post">
            Movie Name : <input type="text" name="movie_name"/>
                         <input type="submit" value="Add"/>
        </form>
    </body>
</html>
```
* Serve this template in the ```fav/movie/views.py```
```
def movies(request):
    return render(request, "movies.html")
```
* Open ```fav/fav/urls.py``` and create a routing for the above view

from movie import views as movie_views

url(r'^movies/$', movie_views.movies),

* Access this URL in the browser: http://127.0.0.1:8000/movies/
* Click "Add" button
* Fix the CSRF verification by providing the token inside the form
<form action="/movies/" method="post"> {% csrf_token %}

5.3 Implement Add functionality and Store it in Database
--------------------------------------------------------
* Create a model (database table) to store the movie name in ```fav/movie/models.py```

class Movie(models.Model): name = models.CharField(max_length = 100, unique = True)

* Create a migration script for the above table with the command: ```python manage.py makemigrations```
* Run the created migration script using the command: ```python manage.py migrate```
* Verify the table structure in DB Browser for SQLite
* Access this table in the Django shell with the following steps
* Open the shell: ```python manage.py shell```
* Import the Movie model: 

from movie.models import Movie

* Count the number of available records

Movie.objects.count()

* Create the first record

Movie.objects.create(name = "Troy")

* Verify the same in the DB Browser for SQLite
* Create few more records

Movie.objects.create(name = "Terminator") Movie.objects.create(name = "Avatar") Movie.objects.create(name = "Ice Age")

* List all the records

Movie.objects.all()

* Fix the object representation in ```models.py```

class Movie(models.Model): name = models.CharField(max_length = 100, unique = True)

def __str__(self):
    return self.name
* Changes won't be auto reflected. Quit the shell (Ctrl + Z) and reopen it
* Import the model and list all the records

from movie.models import Movie Movie.objects.all()

* Fetch the first record

Movie.objects.first()

* Fetch the last record

Movie.objects.last()

* Count the number of available records

Movie.objects.count()

* Fetch a specific record using get API

troy_movie = Movie.objects.get(name="Troy")

* Access its fields

troy_movie.name troy_movie.id troy_movie.pk

* Fetch list of matching records using filter API

Movie.objects.filter(name__contains = "T")

* Exclude a specific record

Movie.objects.exclude(name__contains = "T") Movie.objects.exclude(name__contains = "t")

* Chain multiple APIs

Movie.objects.filter(name__contains = "a").exclude(name__contains = " ")

* Delete a specific record

troy_movie = Movie.objects.get(name="Troy") troy_movie.delete()

* Delete all records

Movie.objects.all().delete()

* Explore all the others APIs from this link https://docs.djangoproject.com/en/dev/ref/models/querysets/
* Handle POST request, extract the movie_name and persist in database

from django.http import HttpResponse

from movie.models import Movie (or) from .models import Movie

def movies(request): if request.method == 'GET': return render(request, "movies.html")

if request.method == 'POST':
    movie_name = request.POST.get("movie_name")
    Movie.objects.create(name = movie_name)
    message = "Movie '{}' is added successfully.".format(movie_name)
    return HttpResponse(message)

return ("Invalid Request")
* We can also consider sending ```HttpResponseBadRequest```, ```HttpResponseNotAllowed``` based on the validations. All these classes are available in ```django.http.response``` module.
* Test this feature by adding a movie name in the browser.
  

5.4 Implement the notification message in the same page
------------------------------------------------------  
* Add the message variable in the template above the HTML form
{% if message %} {{ message }}

{% endif %}
# <HTML From>
``` * Pass the message variable to template ``` if request.method == 'POST': # Other lines return render(request, "movies.html", {"message" : message}) ```

5.5 List all the available movies before Add form

  • Display the movies list in the HTML
<body>

    # <Notification Message>

    {% if movies %}
        {% for movie in movies %}
            <li> {{ movie.name }} </li>
        {% endfor %}
    {% else %}
        No movies added so far. <br> 
    {% endif %} <br>

    # <HTML Form>

</body>
  • Pass the movies list from views to template
def _get_movies():
    return Movie.objects.all()

def movies(request):
    if request.method == 'GET':
        return render(request, 
                      "movies.html",
                      {"movies"  : _get_movies()})

    if request.method == 'POST':
        # Other lines
        return render(request, 
                      "movies.html",
                      {"message" : message,
                       "movies"  : _get_movies()})

5.6 Implement Remove feature in HTTP GET

  • Add the remove link before each movie name
{% for movie in movies %}
    <li> <a href="/movies/remove/?id={{ movie.pk }}"> [x] </a> {{ movie.name }} </li>
{% endfor %}
  • Remove the selected movie name from the persistence
def remove_movie(request):
    if request.method == 'GET':
        movie_id = request.GET.get("id")
        movie    = Movie.objects.get(id = movie_id)
        movie.delete()
        message  = "Movie '{}' is removed successfully.".format(movie.name)
        return render(request, 
                      "movies.html",
                      {"message" : message,
                       "movies"  : _get_movies()})

    return ("Invalid Request")
  • Add a routing for remove option in urls.py
url(r'^movies/remove/$', movie_views.remove_movie),

5.7 Validations, Integrity and other Error

  • Implement movie length validation and handle IntegrityError
from django.db.utils import IntegrityError

def movies(request):
    # GET implementation
    if request.method == 'POST':
        movie_name = request.POST.get("movie_name")
        movie_name = movie_name.strip()

        if not movie_name:
            message = "Enter a Movie Name"
        elif len(movie_name) < 3:
            message = "Not enough words!"
        else:
            try:
                Movie.objects.create(name = movie_name)
                message = "Movie '{}' is added successfully.".format(movie_name)
            except IntegrityError:
                message =  "Movie '{}' is already exists.".format(movie_name)

    # Remaining code here
  • Handle the invalid movie id in the remove method
try:
    movie    = Movie.objects.get(id = movie_id)
    movie.delete()
    message  = "Movie '{}' is removed successfully.".format(movie.name)
except Movie.DoesNotExist as e:
    message = "Given movie does not exists."

5.8. Implement Django Forms and remove HTML Form

  • Create fav/movie/forms.py and implement Django Form
from django import forms

class MovieForm(forms.Form):
    movie_name = forms.CharField(required = False)
  • Enhance the 'movies' view to use the above form
def movies(request):
    if request.method == 'GET':
        return render(request, 
                      "movies.html",
                      {"movies"  : _get_movies(),
                       "form"    : MovieForm()}) # Added

    if request.method == 'POST':
        form = MovieForm(request.POST)

        if form.is_valid():
            movie_name = form.cleaned_data["movie_name"]
            # Remaining code
                try:
                    Movie.objects.create(name = movie_name)
                    message = "Movie '{}' is added successfully.".format(movie_name)
                    form = MovieForm() # Added
                except IntegrityError:
                    message =  "Movie '{}' is already exists.".format(movie_name)

        return render(request, 
                      "movies.html",
                      {"message" : message,
                       "movies"  : _get_movies(),
                       "form"    : form}) # Added
  • Enhance the 'remove_movie' view to use the Django form
def remove_movie(request):
    # Other code
    return render(request, 
                  "movies.html",
                  {"message" : message,
                   "movies"  : _get_movies(),
                   "form"    : MovieForm()}) # Added
  • Enhance the template to use the Django form
<form action="/movies/" method="post"> {% csrf_token %}
    {{ form }}
    <input type="submit" value="Add">
</form>

5.9 Move Validations from views to Django Form

  • Implement the validations in the Django form
class MovieForm(forms.Form):
    movie_name = forms.CharField(required = True)

    def clean_movie_name(self):
        movie_name = self.cleaned_data['movie_name'].strip()

        if len(movie_name) < 3:
            raise forms.ValidationError("Not enough words!")

        return movie_name
  • Remove the validations from views and use Form to fetch the values from the query string (or) request body.
if request.method == 'POST':
    form = MovieForm(request.POST)

    if form.is_valid():
        movie_name = form.cleaned_data["movie_name"].strip()

        try:
            # Other code
            form = MovieForm()  # New form since there is no validation errors
        except IntegrityError:
            message =  "Movie '{}' is already exists.".format(movie_name)            
    else:
        message = "Please correct all the validation errors below."
  • Use the populated form instead of new form to display the form errors.
                       "form": form})  # Updated

5.10 Implement Delete Confirm using GET and delete it in POST

  • Lets use URL string instead of query string for remove
  • Enhance the HTML
<li> <a href="/movie/remove/{{ movie.pk }}/"> [x] </a> {{ movie.name }} </li>
  • Enhance the routing configuration (urls.py) to accomodate the same
url(r'^movie/remove/(?P<movie_id>\d+)/$', movie_views.remove_movie),
  • Create a new template for delete confirmation fav/movie/templates/movie_delete_confirm.html
<!DOCTYPE html>
<html>
    <head>
        <title>My Fav Movie List - Delete Confirmation</title>
    </head>
    <body>

        <form method="post"> {% csrf_token %}
            Would you like to delete the movie <b> '{{ movie.name }}' </b>?
            <input type="submit" value="Yep. Sure!">
        </form>

    </body>
</html>
  • Enhance the remove view for confirmation and delete using post
def remove_movie(request, movie_id):
if request.method == 'GET':
    try:
        movie    = Movie.objects.get(id = movie_id)
    
        return render(request, 
                      "movie_delete_confirm.html", 
                      { 'movie' : movie})
    except Movie.DoesNotExist as e:
        message = "Given movie does not exists."
    
    return render(request, 
                  "movies.html",
                  {"message" : message,
                   "movies"  : _get_movies(),
                   "form"    : MovieForm()})

if request.method == 'POST':
    try:
        movie    = Movie.objects.get(id = movie_id)
        movie.delete()
        message  = "Movie '{}' is removed successfully.".format(movie.name)
    except Movie.DoesNotExist as e:
        message = "Given movie does not exists."

    return render(request, 
                  "movies.html",
                  {"message" : message,
                   "movies"  : _get_movies(),
                   "form"    : MovieForm()})

return ("Invalid Request")

5.11 Implement Edit feature

  • Add the Edit option the HTML (movies.html)
<li> <a href="/movie/remove/{{ movie.pk }}/"> [x] </a> 
     <a href="/movie/edit/{{ movie.pk }}/"> [Edit] </a> 
     {{ movie.name }} 
</li>
  • Add the routing for edit feature
url(r'^movie/edit/(?P<movie_id>\d+)/$', movie_views.edit_movie),
  • Create a new template for edit fav/movie/templates/movie_edit.html
<!DOCTYPE html>
<html>
    <head>
        <title>My Fav Movie List - Edit</title>
    </head>
    <body>

        {% if message %}
            {{ message }} <br> <br>
        {% endif %}

        <form method="post"> {% csrf_token %}
            {{ form }}
            <input type="submit" value="Edit">
        </form>

    </body>
</html>
  • Enhance the remove view for confirmation and delete using post
def edit_movie(request, movie_id):
    if request.method == 'GET':

        try:
            movie    = Movie.objects.get(id = movie_id)
            form = MovieForm(initial = {'movie_name': movie.name })
            return render(request, 
                          "movie_edit.html", 
                          { 'form' : form})
        except Movie.DoesNotExist as e:
            message = "Given movie does not exists."
        
            return render(request, 
                          "movies.html",
                          {"message" : message,
                           "movies"  : _get_movies(),
                           "form"    : MovieForm()})

    if request.method == 'POST':
        form = MovieForm(request.POST)

        if form.is_valid():
            movie_name = form.cleaned_data["movie_name"].strip()

            try:
                movie = Movie.objects.get(id = movie_id)
                movie.name = movie_name
                movie.save()
                message = "Movie '{}' is  successfully.".format(movie_name)
                form = MovieForm()
            except IntegrityError:
                message =  "Movie '{}' is already exists.".format(movie_name)
            except Movie.DoesNotExist as e:
                message = "Given movie does not exists."

        else:
            message = "Please correct all the validation errors below."

            return render(request, 
                          "movie_edit.html", 
                          { 'form' : form})


        return render(request, 
                      "movies.html",
                      {"message" : message,
                       "movies"  : _get_movies(),
                       "form"    : form})

    return ("Invalid Request")

5.12 Enable Django Admin for Movie Model

  • Enhance the Django Form to use Django Model Form
from movie.models import Movie


class MovieForm(forms.ModelForm):
    # movie_name = forms.CharField(required = True)

    class Meta:
        model = Movie

    def clean_name(self):
        movie_name = self.cleaned_data['name'].strip()

        if len(movie_name) < 3:
            raise forms.ValidationError("Not enough words!")

        return movie_name
  • Create a Admin class for Movie Model and register it (fav/movie/admin.py)
        from movie.models import Movie
        from movie.forms import MovieForm


        class MovieAdmin(admin.ModelAdmin):
            form = MovieForm

         
        admin.site.register(Movie, MovieAdmin)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment