Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Large refactoring of view code backed by adding tests for sqlalchemy model classes #4

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
36effd9
start to pin versions for deform, requests and pyramid in setup.py,
Feb 4, 2014
7026b52
refactor Items<->Tags many-to-many relationship in order to enable te…
Feb 4, 2014
51d2383
continue refactoring, move SQLAlchemy related code from views into mo…
Feb 5, 2014
59b668b
extend README with notes about using IPython to explore SQLAlchemy mo…
Feb 5, 2014
ed5adf3
refactor code that updates user account preferences into user model,
Feb 10, 2014
88c649c
pin pyramid_deform in requirements.txt to version 0.2
Feb 10, 2014
e63fc21
create several FormViews based on pyramid_deform helper class to break
Feb 11, 2014
3b5f46c
small changes to README
Feb 11, 2014
054aba7
add Travis CI file - closes #23
Apr 3, 2014
e0462cd
small changes for TRAVIS CI to pick up the repo
Apr 3, 2014
d4ab254
fix travis install command
Apr 3, 2014
e85def5
add TRAVIS CI build status to README.md
Apr 3, 2014
8ae4f46
tests try to import unittest2 for python 2.6 compatibility
Apr 3, 2014
ffef465
switch to nose2 as testrunner
Apr 3, 2014
9e0825b
run nose2 as TRAVIS CI testrunner
Apr 3, 2014
e8bc022
try pip option --allow-external and remove deprecated option --use-mi…
Apr 3, 2014
6a7d59b
remove pip option --allow-external
Apr 3, 2014
c817db0
switch back to nose, because nose2 dependency argparse==1.2.1 can not…
Apr 3, 2014
2952afa
make DB session and user available available from request object (closes
Apr 4, 2014
b9c0ca5
closes #7
saschagottfried Apr 4, 2014
ee94eeb
revert to handling transaction outside of content creation function,
saschagottfried Apr 4, 2014
9d4a462
skip testing a view, fix typo in utils.py
saschagottfried Apr 4, 2014
f4faa81
closes #29
saschagottfried Apr 4, 2014
abe3ccc
Reflect recent changes in TodoPyramid at readme page
saschagottfried Apr 4, 2014
baa2c71
move site-menu/navbar logic in from global_layouts.pt to layouts.py, …
saschagottfried Apr 15, 2014
559b4ed
remove unused code, fix small bug regarding what is in includeme()
saschagottfried Apr 15, 2014
10af11f
add link to OpenShift deployment
saschagottfried Jun 12, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
todopyramid.sqlite
*.pyc
*.egg-info
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
language: python
python:
- "2.7"
- "2.6"
# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install:
- "pip install -r requirements.txt -e ."
- "pip install nose"
# command to run tests, e.g. python setup.py test
script: "python setup.py nosetests"
94 changes: 68 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# ToDo Pyramid App

This is the Pyramid app for the Python Web Shootout.
[![Build Status](https://travis-ci.org/saschagottfried/todopyramid.svg?branch=master)](https://travis-ci.org/saschagottfried/todopyramid)

This is a refactored version of the Pyramid app for the Python Web Shootout originally crafted by SixFeetUp.

Try it out here: <http://todopyramid-sgottfried.rhcloud.com/>

Try it out here: <http://demo.todo.sixfeetup.com>

## Install

Expand Down Expand Up @@ -139,18 +142,18 @@ Then we need to pull in its dependencies (which includes Deform itself). Then up
(todopyramid)$ pip freeze > requirements.txt
```

Then add the static resources to the `__init__.py`

Since we include deform_bootstrap_extra, it does all the static resources registration usually done manually in __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)
pyramid.includes =
pyramid_tm
pyramid_persona
deform_bootstrap_extra
```

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][customux] tutorial for more details):

```
ifrom pyramid.renderers import get_renderer
from pyramid.renderers import get_renderer
from pyramid.decorator import reify


Expand All @@ -166,14 +169,18 @@ Add the `global_layout.pt` with at least the following (look at the source code

```
<!DOCTYPE html>
<!-- The layout macro below is what is referenced in the layouts.Laytouts.global_template -->
<!-- The layout macro below is what is referenced in the layouts.Layouts.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" />
<link rel="stylesheet" type="text/css" media="screen" charset="utf-8"
href="${request.static_url('deform_bootstrap:static/deform_bootstrap.css')}" />
<link rel="stylesheet" type="text/css" media="screen" charset="utf-8"
href="${request.static_url('todopyramid:static/bootglyph/css/icon.css')}" />

<!-- jQuery -->
<script src="${request.static_url('deform:static/scripts/jquery-1.7.2.min.js')}"></script>
</head>

<body>
Expand All @@ -184,17 +191,10 @@ Add the `global_layout.pt` with at least the following (look at the source code
</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>
<!-- Persona, loading at the bottom because it takes forever -->
<script src="https://login.persona.org/include.js" type="text/javascript"></script>
<script type="text/javascript">${request.persona_js}</script>

</body>
</html>
```
Expand All @@ -207,9 +207,9 @@ from .layouts import Layouts

class ToDoViews(Layouts):

def __init__(self, context, request):
self.context = context
def __init__(self, request):
self.request = request
self.context = request.context

@view_config(route_name='home', renderer='templates/home.pt')
def home_view(request):
Expand All @@ -220,7 +220,7 @@ class ToDoViews(Layouts):
Now we can add a `todopyramid/templates/home.pt` to our app with the following

```
<metal: master use-macro="view.global_template">
<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>
Expand Down Expand Up @@ -248,6 +248,48 @@ Now that we have created the shell for our app, it is time to create some models

We will create a `TodoItem` and `Tag` model to start out with. This will give us the basis for our todo list.


### Model Relationships

TBD - Add notes about model relationships that support features offered by todopyramid.

### Explore SQLAlchemy model with IPython

```
$ bin/pshell production.ini
Adding asdict2() to Colander.
Python 2.7.2+ (default, Jul 20 2012, 22:12:53)
Type "copyright", "credits" or "license" for more information.

IPython 0.13.1 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.

Environment:
app The WSGI application.
registry Active Pyramid registry.
request Active request object.
root Root of the default resource tree.
root_factory Default root factory used to create `root`.

In [1]: from todopyramid.models import DBSession, TodoUser

In [2]: user = DBSession.query(TodoUser).filter_by(first_name='Arthur').one()

In [3]: user
Out[3]: <todopyramid.models.TodoUser at 0xa73cb8c>

In [4]: user.email
Out[4]: u'[email protected]'
```

### Sorting

TodoPyramids TodoGrid can order a rendered list of TodoItems by task name & due date - ascending and descending.


[install]: http://pyramid.readthedocs.org/en/latest/narr/install.html
[deform]: http://docs.pylonsproject.org/projects/deform/en/latest/
[deform_bootstrap]: http://pypi.python.org/pypi/deform_bootstrap
Expand Down
6 changes: 4 additions & 2 deletions development.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar
pyramid_tm
pyramid_persona
deform_bootstrap_extra

sqlalchemy.url = sqlite:///%(here)s/todopyramid.sqlite

persona.secret = s00per s3cr3t
persona.audiences = http://localhost:6543
persona.audiences = todopyramid.localhost
persona.siteName = ToDo Pyramid

# Option to generate content for new users to try out
Expand All @@ -34,7 +36,7 @@ todopyramid.generate_content = true

[server:main]
use = egg:waitress#main
host = 0.0.0.0
host = 127.0.0.1
port = 6543

###
Expand Down
8 changes: 5 additions & 3 deletions production.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@ pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_tm
pyramid_persona
deform_bootstrap_extra

sqlalchemy.url = sqlite:///%(here)s/todopyramid.sqlite

persona.secret = s00per s3cr3t
persona.audiences = http://demo.todo.sixfeetup.com
persona.audiences = todopyramid.localhost
persona.siteName = ToDo Pyramid

# Option to generate content for new users to try out
todopyramid.generate_content = true
todopyramid.generate_content = false

[server:main]
use = egg:waitress#main
host = 0.0.0.0
host = 127.0.0.1
port = 6543

###
Expand Down
12 changes: 6 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ MarkupSafe==0.15
PasteDeploy==1.5.0
PyBrowserID==0.9.1
Pygments==1.6
SQLAlchemy==0.8.0b2
SQLAlchemy==0.8.0
WebHelpers==1.3
WebOb==1.2.3
bag==0.3.2
bag==0.3.4
colander==1.0a2
deform==0.9.6
deform-bootstrap==0.2.6
deform-bootstrap-extra==0.2
deform-bootstrap==0.2.8
deform-bootstrap-extra==0.2.8
distribute==0.6.35
ipython==0.13.1
peppercorn==0.4
pyramid==1.4
pyramid==1.4.3
pyramid-debugtoolbar==1.0.4
pyramid-deform==0.2a5
pyramid-deform==0.2
pyramid-persona==1.3.1
pyramid-tm==0.7
pytz==2012j
Expand Down
11 changes: 6 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()

requires = [
'pyramid',
'pyramid==1.4.3',
'SQLAlchemy',
'transaction',
'pyramid_tm',
'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
'deform_bootstrap',
'deform_bootstrap_extra',
'deform<=1.999',
'deform_bootstrap==0.2.8',
'deform_bootstrap_extra==0.2.8',
'pyramid_persona',
'WebHelpers',
'pytz',
'requests==1.1.0',
]

setup(
name='todopyramid',
version='1.0',
version='1.1',
description='todopyramid',
long_description=README + '\n\n' + CHANGES,
classifiers=[
Expand Down
53 changes: 35 additions & 18 deletions todopyramid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
Base,
)

from .views import get_user

def get_db_session(request):
"""return thread-local DB session"""
return DBSession

def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
Expand All @@ -17,29 +22,41 @@ def main(global_config, **settings):
settings=settings,
root_factory='todopyramid.models.RootFactory',
)
config.include('pyramid_persona')
config.include('deform_bootstrap_extra')

includeme(config)

# scan modules for config descriptors
config.scan()
return config.make_wsgi_app()


def includeme(config):
"""we use this concept to include routes and configuration setup in test cases

http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/testing.html#creating-integration-tests
"""
config.add_static_view('static', 'static', cache_max_age=3600)
# 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
)
config.add_static_view(
'deform_bootstrap_extra_static', 'deform_bootstrap_extra:static',
cache_max_age=3600
)

# Misc. views
config.add_route('home', '/')
config.add_route('about', '/about')
# Users
config.add_route('account', '/account')
# Viewing todo lists
config.add_route('list', '/list')
config.add_route('todos', '/todos')
config.add_route('tags', '/tags')
config.add_route('tag', '/tags/{tag_name}')
config.scan()
return config.make_wsgi_app()
config.add_route('taglist', '/tags/{tag_name}')
# AJAX
config.add_route('todo', '/todos/{todo_id}')
config.add_route('delete.task', '/delete.task/{todo_id}')
config.add_route('tags.autocomplete', '/tags.autocomplete')

# make DB session a request attribute
# http://blog.safaribooksonline.com/2014/01/07/building-pyramid-applications/
config.add_request_method(get_db_session, 'db', reify=True)

# Making A User Object Available as a Request Attribute
# http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/auth/user_object.html
config.add_request_method(get_user, 'user', reify=True)


8 changes: 6 additions & 2 deletions todopyramid/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,13 @@ def __html__(self):

def tags_td(self, col_num, i, item):
"""Generate the column for the tags.

Apply special tag CSS for currently selected tag matched route '/tags/{tag_name}'
"""
tag_links = []

for tag in item.sorted_tags:
tag_url = '%s/tags/%s' % (self.request.application_url, tag.name)
tag_url = self.request.route_url('taglist', tag_name=tag.name)
tag_class = 'label'
if self.selected_tag and tag.name == self.selected_tag:
tag_class += ' label-warning'
Expand All @@ -112,13 +114,15 @@ def tags_td(self, col_num, i, item):

def due_date_td(self, col_num, i, item):
"""Generate the column for the due date.

Time-Zone Localization is done in the model
"""
if item.due_date is None:
return HTML.td('')
span_class = 'due-date badge'
if item.past_due:
span_class += ' badge-important'
due_date = localize_datetime(item.due_date, self.user_tz)
due_date = item.due_date
span = HTML.tag(
"span",
c=HTML.literal(due_date.strftime('%Y-%m-%d %H:%M:%S')),
Expand Down
Loading