- Installation/Setup ======================
- Python 2.7.12+ (or) 3.5.2+
- virtualenvwrapper 4.7.2+ (or) virtualenvwrapper-win 1.2.1+
- Django 1.10.3+
- DB Browser for SQLite 3.9.1+
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 |
- 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 usingpip freeze
- 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
- Windows :
- List and verify the project files created using the command:
- Windows:
tree /f
- Gnu/Linux:
tree
- Windows:
- Navigate to the project directory:
cd fav
- Run the Django Development Server:
python manage.py runserver
- It can be stopped with
CTRL + C
- It can be stopped with
- Check the project's home page using the link: http://127.0.0.1:8000/
- Open these files in your favorite editor.
- Refer the settings.py(
fav/fav/settings.py
) for theINSTALLED_APPS
andDATABASES
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
withpython manage.py migrate
- Open the file in DB Browser for SQLite tool and verify the newly created tables
- 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
- Windows:
- 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})
```
- 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()})
- 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),
- 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."
- 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>
- 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
- 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")
- 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")
- 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)