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.
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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!
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
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
to create database tables •Integrated with the ORM to interact with the database •Need to create & run migrations when adding or changing model classes
•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
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
•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
•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
•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!
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
•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
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)
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...
•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
•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
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
•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)
•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?
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]
•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
•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?
•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
•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
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
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
•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
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
•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
•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!
•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?
•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
•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
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/