Project

General

Profile

CompleteTutorial » History » Version 4

Lacey Powers, 06/18/2009 04:47 PM

1 1 bford -
== Creating your first "Hello World" Project in Pylons. ==
2
3
This is a tutorial to show you the basics of creating a small project within Pylons, with Simpycity.
4
This tutorial assumes that you have already set up a Linux development environment for Pylons. 
5
Preferably on Ubuntu, though these directions should be general enough for any
6
other Linux environment, meaning that you have installed Apache2, Python,
7
mod-wsgi, and PostgreSQL 8.3.
8
9
10
Choose a working directory for your project. 
11
12
For this example, I am using "project" for my working directory.
13
14
Create the working directory, if it doesn't already exist.
15
16
{{{
17
mkdir project
18
}}}
19
20
Move into the working directory.
21
22
{{{
23
cd project
24
}}}
25
26
Create your project:
27
28 2 Lacey Powers
{{{
29 1 bford -
paster create -t pylons helloworld
30 2 Lacey Powers
}}}
31 1 bford -
32
This should give you a set of choices.
33
34
{{{
35
Enter template_engine (mako/genshi/jinja2/etc: Template language) ['mako']: genshi
36
}}}
37
38
For templating, choose "genshi".
39
40
{{{
41
Enter sqlalchemy (True/False: Include SQLAlchemy 0.5 configuration) [False]: False
42
}}}
43
44
Include sqlalchemy. "False".
45
46
We will be using Simpycity instead.
47
48
You should have a directory that looks like this:
49
50
{{{
51
lacey@blinky:~/project$ ls -l
52
total 4.0K
53
drwxr-xr-x 5 lacey lacey 4.0K 2009-06-05 08:46 helloworld
54
lacey@blinky:~/project$
55
}}}
56
57
cd into the helloworld project.
58
59
{{{
60
cd helloworld
61
}}}
62
63
The base directory should look like this:
64
65
{{{
66
lacey@blinky:~/project/helloworld$ ls -l
67
total 48K
68
-rw-r--r-- 1 lacey lacey 1.6K 2009-06-05 08:46 development.ini
69
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 docs
70
-rw-r--r-- 1 lacey lacey 9.5K 2009-06-05 08:46 ez_setup.py
71
drwxr-xr-x 9 lacey lacey 4.0K 2009-06-05 08:46 helloworld
72
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 helloworld.egg-info
73
-rw-r--r-- 1 lacey lacey  125 2009-06-05 08:46 MANIFEST.in
74
-rw-r--r-- 1 lacey lacey  463 2009-06-05 08:46 README.txt
75
-rw-r--r-- 1 lacey lacey  597 2009-06-05 08:46 setup.cfg
76
-rw-r--r-- 1 lacey lacey  959 2009-06-05 08:46 setup.py
77
-rw-r--r-- 1 lacey lacey  509 2009-06-05 08:46 test.ini
78
lacey@blinky:~/project/helloworld$
79
}}}
80
81
This is the basic enviroment for your "Hello World" project. 
82
83
There are several important files and directories here. To avoid confusion, I
84
will only explain the most important, and most used files within the project.
85
86
setup.py   -- A command and control file for the project. With setup.py, you
87
can run unit tests, update the packages associated with the project, and
88
package it into a Python .egg. 
89
90
test.ini   -- The test configuration file for the project. This file mostly
91
inherits from the development.ini file, but if you want to have testing
92
specific settings for this project, you put them here. For now, this will just
93
inherit from the development.ini file.
94
95
development.ini  --  This file contains basic configuration of the project.
96
This is where you toggle debugging settings, add database configuration
97
information, and so forth.
98
99
We need to customize this environment to use Simpycity, a simple and effective
100
ORM based on stored procedures and queries. 
101
102
First, we edit the "setup.py" file. You can use your preferred text editor to
103
open this file, e.g. Vim, Emacs, Joe, Nano, Gedit, ect. For this example, we
104
will use the basic editor "nano".
105
106
{{{
107
nano setup.py
108
}}}
109
110
There are only a few options that need to be modified in the base setup.py
111
file.
112
113
Note the section that looks like this: 
114
115
{{{
116
    install_requires=[
117
        "Pylons>=0.9.7",
118
        "Genshi>=0.4",
119
    ],
120
}}}
121
122
This section governs the requirements for your particular project. This currently 
123
shows that a version of Pylons of 0.9.7 or greater, and a version of Genshi of 0.4 
124
or greater is required to for this project. 
125
126
Since we are using Simpycity for our ORM, we need to add a line for Simpycity.
127
128
{{{
129
    install_requires=[
130
        "Pylons>=0.9.7",
131
        "Genshi>=0.4",
132 3 Lacey Powers
        "Simpycity>=0.2.1"
133 1 bford -
    ],
134
}}}
135
136 3 Lacey Powers
The number 0.2.1 is the current version of Simpycity. 
137 1 bford -
138 2 Lacey Powers
Since new features and bug fixes are added to Simpycity regularly, you should check for updated versions regularly. 
139
140
To check that the version of Simpycity is up to date, please go to:
141
142 1 bford -
https://projects.commandprompt.com/public/simpycity
143
144
Down in the middle of the page, there is an area marked "Download". 
145
146
The first item in the download area is the current version of Simpycity.
147 3 Lacey Powers
(Which at the time of writing this tutorial is 0.2.1, so be sure to check.)
148 1 bford -
149
{{{
150 3 Lacey Powers
The current release version of Simpycity is 0.2.1, which can be downloaded
151 1 bford -
from here 
152
}}}
153
154 2 Lacey Powers
All that you need to do to keep Simpycity up to date is copy that version number, and paste over the version number in the install_requires stanza in setup.py.
155
156
After that, there is one last thing we need to add to the setup.py file
157
158 1 bford -
Near the bottom, before the closing parenthesis, we need to add another snippet
159
of code.
160
161
{{{
162
    dependency_links=[
163
        "https://projects.commandprompt.com/public/simpycity/repo/dist/"
164
    ] 
165
}}}
166
167 2 Lacey Powers
This is the link to the Simpycity repository at Command Prompt. 
168 1 bford -
169 3 Lacey Powers
easy_install will use that URI, combined with the version number (in this case 0.2.1) to locate and install the proper Simpycity .egg file.
170 1 bford -
171 2 Lacey Powers
Now, we save, and exit from setup.py.
172
173
Following the insertion of those lines, we will do one last check. 
174
175
Sometimes, the newest python packages with easy_install require that they be built on the machine before they are deployed.
176
177
This is the case with newer versions of psycopg2. To ensure that all of the build requirements are present, run the following command:
178
179 1 bford -
{{{
180 2 Lacey Powers
aptitude search postgresql-server-dev-8.3 python2.5-dev build-essential
181
}}}
182
183
Which should return like this:
184
185
{{{
186
lacey@blinky:~/project/helloworld$ aptitude search postgresql-server-dev-8.3 python2.5-dev build-essential
187
i A build-essential                                                                     - Informational list of build-essential packages                                                
188
i   postgresql-server-dev-8.3                                                           - development files for PostgreSQL 8.3 server-side programming                                  
189
i   python2.5-dev                                                                       - Header files and a static library for Python (v2.5)                                           
190
lacey@blinky:~/project/helloworld$
191
}}}
192
193
If it does not, then running the following command:
194
195
{{{
196
sudo aptitude install postgresql-server-dev-8.3 python2.5-dev build-essential
197
}}}
198
199
Will install the essential libraries for you.
200
201
Now, we run the following command:
202
203
{{{
204 1 bford -
sudo python setup.py develop
205
}}}
206
207
This will check and process all of the dependencies for your project, including
208
Simpycity. This process will install the dependencies required for your
209
project, so that they may be used within the project.
210
211
Now that we have Simpycity installed, we pause to do a bit of database setup.
212
213
With sudo become the postgres user.
214
215
{{{
216
sudo su - postgres
217
}}}
218
219
It will ask you for your password.
220
221
Success should look something like this.
222
223
{{{
224
lacey@blinky:~/project/helloworld$ sudo su - postgres
225
[sudo] password for lacey: 
226
postgres@blinky:~$
227
}}}
228
229
Now, we run a PostgreSQL command to create a user.
230
231
{{{
232
postgres@blinky:~$ psql -U postgres
233
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
234
235
Type:  \copyright for distribution terms
236
       \h for help with SQL commands
237
       \? for help with psql commands
238
       \g or terminate with semicolon to execute query
239
       \q to quit
240
241
postgres=# 
242
}}}
243
244
You are now on the postgresql command line.
245
246
While there, run the following commands.
247
248
{{{
249
CREATE USER helloworld WITH ENCRYPTED PASSWORD '12345';
250
CREATE DATABASE helloworld WITH OWNER helloworld;
251
}}}
252
253
The successful execution of these two commands should look like this:
254
255
{{{
256
postgres=# CREATE USER helloworld WITH ENCRYPTED PASSWORD '12345';
257
CREATE ROLE
258
postgres=# CREATE DATABASE helloworld WITH OWNER helloworld;
259
CREATE DATABASE
260
postgres=#
261
}}}
262
263
Exit PostgreSQL with the \q command. This should drop you back to the command
264
line.
265
266
{{{
267
postgres=# \q
268
postgres@blinky:~$
269
}}}
270
271
If you haven't already, take a moment to modify your pg_hba.conf so you can
272
easily log in. 
273
274
{{{
275
nano /etc/postgresql/8.3/main/pg_hba.conf
276
}}}
277
278
Near the bottom, there should be a line that looks like *exactly* like this.
279
280
{{{
281
local   all         all                               ident sameuser
282
}}}
283
284
The very last part, "ident sameuser" should be changed to md5, so that the line
285
looks like this:
286
287
{{{
288
local   all         all                               md5
289
}}}
290
291
Save and quit.
292
293
Now, execute the command:
294
{{{
295
/etc/init.d/postgresql-8.3 force-reload
296
}}}
297
298
This will reload the settings that you changed.
299
300
{{{
301
postgres@blinky:~$ /etc/init.d/postgresql-8.3 force-reload
302
* Reloading PostgreSQL 8.3 database server [ OK ] 
303
postgres@blinky:~$
304
}}}
305
306
307
Exit the postgres user environment with the exit command.
308
309
{{{
310
postgres@blinky:~$ exit
311
logout
312
lacey@blinky:~/project/helloworld$
313
}}}
314
315
Now we should be back in the "helloworld" project directory.
316
317
Next, as a convenience, we will create a .pgpass file.
318
319
cd to your home directory. 
320
321
{{{
322
cd ~
323
}}}
324
325
{{{
326
nano .pgpass
327
}}}
328
329
When the editor window comes up, add the following line.
330
331
{{{
332
localhost:5432:*:helloworld:12345
333
}}}
334
335
Save and quit.
336
337
Following that, execute the following command.
338
339
{{{
340
chmod 600 .pgpass
341
}}}
342
343
This will appropriately set the permissions for your user.
344
345
You can check them with the following command.
346
347
{{{
348
lacey@blinky:~$ ls -l .pgpass
349
-rw------- 1 lacey lacey 135 2009-06-05 12:10 .pgpass
350
lacey@blinky:~$
351
}}}
352
353
Now, there is the final test. 
354
355
Execute the following command.
356
357
{{{
358
psql -U helloworld
359
}}}
360
361
If everything has been successfully set up, your terminal screen should look
362
like this:
363
364
{{{
365
lacey@blinky:~$ psql -U helloworld
366
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
367
368
Type:  \copyright for distribution terms
369
       \h for help with SQL commands
370
       \? for help with psql commands
371
       \g or terminate with semicolon to execute query
372
       \q to quit
373
374
helloworld=>
375
}}}
376
377
With the command logging you in without issue, or a password prompt.
378
379
While we are here, in PostgreSQL, we should issue the following command:
380
381
{{{
382
CREATE LANGUAGE plpgsql;
383
}}}
384
385
Again, success should look like this:
386
387
{{{
388
helloworld=> CREATE LANGUAGE plpgsql;
389
CREATE LANGUAGE
390
helloworld=>
391
}}}
392
393
And again, we exit with "\q".
394
395
{{{
396
helloworld=> \q
397
lacey@blinky:~$
398
}}}
399
400
Now, we return to the project directory with the following command.
401
402
{{{
403
cd project/helloworld/
404
}}}
405
406
In this directory, we edit development.ini
407
408
{{{
409
nano development.ini
410
}}}
411
412
At line 36, uncomment the line set debug = false, and add the following lines
413
so that the section of the configuration file looks like this:
414
415
{{{
416
db.database = helloworld
417
db.user     = helloworld
418
db.host     = localhost
419
db.port     = 5432
420
db.password = 12345
421
}}}
422
423
These are the values that Simpycity requires for the database connections.
424
database = a database that belongs to the user.
425
user = the user that you are connecting as.
426
host = the server being connected to. localhost for a development machine.
427
port = the port that PostgreSQL expects you to connect on.
428
password = the password for the user.
429
430
Before we go on, a little more explanation is needed, so that you better
431
understand what is going on in later steps.
432
433
You may have noticed the "helloworld" directory within the project earlier.
434
435
{{{
436
lacey@blinky:~/project/helloworld$ ls -l
437
total 48K 
438
-rw-r--r-- 1 lacey lacey 1.6K 2009-06-05 08:46 development.ini
439
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 docs
440
-rw-r--r-- 1 lacey lacey 9.5K 2009-06-05 08:46 ez_setup.py
441
drwxr-xr-x 9 lacey lacey 4.0K 2009-06-05 08:46 helloworld
442
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 helloworld.egg-info
443
-rw-r--r-- 1 lacey lacey  125 2009-06-05 08:46 MANIFEST.in
444
-rw-r--r-- 1 lacey lacey  463 2009-06-05 08:46 README.txt
445
-rw-r--r-- 1 lacey lacey  597 2009-06-05 08:46 setup.cfg
446
-rw-r--r-- 1 lacey lacey  959 2009-06-05 08:46 setup.py
447
-rw-r--r-- 1 lacey lacey  509 2009-06-05 08:46 test.ini
448
lacey@blinky:~/project/helloworld$
449
}}}
450
451
This directory contains all of your project specific code is placed.
452
453
If you issue the following commands:
454
455
{{{
456
cd helloworld
457
ls -l
458
}}}
459
460
You will note that the directory has the following structure: 
461
462
{{{
463
lacey@blinky:~/project/helloworld/helloworld$ ls -l
464
total 32K
465
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 config
466
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 controllers
467
-rw-r--r-- 1 lacey lacey    0 2009-06-05 08:46 __init__.py
468
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 lib
469
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 model
470
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 public
471
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 templates
472
drwxr-xr-x 3 lacey lacey 4.0K 2009-06-05 08:46 tests
473
-rw-r--r-- 1 lacey lacey  296 2009-06-05 08:46 websetup.py
474
lacey@blinky:~/project/helloworld/helloworld$ 
475
}}}
476
477
The directories here break down as follows: 
478
479
   config -- Configuration files for your application as a whole.
480
481
   controllers -- This is where most of the application logic goes. The files here
482
control what you display, and how you display it.
483
484
   lib -- This is where miscellaneous files that are necessary for your
485
application, but have no distinct place go.
486
487
   model -- This is where the files related to the ORM go. This will be where most
488
of the files related to the database and Simpycity will live.
489
490
   public -- This is where static html, docs, css, and such live. 
491
492
   templates -- This is where Genshi will create the dynamic html templates for
493
the dynamic pages that you will use.
494
495
   __init__.py -- This is a helper file for packaging your application. That
496
will be covered later in a different tutorial.
497
498
   tests -- This is where your unit tests live. These test controllers and other
499
application functionality.
500
501
   websetup.py -- This is another helper file for packaging your application. It
502
too will be covered later in a different tutorial.
503
504
505
Now that you have an overview of the directories within a Pylons project, we
506
can start making some changes within this directory and its child directories.
507
508
First, we should make an important edit to lib/base.py, related to Simpycity.
509
510
{{{
511
nano lib/base.py
512
}}}
513
514
The contents of the file will look like this.
515
516
{{{
517
"""The base Controller API
518
519
Provides the BaseController class for subclassing.
520
"""
521
from pylons.controllers import WSGIController
522
from pylons.templating import render_genshi as render
523
524
class BaseController(WSGIController):
525
526
    def __call__(self, environ, start_response):
527
        """Invoke the Controller"""
528
        # WSGIController.__call__ dispatches to the Controller method
529
        # the request is routed to. This routing information is
530
        # available in environ['pylons.routes_dict']
531
        return WSGIController.__call__(self, environ, start_response)
532
}}}
533
534
We will add the following lines, to fix a troublesome issue before it starts.
535
536
For the curious, more detail can be found here:
537
http://www.commandprompt.com/blogs/aurynn_shaw/2009/05/long_running_request_handlers_and_python_garbage_collection/
538
539
For purposes of this tutorial, it should be sufficient to say that this will
540
allow Pylons and mod_wsgi to properly close and clean up connections to the database, so
541
that connections are not left hanging idle in transaction.
542
543
So this is very important to include in any project.
544
545
{{{
546
        import psycopg2
547
        import psycopg2.extensions
548
        psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
549
        from simpycity.handle import Manager
550
            
551
        m = Manager()
552
        try:
553
            return WSGIController.__call__(self, environ, start_response)
554
        finally:
555
            # Shut down *all* the extant handles.
556
            m.close()
557
}}}
558
559
We paste this in so that the controller code will look like this:
560
561
{{{
562
"""The base Controller API
563
564
Provides the BaseController class for subclassing.
565
"""
566
from pylons.controllers import WSGIController
567
from pylons.templating import render_genshi as render
568
569
class BaseController(WSGIController):
570
571
    def __call__(self, environ, start_response):
572
        """Invoke the Controller"""
573
        # WSGIController.__call__ dispatches to the Controller method
574
        # the request is routed to. This routing information is
575
        # available in environ['pylons.routes_dict']
576
        #return WSGIController.__call__(self, environ, start_response)
577
        import psycopg2
578
        import psycopg2.extensions
579
        psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
580
        from simpycity.handle import Manager
581
                
582
        m = Manager()
583
        try:
584
            return WSGIController.__call__(self, environ, start_response)
585
        finally:
586
            # Shut down *all* the extant handles.
587
            m.close()
588
}}}
589
590
Save, and exit nano.
591
592
Next we edit environment.py
593
594
{{{
595
nano config/environment.py
596
}}}
597
598
Within this file, at the very end, below the lines:
599
600
{{{
601
# CONFIGURATION OPTIONS HERE (note: all config options will override
602
# any Pylons config options)
603
}}}
604
605
we add the follwing lines, for Simpycity setup. 
606
607
{{{
608
app_conf = config['app_conf']
609
610
db_config.port = app_conf['db.port']
611
db_config.database= app_conf['db.database']
612
db_config.host= app_conf['db.host']
613
db_config.user = app_conf['db.user']
614
db_config.password = app_conf['db.password']
615
db_config.debug = False
616
}}}
617
618
And at the top of environment.py, with the rest of the imports, we add the
619
following line.
620
621
{{{
622
from simpycity import config as db_config
623
}}}
624
625
So that the proper configuration options are read.
626
627
It should be noted here, if you need to see the debugging options from
628
Simpycity itself, you should set db_config.debug to True.
629
630
Having added the proper configuration parameters to base.py and environment.py,
631
we can start looking at how to serve content from Pylons. There are two basic
632
ways to serve content through Pylons: 
633
634
   1. To server static content from the public/ directory. 
635
   2. Use a controller, which contains the logic for what to control and how to
636
   display it.
637
638
The trivially easy case is static content.
639
640
Execute the following command.
641
642
{{{
643
nano helloworld/public/hello.html
644
}}}
645
646
Paste the following content. 
647
648
{{{
649
<html>
650
   <body>
651
      Hello World!
652
   </body>
653
</html>
654
}}}
655
656
Save and exit.
657
658
This creates a basic static html file.
659
660
Now, from our current directory, execute the following command
661
662
{{{
663
paster serve --reload development.ini
664
}}}
665
666
This command starts the Pylons server, and is great for a bit of quick
667
debugging, or if you haven't got Apache installed or configured. 
668
669
Entering the following URI into your web browser:
670
671
{{{
672
http://localhost:5000/hello.html
673
}}}
674
675
You should see a simple page with the words "Hello World!".
676
677
Now, stop the server on the command line with <ctrl>-<c>.
678
679
We are moving on to the more interesting and complicated case of using a
680
controller to serve content within Pylons.
681
682
Execute the following command:
683
684
{{{
685
paster controller hello
686
}}}
687
688
Successful execution of this command should look something like this:
689
690
{{{
691
lacey@blinky:~/project/helloworld$ paster controller hello
692
Creating /home/lacey/project/helloworld/helloworld/controllers/hello.py
693
Creating /home/lacey/project/helloworld/helloworld/tests/functional/test_hello.py
694
}}}
695
696
This is an interesting and useful command. As expected, it created a file
697
called hello.py in the helloworld/controllers directory, but it also did a
698
wonderful thing for us, and created a file in helloworld/tests/functional/test_hello.py
699
to contain the specific unit tests for the hello.py controller.
700
701
Executing the following command:
702
703
{{{
704
nano helloworld/controller/hello.py
705
}}}
706
707
Shows us the contents of the hello.py controller, which are very basic.
708
709
{{{
710
import logging
711
712
from pylons import request, response, session, tmpl_context as c
713
from pylons.controllers.util import abort, redirect_to
714
715
from helloworld.lib.base import BaseController, render
716
717
log = logging.getLogger(__name__)
718
719
class HelloController(BaseController):
720
721
    def index(self):
722
        # Return a rendered template
723
        #return render('/hello.mako')
724
        # or, return a response
725
        return 'Hello World'
726
}}}
727
728
Exiting from Nano, we can also have a look at test_hello.py by executing a similar command.
729
730
{{{
731
nano helloworld/tests/functional/test_hello.py
732
}}}
733
734
The contents of which are:
735
736
{{{
737
from helloworld.tests import *
738
739
class TestHelloController(TestController):
740
741
    def test_index(self):
742
        response = self.app.get(url(controller='hello', action='index'))
743
        # Test response...
744
}}}
745
746
The paster command has created a basic framework for a unit test of the
747
hello.py controller. We will come back to this later in the document.
748
749
For right now, we want to see the controller in action. 
750
751
But there is a bit more setup that we need to do first. 
752
753
Execute the following command:
754
755
{{{
756
nano helloworld/config/routes.py
757
}}}
758
759
This should bring up a file that looks like this:
760
761
{{{
762
"""Routes configuration
763
764
The more specific and detailed routes should be defined first so they
765
may take precedent over the more generic routes. For more information
766
refer to the routes manual at http://routes.groovie.org/docs/
767
"""
768
from pylons import config
769
from routes import Mapper
770
771
def make_map():
772
    """Create, configure and return the routes Mapper"""
773
    map = Mapper(directory=config['pylons.paths']['controllers'],
774
                 always_scan=config['debug'])
775
    map.minimization = False
776
777
    # The ErrorController route (handles 404/500 error pages); it should
778
    # likely stay at the top, ensuring it can always be resolved
779
    map.connect('/error/{action}', controller='error')
780
    map.connect('/error/{action}/{id}', controller='error')
781
782
    # CUSTOM ROUTES HERE
783
784
    map.connect('/{controller}/{action}')
785
    map.connect('/{controller}/{action}/{id}')
786
787
    return map
788
}}}
789
790
This is another relatively spartan, but highly important file.
791
Within this file, the routes for the application are defined. Routes are what
792
translates the URI within the browser into directions in the application, which
793
point data at a specific controller, for manipulation.
794
795
This may sound a bit obtuse right now, but at the end of this particular
796
example, it should be clear.
797
798
Currently, if you were to restart Pylons, with the aformentioned command....
799
800
{{{
801
paster serve --reload development.ini
802
}}}
803
804
And attempted to browse to the URI:
805
806
{{{
807
http://localhost:5000/hello
808
}}}
809
810
You would be presented with a very cheery orange-yellow, and black 404 page.
811
812
This is because you haven't specified the route. Without a route, Pylons has no
813
idea what you want. So, to let it know that we want to see what is within the
814
HelloController (located in helloworld/controller/hello.py) we will add the
815
following line to helloworld/config/routes.py :
816
817
{{{
818
map.connect('hello', '/hello', controller='hello', action='index')
819
}}}
820
821
The first item in map.connect gives the route the name "hello" (this will be
822
handy later). The second part is what maps the part of the URI that you put in
823
the search bar as in:
824
825
{{{
826
http://localhost:5000/hello
827
}}}
828
829
Now, when we run:
830
831
{{{
832
paster serve --reload development.ini
833
}}}
834
835
And attempted to browse to the URI:
836
837
{{{
838
http://localhost:5000/hello
839
}}}
840
841
We will see the words "Hello World".
842
843
Note that this is different from our static html entry.
844
845
{{{
846
http://localhost:5000/hello.html
847
}}}
848
849
Which says "Hello World!".
850
851
And both are available.
852
853
Moving to a more generic route....
854
855
{{{
856
http://localhost:5000
857
}}}
858
859
Will show you a very cheery welcome page, with the same orange-yellow and black
860
color scheme, with links. 
861
862
This is served from helloworld/public/public.html
863
864
Now, that is all fine for an example, but in an actual production application, you don't
865
want someone to see "Welcome To Pylons" when they browse to your root URI.
866
867
So, lets fix this.
868
869
First, stop the server on the command line with <ctrl>-<c>.
870
871
Execute the command:
872
873
{{{
874
nano helloworld/config/routes.py
875
}}}
876
877
At the bottom, just before "return map", we will add the following line.
878
879
{{{
880
map.connect('root', '/', controller='hello', action='index')
881
}}}
882
883
Why do we add this way down there, you might ask? Because the Pylons developers
884
left us with a handy comment at the top of this file.
885
886
{{{
887
The more specific and detailed routes should be defined first so they
888
may take precedent over the more generic routes.
889
}}}
890
891
Taking heed of this advice, and realizing that '/' is the most generic route
892
that you can have, we place this at the very bottom so it is chosen last in the
893
evaluation of routes.
894
895
Again, we restart the server, from the ~/projects/hellworld directory.
896
897
{{{
898
paster serve --reload development.ini
899
}}}
900
901
And browse to:
902
903
{{{
904
http://localhost:5000
905
}}}
906
907
....We still see the cheery orange-yellow and black welcome page.
908
909
(Be sure to shut down the server with <ctrl>-<c>)
910
911
Why is that!?!?
912
913
Well, there's an interesting thing about Pylons. It will evaluate the static
914
content in helloworld/public first. In this case, it is index.html. 
915
916
If we poke around a bit, we find the culprit in helloworld/config/middleware.py
917
on line 67.
918
919
{{{
920
app = Cascade([static_app, app])
921
}}}
922
923
This line basically says that, look for static content first, and evaluate that
924
over the dynamic application content. 
925
926
There are two ways to solve this particular issue.
927
928
1. Execute the following command:
929
{{{
930
mv helloworld/public/index.html helloworld/public/index.html.dont_evaluate_me_pylons
931
}}}
932
933
If you do that, Pylons won't be able to find it, and will default to the route
934
set in routes.py. 
935
936
In this case, if you restart the server, and browse to the aformentioned URI,
937
you will see Hello World (without the exclamation point).
938
939
2. Swap the order of Cascade. 
940
941
Since the Cascade command defines whether or not you evaluate static content or
942
dynamic content first, swapping the order will mean that dynamic content is
943
evaluated first, and static content second.
944
945
So, we execute the following command:
946
947
{{{
948
mv helloworld/public/index.html.dont_evaluate_me_pylons helloworld/public/index.html 
949
}}}
950
951
To return the file back to its original state. If we are incorrect, this will
952
show us by displaying that cheery welcome page again. 
953
954
So, we execute the following command:
955
956
{{{
957
nano helloworld/config/middleware.py
958
}}}
959
960
Once there, we will copy line 67, and paste it directly below itself. Then we
961
will comment out line 67i (we have the original line for reference), so that the file 
962
now looks like this:
963
964
{{{
965
#app = Cascade([static_app, app])
966
app = Cascade([app, static_app])
967
}}}
968
969
With the order of app, and static_app swapped. 
970
971
In this case, if you restart the server, and browse to the aformentioned URI,
972
you will see Hello World (without the exclamation point).
973
974
And index.html is still happily sitting in helloworld/public
975
976
{{{
977
lacey@blinky:~/project/helloworld$ ls -l helloworld/public/index.html 
978
-rw-r--r-- 1 lacey lacey 4.6K 2009-06-05 08:46 helloworld/public/index.html
979
}}}
980
981
Now, even though we're using the HelloController in hello.py, this is still
982
little better than a static application. So, now, we will work on making
983
hello.py dynamic, letting it read and write to and from the database by using
984
Simpycity.
985
986
First, we will need a simple set of tables and functions for use in PostgreSQL.
987
988
We are currently still in the ~/project/helloworld directory. We are going to
989
move one directory back.
990
{{{
991
cd ..
992
}}}
993
994
And make a new directory called 'sql'.
995
996
{{{
997
mkdir sql
998
}}}
999
1000
Now, we consider the following table and functions, already designed and provided for you to use. 
1001
These will be the examples that we use for the rest of the tutorial.
1002
1003
This is a very simple table, that contains ways to say "Hello" in different languages.
1004
1005
{{{
1006
CREATE TABLE hello
1007
(
1008
  salutation text not null primary key,
1009
  language text not null
1010
);
1011
1012
-- Various formal ways of saying hello or good morning.
1013
INSERT INTO hello (salutation, language) VALUES ('hello','English');
1014
INSERT INTO hello (salutation, language) VALUES ('hola','Spanish');
1015
INSERT INTO hello (salutation, language) VALUES ('bonjour','French');
1016
INSERT INTO hello (salutation, language) VALUES ('merhaba selam','Turkish');
1017
INSERT INTO hello (salutation, language) VALUES ('zdravstvuyte','Russian');
1018
INSERT INTO hello (salutation, language) VALUES ('buon giorno','Italian');
1019
1020
1021
CREATE OR REPLACE FUNCTION add_salutation(in_salutation text, in_language text)
1022
RETURNS boolean AS $BODY$
1023
  DECLARE
1024
  BEGIN
1025
    INSERT INTO hello (salutation, language) VALUES (in_salutation, in_language);
1026
    RETURN TRUE;
1027
  END;
1028
$BODY$ LANGUAGE PLPGSQL;
1029
1030
1031
CREATE OR REPLACE FUNCTION remove_salutation(in_salutation text, in_language text)
1032
RETURNS boolean AS $BODY$
1033
  DECLARE
1034
  BEGIN
1035
    DELETE FROM hello WHERE salutation = in_salutation AND language = in_language;
1036
    RETURN TRUE;
1037
  END;
1038
$BODY$ LANGUAGE PLPGSQL;
1039
1040
1041
CREATE OR REPLACE FUNCTION update_salutation(in_old_salutation text, in_new_salutation text)
1042
RETURNS boolean AS $BODY$
1043
  DECLARE
1044
  BEGIN
1045
    UPDATE hello SET salutation = in_new_salutation WHERE salutation = in_old_salutation;
1046
    RETURN TRUE;
1047
  END;
1048
$BODY$ LANGUAGE PLPGSQL;
1049
1050
}}}
1051
1052
Execute the following command, in your ~/project/sql directory.
1053
1054
{{{
1055
  nano hello.sql
1056
}}}
1057
1058
Paste the above SQL code into that file, save and quit.
1059
1060
Then run the following command.
1061
1062
{{{
1063
psql -U helloworld helloworld -f hello.sql
1064
}}}
1065
1066
Success should look like this.
1067
1068
{{{
1069
lacey@blinky:~/project/sql$ psql -U helloworld helloworld -f hello.sql 
1070
psql:hello.sql:5: NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "hello_pkey" for table "hello"
1071
CREATE TABLE
1072
INSERT 0 1
1073
INSERT 0 1
1074
INSERT 0 1
1075
INSERT 0 1
1076
INSERT 0 1
1077
INSERT 0 1
1078
CREATE FUNCTION
1079
CREATE FUNCTION
1080
CREATE FUNCTION
1081
lacey@blinky:~/project/sql$
1082
}}}
1083
1084
Now that the necessary tables, functions, and data are in place in PostgreSQL, we can move on to making our hello model.
1085
1086
Execute the following command to move back into your helloworld project directory.
1087
1088
{{{
1089
cd ../helloworld/helloworld
1090
}}}
1091
1092
The directory you are in should look like this, if you execute the command ls -l :
1093
1094
{{{
1095
lacey@blinky:~/project/helloworld/helloworld$ ls -l
1096
total 36K
1097
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 16:52 config
1098
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-06 18:57 controllers
1099
-rw-r--r-- 1 lacey lacey    0 2009-06-05 08:46 __init__.py
1100
-rw-r--r-- 1 lacey lacey  140 2009-06-05 15:07 __init__.pyc
1101
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 15:26 lib
1102
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-06 19:35 model
1103
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 16:40 public
1104
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 templates
1105
drwxr-xr-x 3 lacey lacey 4.0K 2009-06-05 08:46 tests
1106
-rw-r--r-- 1 lacey lacey  296 2009-06-05 08:46 websetup.py
1107
lacey@blinky:~/project/helloworld/helloworld$ 
1108
}}}
1109
1110
Recall that earlier, when discussing the structure of the projects, that the "model" directory was where the files related to your ORM are placed.
1111
1112
We are going to create our model for our Hello World project. 
1113
1114
Execute the following command:
1115
1116
{{{
1117
nano model/hello_model.py
1118
}}}
1119
1120
And paste the following code into it:
1121
1122
{{{
1123
from simpycity.core import Function, Raw 
1124
from simpycity.model import SimpleModel, Function, Construct
1125
from simpycity.core import Raw 
1126
1127
class HelloModel(SimpleModel):
1128
1129
   # Getters
1130
   get_salutation = Raw("SELECT salutation FROM public.hello ORDER BY random() LIMIT 1");
1131
1132
   # Setters
1133
   add_salutation = Function("public.add_salutation",['salutation','language']);
1134
   upd_salutation = Function("public.update_salutation",['old_salutation','new_salutation']);
1135
1136
   # Deleters
1137
   del_salutation = Function("public.remove_salutation",['salutation','language']);                                                                          
1138
}}}
1139
1140
This code sets up Simpycity for use in our application.
1141
1142
At the top, are the required imports from Simpycity.
1143
1144
There are two basic ways that Simpycity interacts with the PostgreSQL database beneath it.
1145
1146
Function is how Simpycity maps Python to the stored procedures in the database.
1147
1148
Looking at this line:
1149
1150
{{{
1151 2 Lacey Powers
add_salutation = Function("public.add_salutation",['salutation','language']);
1152 1 bford -
}}}
1153
1154
The double-quoted portion of this function, "public.add_salutation", is the schema qualified name of the stored procedure we are looking to map to.
1155
1156
The bracketed portion of this function, ['salutation','language'], map the arguments to the function.
1157
1158
For example, when we call this function:
1159
1160 2 Lacey Powers
return_status = HelloModel.add_salutation("sawadee ka","Thai")
1161 1 bford -
1162
Will map to "SELECT * FROM public.add_salutation('sawadee ka','Thai');", which will insert the salutation "namaste" and the language "hindi" into the database, and return true, which sets the value of "return_status" to true.
1163
1164
Raw is the Simpycity way of mapping straight SQL to the database.
1165
1166
get_salutation = Raw("SELECT salutation FROM public.hello ORDER BY random() LIMIT 1");
1167
1168
For example, calling this function: 
1169
1170
return_item = HelloModel.get_salutation()
1171
1172
Raw simply executes the provided query, which in this example, is:
1173
1174
{{{
1175
SELECT salutation FROM public.hello ORDER BY random() LIMIT 1;
1176
}}}
1177
1178
Returning a random way to say hello into the variable "return_item".
1179
1180
Now, we need to modify the Hello Controller.
1181
1182
Execute the command:
1183
1184
{{{
1185
nano controller/hello.py
1186
}}}
1187
1188
The contents of which are:
1189
1190
{{{
1191
import logging
1192
1193
from pylons import request, response, session, tmpl_context as c
1194
from pylons.controllers.util import abort, redirect_to
1195
1196
from helloworld.lib.base import BaseController, render
1197
1198
log = logging.getLogger(__name__)
1199
1200
class HelloController(BaseController):
1201
1202
    def index(self):
1203
        # Return a rendered template
1204
        #return render('/hello.mako')
1205
        # or, return a response
1206
        return 'Hello World'
1207
}}}
1208
1209
We will add:
1210
1211
{{{
1212
from helloworld.model.hello_model import HelloModel
1213
}}}
1214
1215
and:
1216
1217
{{{
1218
salutation = HelloModel.get_salutation(options=dict(fold_output=True))
1219
return salutation
1220
}}}
1221
1222
To the hello controller, and we will comment out the line "return 'Hello World'", so that our controller now looks like this:
1223
1224
{{{
1225
import logging
1226
1227
from pylons import request, response, session, tmpl_context as c
1228
from pylons.controllers.util import abort, redirect_to
1229
1230
from helloworld.lib.base import BaseController, render
1231
1232
from helloworld.model.hello_model import HelloModel
1233
1234
log = logging.getLogger(__name__)
1235
1236
class HelloController(BaseController):
1237
1238
    def index(self):
1239
        # Return a rendered template
1240
        #return render('/hello.mako')
1241
        # or, return a response
1242
        #return 'Hello World'
1243
        salutation = HelloModel.get_salutation(options=dict(fold_output=True))
1244
        return salutation
1245
}}}
1246
1247
Now, combining the database functions we created, the HelloModel that we created, and this code, we will be able to query the database.
1248
1249
To see the code in action, all we have to do is start the server again.
1250
1251
So we execute the command
1252
{{{
1253 2 Lacey Powers
cd ..
1254 1 bford -
}}}
1255
1256
And then execute: 
1257
1258
{{{
1259
paster serve --reload development.ini
1260
}}}
1261
1262
And open our browsers to http://localhost:5000/
1263
1264
This should show you a randomly chosen way to say hello.
1265
1266
Pressing the refresh button will show you another, and another.
1267
1268
Now, let's add a bit to it with a Genshi Template.
1269
1270
Press <ctrl>-<c> to stop your server.
1271
1272
Now, we refer back to the directory layout of the project.
1273
1274
{{{
1275
lacey@blinky:~/project/helloworld/helloworld$ ls -l
1276
total 36K
1277
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 16:52 config
1278
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-06 18:57 controllers
1279
-rw-r--r-- 1 lacey lacey    0 2009-06-05 08:46 __init__.py
1280
-rw-r--r-- 1 lacey lacey  140 2009-06-05 15:07 __init__.pyc
1281
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 15:26 lib
1282
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-06 19:35 model
1283
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 16:40 public
1284
drwxr-xr-x 2 lacey lacey 4.0K 2009-06-05 08:46 templates
1285
drwxr-xr-x 3 lacey lacey 4.0K 2009-06-05 08:46 tests
1286
-rw-r--r-- 1 lacey lacey  296 2009-06-05 08:46 websetup.py
1287
lacey@blinky:~/project/helloworld/helloworld$ 
1288
}}}
1289
1290
The templates directory is where all of our dynamically generated page templates (created by Genshi, as you recall from the very beginning of this tutorial).
1291
1292
So we should execute the following command.
1293
1294
{{{
1295
cd helloworld/templates
1296
}}}
1297
1298
Now, we are going to look at the contents of this directory. 
1299
1300
{{{
1301
lacey@blinky:~/project/helloworld/helloworld/templates$ ls -l
1302
total 0
1303
-rw-r--r-- 1 lacey lacey    0 2009-06-05 08:46 __init__.py
1304
lacey@blinky:~/project/helloworld/helloworld/templates$ 
1305
}}}
1306
1307
This only contains a blank __init__.py file. This is only because the Genshi templating engine within Pylons will ignore any template directories without an __init__.py in them.
1308
1309
Armed with that knowledge, we will create our own directory.
1310
1311
{{{
1312
mkdir hello
1313
}}}
1314
1315
We will move into it:
1316
{{{
1317
cd hello
1318
}}}
1319
1320
and we will make a blank __init__.py file.
1321
1322
{{{
1323
touch __init__.py
1324
}}}
1325
1326
We will also make another file.
1327
1328
{{{
1329
nano hello.html
1330
}}}
1331
1332
And within that we will paste the following contents.
1333
1334
{{{
1335
 <html xmlns="http://www.w3.org/1999/xhtml">
1336
  <head>
1337
    <title>${c.hello}</title>
1338
  </head>
1339
  <body class="index">
1340
    <div id="header">
1341
      <h1>${c.hello}</h1>
1342
    </div>
1343
    <p>That is hello in: ${c.language}</p>
1344
  </body>
1345
</html>
1346
}}}
1347
1348
Save and quit.
1349
1350
This is a very basic xHTML Genshi template. 
1351
1352
Aside from having syntax for dynamically setting variables, Genshi templates can be treated the same as any other xHTML page.
1353
1354
Within this template are dynamically setting the title, a header, a small bit of content.
1355
1356
${c.hello} And ${c.language} are variables within the template, that we will set within our controller.
1357
1358
For more information on the Genshi templates, please consult the documentation, found here:
1359
1360
http://genshi.edgewall.org/wiki/Documentation
1361
1362
But before we render this page, we need to make a few more modifications.
1363
1364
We move out of this directory, into the model directory.
1365
1366
{{{
1367
cd ../../model
1368
}}}
1369
1370
And open the file hello_model.py. We will add the following content. 
1371
1372
{{{
1373
get_language = Raw("SELECT language FROM public.hello WHERE salutation = %s", ['salutation']);
1374
}}}
1375
1376
This is another variation of the Raw function, in which we are able to substitute variables into the query.
1377
1378
Save and quit.
1379
1380
Lastly, we move to the controller directory, and modify our controller.
1381
1382
{{{
1383
cd ../controller
1384
}}}
1385
1386
{{{
1387
nano hello.py
1388
}}}
1389
1390
We will add the following import:
1391
1392
{{{
1393
from pylons.templating import render_genshi as render
1394
}}}
1395
1396
Which now brings the render_genshi function into our controller, and aliases it to render.
1397
1398
We will also comment out "return salutation"
1399
1400
{{{
1401
#return salutation
1402
}}}
1403
1404
We will add the following lines:
1405
1406
{{{
1407
c.language = HelloModel.get_language(salutation, options=dict(fold_output=True))
1408
c.hello = salutation
1409
return render("hello/hello.html");
1410
}}}
1411
1412
This should leave the file looking like this:
1413
1414
{{{
1415
import logging
1416
1417
from pylons import request, response, session, tmpl_context as c
1418
from pylons.controllers.util import abort, redirect_to
1419
1420
from helloworld.lib.base import BaseController, render
1421
1422
from helloworld.model.hello_model import HelloModel
1423
from pylons.templating import render_genshi as render
1424
1425
log = logging.getLogger(__name__)
1426
1427
class HelloController(BaseController):
1428
1429
    def index(self):
1430
        # Return a rendered template
1431
        #return render('/hello.mako')
1432
        # or, return a response
1433
        #return 'Hello World'
1434
        salutation = HelloModel.get_salutation(options=dict(fold_output=True))
1435
        c.language = HelloModel.get_language(salutation, options=dict(fold_output=True))
1436
        c.hello = salutation
1437
        return render("hello/hello.html");
1438
        #return salutation
1439
}}}
1440
1441
The c. prefix to the variables are special to Genshi, and let it know what variables it should be rendering.
1442
1443
We are still getting a salutation from the HelloModel.get_salutation() function.
1444
Now, we are taking that result, and using it to get the language for the saluation, using the HelloModel.get_language() function.
1445
1446
We are then setting the c.hello variable to the same value as the salutation variable.
1447
And the c.language variable is set to the return value of the HelloModel.get_language() function.
1448
1449
return render("hello/hello.html") tells Genshi where the file we want to render is in relation to the template directory.
1450
1451
If hello.html was simply in the template directory, the return statement would be:
1452
1453
return render("hello.html")
1454
1455
But since it is in the hello directory, we have to represent the rest of the path to the xHTML template.
1456
1457
And for the HelloModel Simpycity functions, we add new parameter. options=dict(fold_output=True)
1458
1459
Since we know these are going to return a single row only, we are collapsing the output of the query, causing it to return a value into the variable that is ready to be used by Python.
1460
1461
Now, to test.
1462
1463
Again, we back out of this directory to the outermost helloworld directory, in order to start the server.
1464
1465
{{{
1466
cd ../..
1467
}}}
1468
1469
{{{
1470
paster serve --reload development.ini
1471
}}}
1472
1473
And again, we browse to the page http:://localhost:5000 in our browser.
1474
1475
Which should now look something like this: 
1476
1477
{{{
1478
bonjour
1479
1480
That is hello in: French
1481
}}}
1482
1483
Now, displaying dynamic content is only one part of dealing with the database.
1484
1485
We need to be able to manipulate the content. 
1486
1487
Deleting and updating content within the database are both crucial parts. 
1488
1489
So, we're going to add the remaining parts to make this a well-rounded example.
1490
1491
So we stop the server (<ctrl>-<c>, again), and we move to the helloworld/public directory.
1492
{{{
1493
cd helloworld/public
1494
}}}
1495
1496
And we copy/paste the following files:
1497
1498
{{{
1499
nano add_salutation.html
1500
}}}
1501
1502
And we paste the following code.
1503
1504
{{{
1505
<html>
1506
   <body>
1507
    <form action="/hello/add_salutation" method="POST">
1508
       <p> 
1509
          Add Salutation:<br/>
1510
          Salutation: <input type="text" name="salutation"><br/>
1511
          Language: <input type="text" name="language"><br/>
1512
          <input type="submit" value="submit">
1513
       </p>
1514
    </form>
1515
   </body>
1516
</html>
1517
}}}
1518
1519
Save and close. 
1520
1521
{{{
1522
nano modify_salutation.html
1523
}}}
1524
1525
And we paste the following code.
1526
1527
{{{
1528
<html>
1529
   <body>
1530
    <form action="/hello/modify_salutation" method="POST">
1531
       <p> 
1532
          Modify Salutation:<br/>
1533
          Old Salutation: <input type="text" name="old_salutation"><br/>
1534
          New Salutation: <input type="text" name="new_salutation"><br/>
1535
          <input type="submit" value="submit">
1536
       </p>
1537
    </form>
1538
   </body>
1539
</html>
1540
}}}
1541
1542
Save and close.
1543
1544
{{{
1545
nano remove_salutation.html
1546
}}}
1547
1548
And we paste the following code.
1549
1550
{{{
1551
<html>
1552
   <body>
1553
    <form action="/hello/remove_salutation" method="POST">
1554
       <p> 
1555
          Remove Salutation:<br/>
1556
          Salutation: <input type="text" name="salutation"><br/>
1557
          Language: <input type="text" name="language"><br/>
1558
          <input type="submit" value="submit">
1559
       </p>
1560
    </form>
1561
   </body>
1562
</html>
1563
}}}
1564
1565
Save and close.
1566
1567
Each of these files are simple, static pages, that submit forms to different actions in the hello controller. 
1568
Each of the forms has two fields in which to specify the language and salutation. 
1569
In the case of modify_salutation.html, we specify an old and new salutation.
1570
Plus, the ever helpful submit button.
1571
1572
Currently, though, the hello controller only contains the index action. So we will need to modify that.
1573
1574
We navigate to the controllers directory.
1575
1576
{{{
1577
cd ../controllers
1578
}}}
1579
1580
And open hello.py for editing
1581
1582
{{{
1583
nano hello.py
1584
}}}
1585
1586
We will add the following functions.
1587
{{{
1588
    def add_salutation(self):
1589
       if 'salutation' in request.params:
1590
         if 'language' in request.params:
1591
1592
            salutation = request.params['salutation']
1593
            language = request.params['language']
1594
1595
            rs = HelloModel.add_salutation(salutation, language)
1596
            return_status = rs.next()[0]
1597
            rs.commit()
1598
1599
            if return_status == True:
1600
               c.language = language
1601
               c.hello = salutation
1602
               response.status = '200 OK'
1603
               return render("hello/added_salutation.html")
1604
            else:
1605
               c.language = language
1606
               c.hello = salutation
1607
               return render("hello/error.html")
1608
1609
         
1610
    def remove_salutation(self):
1611
      if 'salutation' in request.params:
1612
         if 'language' in request.params:
1613
            salutation = request.params['salutation']
1614
            language = request.params['language']
1615
1616
            rs = HelloModel.del_salutation(salutation, language)
1617
            return_status = rs.next()[0]
1618
            rs.commit()
1619
1620
            if return_status == True:
1621
               c.hello = salutation
1622
               c.language = language
1623
               response.status = '200 OK'
1624
               return render("hello/removed_salutation.html")
1625
            else:
1626
               c.hello = salutation
1627
               c.language = language
1628
               return render("hello/error.html")
1629
1630
1631
    def modify_salutation(self):
1632
      if 'old_salutation' in request.params:
1633
         if 'old_salutation' in request.params:
1634
            old_salutation = request.params['old_salutation']
1635
            new_salutation = request.params['new_salutation']
1636
            rs = HelloModel.upd_salutation(old_salutation, new_salutation)
1637
            return_status = rs.next()[0]
1638
            rs.commit()
1639
1640
            if return_status == True:
1641
               c.old = old_salutation
1642
               c.new = new_salutation
1643
               response.status = '200 OK'
1644
               return render("hello/modified_salutation.html")
1645
            else:
1646
               c.old = old_salutation
1647
               c.new = new_salutation
1648
               return render("hello/error.html")
1649
}}}
1650
1651
Each of these functions does basically the same thing, with small variations. 
1652
They read the request parameters, and check for essential parameters in the request dict.
1653
Then they pull the parameters out, use them to insert, update, or delete fields in the database, and check for success.
1654
If the change is successful, there is a page showing what the change was, and if it was not successful, it gives you a very basic error message.
1655
Both pages redirect you back to the main page that shows the various salutations and languages.
1656
1657
Now, in each of these functions, there are references to rendered pages that are returned. We will go construct those now.
1658
1659
{{{
1660
cd ../templates/hello
1661
}}}
1662
1663
And we will create several files here as well:
1664
1665
{{{
1666
nano added_salutation.html
1667
}}}
1668
1669
And we will paste the following code.
1670
1671
{{{
1672
<html xmlns="http://www.w3.org/1999/xhtml">
1673
  <head>
1674
    <title>SUCCESS</title>
1675
  </head>
1676
  <body class="index">
1677
    <div id="header">
1678
      <h1>SUCCESS: Added:</h1>
1679
    </div>
1680
    <p>${c.hello}</p>
1681
    <p>${c.language}</p>
1682
    <p/>
1683
    <a href="/hello">Return home</a>
1684
  </body>
1685
</html>
1686
}}}
1687
1688
Save and quit.
1689
1690
{{{
1691
nano modified_salutation.html
1692
}}}
1693
1694
And we will paste the following code.
1695
1696
{{{
1697
<html xmlns="http://www.w3.org/1999/xhtml">
1698
  <head>
1699
    <title>SUCCESS</title>
1700
  </head>
1701
  <body class="index">
1702
    <div id="header">
1703
      <h1>SUCCESS: Modified:</h1>
1704
    </div>
1705
    <p>${c.old}</p>
1706
    <p>${c.new}</p>
1707
    <p/>
1708
    <a href="/hello">Return home</a>
1709
  </body>
1710
</html>
1711
}}}
1712
1713
Save and quit.
1714
1715
{{{
1716
nano removed_salutation.html
1717
}}}
1718
1719
And we will paste the following code.
1720
1721
{{{
1722
<html xmlns="http://www.w3.org/1999/xhtml">
1723
  <head>
1724
    <title>SUCCESS</title>
1725
  </head>
1726
  <body class="index">
1727
    <div id="header">
1728
      <h1>SUCCESS: Removed:</h1>
1729
    </div>
1730
    <p>${c.hello}</p>
1731
    <p>${c.language}</p>
1732
    <p/>
1733
    <a href="/hello">Return home</a>
1734
  </body>
1735
</html>
1736
}}}
1737
1738
Save and quit.
1739
1740
We will also add a few links to hello.html
1741
1742
{{{
1743
nano hello.html
1744
}}}
1745
1746
{{{ 
1747
<html xmlns="http://www.w3.org/1999/xhtml">
1748
  <head>
1749
    <title>${c.hello}</title>
1750
  </head>
1751
  <body class="index">
1752
    <div id="header">
1753
      <h1>${c.hello}</h1>
1754
    </div>
1755
    <p>That is hello in: ${c.language}</p>
1756
    <p/>
1757
    <a href="add_salutation.html">Add a salutation</a><br/>
1758
    <a href="remove_salutation.html">Remove a salutation</a><br/>
1759
    <a href="modify_salutation.html">Modify a salutation</a>
1760
  </body>
1761
</html>
1762
}}}
1763
1764
Save and quit.
1765
1766
We will also add the following code to a new file called error.html
1767
1768
{{{
1769
nano error.html
1770
}}}
1771
1772
And we will paste the following code.
1773
1774
{{{
1775
<html xmlns="http://www.w3.org/1999/xhtml">
1776
  <head>
1777
    <title>ERROR</title>
1778
  </head>
1779
  <body class="index">
1780
    <div id="header">
1781
      <h1>ERROR: Failed to add/modify/delete</h1>
1782
    </div>
1783
    <p>${c.hello}</p>
1784
    <p>${c.language}</p>
1785
    <p/>
1786
    <a href="/hello">Return home</a>
1787
  </body>
1788
</html>
1789
}}}
1790
1791
Save and quit.
1792
1793
Finally, we add a couple of lines to hello.html
1794
1795
{{{
1796
nano hello.html
1797
}}}
1798
1799
{{{
1800
 <html xmlns="http://www.w3.org/1999/xhtml">
1801
  <head>
1802
    <title>${c.hello}</title>
1803
  </head>
1804
  <body class="index">
1805
    <div id="header">
1806
      <h1>${c.hello}</h1>
1807
    </div>
1808
    <p>That is hello in: ${c.language}</p>
1809
    <p/>
1810
    <a href="add_salutation.html">Add a salutation</a><br/>
1811
    <a href="remove_salutation.html">Remove a salutation</a><br/>
1812
    <a href="modify_salutation.html">Modify a salutation</a>
1813
  </body>
1814
</html>
1815
}}}
1816
1817
This allows us to access the static pages that are in helloworld/public.
1818
1819
Now, if you recall from eariler, Pylons needs to know how to point these pages at the appropriate controller and action.
1820
1821
And it does that through the routes defined in routing.py.
1822
1823
So we now go modify that.
1824
1825
{{{
1826
cd ../../config
1827
}}}
1828
1829
{{{
1830
nano routing.py
1831
}}}
1832
1833
And we add the following lines
1834
1835
{{{
1836
    map.connect('add_salutation', '/hello/add_salutation', controller='hello', action='add_salutation', conditions=dict(method=['POST']))
1837
    map.connect('remove_salutation', '/hello/remove_salutation', controller='hello', action='remove_salutation', conditions=dict(method=['POST']))
1838
    map.connect('modify_salutation', '/hello/modify_salutation', controller='hello', action='modify_salutation', conditions=dict(method=['POST']))
1839
}}}
1840
1841
These routes will, point the pages at the appropriate controllers and actions, as advertised. The additional conditional dict specifies what sort of HTTP method (GET,POST,DELETE, ect) are supported for this URI. Here we are specifiying POST because all of the form methods associated with these URIs are POST.
1842
1843
And this should be the last set of modifications that we need to do to the application.
1844
1845
Now, we test our new code.
1846
1847
{{{
1848
cd ../..
1849
}}}
1850
1851
Which should put us into the outermost helloworld directory, with development.ini, so that we can start the server.
1852
1853
{{{
1854
paster serve --reload development.ini
1855
}}}
1856
1857
Now, browsing to 
1858
1859
{{{
1860
http://localhost:5000/hello
1861
}}}
1862
1863
Should show you a page like this:
1864
1865
{{{
1866
1867
hello
1868
1869
That is hello in: English
1870
1871
Add a salutation
1872
Remove a salutation
1873
Modify a salutation
1874
}}}
1875
1876
With the bottom text being HTML links.
1877
1878
Refreshing should take you through various incarnations of "hello" in different languages. 
1879
1880
Now, we will add an additional greeting.
1881
1882
Click the link for Add a salutation.
1883
1884
This should take you to another very simple page with a form and a submit button.
1885
1886
Here, we are going to add hello for the Thai language.
1887
1888
Saluation: sawadee ka
1889
1890
Language: Thai
1891
1892
So we copy those words into the appropriate form boxes, and click "Submit".
1893
1894
We should be taken to a page that looks like this.
1895
1896
{{{
1897
1898
SUCCESS: Added:
1899
1900
sawadee ka
1901
1902
Thai
1903
1904
Return home
1905
}}}
1906
1907
And if we were to look in the database at this time, we would see the entry has been successfully entered into the database.
1908
1909
{{{
1910
lacey@blinky:~/project/sql$ psql -U helloworld
1911
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
1912
1913
Type:  \copyright for distribution terms
1914
       \h for help with SQL commands
1915
       \? for help with psql commands
1916
       \g or terminate with semicolon to execute query
1917
       \q to quit
1918
1919
helloworld=> SELECT * FROM hello;
1920
  salutation   | language 
1921
---------------+----------
1922
 hello         | English
1923
 hola          | Spanish
1924
 bonjour       | French
1925
 merhaba selam | Turkish
1926
 zdravstvuyte  | Russian
1927
 buon giorno   | Italian
1928
 sawadee ka    | Thai
1929
(7 rows)
1930
1931
helloworld=> 
1932
}}}
1933
1934
The chain of events is as follows. 
1935
{{{
1936
  1. We type the words into the form.
1937
  2. We click submit.
1938
  3. The words are placed into the http request:
1939
1940
  webob._parsed_post_vars: (MultiDict([('salutation', 'sawadee ka'), ('language', 'Thai')]), <FakeCGIBody at 0x354b5d0 viewing MultiDict([('ol...R')])>)
1941
1942
  4. We are routed to the add_salutation function in the hello controller via routing.py
1943
  5. The add_salutation function parses the salutation and language out of the http request.
1944
  6. It then hands it off to Simpycity, which translates it into:
1945
1946
  SELECT * FROM public.add_salutation(E'sawadee ka',E'Thai')
1947
1948
  7. Which then inserts it into the database, returning true.
1949
  8. We come back to the add_salutation function, which checks the return, which is True.
1950
  9. We are routed to the success page. Here, we can click to go back to the /hello page.
1951
}}}
1952
  
1953
We can now click to see our newly added salutation and language. (which may take a few tries, since it is randomly selected.)
1954
1955
{{{
1956
1957
sawadee ka
1958
1959
That is hello in: Thai
1960
1961
Add a salutation
1962
Remove a salutation
1963
Modify a salutation
1964
}}}
1965
1966
We can modify a salutation as well.
1967
1968
Click the modify salutation link, which will bring us to the modify salutation page.
1969
1970
We will add capitalization to the Thai greeting that we just added.
1971
1972
Type the following into the form pages:
1973
1974
Old Salutation: sawadee ka
1975
1976
New Salutation: Sawadee Ka
1977
1978
And click submit.
1979
1980
Again, you will be greeted by a page that looks like this.
1981
1982
{{{
1983
1984
SUCCESS: Modified:
1985
1986
sawadee ka
1987
1988
Sawadee Ka
1989
1990
Return home
1991
}}}
1992
1993
And if we check the database here as well...
1994
1995
{{{
1996
lacey@blinky:~/project/helloworld/helloworld/model$ psql -U helloworld
1997
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
1998
1999
Type:  \copyright for distribution terms
2000
       \h for help with SQL commands
2001
       \? for help with psql commands
2002
       \g or terminate with semicolon to execute query
2003
       \q to quit
2004
2005
helloworld=> SELECT * FROM hello;
2006
  salutation   | language 
2007
---------------+----------
2008
 hello         | English
2009
 hola          | Spanish
2010
 bonjour       | French
2011
 merhaba selam | Turkish
2012
 zdravstvuyte  | Russian
2013
 buon giorno   | Italian
2014
 Sawadee Ka    | Thai
2015
(7 rows)
2016
2017
helloworld=> 
2018
}}}
2019
2020
We note that the salutation is now capitalized.
2021
2022
The lifecycle of this event is almost exactly the same as the one noted above, except in two places. 
2023
2024
The routing points at the modify_salutation function, and the upd_salutation function is translated into 
2025
{{{
2026
SELECT * FROM public.update_salutation(E'sawadee ka',E'Sawadee Ka')
2027
}}}
2028
2029
Finally, if we decide that we no longer want the salutation Sawadee Ka in the database, we can delete with a very similar lifecycle.
2030
2031
Click on the Remove a Salutation Link.
2032
2033
Salutation: Sawadee Ka (note that it has to be capitalized now)
2034
Language: Thai
2035
2036
{{{
2037
2038
SUCCESS: Removed:
2039
2040
Sawadee Ka
2041
2042
Thai
2043
}}}
2044
2045
Checking the database, we note that it is indeed removed.
2046
2047
{{{
2048
lacey@blinky:~/project/helloworld/helloworld/model$ psql -U helloworld
2049
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
2050
2051
Type:  \copyright for distribution terms
2052
       \h for help with SQL commands
2053
       \? for help with psql commands
2054
       \g or terminate with semicolon to execute query
2055
       \q to quit
2056
2057
helloworld=> SELECT * FROM hello;
2058
  salutation   | language 
2059
---------------+----------
2060
 hello         | English
2061
 hola          | Spanish
2062
 bonjour       | French
2063
 merhaba selam | Turkish
2064
 zdravstvuyte  | Russian
2065
 buon giorno   | Italian
2066
(6 rows)
2067
2068
helloworld=> 
2069
}}}
2070
2071
And as noted above, the lifecycle is the same.
2072
2073
The routing points at the remove_salutation function, and the del_salutation function is translated into 
2074
2075
{{{
2076
SELECT * FROM public.remove_salutation(E'Sawadee Ka',E'Thai')
2077
}}}
2078
2079
Thus, removing it from the database.
2080
2081
This now completes our small web application.
2082
2083
Combining Simpycity, Pylons and PostgreSQL, we have made an app that displays database content, and creates, updates, and deletes that content, which is the core functionality of any database driven web application. 
2084
2085
And this also concludes our PostgreSQL->Simpycity->Pylons tutorial. 
2086
2087
Thank you for following along. =)