This is the Pyramid app for the Python Web Shootout.
Try it out here: http://demo.todo.sixfeetup.com
You can follow the instructions in the Pyramid docs on installation.
Once you have Python and virtualenv installed, you can do the following:
$ mkdir ~/.virtualenvs
$ cd ~/.virtualenvs
$ virtualenv -p python2.7 todopyramid
$ cd todopyramid
$ source bin/activate
This creates the new virtual environment, now you can install the app.
(todopyramid)$ cd ~/Desktop
(todopyramid)$ git clone https://github.com/indypy/todopyramid.git
(todopyramid)$ cd todopyramid
(todopyramid)$ pip install -r requirements.txt -e .
This gives us the end result of the finished app. If it is the first time you are running the app, you will need to initialize the database.
(todopyramid)$ initialize_todopyramid_db development.ini
It can now be started up by doing the following.
(todopyramid)$ pserve development.ini
Now go to http://localhost:6543 and enjoy!
The above install directions tell you how to get the finished application started. Here we will document how the app was created from scratch.
Started by creating a virtualenv and installing Pyramid into it.
(todopyramid)$ pip install pyramid
This gives us a starting point and the pcreate
command to create a new app. In this case, we used the alchemy
scaffold.
(todopyramid)$ cd ~/Desktop
(todopyramid)$ pcreate -s alchemy todopyramid
Since we are responsible developers, the first thing we should do is put this code into version control.
(todopyramid)$ cd todopyramid
(todopyramid)$ git init
(todopyramid)$ git add .
(todopyramid)$ git commit -m 'initial package from pcreate alchemy scaffold'
Before we start up the app for the first time, we need to install the new package that we've created, and all of its dependencies.
(todopyramid)$ python2.7 setup.py develop
Now that we've installed some more packages, we need to freeze the list of packages.
(todopyramid)$ pip freeze > requirements.txt
(todopyramid)$ git add requirements.txt
(todopyramid)$ git commit -m 'committing first version of requirements file'
Now let's initialize the database. In our example, we will be using SQLite.
(todopyramid)$ initialize_todopyramid_db development.ini
We don't want to check in the database, add it to the .gitignore
file.
(todopyramid)$ echo "todopyramid.sqlite" > .gitignore
(todopyramid)$ git add .gitignore
(todopyramid)$ git commit -m 'ignore the SQLite database'
Now we can start up the app and see what it looks like.
(todopyramid)$ pserve development.ini
This will include all the boilerplate code from the alchemy template.
For our purposes, we won't need some of the boilerplate code that has been added. We'll just get rid of it.
(todopyramid)$ git rm static/*
(todopyramid)$ touch static/.gitignore
(todopyramid)$ git add static/.gitignore
(todopyramid)$ git rm templates/*
(todopyramid)$ git commit -m 'removing boilerplate templates'
We've put a .gitignore
in the static
dir so that the directory stays in place. We'll add templates to the templates
dir soon, so it isn't necessary for that dir.
Now we are ready to start adding our customizations. First thing we want to do is add in Bootstrap to ease creation of layouts. Since we will be using Deform to create forms later on, we will use the deform_bootstrap package.
Add it to the setup.py
requires = [
# ...
'deform_bootstrap',
]
Then we need to pull in its dependencies (which includes Deform itself). Then update the requirements.txt
file.
(todopyramid)$ python2.7 setup.py develop
(todopyramid)$ pip freeze > requirements.txt
Then add the static resources to the __init__.py
# Adding the static resources from Deform
config.add_static_view('deform_static', 'deform:static', cache_max_age=3600)
config.add_static_view('deform_bootstrap_static', 'deform_bootstrap:static', cache_max_age=3600)
Now we need to get our template structure in place. We'll add a todopyramid/layouts.py
with the following (see the Creating a Custom UX for Pyramid tutorial for more details):
ifrom pyramid.renderers import get_renderer
from pyramid.decorator import reify
class Layouts(object):
@reify
def global_template(self):
renderer = get_renderer("templates/global_layout.pt")
return renderer.implementation().macros['layout']
Add the global_layout.pt
with at least the following (look at the source code for the complete template):
<!DOCTYPE html>
<!-- The layout macro below is what is referenced in the layouts.Laytouts.global_template -->
<html lang="en" metal:define-macro="layout">
<head>
<!-- Styles from Deform Bootstrap -->
<link rel="stylesheet" href="${request.static_url('deform_bootstrap:static/deform_bootstrap.css')}" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="${request.static_url('deform_bootstrap:static/chosen_bootstrap.css')}" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="${request.static_url('deform:static/css/ui-lightness/jquery-ui-1.8.11.custom.css')}" type="text/css" media="screen" charset="utf-8" />
</head>
<body>
<div class="container">
<!-- This is where our subsequent templates will fill in content -->
<div metal:define-slot="content">
Site content goes here
</div>
</div>
<!-- The javascript resources from Deform -->
<script src="${request.static_url('deform:static/scripts/jquery-1.7.2.min.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/jquery-ui-1.8.18.custom.min.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/jquery-ui-timepicker-addon-0.9.9.js')}"></script>
<script src="${request.static_url('deform:static/scripts/deform.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/deform_bootstrap.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/bootstrap.min.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/bootstrap-datepicker.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/bootstrap-typeahead.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/jquery.form-2.96.js')}"></script>
<script src="${request.static_url('deform_bootstrap:static/jquery.maskedinput-1.3.js')}"></script>
</body>
</html>
Now we have to modify our boilerplate view to use the layout in todopyramid/views.py
, notice that we've also changed the view name and template name to reflect what this view does, showing the home
page.
from .layouts import Layouts
class ToDoViews(Layouts):
def __init__(self, context, request):
self.context = context
self.request = request
@view_config(route_name='home', renderer='templates/home.pt')
def home_view(request):
# view code here
return {}
Now we can add a todopyramid/templates/home.pt
to our app with the following
<metal: master use-macro="view.global_template">
<div metal:fill-slot="content">
<h1>Home</h1>
<p>Welcome to the Pyramid version of the ToDo app.</p>
</div>
</metal:master>
Now subsequent templates can be set up in the same manner.
Our app will need to authorize users in order to be able to add a ToDo list. Pyramid, having no opinions on the matter, leaves us with a myriad of options. One quick way is to utilize the Mozilla Persona login system. There just so happens to be a plugin for this called pyramid_persona
Following the documentation for the personas plugin, we add it to the dependencies of our app, build the latest version and include the plugin in our config.
We also override the default forbidden view in order to integrate a login form into our global template layout.
In order to keep up appearances, we add a custom Not Found view that integrates into our global layout. This is quite simple using the pyramid.view.notfound_view_config
Now that we have created the shell for our app, it is time to create some models. We will be utilizing SQLAlchemy in this case since it fits the needs of our application.
We will create a TodoItem
and Tag
model to start out with. This will give us the basis for our todo list.