Share
Embed
Web Development with Python and Django (2015)
Slide deck from the CodeMash 2015 edition of the "Web Development with Python and Django" tutorial by Mike Pirnat and David Stanek, featuring the "Dungeons & Djangos" sample application.
Mike Pirnat
January 06, 2015
Transcript
191 slides
-
Web Development with Python and Django Mike Pirnat • David
Stanek • CodeMash 2015 -
About Us Mike Pirnat American Greetings http://mike.pirnat.com @mpirnat David Stanek
Rackspace http://traceback.org @dstanek -
Today •Iteratively build a full-featured site •Describe a feature we
need •Implement a feature together •Review our example solution • Keep yours? git show tag • Follow directly? git reset --hard tag -
Everything* You Need to Know About Python in One Slide
*(more or less) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
from module.submodule import SomeClass class MyClass(SomeClass): "This explains what the
class is about." def method(self, x, y=1): "This explains what the method does." dictionary = {'key1': 'val1', 'key2': 'val2'} if y == 1: return ["Hello", dictionary['key1']] else: return ["World", dictionary.get(x, 'val3')] instance = MyClass() for word in instance.method(42): print(word) -
Django! •A high-level Python web framework •Encourages rapid development and
clean, pragmatic design •“For perfectionists with deadlines” •Focus on automation and DRY •Widely supported, many deployment options -
Django •ORM •Automatic admin interface •Regex-based URL design •Templating system
•Cache infrastructure •Internationalization •Command-line job framework -
Framework Middleware URLs Views Models Templates DB Tags & Filters
Request Response -
Download and Install •Python 3.4 – https://python.org/downloads (or get it
from your Linux distro’s package manager) •Git – http://git-scm.com -
Got Ubuntu? •Ubuntu’s Python 3.4 is currently broken •Prevents normal
setup of virtual environments •We have a cloud server already set up that you can use –104.130.195.216 •Or make a new friend -
Got Windows? •Be sure to update the PATH so that
Windows can find your scripts: C:\Python34\;C:\Python34\Scripts\;C:\Python34\Tools \Scripts -
Installing Packages •pip install package •Installed packages go into a
site-packages directory in your Python lib •That’s the “system Python” by default •But different programs may need different versions of packages... •So we have virtual environments! -
Virtual Environments •Creates an isolated Python environment with its own
site-packages •Install whatever you want without fouling anything else up •Now part of the standard library -
Create & Activate the Virtual Environment # Mac/Linux/etc... $ pyvenv
django-tutorial-v2 $ cd django-tutorial-v2 $ source bin/activate # Windows > pyvenv.py django-tutorial-v2 > cd django-tutorial-v2 > Scripts\activate.bat -
Get the Example Code # Normally... $ git init src
# Today... $ git clone https://github.com/mpirnat/django-tutorial- v2.git ./src -
Installing Requirements # Still in the 'src' directory from before...
# Mac/Linux/etc... $ cd src $ pip install -r requirements.txt # Windows > cd src > pip.exe install -r requirements.txt -
Check Prerequisites # In that same 'src' directory... # Mac/Linux/etc...
$ python prerequisites.py # Windows > python.exe prerequisites.py -
Rewind to the Start of Exercises # In that same
'src' directory... $ git reset --hard exercise01.1 -
“Dungeons & Djangos” •Web-based fantasy role playing game •Inspired by
D&D (5E is great, btw) •We’ll build the core of the app: • character creation • user registration, login/logout • admin interface • a fancy landing page -
Starting a Project •Mac/Linux: $ django-admin.py startproject projectname ./ $
python manage.py runserver •Windows: > python Scripts\django-admin.py startproject projectname > python manage.py runserver -
git reset --hard exercise01.1 | git show solution01.1 Exercise 1.1
•Start a new project “mysite” •Run the server •Open your browser to localhost:8000 -
Start an App •Django believes strongly in separating chunks of
a site into apps that can be reused •Ecosystem of reusable apps available •Create an app; from the src directory: $ python manage.py startapp myapp •Add it to INSTALLED_APPS in settings.py -
git reset --hard exercise01.2 | git show solution01.2 Exercise 1.2
•Start a new app named “characters” -
New App Contents src/ mysite/ characters/ __init__.py admin.py models.py tests.py
<-- you should write them! views.py migrations/ -
git reset --hard exercise01.3 | git show solution01.3 Exercise 1.3
•Add the characters app to the list of INSTALLED_APPS in settings.py -
Models •Model classes are the nouns of the system •Used
to create database tables •Integrated with the ORM to interact with the database •Need to create & run migrations when adding or changing model classes -
git reset --hard exercise02.1 | git show solution02.1 Exercise 2.1
•Create a Character model; it should have: • name – text, up to 200 characters • background – text, open-ended • level, experience_points, max_hit_points, current_hit_points – integers • strength, dexterity, constitution, intelligence, wisdom, charisma – integers • created, modified – datetimes, automatically populated • a __str__ method that returns the character’s name -
Migrations •Propagates model changes into the database •Keeps a record
of changes made to the models •Can migrate forward and backward •Formerly provided by third-party tools •New in Django 1.7, required by Django 1.9 -
Making & Running Migrations •Creating a migration for an app:
$ python manage.py makemigrations <app> •See the SQL that will be executed: $ python manage.py sqlmigrate <app> <number> •Run migrations: $ python manage.py migrate -
git reset --hard exercise02.2 | git show solution02.2 Exercise 2.2
•Create a migration for the initial version of the Character model •Run the migration -
Trying Out the Model •Start a Python shell with all
the Django stuff all ready to go: $ python manage.py shell •Now we can test out our model -
Try the Admin Interface •Django comes with a built-in admin
interface to explore and manipulate models •Runs on /admin/ by default •Need to create an admin superuser: $ python manage.py createsuperuser •Need to register models in the app’s admin.py -
git reset --hard exercise02.3 | git show solution02.3 Exercise 2.3
•Create a superuser for using the admin interface •Register the Character model for use with the admin interface •Open your browser to localhost:8000/ admin -
Anatomy of a Dice Roll 2d8+ 1 type of die
number of dice -
Anatomy of a Dice Roll 2d8+ 1 type of die
number of dice modifier -
git reset --hard exercise03.1 | git show solution03.1 Exercise 3.1
•Add defaults to the Character model: • level – default to 1 • experience_points – default to 0 • max_hit_points, current_hit_points – default to 10 • stats (strength, dexterity, etc.) – use a function that uses Python’s random.randint to model a 3d6 dice roll -
git reset --hard exercise03.2 | git show solution03.2 Exercise 3.2
•Extract dice rolling from stat generation so that we can model any XdY + Z dice roll •Can do it in a one-line function •Don’t panic — this is the weirdest the code gets today! -
Choices •When a field can have only a finite set
of values that won’t change often, use choices •Can define both database native values and their human-friendly equivalents that will be displayed in the app -
git reset --hard exercise04.1 | git show solution04.1 Exercise 4.1
•Add alignment to the Character model; it should have a fixed set of choices: • Lawful Good • Lawful Neutral • Lawful Evil • Neutral Good • Neutral • Neutral Evil •It should default to Neutral • Chaotic Good • Chaotic Neutral • Chaotic Evil -
git reset --hard exercise04.2 | git show solution04.2 Exercise 4.2
•Create the migration to add alignment to the Character model •Run the migration -
Foreign Keys •Use a separate model when the data is
too rich for a simple set of choices •When a model (Character) can be related to only one of some other model (Race, Class), use a ForeignKey •When using the model, need an instance: hobbit = Race('hobbit').save() bilbo = Character(..., race=hobbit) -
Foreign Keys •Have to be careful when adding non-null fields
to an existing model, doubly so when it’s a ForeignKey •Migration will ask for a default •If adding a new model and an FK relationship, no defaults yet! •Do it in several steps... -
git reset --hard exercise05.1 | git show solution05.1 Exercise 5.1
•Create very basic models for Race and Class; they should each have: • name – text, up to 200 characters • description – text, open-ended • a __str__ method that returns the name -
git reset --hard exercise05.2 | git show solution05.2 Exercise 5.2
•Create the migration to create the Race and Class models in the database •Run the migration -
git reset --hard exercise05.3 | git show solution05.3 Exercise 5.3
•Add a ForeignKey relationship from the Race model to the Character model •Add a ForeignKey relationship from the Class model to the Character model •Be careful about names here; “class” is a Python builtin that we don’t want to shadow -
Data Migrations •Adding default values into a DB is best
accomplished with data migrations •Allow you to reliably populate data at the right point in the evolution of the DB •To create one: $ python manage.py makemigrations --empty appname -
git reset --hard exercise05.4 | git show solution05.4 Exercise 5.4
•Create a data migration that populates a default race (“Human”) and class (“Generic”) into the database •Run the migration -
git reset --hard exercise05.5 | git show solution05.5 Exercise 5.5
•Create a migration to add the ForeignKey relationships for Character/Race and Character/Class •Run the migration •When prompted for a default for the FK, enter the id you want (in this case 1 to match the data migration) -
git reset --hard exercise05.6 | git show solution05.6 Exercise 5.6
•Register the Race and Class models for use with the admin interface •What do you notice about the Class model in the admin interface? -
git reset --hard exercise05.7 | git show solution05.7 Exercise 5.7
•Fix the pluralization of “class” in the admin interface by adding verbose_name_plural metadata to the Class model •Observe the change in the admin app •What do you notice about how the class appears when looking at a character? -
git reset --hard exercise05.8 | git show solution05.8 Exercise 5.8
•Add a verbose_name of “class” to the Character/Class ForeignKey in the Character model •Observe the change in the admin interface -
git reset --hard exercise05.9 | git show solution05.9 Exercise 5.9
•Create a migration to sync up the previous change •Run the migration -
Many-to-Many Relations •When a model (Character) can be related to
many of some other model (Item), use a ManyToManyField •When using the model, need a list of instances: arkenstone = Item(name='Arkenstone').save() sting = Item(name='Sting (shortsword)').save() bilbo.inventory = [arkenstone, sting] -
git reset --hard exercise06.1 | git show solution06.1 Exercise 6.1
•Create an Item model; it should have: • name – text, up to 200 characters • description – text, open-ended • value – integer, default to 0 • weight – integer, default to 1 • a __str__ method that returns the name •Add a ManyToMany relation between Character and Item to the Character -
git reset --hard exercise06.2 | git show solution06.2 Exercise 6.2
•Create a migration to add the Item model and the ManyToMany relation to the database •Run the migration -
git reset --hard exercise06.3 | git show solution06.3 Exercise 6.3
•Register the Item model for use with the admin interface •Create an Item using the admin interface •Edit a Character in the admin interface; add an item to it •Create a Character without any items in the admin interface; what do you notice? -
git reset --hard exercise06.4 | git show solution06.4 Exercise 6.4
•Change the ManyToMany to be nullable and blank -
git reset --hard exercise06.5 | git show solution06.5 Exercise 6.5
•Create a migration to alter the relationship •Run the migration •Observe the change in the admin app -
git reset --hard exercise07.1 | git show solution07.1 Exercise 7.1
•Modify the admin wiring to group character fields in meaningful groups: • name, level, class, background • max hit points, current hit points • stats (strength, dexterity, constitution, intelligence, wisdom, charisma) • experience points, inventory -
git reset --hard exercise07.2 | git show exercise07.2 Exercise 7.2
•Make the character’s creation and modification dates appear in the admin interface •Show the creation and modification dates in a separate fieldset -
git reset --hard exercise07.3 | git show solution07.3 Exercise 7.3
•Add more fields to the character list in the admin interface: • name • level • race • class • creation & modification dates -
git reset --hard exercise07.4 | git show solution07.4 Exercise 7.4
•Add filters for race, class, creation, and modification date to the admin interface -
git reset --hard exercise07.5 | git show solution07.5 Exercise 7.5
•Change the model name of Race.name to “race” •Change the model name of Class.name to “class” -
git reset --hard exercise07.6 | git show solution07.6 Exercise 7.6
•Create the migration for adding model names to Race.name and Class.name •Run the migration -
git reset --hard exercise07.7 | git show solution07.7 Exercise 7.7
•Add search capability to the character list in the admin interface •Make characters searchable by name, background, race, and class -
URLs •Map URLs in requests to code that can be
executed •Regular expressions! •Each app has its own urls.py -
Views •Code that handles requests •Other frameworks often call these
“controllers” •Basically a function that: • gets a request passed to it • returns text or a response -
git reset --hard exercise08.1 | git show solution08.1 Exercise 8.1
•Create a simple view in the characters app •It can just emit “hello world” for now •Create a URL mapping for /characters/ to this view -
git reset --hard exercise08.2 | git show solution08.2 Exercise 8.2
•Create a stub view for viewing details of a single character •The URL should include the character_id •The view should say which character_id is being displayed •The view does NOT need to query the DB -
git reset --hard exercise08.3 | git show solution08.3 Exercise 8.3
•Change the character index view to return a list of all characters •Change the character detail view to return the requested single character -
Django Template Language •Call a function or do logic: {%
... %} •Variable substitution: {{ bar }} •Filters: {{ foo|bar }} -
git reset --hard exercise09.1 | git show solution09.1 Exercise 9.1
•Create a template to list characters •Change the character list view to use the template -
git reset --hard exercise09.2 | git show solution09.2 Exercise 9.2
•Create a template to show the details of a character •Change the character detail view to use the template -
git reset --hard exercise09.3 | git show solution09.3 Exercise 9.3
•Change the character details template to show a character’s inventory -
Relations in Templates •When a model object retrieved from the
ORM has relations, you get a relation manager and not an actual iterable collection •Need to call .all() (or get or filter) on it before you get back the related model objects -
Optimizing Queries •Be careful using relations in loops in templates;
can result in many extra queries •Go read up on: • select_related: does a join in SQL • prefetch_related: queries in advance, caches results, allows “join” in Python -
git reset --hard exercise09.4 | git show solution09.4 Exercise 9.4
•Change the character details view to return a 404 if it can’t find a character -
git reset --hard exercise09.5 | git show solution09.5 Exercise 9.5
•Change the character details view to use the get_object_or_404 shortcut -
git reset --hard exercise09.6 | git show solution09.6 Exercise 9.6
•Change the templates to use dynamically generated URLs in links -
git reset --hard exercise09.7 | git show solution09.7 Exercise 9.7
•Add a namespace to the characters app URLs to prevent collisions with other apps •Update dynamic links in templates to include the namespace -
Form Validation •Why? •Classes for each kind of input field
•Form class to gather input fields •View uses the form class to validate inputs -
git reset --hard exercise10.1 | git show solution10.1 Exercise 10.1
•Create a page with a simple form to create new characters •Just show minimal fields (name, background) for now •Default race and class to the defaults we created earlier •Redirect to the character list -
git reset --hard exercise10.2 | git show solution10.2 Exercise 10.2
•Change how race and class are set to reduce the number of database queries during character creation -
Class-Based Views • Separate methods for separate HTTP methods instead
of conditional logic • Ease development for common cases • Enables advanced designs • Optional; our example code today doesn’t use them -
Generic Views •Many views fall into the same patterns •Django
provides generic view classes for things like showing a list of objects, creating an object, updating an object, deleting, etc. •Subclass and set properties or override certain methods to customize -
git reset --hard exercise11.1 | git show solution11.1 Exercise 11.1
•Change the character list to use a generic ListView •Change the character detail view to use a generic DetailView -
git reset --hard exercise11.2 | git show solution11.2 Exercise 11.2
•Change the character list queryset to order results by character name -
git reset --hard exercise11.3 | git show solution11.3 Exercise 11.3
•Change the character creation view to use a generic CreateView -
Model Forms •Forms must declare every field explicitly •ModelForms read
a model and automatically create the necessary fields •Can quickly customize •Use with caution; exclude fields which should not be visible/editable -
git reset --hard exercise12.1 | git show solution12.1 Exercise 12.1
•Refactor character creation to use a ModelForm -
git reset --hard exercise12.2 | git show solution12.2 Exercise 12.2
•During character creation, only show the fields the user can choose: • name • background • race • class • alignment -
git reset --hard exercise12.3 | git show solution12.3 Exercise 12.3
•Show the stats (strength, dexterity, constitution, intelligence, wisdom, charisma) as read only during character creation •The solution is for illustration only; it has some security issues outside of our scope -
git reset --hard exercise13.1 | git show solution13.1 Exercise 13.1
•Create a page to update characters using a generic UpdateView -
git reset --hard exercise13.2 | git show exercise13.2 Exercise 13.2
•Create a page to delete characters using a generic DeleteView -
No Need to Reinvent •Django comes with a robust user
framework: django.contrib.auth •Registration •Login/Logout •Password Recovery •Etc. -
git reset --hard exercise14.1 | git show solution14.1 Exercise 14.1
•Start a new “accounts” app for managing user accounts -
git reset --hard exercise14.2 | git show solution14.2 Exercise 14.2
•Create a page for registering users •Use django.contrib.auth’s user creation form •Provide our own template and view; base the view on a generic FormView •Don’t forget URLs and settings.py! -
The User Object •Always have one in every request •Always
available to templates •Can be anonymous or populated depending on whether the user has authenticated -
git reset --hard exercise14.3 | git show solution14.3 Exercise 14.3
•Change the character list template •Show the username when signed in •Show the registration link when signed out -
More Reusable Goodness •django.contrib.auth provides URLs and views for login
and logout •Will want to provide our own templates -
git reset --hard exercise15.1 | git show solution15.1 Exercise 15.1
•Enable user login and logout •Create templates for login and logged out pages •Use django.contrib.auth views •Link to the login/logout in the character list template •Try it out; what do you observe? -
git reset --hard exercise15.2 | git show solution15.2 Exercise 15.2
•Change the default login redirect URL to the character list page (/characters) -
git reset --hard exercise15.3 | git show solution15.3 Exercise 15.3
•Fix the template resolution order for logout -
git reset --hard exercise15.4 | git show solution15.4 Exercise 15.4
•Create a base template for the site •Change all existing templates to extend the base template •Move the header navigation block (with our login/logout and registration links) to the base template -
git reset --hard exercise16.1 | git show solution16.1 Exercise 16.1
•Add a ForeignKey relationship between Characters and Users -
git reset --hard exercise16.2 | git show solution16.2 Exercise 16.2
•Create the migration to add the Character/ User relationship •Run the migration -
git reset --hard exercise16.3 | git show solution16.3 Exercise 16.3
•Add the User that created a Character to the admin fields -
git reset --hard exercise16.4 | git show solution16.4 Exercise 16.4
•Add the player username to the player details page -
git reset --hard exercise16.5 | git show solution16.5 Exercise 16.5
•Require the user to be logged in when creating, editing, or deleting a character -
git reset --hard exercise16.6 | git show solution16.6 Exercise 16.6
•When creating a character, save the user who created the character -
Model Managers •A place to encapsulate data queries •Extend to
provide extra queries with developer-friendly interfaces •Generic views can restrict access by limiting the queryset available in the view -
git reset --hard exercise17.1 | git show solution17.1 Exercise 17.1
•Create a model manager for the Character model •Use the model manager to lock down the edit and delete functions -
git reset --hard exercise17.2 | git show solution17.2 Exercise 17.2
•Remove the edit and delete character links when the user doesn’t own the character -
git reset --hard exercise18.1 | git show solution18.1 Exercise 18.1
•Add a basic landing page on the root URL •Include a spiffy logo image -
git reset --hard exercise18.1 | git show solution18.2 Exercise 18.2
•Make sure our pages have awesome branding •Put a small logo on every page -
git reset --hard bonus | git show bonus Bonus Ideas
•User profiles •Character pictures •Parties (groups of characters) •Rooms/locations •Combat •Loot! •Experience points/ leveling up •Weapons & armor •Race and class, for real •Spells •Multi-classing •REST API -
Where to Deploy •Gondor.io •Heroku •Webfaction •Your favorite VPS (Rackspace,
AWS, etc.) •Google App Engine (if that’s what you’re into) -
Production Advice •Don’t enable admin in production •Don’t enable debug
in production •Don’t enable the Django Debug Toolbar in production •Don’t publish your production settings & secrets on github -
Credits • Image from Django Unchained by Sony Pictures http://www.nydailynews.com/entertainment/tv-movies/django-star-foxx-life-
built-race-article-1.1220275 • Image of Django Reinhardt by ~Raimondsy http://raimondsy.deviantart.com/art/Django-Reinhardt-314914547 • Image of Gaming Dice http://www.clker.com/cliparts/R/y/7/z/q/z/role-playing-dice-hi.png • Other images from ClipArt Etc. http://etc.usf.edu/clipart/ -
Contact Information Mike Pirnat American Greetings http://mike.pirnat.com @mpirnat David Stanek
Rackspace http://traceback.org @dstanek