Skip to content

Commit

Permalink
Todo list application with add functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
tachang committed Dec 29, 2011
1 parent 1e53c0c commit 2075289
Show file tree
Hide file tree
Showing 19 changed files with 233 additions and 198 deletions.
199 changes: 1 addition & 198 deletions README
Original file line number Original file line Diff line number Diff line change
@@ -1,198 +1 @@
Intro This is a simple todo application for the Noisebridge Python class.
=====
* About this section of the class
* Schedule: https://www.noisebridge.net/wiki/Working_Syllabus
* Ultimate goal is a long term project: https://www.noisebridge.net/wiki/PyClass_Project_Ideas
* No pre-existing skills required, but...
* Feedback

*NOTE*: Unlike the intro to python classes, this section of the course will not have time to pay special attention to windows users. This part focuses on web development in python, which is NOT windows friendly. Of course you may join and I will help as much as possible but in general if you are serious about web development you should borrow a friends computer or pair up with someone else in the class on a nix-like system, which includes Mac OS X. Sorry!

Goals
-----
* Become familiar with web2y
* Learn and/or solidify working with git
* Basic skills/methodology for evaluating python frameworks
* Basic introduction to debugging

Prereqs
-------
* Install python 2.6 or 2.7. We will NOT be working from python 3. If you are on mac os X, it will help you to install a python that is NOT the built in python. See python.org for instructions. You may also want to install XCode, which can usually be done through the app store as "Apple Developer Tools". Neither of these are required for the first class but you will get there at some point.
* Find your terminal. That's it. Just know where it is :)
* Install Git

Further Documentation
---------------------
* http://www.web2py.com/book/

Steps to your own noiselist
---------------------------
For this class, we will go through the process of creating a personal TODO list on 3 different frameworks. This is the first. So let's go.

**Get web2py downloaded and running with demo app using buildout**::

> git clone git://github.com/eleddy/web2py-noiselist.git
> cd web2py-noiselist
> python bootstrap.py # use the non-local python if you are on mac osx please
> ./bin/buildout
> ./bin/web2py start

**Create a new simple app**

- Start at http://127.0.0.1:8000/admin/default/site
- Create a new simple app named noiselist. web2py will setup a default folder structure for you with basic routing. This app can be viewed at http://127.0.0.1:8000/noiselist/default/index .
- You can edit your new app through the web or on the filesystem, whichever feels better to you. The following instructions will work for either.
- Through the web, you can edit your files at http://127.0.0.1:8000/admin/default/design/noiselist
- On the filesystem, you can edit your app at at web2py-noiselist/web2py/applications/noiselist

**Intro to the file system and templating**

The Model section/folder will have all of your database definitions. The View section/folder will have all of your templates, css, js, and images. The controllers section/folder will have all of your business logic, or for all intensive purposes most of your .py files.

In views > default > index.html, add html to make the front page resemble a list. Replace the boiler plate code with something like::

<ul>
<li>Tequila</li>
<li>Pants</li>
<li>Pre-party</li>
</ul>

What you add and remove is up to you. Play with adding and removing different tags to see what happens.

**Controllers and sending data**

Here we will see how to use variables in templates, and make the list dynamically populated. We aren't starting right away with database muckery because we want to learn how things fit together nicely and minimize the chance that we have multiple errors. Reload often after each code change to make sure you haven't borked things.

In controllers > default.py, add a function which returns a list::

def get_list():
"""
Return the current todo list
"""
return [
"Go to the store and buy pants",
"Eat breakfast",
"Flash mob"
]

Still in controllers > default.py, call your new function from the index page. Note that you must return your new variable in a dictionary. This adds the variable to the scope of the template::

def index():
todo_list = get_list()
return dict(todo_list=todo_list)

In views > default > index.html, update your html to use the todo_list variable to dynamically display the list::

<ul>
{{for todo in todo_list:}}
<li>{{=todo}}</li>
{{pass}}
</ul>

Now your app is wired to get a list of todo items from the controller logic and display them. Yay!

**Hook up to a real database**

For this example, we will be working from the default SQLite database. This is a very simple relational database that lives on the filesystem. Later on in the class we will work with more robust databases.

At the very bottom of models > db.py, let's add code to define our todo list model::

db.define_table('todo_list',Field('todo'),)
db.todo_list.todo.requires=IS_NOT_EMPTY()

This code says: Create a table called "todo_list", and give it one field (or column if you like) called "todo". The second line says: make sure no one enters empty todos. You can add as few or as many restrictions as you like. We are adding the empty check here to demo the way that web2py handles data validation.

Web2py will automatically reload changed code AND database schemas every time you save a file. Neato.

Now that we have a database, we need to get info from that database. Keep in mind that your database is currently empty so the net end result will be an empty todo list on your index page.

In controllers > default.py, update the get_list function to pull from the database instead of a list ([])::

def get_list():
"""
Return the current todo list
"""
return db().select(db.todo_list.ALL)

We have one small change to make in the template. The db select above will return rows, and we really just want one item in that row - the todo. Update views > default > index.html to say::

<ul>
{{for todo in todo_list:}}
<li>{{=todo.todo}}</li>
{{pass}}
</ul>

If you had more fields in your model like 'created_by' for example, you can access that by saying todo.created_by.

**Adding to the db**

Currently we are pulling from the db, but pulling an empty list. Let's put a form on the front page to add list items. Web2py will auto generate and validate forms for you so we will take that approach.

To create a form, add a function in controllers > default.py::

def add_to_list():
"""
Render and handle response from adding to a todo form
"""
form=SQLFORM(db.todo_list)
message = None
if form.accepts(request,session):
message = 'Added to list!'
else:
message = 'something went wrong'
if message:
response.flash=message
return form

There are a couple things going on there. First, we see that web2py has some whacky globals lying around (SQLFORM) so be careful. Second, given a database table it will auto-generate a form for you. Third, form.accepts will do validation of the form for you. Last but not least is the introduction of flash. response.flash automatically adds a growl style notification to the resulting page which you will see when you violate the "no empty todo's" restraint that we added earlier.

Before leaving that file, make sure to send that form to the index page with::

def index():
"""
example action using the internationalization operator T and flash
rendered by views/default/index.html or views/generic.html
"""
add_form = add_to_list()
todo_list = get_list()
return dict(todo_list=todo_list,
add_list_item_form=add_form)

Last but not least, add the code to render the form to your front page. In views > default > index.html::

{{if 'add_list_item_form' in globals():}}
{{=add_list_item_form}}
{{pass}}

Now reload the front page and voila! You should be able to view and add items in your list! Note that if you add an empty item, a error response is flashed.

Packaging
---------
If you did all of the work through the web (or FS even), you can package up your app and redistribute with the built in tools.
* Go to http://127.0.0.1:8000/admin/default/site
* Click "Pack all"
* Move the w2p export into web2pyapps
* Update buildout
* Commit!

Homework
--------
If you are captivated with web2py, try to do the following at home:
* Delete an item from a list
* Configure multiple users
* Review at the beginning of next class






Create a project named django_todo. You can't use dashes (only numbers, letters, and underscores)

$ django-admin.py startproject django_todo


Inside the django_todo project create a Django application called todo:
$ django-admin.py startapp todo


Empty file added __init__.py
Empty file.
Binary file added __init__.pyc
Binary file not shown.
Binary file added database.sqlite
Binary file not shown.
11 changes: 11 additions & 0 deletions manage.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)

if __name__ == "__main__":
execute_manager(settings)
Empty file added noiselist/__init__.py
Empty file.
Binary file added noiselist/__init__.pyc
Binary file not shown.
7 changes: 7 additions & 0 deletions noiselist/models.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.db import models

# Create your models here.

# A single todo item. Only supports a single list.
class TodoItem(models.Model):
name = models.CharField(max_length=256)
Binary file added noiselist/models.pyc
Binary file not shown.
23 changes: 23 additions & 0 deletions noiselist/tests.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""

from django.test import TestCase

class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)

__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}

30 changes: 30 additions & 0 deletions noiselist/views.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.template import Context, RequestContext
from django.shortcuts import render_to_response, redirect
from django import forms
from noiselist.models import *

class TodoForm(forms.Form):
name = forms.CharField(max_length=1000, widget=forms.Textarea)

def index(request):
todos = TodoItem.objects.all()

return render_to_response('index.html', { 'todos' : todos }, context_instance=RequestContext(request))

def add_todo(request):

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

if( form.is_valid() == True ):
item = TodoItem()
item.name = form.cleaned_data['name']
item.save()
# All saved so return the user to the frontpage
return redirect('/')
else:
return render_to_response('add_todo.html', { 'form' : form}, context_instance=RequestContext(request))
else:
form = TodoForm()

return render_to_response('add_todo.html', { 'form' : form}, context_instance=RequestContext(request))
Binary file added noiselist/views.pyc
Binary file not shown.
103 changes: 103 additions & 0 deletions settings.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,103 @@
import os, sys
import django
# Django settings for django_noiselist project.

# Some custom settings that are useful to be used in the settings file
DJANGO_ROOT = os.path.dirname(os.path.realpath(django.__file__))
SITE_ROOT = os.path.dirname(os.path.realpath(__file__))

DEBUG = True
TEMPLATE_DEBUG = DEBUG

ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)

MANAGERS = ADMINS

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': os.path.join(SITE_ROOT, 'database.sqlite'), # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = True

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'

# Make this unique, and don't share it with anybody.
SECRET_KEY = 'bvhg=-cet&0!6qhx#&@d349+pu!*i+jqt)%wk6(#x2(g$608dd'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)

ROOT_URLCONF = 'django_noiselist.urls'

TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(SITE_ROOT, 'templates')

)

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
'noiselist',
)
Binary file added settings.pyc
Binary file not shown.
16 changes: 16 additions & 0 deletions templates/add_todo.html
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "base.html" %}

{% block content %}

<h1>Add a Todo</h1>

<form method="POST" action="/todo">

{{ form.as_ul }}

{% csrf_token %}
<input type="submit"/>
</form>

{% endblock %}

Loading

0 comments on commit 2075289

Please sign in to comment.