Skip to content
October 24, 2011 / ecschroder

Remake Part 2

In this post, I’ll finish up the remake of my first Django project. I made a lot of mistakes the first time around, so I’m starting over and trying to do it better.

Remake Part 1 went like this:

  • Started the project, created a repo
  • Set up the database
  • Created models.py

Next, I gotta:

  • Write URLs
  • Write views
  • Create templates

4. Write URLs.

First, I’ll enable the admin view by uncommenting a few lines in urls.py. Also need to add admin to the list of INSTALLED_APPS in setttings.py. Running the server, checking the admin view at 127.0.0.1:8000/admin, et voilà. It works.
Oops, I don’t see anything about KnickKnacks in the admin view. Need to follow these instructions from the tutorial to make the KnickKnack app available in the admin interface. Within the trashure app, I created an admin.py file. It reads like this:
from trashure.models import KnickKnack
from django.contrib import admin

admin.site.register(KnickKnack)
Restarting the dev server and checking again…eccolo, Knickknacks appear in the admin interface.
Second, I’ll write URLs for two different views: index and detail. I want this app to be stupid-easy to read, so I’ll write the tuples like this:
	url(
		regex = '^trashure/$',
		view = 'trashure.views.index',
		name = 'index',
	),

	url (
		regex = '^trashure/(?P<knickknack_id>\d+)/$',
		view = 'trashure.views.detail',
		name = 'detail',
	),
Of course, those URLs won’t resolve yet, because the views that they will call don’t actually exist. But I suppose I could test them anyway. I’m expecting to see a “View does not exist” error, or something like that.
Testing 127.0.0.1:8000/trashure, got a NameError: “name ‘trashure’ does not exist.” Is that what I was expecting? Yes, close enough.

5. Write the views.

Right now, views.py exists but is a blank slate. As far as I understand, views.py is where you’ll do all of your calculations and queries. It’s going to grab stuff from the database, and spit that into the templates.
As a reminder, the KnickKnack relation (which we’ve defined in models.py during Remake Part 1) goes like this:
KnickKnack (name, photo, description, memory, year, era, pub_date, votes_treasure, votes_trash, score)
Hmm, no primary key? (Oh wait…Django adds an ID (primary key) automatically.)
For the record, all of this makes a hell of a lot more sense after a few weeks of Stanford’s online database class.

Detail view

The detail view is the simpler of the two views, so I’ll start there. There are basically two sections of the detail view that are coming from the database:
  • 1 full KnickKnack, with its name, photo, description, etc.
  • 4 random KnickKnacks, but just the photo. (“Related Trash,” that is.)
In other words, we’ll need to return those things into a context, like this:
#...
return render_to_response(
		'trashure/detail.html',
		dictionary={
			'knickknack': p,
			'relatedtrash': relatedtrash,
		},
		context_instance=RequestContext(request)
#...
Of course, we’re working backwards here, from the bottom up. Now that we know what we’re returning from views.py, we can construct those things. To get a knickknack, we’ll need two attributes: (1) a request (HTTP request, I guess?) and (2) a knickknack_id, which we parsed out of the URL in urls.py. It will go like this:
def detail(request, knickknack_id):
p = get_object_or_404(KnickKnack, pk=knickknack_id)
#… 
To get some “Related Trash,” we will
  • grab all of the KnickKnacks
  • order them randomly
  • take the first four of those random objects
It goes like this:
randomtrash = KnickKnack.objects.order_by('?')[:4]
Now I’ll add the required import statements to the top of views.py. We’ve used these packages, so need to import ’em:
from django.template import Context
from django.template import loader
from django.template import RequestContext
from django.shortcuts import get_object_or_404
from django.shortcuts import render_to_response

from trashure.models import KnickKnack

Index view

The index view will show a few different pieces of the pie:
  • three random objects
  • two “Featured Treasure” objects
  • four “Featured Trash” objects
First, we’ll define our index view. This time, we’re only passing one attribute, request. We’ll construct the aforementioned sets like this:
def index(request):
    randomtrash = KnickKnack.objects.order_by('?')[:3]
    toptreasure = KnickKnack.objects.order_by('-score')[:2]
    toptrash = KnickKnack.objects.order_by('score')[:4]
    # ... 

If we want to order_by a negative score, we'll have to import the reverse function, like this:

from django.core.urlresolvers import reverse
To finish up, we’ll pass those sets into the context dictionary, and spit them into a template:
return render_to_response(
    'trashure/index.html',
    dictionary={
        'randomtrash': randomtrash,
        'toptreasure': toptreasure,
        toptrash': toptrash,
        },
        context_instance=RequestContext(request)
    )

You can see the code at the end of Step 5 on GitHub.

6. Incorporate the templates.

I’ve already prepared two static HTML pages and the all-important CSS stylesheet. First, I’ll set up the directory structure so that I can put everything in the right places in my project. Then I’ll swap out some static parts of the HTML templates and replace them with template tags. (A little bit of background reading before this step; definitely didn’t catch everything the first time.)

That was some very nice reading. Got some notes.

Variables
dot tries, in this order:
1. dictionary lookup
2. attribute lookup
3. method call
4. list-index lookup
{{variable}}
{{variable.something}}

Filters
30+ built in filters
{{ name | lower }}

Tags
{% for %}{% endfor %}
{% if %}{% else %} {%endif%}
{%block%}{%extends%}

Comments
{# single line comment #}
{%comment%} multiline comment{%endcomment%}

Set up the directories to images, CSS and JS

Let’s see if I can remember how this goes…. Nope. Where do I put static files? I’m uploading a few “knickknacks” through the admin interface so I’ll be able to see where the new media/ folder gets created. I’ll put static/ at the same level. It will go like this:

remake/
    trashure/
    media/
    static/
        img/
        css/
        js/

I’m pretty lazy and it’s 1 am, so I’m just copying everything in static/ over from the first project. Making a few tweaks (new variable names in the remake, etc.) Testing it out.

Oops, no stylesheet is showing. I must have forgotten something when setting up the directories. Indeed…forgot to change this part of settings.py:

STATICFILES_DIRS = (
	'C:/remake/static',
)

I don’t understand why I need to set an absolute path here. Why does that still work when it’s deployed online? Does Django-Zoom (or similar) automagically change the path to something relative during the deployment process?

New problem, new solution

In the first project, I choose to represent the “year” attribute as a string, or CharField. That is, models.py had the line:

year = models.CharField(max_length=4)

This (slightly-less-totally-ghetto) time around, I used a DateField for the “year” attribute.

year = models.DateField('year acquired')

Before, Django would display the four digits of the year, but only because they were a string. Now, the attribute “year” is the datetime data type, and Django is displaying it in the date format: Mon DD, YYYY. Since I only want to show the year, I can add a template filter. Within the detail.html, changing {{ knickknack.year}} to {{ knickknack.year|date:”Y”}} , and the output is tout simplement YYYY.

Where are the * pictures?

Gah, where are the #$%#images? Getting the path to the images was super confusing last time.

Adding this chunk to the bottom of urls.py does the trick (thanks Jacob!):

if settings.DEBUG:
	urlpatterns += patterns('', url(
		r'^media/(?P<path>.*)$',
		'django.views.static.serve',
		{'document_root': settings.MEDIA_ROOT, }
	))

The remake is finished! You can see the code at the End of Step 6 on GitHub. Or the code at the End of Step 6 for real. Oops.

On to users!

Advertisements
October 22, 2011 / ecschroder

Lightning Talk: Hello World

Site:

http://bit.ly/oYUjnI

Code:

https://github.com/ecschroder

Blog:

https://djangolookslikefun.wordpress.com/

Starting point:

  • some HTML, CSS
  • a tiny bit of Python
  • nothing about databases

Lots of problems:

  • Setting up dev environment on Windows
  • Getting used to Git
  • General n00biness

Lots of help:

  • PyLadies
  • DjangoCon
  • Documentation!

Notable moments in history:

  • Deploying on DjangoZoom
  • Code review from PyLadies
  • Starting over

Next steps:

  • Finish Remake Part 2
  • Set up OAuth :)
October 5, 2011 / ecschroder

Database scavenger hunt

The first time you run the command python manage.py syncdb, Django creates a database based on your models.

Um, okay. Cool.

I’m completely new to databases, so that doesn’t really mean much to me. Today I’ve only got a few minutes to play, so I’m going to try to see what really happens when you sync the database.

Reading: Introduction to Relational Database Design

Looking forward to: Stanford’s Introduction to Database course, online & free (begins October 10, 2011)

Let’s syncdb.

I’ve already created my database, but haven’t added any records yet. Seems like a good time to delete it & take it from the top.

C:\remake>python manage.py syncdb
Creating tables ...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_user_permissions
Creating table auth_user_groups
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site Creating table trashure_knickknack

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'emma'):
E-mail address: fake_address@fake.com
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
No fixtures found.

So…what just happened?

First of all, we’ve got a new file in our project folder: objects.db. What’s going on in there? If only we could see inside….

Joe told me about SQLite Database Browser, which is open-source tool for creating & editing databases. We can use the tool to open the database and take a look. Here’s what I see:

Look kids, it's a database!

I see lots of tables: auth_permission, auth_group_permission, trashure_knickknack, etc. These tables bears a striking resemblance to the tables that Django told us it was creating when we ran python manage.py syncdb. Hmm….

One of those tables–trashure_knickknack–is my own creation. I wrote that model in my last episode. All the other ones? I have no idea where they came from or what they are.

Scavenger hunt for auth_permission.

Let’s look at one of the tables: auth_permission. As we can see in the screenshot, that table has four fields: id, name, content_type_id, and codename. Where did it come from? Let’s go on a scavenger hunt in the Django source code. Maybe we’ll find some clues.

Where did it come from?

In our last episode, we created the model for the trashure_knickknack table. To finish that process, we had to add trashure to a list of INSTALLED_APPS, which was found inside settings.py. Maybe we can start there?

Looking in settings.py, we see the entire list of INSTALLED_APPS goes like this:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
	'trashure',
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

What is it?

Peeking inside C:\django\django\contrib\auth, we find another file called models.py. That sounds like a good place to look for clues.

The models.py has a class called “Permissions.” It goes like this:

class Permission(models.Model):
    """The permissions system provides a way to assign permissions to specific users and groups of users.

    The permission system is used by the Django admin site, but may also be useful in your own code. The Django admin site uses permissions as follows:

        - The "add" permission limits the user's ability to view the "add" form and add an object.
        - The "change" permission limits a user's ability to view the change list, view the "change" form and change an object.
        - The "delete" permission limits the ability to delete an object.

    Permissions are set globally per type of object, not per specific object instance. It is possible to say "Mary may change news stories," but it's not currently possible to say "Mary may change news stories, but only the ones she created herself" or "Mary may only change news stories that have a certain status or publication date."

    Three basic permissions -- add, change and delete -- are automatically created for each Django model.
    """
 name = models.CharField(_('name'), max_length=50) content_type = models.ForeignKey(ContentType) codename = models.CharField(_('codename'), max_length=100) objects = PermissionManager() 

    class Meta:
        verbose_name = _('permission')
        verbose_name_plural = _('permissions')
        unique_together = (('content_type', 'codename'),)
        ordering = ('content_type__app_label', 'content_type__model', 'codename')

    def __unicode__(self):
        return u"%s | %s | %s" % (
            unicode(self.content_type.app_label),
            unicode(self.content_type),
            unicode(self.name))
    def natural_key(self):
        return (self.codename,) + self.content_type.natural_key()
    natural_key.dependencies = ['contenttypes.contenttype']

Mystery and intrigue.

I would not expect some random source code file to make any sense at all. But wait…there are a ton of comments and explanations in there. I don’t know what it is or what it all means, but it seems strange and beautiful. Like this thing:

Another strange and beautiful creature.

So…how was school today?

I definitely don’t understand much, but have a sliver of hope that it will make sense eventually. Someday.

October 3, 2011 / ecschroder

Remake Part 1

After lots of help from friends, my first Django project–Trash or Treasure–is up and running. A lot of new things cropped up along the way, so I want to review before going any further. Time for a remake.

The Trash or Treasure app will allow people to vote on the assorted items that are collecting dust back at home. Ooh, a box of coins from 1992! What a treasure! Yep, basically like Hot or Not, but for the junk in my closet.

We’re going to make a very simple voting site. The admin (that’s me!) will be able to post “knickknacks” on a site. Each “knickknack” will have a title, image, and some more meta-data. Users will be able to vote “trash” or “treasure” on each item. Votes will be recorded and displayed. There will be two outward-facing views–an index view and a detail view–as well as an admin interface.

0. Here’s the starting point.

  • Django is installed.
  • My GitHub account is set up. (I’ll create a repo in Step 1.)
  • HTML/CSS for two types of pages–an index page and a detail page–is finished.
  • Windows environmental variables are set up. That is, I’ve already added the path to Python.

1. Start project, create repo.

This step is like rolling a six in Trouble. It’s going to get us out of the starting block. First, make a directory. No, wait–first, create a repo. Er, I mean–first, start a project.

Start a project.

I’m going to put my project in C:\remake. I’ll change directory into C:, then run this command:

django-admin.py startproject remake

Django creates the new directory, plus four initial project files:

remake/
	__init__.py
	manage.py
	settings.py
	urls.py

Create a repository in GitHub.

Since I’ve already registered on GitHub, this step is a snap. Click on Create a Repository, filled out the form, then followed the instructions.

Only one tiny deviation from the instructions (which I forgot the first time ’round). Change into the directory that was created by Django’s startproject command, rather than making another new one. In other words, skip this step:

$ mkdir ~/Hello-World

Instead, use the directory that we’ve just created with manage.py startproject.

Just checking: Did it work?

Per the Django tutorial, I’ll run the server to see if everything worked:

python manage.py runserver

Server is up and running. We can see for ourselves by going to 127.0.0.1:8000 in a browser.

Since we’re using two Python scripts to run the server (__init__.py and settings.py), two new Python compiled files are created (__init__.pyc and settings.pyc). That reminds me: I should create a .gitignore file so those extra files don’t end up in the eternal memory of Git.

Create a .gitignore file.

I’m going to use the beginnings of a .gitignore from Danny:

*.pyc
local_settings.py
coverage
docs/_build
docs/_static
*~
Created a text file called .gitignore with aforementioned contents, added it to the repo, et voilà…no extra files shall pass into eternity.

Just checking: Did we Git it all?

(Sorry.) Adding and committing all the essential stuff to Git, to make sure the project is up to date. Just to make sure that I’m not missing any basics, skimming this overview of Git. Another overview found here. Learning about: git status command, untracked / unmodified / modified / staged files.

Just checking: Remake of a remake.

Intermission for a few experiments with Git. Deleted the repo from Git. Created it again. Add, commit, push. Repeat.

At the end of Step 1, the project looks like this.

2. Set up the database.

If we run the server and peek at 127.0.0.1:8000, Django prompts us to edit the DATABASES setting in remake/settings.py.

Edit settings.py.

DATABASES = {

    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'C:\remake\objects.db',
    }
}

I’m using the built-in sqlite3 database, so I can skip the other settings. When the project is finished, I’ll be deploying on DjangoZoom. During deployment, DjangoZoom will create a new–and different kind of–database than what I specify here. (PostgreSQL, that is.) No problem. For now, we’ll stick to sqlite3.

Sync the database.

python manage.py syncdb

That will create the objects.db in our remake directory. Along the way, I’ll create a superuser account for myself. At this point, I’m just following along with the tutorial.

Add, commit, push.

I didn’t do much in this step, but I’ll push to Git just for the heck of it. You can see the infant database on GitHub here.

3. Create models.

Since I’ve only created one app and one project, I’m a little fuzzy on the distinction. How about an outside resource or two or three?

It’s hard to guess a good name for the app that I’m about to create, so I’ll stick with the non-sensical “trashure.” (trash + treasure, get it? Hardy har har.)

Start an app.

Name picked out, ready to start my app. Running this command:

python manage.py startapp trashure

Now we’ve got a new directory with four more files:

trashure/
	__init__.py,
	models.py
	tests.py
	views.py

Cool. Now I’ve got a models.py file to play with.

Edit INSTALLED_APPS to include the new app.

Easy breezy, just add the new app to the list of INSTALLED_APPS in settings.py.
INSTALLED_APPS = (
	'django.contrib.auth',
	'django.contrib.contenttypes',
	'django.contrib.sessions',
	'django.contrib.sites',
	'django.contrib.messages',
	'django.contrib.staticfiles',
	'trashure',
)

Plan the model.

Like any bad remake (e.g. Pyscho (1998)), this project has a predictable ending. This project has just one model, and we already know which fields to define. Just in case I missed a key point, I’m taking another look at the Models documentation.

For each knickknack, I’d like to have this information:

  • a name
  • a photo
  • a description
  • my memory of the item
  • the “era in my life” during which  I acquired the item
  • the date I acquired the item (Optional: only if I can remember.)
Just for kicks, let’s get some meta-information, too:
  • the date this record was added to the database
Later on, a few more fields will become useful. These fields will simplify the voting functionality:
  • a counter to keep track of “treasure” votes
  • a counter to keep track of “trash” votes
  • a total score

Write the model.

Where does this go? Models.py, of course:

import math

from django.db import models
from django.db.models.fields.files import ImageField
from django.utils.translation import ugettext_lazy as _

ERA_CHOICES = (
	('Childhood', _('Childhood')),
	('High_School', _('High School')),
	('College', _('College')),
	('Adult', _('Adult')),
	('unknown', _('unknown')),
)

class KnickKnack(models.Model):
	name = models.CharField(max_length=200)
	photo = ImageField(upload_to'../media', height_field=None, width_field=None)
	description = models.TextField()
	memory = models.TextField()
	year = models.DateField('year acquired')
	era = models.CharField(max_length=20, choices=ERA_CHOICES)
	pub_date = models.DateTimeField('date published', auto_now=True)
	votes_treasure = models.IntegerField(default=0)
	votes_trash = models.IntegerField(default=0)
	score = models.IntegerField(default=0)
	def __unicode__(self):
		return self.name
	def save(self, *args, **kwargs):
		self.score = self.votes_trash - self.votes_treasure
		super(KnickKnack, self).save(*args, **kwargs)

A few notes:

  • In order to upload images using ImageField, we have to set a media path. That takes a bit of tinkering in settings.py. See below.
  • ERA_CHOICES is a list of  2-tuples. The human-readable part of the tuple has some extra syntax to make it ready for future translations. See section #4 of this previous entry for more about getting ready for the translation utility.
  • We’ll calculate the score from “treasure votes” minus “trash votes.” We’ll need the python math library for that calculation, so we import it up at the top.
  • The score must get updated each time that a user votes. In other words, whenever any part of the record is changed, the score will get saved again. Hence the custom save function at the end of the model. That function overrides the predefined model method.

Setting a media path for ImageField

If we want to upload images, we’ll need to give Django a place to put them. We’ll need to set up a media path. In settings.py, I added this:

import os
# ..
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
MEDIA_ROOT = os.path.join(SITE_ROOT, 'media')
MEDIA_URL = '/media/'

That was one step that I didn’t understand very well. Thank goodness for expert help.

Sync the database.

Like a butterfly emerging from a cocoon, a beautiful relational database will emerge from the model that we’ve just created. The magic happens when we sync the database:

python manage.py syncdb

Add, commit, push.

…and that’s it for today. You can see the code after this step on GitHub.

September 25, 2011 / ecschroder

Code review

Audrey and Esther of the PyLadies group graciously offered to review my code. Here are the results. The code in question can be found on GitHub here.

0. Nice custom save, Joe!

Kudos for the custom save bit (from models.py). Impressive! Too bad I didn’t come up with it myself. All credit goes to Joe.

1. Only store the code in Git.

Don’t use Git to keep track of .pyc files, Photoshop comps, or any other junk that’s not code. You don’t want to keep big files in Git, because collaborators on your project will need to wait (sometimes 3 minutes!) to download your repo.

Git is really just for source code control, not backing up your entire project. No one’s stopping you from putting lots of images, Photoshop files, and other large binary files in Git. But don’t.

What you should do instead:

  • Create a file called “.gitignore” and stick it in the root of your repo directory.
  • You can see the gist of a .gitignore file here, thanks to Danny. Go ahead…copy and paste it into your own .gitignore file.
  • Use .gitignore on the folders that contain major assets (like large images).

Git will not copy those files into the repo. Everyone is happy!

Cleaning up a mess:

Git never forgets. Once a file is tracked, it will always be tracked–unless you explicitly remove it.

Since I’ve already committed a bunch of extra junk to GitHub, I needed a pro to help clean it up. Michael did some ninja moves to remove extraneous files from my repo. Caution, danger, alert: Make a local backup first, you can’t undo ninja moves.

2. Organize your import statements.

When your project grows, you’ll want a nice and tidy list of imports.

What you should do:

  • Alphabetize the list of import statements.
  • Put core imports at the top of the list.
  • Use line breaks to make the code more readable.

Before:

from django.db import models
import math
from django.db.models.fields.files import ImageField

After:

import math
# imagine a blank line here #ialreadyhatewordpress
from django.db import models
from django.db.models.fields.files import ImageField

Here’s a handy household hint from Michael in Los Angeles, California. If you want those import statements to really sparkle, put each one on it’s own line.

This is okay:

from django.http import HttpResponseRedirect, HttpResponse, Http404

But this is better:

from django.http import Http404
from django.http import HttpResponse
from django.http import HttpResponseRedirect

3. Spell things out.

My model has a field called “era.” (Meaning: during which era of your life did this piece of trash/treasure come into your possession?) I’m giving a few pre-defined choices, such as “childhood” or “high school.” Originally, I set up those choices in a tuple that looked like this:

ERA_CHOICES = (
    ('CH', 'Childhood'),
    ('HS', 'High School'),
    ('CL', 'College'),
    ('AD', 'Adult'),
    ('UN', 'unknown'),
)

See those 2-letter abbreviations? I was trying to be clever. Danny’s advice: don’t be clever. Be explicit. Those choices might make perfect sense when you’re staring at the set of ’em in models.py, but they’re probably going to confuse you later on.

Here’s the revised version:

ERA_CHOICES = (
    ('Childhood', 'Childhood'),
    ('High_School', 'High School'),
    ('College', 'College'),
    ('Adult', 'Adult'),
    ('unknown', 'unknown'),
)

To accomodate the longer choice names, tweak the max_length of that field  model. Change that 2 to a 20.

class JunkPollItem(models.Model):
    # ...
    era = models.CharField(max_length=20, choices=THE_ERA_CHOICES)
    # ...

4. Set good habits now.

When your super awesome app rocks the world, you’ll want to translate it into the world’s languages. Luckily, there are some scripts that will make the translation process a breeze. You guessed it. We’re talking about utils.translation.

You might not be ready to translate yet, but you can start naming variables in a translation-friendly way. It’s just a good habit.

What you should do:

You’ll need to import the translation script to models.py.

from django.utils.translation import ugettext_lazy as _

Throughout your code, prefix English variable names with an underscore.

_(‘likeThis’)

Before:

ERA_CHOICES = (
    ('Childhood', 'Childhood'),
    ('High_School', 'High School'),
    ('College', 'College'),
    ('Adult', 'Adult'),
    ('unknown', 'unknown'),
)

After:

ERA_CHOICES = (
    ('Childhood', _('Childhood')),
    ('High_School', _('High School')),
    ('College', _('College')),
    ('Adult', _('Adult')),
    ('unknown', _('unknown')),
)

Later on, this little bit of prep work will save you time. If you ever need to translate your app to another language, you’ll be able to run the ugettext_lazy script. It will make a list of all of the variables that you’ve written in English, so you can get those words translated. That’s much better than digging around in your code to find all the words that need to be translated.

Of course, you don’t need to do this, but it’s a good habit. Might as well get into it now.

Oh, and “i18n” stands for “internationalization.” Cool.

5. Make urls.py into an easy reader.

The Django Polls tutorial shows you how to set up your urls.py file with tuples. Each tuple contains a few pieces of information:

  • a regular expression
  • a view
  • a name for that view

As my dear reader will recall, Django resolves URLs like this:

  • Beginning with a string of characters in the URL…
  • …Django tries to match that string to a regular expression in urls.py.
  • When it finds a match, it serves the corresponding view.

Following the example of the tutorial, I wrote urls in a tuple, like this:

url(r'^trashure/(?P<junkpollitem_id>\d+)/((?P<vote>\w+)/)?$', 'trashure.views.detail', name='detail'),

Here is Danny’s better idea. Tuples are hard to understand at a glance. Rather than using tuples to define URLs, write each one out, like this:

url(
    regex = '^trashure/(?P<junkpollitem_id>\d+)/((?P<vote>\w+)/)?$',
    view = 'trashure.views.detail',
    name = 'detail',
),

At the end of the day, urls.py will be longer. It will also be much easier to read and understand. You can see a complete example from Danny here.

7. Give your “other_stuff” folder a respectable name.

I have a folder called “other stuff,” wherein reside mock-ups and notes-to-self. It’s a junk drawer. That doesn’t mean it has to have a stupid name.

What I should do instead:

If I ever have a folder, I think I’m gonna name him “assets” or “etc”, any damn thing but “other_stuff.” I still hate that name.

Refer to #1: Only store the code in Git:

.gitignore, fo sho.

PyLadies are lovely.

Thank you for all of the help, PyLadies & Gents. The corrections are valuable. The encouragement is priceless.

Very much looking forward to Christine’s “front-end code roast” this coming Thursday.

September 25, 2011 / ecschroder

Making friends with Git

Have started a joint project with Joe. Need to make friends with GitHub so that we can collaborate. Top two nuggets of information:

1. Git is confusing to other people, too.

2. There’s a lot of good documentation.

Nugget #1 is reassuring. Nugget #2 is very reassuring.

Resources:

Got Git? HOWTO git and github

(“Your confidence is about to be shaken like a dry martini.”)

Getting Started with Git and GitHub on Windows

(Much appreciated section on Workflow.)

GitHub Help

(Of course.)

September 14, 2011 / ecschroder

Small tweaks

No big plans for my app today, just a few small tweaks.

Goals for today:

  • Add jQuery to show/hide results
  • Do a proper data migration when deploying
  • Don’t screw it up

So…how was school today?

I did a bad, bad thing. The entire purpose of this project is to learn Django. But once you pimp your ride, you want to paint it.

Yes. I spent the evening playing with the design. The original font turned out to be kinda hard to read, especially on Chrome. And well, you know. It’s hard to resist playing with stuff.

Next steps:

There are two little interactive design things that I want to do, but don’t know how to do yet. Here’s what I want:

  • #body background changes color depending on the user’s vote. Vote “trash,” turns red. Vote “treasure,” turns moss.
  • “You might also dislike” changes to “you might also like” depending on user’s vote.

The first two would be really easily solved by writing three different views

  • detail
  • voted trash detail
  • voted treasure detail

But that seems ugly to maintain. I need to learn how to put one template inside of another one.

Maybe this is where base template comes in? Reading about template inheritance.

This looks so easy, I’m wanna try now

Making these template changes was pretty easy. Just a matter of slicing & dicing and then hooking up some dependencies, like this.

Tell the child to look for the parent, in detail.html:

{% extends "base.html" %}

and tell the parent where to put the child, in base.html:

{% block content %}{% endblock %}

Oh, and make sure that the path to base.html is communicated loud & clear to Django. (J/k. Django is supposed to be good at finding things like templates. This guy says so.) You can communicate the path in a gentle voice. Just one line in settings.py.

TEMPLATE_DIRS = ("C:/projects/mystuff/trashure/templates/trashure")
It doesn't make sense to me that I should set a local path from my C: drive, since obviously I'm not running the site of my computer. Need to learn more about how paths work. Problems on the horizon if I don't...
Almost there...just one tiny change

Locally, the templates found each other sans problème with this line at the beginning of detail.html:

{% extends "base.html" %}

But deployed, it threw a “template not found” error. Changed the first line to this, and it worked like a charm:

{% extends "trashure/base.html" %}

I’m not quite sure why it needs the extra directory once my project is deployed. Stay tuned…we will see a very similar problem again shortly.

Data migration…grr

Nate gave clear instructions for migrating data. On Windows, I ran this command locally:

manage.py dumpdata trashure > trashure.json

Then I committed all my changes to my Git repo and pushed it to GitHub.

(Oops, one kink. I forgot that I had made some changes to a file directly on GitHub. Maybe that’s a bad practice? I was feeling lazy and just needed a quick change…. Now I have a merge conflict on my hands. <waste 10 minutes> Resolved.)

Alright, ready for the next problem. Trying to run the following command through DjangoZoom’s manage.py tab:

manage.py loaddata user-src/trashure.json

But, it says:

No fixtures found.

I’m bummed, because I want to see my changes live. Try again tomorrow.

It’s tomorrow.

Jacob helped me do a data migration last Monday. (That’s the black box that I mentioned in this earlier post.) I was about to call him up to ask for help again, but then I remembered a beautiful thing: log files.

Since he solved the problem before, I just had to check the log files on DjangoZoom to find the command that worked. It was nearly identical to the one Nate suggested, with slightly different file path. (That is, the file path now includes the directory of the project.)

manage.py loaddata user-src/mystuff/trashure.json

Success!

Installed 1 object(s) from 1 fixture(s)

Yo, data migration! How exciting is that?!

Next up:

Splitting up the templates into base.html, index.html and base.html was super easy. But, I’m not sure that template inheritance is the right solution for changing the background color based on user input. I suppose I could put each section of this site into its own pluggable app, but that sounds like overkill. The solution is probably easier than that.

Will investigate later.