Project

General

Profile

CompleteTutorial » History » Version 1

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