Posted
over 14 years
ago
by
Chatterbox, Reloaded
Over the last year I’ve done more work with jQuery and specifically, widget frameworks atop it. I’ve spent most of my time with jQuery UI and jQuery Tools. I like them both, but I wound up settling on the former and spending quite a bit of time
... [More]
with it.
Thus I’ve watched with some amount of curiosity an anti-jQuery-UI meme emerging in some projects I’ve participated in. It isn’t just that they favor jQuery Tools. Rather, they also seem intent on some broad-brushed jQuery UI bashing to go with it. Most of the time the word “bloat” emerges, and I thought I’d look into it.
“Bloat” is usually something that can be easily measured. For example, ahem, some open source content management systems ship with a significant amount of code, thus a lot of “bloat”. But when looking at jQuery UI (jqUI) vs. jQuery Tools (jqT), I found:
jqUI: 19 Kb
jqT: 2.4 Kb
That’s the size of the JS for each, gzip’d (in the way any normal production webserver would deliver it.) For the comparison, I made a jQuery UI download that matches the same widget framework features used by Plone, to make it even. It isn’t utterly apples-to-apples: jQuery UI also has CSS and images which add marginally to the Kb needed on a site.
But just to put this into perspective, the difference is about 1/2 the size of the Plone logo. I don’t think “bloat” could apply to pageload impact, but even if it did…who cares? Any intelligent site uses far-future expires and versioned URLs to ensure visitors only need one request for the JS library…ever.
So perhaps “heavy” and “bloat” don’t mean measurable things. Perhaps the point is conceptual heaviness. It’s true that the jQuery UI widget framework adds some ceremony in the constructor and method dispatch. But two notes in its defense.
First, the method dispatch pattern is the one recommended by the jQuery core team for plugin extensibility. Well, I’m sure “jQuery core team” probably includes lots of viewpoints, but the page I read on the topic seemed authoritative.
Second, there is a reason the ceremony is there: widget re-use and extensibility. There’s always a price to be paid for this. But when building a big system, the price can be worthwhile. I suspect systems like Plone will be paying that price at some point, just with perhaps different ceremony.
I’ve seen “bloat” connected to “CSS bloat”. I’m also not sure what specifically that means, I haven’t seen any comparisons to jqT selector hierarchies. I certainly can sympathize with this point: the jQuery UI CSS Framework has lots of classes for things such as .ui-widget and .ui-widget-container and .ui-widget-content. But so does Plone’s templating, and for the same reason as the above: large systems need fine-grained extensibility and customizability.
Personally I’m happy jqUI went that way, as they are busy stripping their main widgets down dramatically, composed from a small set of building blocks that get extended. Even better, they documented their selector hierarchy in a fairly-visible contract.
At the end of the day, my best guess is that “bloat” is being used in a non-measurable way as a style choice. Which is absolutely fine. It’s great to say “I prefer jQuery Tools”. It’s great that both exist, as I think they target different audiences. jQuery Tools when you want to quickly get something done without learning a lot of ceremony, jQuery UI when you have a big system that needs a focus on extensibility and customization.
Maybe I have it wrong, and “bloat” is something specific and empirical. Either way, they are both good choices and fairly similar in features, size, and support.
[Less]
|
Posted
over 14 years
ago
by
Chatterbox, Reloaded
First, repoze.bfg had its long-awaited 1.3 release last week. For fans of the lightweight framework, this release was a nice wrapping-up point. i18n/l10n and lots of little tweaks to scratch itches reported by developers. Not only was Chris fast
... [More]
getting those wishes out the door, he did his usual exceptional job on getting the docs up-to-date.
As it turned out, getting 1.3 out the door was important. It’s the last major release of BFG, which is now in maintenance mode, because…
…Pylons and repoze.bfg are merging! First, the projects are merging. Ben Bangert and Chris McDonough as project leads, plus the core developer teams for each project, have combined forces into the Pylons Project. And the first deliverable is already evaluation-ready. BFG 1.3 has been renamed Pyramid and will serve as the lightweight web framework for other activity in the Pylons Project.
Because it is based on BFG, Pyramid gains 600+ pages of docs, 100% test coverage, and many other qualities under the banner of “Small, Fast, Documented, Tested, Stable, Friendly, Extensible.” In addition, Pyramid adds new machinery BFG to fulfill Pylons 1.x style programming.
Both repoze.bfg 1.3 and Pylons 1.x will continue a long period of bug fixing. Applications that don’t want the change, but want maintenance, will thus be pleased.
I had a chance to meet with Ben, Mark Ramm, Chris, and Chris Rossi in Vegas a few weeks ago to talk about all of this. It was an exceptional trip. So often you see pride and egos in open source projects cause forking and splintering. In this case, projects did the opposite: join forces to maximize how many resources are available for producing something of long-term quality.
Moreover, it’s fun working with new people and getting fresh ideas. The problems that were solved 3 years ago in the last big cycle of Python web frameworks aren’t the same as the problems to solve in the next 3 years. I honestly believe the Pylons Project will have some fresh ideas to bring to the table, as well as a commitment to meat-and-potatoes excellence in documentation, testing, support, speed, etc.
Developers from different communities (Pylons, Zope, etc.) have a new choice, one with momentum, very long experience in writing and supporting large applications, and a good group of people involved. I’m looking forward to the next 3 years.
[Less]
|
Posted
over 14 years
ago
by
Mock It!
The Plone CMS includes gettext translations for a great many languages. For some reason, most translations differ in its punctuation in comparison to the original text - typically the translation is missing a punctuation character. This makes
... [More]
translated text look bad in general, but may also have some side-effects such as duplicate status messages being shown because one includes punctuation while the other does not.Armed with a script written for the occasion and a checkout of all Plone translations for both current major versions, I have made an effort to make punctuations uniform such that the punctuation used in the original text is reflected in the translation. Excluded from processing is translation files which contain Chinese punctuation (i.e. "。" and ":").Please visit the changesets for 3.x and 4.x for reference. While I have tried my best to ensure that the processing was correct, it's worthwhile for translators to review - or revert!You can use this script for your gettext-based translations. I use the following command-line: find . -name "*.po" -exec fix-po.py '{}' \;Enjoy! [Less]
|
Posted
over 14 years
ago
by
plope
The web framework previously known as repoze.bfg is now named Pyramid, managed as part of the Pylons Project.
|
Posted
over 14 years
ago
by
ServerZen.Net
Creating the Project Egg
Before getting started we should setup a virtualenv in order to isolate our web
application from our system Python environment.
Note: all examples were developed on Ubuntu 10.10 using the bash shell
1
2
3
4
5
6$ cd ~/dev
$
... [More]
wget http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.5.1.tar.gz
$ tar zxf virtualenv-1.5.1.tar.gz
$ python2.7 virtualenv-1.5.1/virtualenv.py notesenv
$ cd notesenv
$ source ./bin/activate
Next we will being constructing the egg to contain our notes application:
NoteTaker/
src/
notetaker/
templates/
static/
After the directory structure is in place we create the new NoteTaker/setup.py file
used to define our new egg.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# NoteTaker/setup.py
from setuptools import setup
setup(name='NoteTaker',
version='0.1dev',
description='A web-based note taking application',
long_description='',
install_requires=['pyramid',
'pyramid_jinja2',
'sqlalchemy',
'zope.sqlalchemy',
'repoze.tm2'],
url='http://localhost',
packages=['notetaker'],
package_dir={'': 'src'},
test_suite='notetaker',
)
The next step is to create the standard NoteTaker/src/notetaker/__init__.py file to turn our new directory
into a package.
$ touch NoteTaker/src/notetaker/__init__.py
Python Modules
Over the years and introduction of many Python web frameworks there have been some
unofficial conventions put a place that are mostly shared across all frameworks.
Two of the more common conventions are to create the models and views modules.
models.py - stores the base api of all (typically persistable) data structures
views.py - mostly maps data structures from models into templates and other business logic
In the case of the NoteTaker application, we need data structures to represent the
container of notes and a note itself. These would go into the models module.
In this particular case we will only persist our data to a simple sqlite3 db.
Data Models
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61# NoteTaker/src/notetaker/models.py
import collections
import sqlalchemy
from sqlalchemy.ext import declarative
from sqlalchemy import orm
from zope import sqlalchemy as zopesqla
DBSession = orm.scoped_session(orm.sessionmaker(extension=zopesqla.ZopeTransactionExtension()))
DBBase = declarative.declarative_base()
def _owned(obj, name, parent):
'''Simple function to take a given object, change it's __name__ and
__parent__ references and return the mutated object.
'''
obj.__name__ = name
obj.__parent__ = parent
return obj
class Root(collections.OrderedDict):
'''Basic root object that just contains a *notes* item for
handling notes.
'''
__name__ = None
__parent__ = None
def __init__(self, request):
collections.OrderedDict.__init__(self)
self.request = request
self['notes'] = _owned(NoteContainer(), 'notes', self)
class NoteContainer(object):
'''A non-persistend container class that provides an easy interface
for querying the notes'''
def __getitem__(self, k):
return _owned(DBSession().query(Note).filter_by(id=k).one(), str(k), self)
def __len__(self):
return DBSession().query(Note).count()
def __iter__(self):
return (_owned(x, str(x.id), self) for x in DBSession().query(Note))
class Note(DBBase):
'''Simple note model that keeps track of text'''
__tablename__ = 'notetaker_notes'
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
text = sqlalchemy.Column(sqlalchemy.Unicode(255), nullable=True)
def __init__(self, id, text):
self.id = id
self.text = text
def root_factory_maker(db_string):
engine = sqlalchemy.create_engine(db_string)
DBSession.configure(bind=engine)
DBBase.metadata.bind = engine
DBBase.metadata.create_all(engine)
return Root
A few things may seem a little odd here. For starters, we created
(non-persistent) classes Root and NoteContainer. The main
reason for these classes was to facilitate traversal based url
mapping:
http://127.0.0.1:8080/ <-- root (an instance of Root)
http://127.0.0.1:8080/notes/ <-- notes (an instance of NoteContainer)
http://127.0.0.1:8080/notes/23 <-- note (an instance of an actual note with id 23)
This gives our resource handling a RESTful feel making it easier to move to a full
RESTful interface later on.
The second out of place part is the definition of a root_factory_maker
function. This function is used to initialize our sqlalchemy setup and provide
a root object for traversal to begin with.
Views
We really only need two different pages for our application.
One page to display the notes and allow for adding new notes. The
second page for editing existing notes. So counting those two pages
as views, we need action views to handle POST requests. This
gives us a total of four views.
But before we work with the views, we should setup some generic
url-generating functions to use inside our templates. With
Pyramid, we would define these using a BeforeRender event.
This is similar to the concept of context processors used
by both Flask and Django.
Here is our views module.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52# NoteTaker/src/notetaker/views.py
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.url import static_url, model_url
from pyramid.events import subscriber
from pyramid.interfaces import IBeforeRender
from notetaker import models
from pyramid.httpexceptions import HTTPFound
@subscriber(IBeforeRender)
def add_globals(event):
'''Add *context_url* and *static_url* functions to the template
renderer global namespace for easy generation of url's within
templates.
'''
request = event['request']
def context_url(s, context=None, request=request):
if context is None:
context = request.context
url = model_url(context, request)
if not url.endswith('/'):
url = '/'
return url s
event['context_url'] = context_url
event['static_url'] = lambda x: static_url(x, request)
@view_config(renderer='main.html')
def main_view(request):
return {'notes': request.context['notes']}
@view_config(name='add')
def add_note(request):
container = request.context
newid = str(len(container) 1)
session = models.DBSession()
note = models.Note(newid, request.params['text'])
session.add(note)
return HTTPFound(location = model_url(request.root, request))
@view_config(name='show_edit', context=models.Note,
renderer='edit.html')
def show_edit_note(request):
return {}
@view_config(name='edit', context=models.Note)
def edit_note(request):
note = request.context
note.text = request.params['text']
return HTTPFound(location = model_url(request.root, request))
To wrap this altogether, we can provide a main module that will
configure base settings along with give us a simple launchable
entry point.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28# NoteTaker/src/notetaker/main.py
from pyramid.configuration import Configurator
from paste import httpserver
import pyramid_jinja2
from notetaker import models
from repoze import tm
def make_app(global_config={}, **settings):
'''WSGI app factory that sets up a useful default configuration specific
to the NoteTaker app.
'''
settings = dict(settings)
settings.setdefault('jinja2.directories', 'notetaker:templates')
config = Configurator(settings=settings,
root_factory=models.root_factory_maker('sqlite:///notetaker.db'))
config.begin()
config.add_renderer('.html', pyramid_jinja2.renderer_factory)
config.add_static_view(name='s', path='notetaker:static')
config.scan('notetaker')
config.end()
app = config.make_wsgi_app()
app = tm.TM(app) # transaction support
return app
if __name__ == '__main__':
httpserver.serve(make_app(), host='0.0.0.0')
Templates
The only thing missing at this point from the UI of our application is the definition
of actual templates. Pyramid itself encourages the developer to use whatever
template language they're familiar with. For purposes of this example, we will
use Jinja2 since it's based on the Django template language and currently a very
popular (and useful) template language.
The first template we need is the layout template that the rest of our templates
will textend from.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NoteTaker
rel="stylesheet" href="{{ static_url('notetaker:static/style.css') }}" type="text/css" />
NoteTaker :: {% block title %}Main{% endblock %}
{% block main %}{% endblock %}
We will use the main template as the template to list our actual notes
and give us the ability to add new notes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34{% extends "layout.html" %}
{% block main %}
id="notes-section">
Id class="text">Text
{% for note in notes %}
{{ note.id }}
{{ note.text }}
href="{{ context_url('@@show_edit', context=note) }}">edit
{% endfor %}
colspan="3">{{ notes|length }} entries
id="note-add-section">
{% endblock %}
The last template we need is one to edit existing notes. We'll call
that template edit.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{% extends "layout.html" %}
{% block title %}Edit {{ context.id }}{% endblock %}
{% block main %}
{% endblock %}
Static Resources
To top all of the UI off we should add a simple CSS stylesheet. This one will
be called NoteTaker/src/notetaker/static/style.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27body {
font-family: ubuntu, verdana, arial, helvetica, sans-serif;
}
#notes-section table {
border-spacing: 0;
}
#notes-section th {
border-bottom: 2px solid black;
padding-right: 1em;
text-align: left;
}
#notes-section th.text {
width: 20em;
}
#notes-section tfoot td {
text-align: center;
font-style: italic;
color: #999;
}
#note-add-section {
margin-top: 2em;
}
Testing
Of course as all Python developers know, untested code is broken code. So here's
a useful tests module that unit-tests our application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75# NoteTaker/src/notetaker/tests.py
import unittest
from notetaker import models, views, main
from pyramid import testing
class ModelsTest(unittest.TestCase):
def testOwned(self):
class Mock(object): pass
m = Mock()
assert getattr(m, '__name__', None) is None
self.assertEqual(m, models._owned(m, 'foo', None))
assert getattr(m, '__name__', None) == 'foo'
def testRoot(self):
root = models.Root(None)
assert 'notes' in root
def testNote(self):
n = models.Note('foo', 'some text')
self.assertEqual(n.id, 'foo')
self.assertEqual(n.text, 'some text')
def testContainer(self):
c = models.Container()
n = models.Note('abc', '')
assert '__parent__' not in n.__dict__
c['foo'] = n
assert '__parent__' in n.__dict__
self.assertEqual(n.__name__, 'foo')
class ViewsTest(unittest.TestCase):
def testAddGlobals(self):
event = {'request': None}
views.add_globals(event)
assert 'context_url' in event
assert 'static_url' in event
def testContextUrl(self):
event = {'request': testing.DummyRequest()}
views.add_globals(event)
context_url = event['context_url']
self.assertEquals(context_url('foo'), 'http://example.com/foo')
def testMainView(self):
req = testing.DummyRequest()
req.context = {'notes': {}}
assert 'notes' in views.main_view(req)
def testAddNote(self):
req = testing.DummyRequest()
req.context = {}
req.params['text'] = 'testing'
views.add_note(req)
assert '1' in req.context
def testEditNote(self):
req = testing.DummyRequest()
req.context = models.Note('id', 'old text')
self.assertEquals(req.context.text, 'old text')
req.params['text'] = 'new text'
views.edit_note(req)
self.assertEquals(req.context.text, 'new text')
def testShowEditNote(self):
self.assertEquals(len(views.show_edit_note(None).keys()), 0)
class MainTest(unittest.TestCase):
def testMakeApp(self):
app = main.make_app()
assert app.registry is not None
Wrapping Up
We want to setup our egg in our virtualenv so using the python
from the virtualenv bin, we execute the following:
$ python setup.py develop
Now we can run the tests:
$ python setup.py test
And the actual server application itself can be run with:
$ python -m notetaker.main
At which point you should be able to visit your browser
at http://127.0.0.1:8080 to see the application in action
Technology Stack
Pyramid itself (as with Pylons) is actually quite small. It leverages
framework components from other projects that are mature and
easy to use. The stack used in this particular example is as follows:
Jinja2 - template language
SQLAlchemy - data persistence (relational)
repoze.tm2 - transaction management (commits request-scope relational db changes)
Conclusion
Even though Pyramid is in an alpha state and still rough around the edges,
it is able to benefit from repoze.bfg 's long period of maturity. As with
the Pylons philosophy we are able to pick and choose our favourite framework
pieces (Jinja2, SQLAlchemy, etc) to piece together a useful application.
The 0.1.1 release of NoteTaker with this source code can be downloaded at
http://dist.serverzen.com/pypi/d/notetaker/f/NoteTaker-0.1.1.tar.gz
[Less]
|
Posted
over 14 years
ago
by
ServerZen.Net
Creating the Project Egg
Before getting started we should setup a virtualenv in order to isolate our web
application from our system Python environment.
Note: all examples were developed on Ubuntu 10.10 using the bash shell
1
2
3
4
5
6$ cd ~/dev
$
... [More]
wget http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.5.1.tar.gz
$ tar zxf virtualenv-1.5.1.tar.gz
$ python2.7 virtualenv-1.5.1/virtualenv.py notesenv
$ cd notesenv
$ source ./bin/activate
Next we will being constructing the egg to contain our notes application:
NoteTaker/
src/
notetaker/
templates/
static/
After the directory structure is in place we create the new NoteTaker/setup.py file
used to define our new egg.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# NoteTaker/setup.py
from setuptools import setup
setup(name='NoteTaker',
version='0.1dev',
description='A web-based note taking application',
long_description='',
install_requires=['pyramid',
'pyramid_jinja2',
'sqlalchemy',
'zope.sqlalchemy',
'repoze.tm2'],
url='http://localhost',
packages=['notetaker'],
package_dir={'': 'src'},
test_suite='notetaker',
)
The next step is to create the standard NoteTaker/src/notetaker/__init__.py file to turn our new directory
into a package.
$ touch NoteTaker/src/notetaker/__init__.py
Python Modules
Over the years and introduction of many Python web frameworks there have been some
unofficial conventions put a place that are mostly shared across all frameworks.
Two of the more common conventions are to create the models and views modules.
models.py - stores the base api of all (typically persistable) data structures
views.py - mostly maps data structures from models into templates and other business logic
In the case of the NoteTaker application, we need data structures to represent the
container of notes and a note itself. These would go into the models module.
In this particular case we will only persist our data in memory (not on disk) which
means everytime the web app is restarted, the data will go bye-bye.
Data Models
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61# NoteTaker/src/notetaker/models.py
import collections
import sqlalchemy
from sqlalchemy.ext import declarative
from sqlalchemy import orm
from zope import sqlalchemy as zopesqla
DBSession = orm.scoped_session(orm.sessionmaker(extension=zopesqla.ZopeTransactionExtension()))
DBBase = declarative.declarative_base()
def _owned(obj, name, parent):
'''Simple function to take a given object, change it's __name__ and
__parent__ references and return the mutated object.
'''
obj.__name__ = name
obj.__parent__ = parent
return obj
class Root(collections.OrderedDict):
'''Basic root object that just contains a *notes* item for
handling notes.
'''
__name__ = None
__parent__ = None
def __init__(self, request):
collections.OrderedDict.__init__(self)
self.request = request
self['notes'] = _owned(NoteContainer(), 'notes', self)
class NoteContainer(object):
'''A non-persistend container class that provides an easy interface
for querying the notes'''
def __getitem__(self, k):
return _owned(DBSession().query(Note).filter_by(id=k).one(), str(k), self)
def __len__(self):
return DBSession().query(Note).count()
def __iter__(self):
return (_owned(x, str(x.id), self) for x in DBSession().query(Note))
class Note(DBBase):
'''Simple note model that keeps track of text'''
__tablename__ = 'notetaker_notes'
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
text = sqlalchemy.Column(sqlalchemy.Unicode(255), nullable=True)
def __init__(self, id, text):
self.id = id
self.text = text
def root_factory_maker(db_string):
engine = sqlalchemy.create_engine(db_string)
DBSession.configure(bind=engine)
DBBase.metadata.bind = engine
DBBase.metadata.create_all(engine)
return Root
A few things may seem a little odd here. For starters, we created
(non-persistent) classes Root and NoteContainer. The main
reason for these classes was to facilitate traversal based url
mapping:
http://127.0.0.1:8080/ <-- root (an instance of Root)
http://127.0.0.1:8080/notes/ <-- notes (an instance of NoteContainer)
http://127.0.0.1:8080/notes/23 <-- note (an instance of an actual note with id 23)
This gives our resource handling a RESTful feel making it easier to move to a full
RESTful interface later on.
The second out of place part is the definition of a root_factory_maker
function. This function is used to initialize our sqlalchemy setup and provide
a root object for traversal to begin with.
Views
We really only need two different pages for our application.
One page to display the notes and allow for adding new notes. The
second page for editing existing notes. So counting those two pages
as views, we need action views to handle POST requests. This
gives us a total of four views.
But before we work with the views, we should setup some generic
url-generating functions to use inside our templates. With
Pyramid, we would define these using a BeforeRender event.
This is similar to the concept of context processors used
by both Flask and Django.
Here is our views module.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52# NoteTaker/src/notetaker/views.py
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.url import static_url, model_url
from pyramid.events import subscriber
from pyramid.interfaces import IBeforeRender
from notetaker import models
from pyramid.httpexceptions import HTTPFound
@subscriber(IBeforeRender)
def add_globals(event):
'''Add *context_url* and *static_url* functions to the template
renderer global namespace for easy generation of url's within
templates.
'''
request = event['request']
def context_url(s, context=None, request=request):
if context is None:
context = request.context
url = model_url(context, request)
if not url.endswith('/'):
url = '/'
return url s
event['context_url'] = context_url
event['static_url'] = lambda x: static_url(x, request)
@view_config(renderer='main.html')
def main_view(request):
return {'notes': request.context['notes']}
@view_config(name='add')
def add_note(request):
container = request.context
newid = str(len(container) 1)
session = models.DBSession()
note = models.Note(newid, request.params['text'])
session.add(note)
return HTTPFound(location = model_url(request.root, request))
@view_config(name='show_edit', context=models.Note,
renderer='edit.html')
def show_edit_note(request):
return {}
@view_config(name='edit', context=models.Note)
def edit_note(request):
note = request.context
note.text = request.params['text']
return HTTPFound(location = model_url(request.root, request))
To wrap this altogether, we can provide a main module that will
configure base settings along with give us a simple launchable
entry point.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28# NoteTaker/src/notetaker/main.py
from pyramid.configuration import Configurator
from paste import httpserver
import pyramid_jinja2
from notetaker import models
from repoze import tm
def make_app(global_config={}, **settings):
'''WSGI app factory that sets up a useful default configuration specific
to the NoteTaker app.
'''
settings = dict(settings)
settings.setdefault('jinja2.directories', 'notetaker:templates')
config = Configurator(settings=settings,
root_factory=models.root_factory_maker('sqlite:///notetaker.db'))
config.begin()
config.add_renderer('.html', pyramid_jinja2.renderer_factory)
config.add_static_view(name='s', path='notetaker:static')
config.scan('notetaker')
config.end()
app = config.make_wsgi_app()
app = tm.TM(app) # transaction support
return app
if __name__ == '__main__':
httpserver.serve(make_app(), host='0.0.0.0')
Templates
The only thing missing at this point from the UI of our application is the definition
of actual templates. Pyramid itself encourages the developer to use whatever
template language they're familiar with. For purposes of this example, we will
use Jinja2 since it's based on the Django template language and currently a very
popular (and useful) template language.
The first template we need is the layout template that the rest of our templates
will textend from.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NoteTaker
rel="stylesheet" href="{{ static_url('notetaker:static/style.css') }}" type="text/css" />
NoteTaker :: {% block title %}Main{% endblock %}
{% block main %}{% endblock %}
We will use the main template as the template to list our actual notes
and give us the ability to add new notes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34{% extends "layout.html" %}
{% block main %}
id="notes-section">
Id class="text">Text
{% for note in notes %}
{{ note.id }}
{{ note.text }}
href="{{ context_url('@@show_edit', context=note) }}">edit
{% endfor %}
colspan="3">{{ notes|length }} entries
id="note-add-section">
{% endblock %}
The last template we need is one to edit existing notes. We'll call
that template edit.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{% extends "layout.html" %}
{% block title %}Edit {{ context.id }}{% endblock %}
{% block main %}
{% endblock %}
Static Resources
To top all of the UI off we should add a simple CSS stylesheet. This one will
be called NoteTaker/src/notetaker/static/style.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27body {
font-family: ubuntu, verdana, arial, helvetica, sans-serif;
}
#notes-section table {
border-spacing: 0;
}
#notes-section th {
border-bottom: 2px solid black;
padding-right: 1em;
text-align: left;
}
#notes-section th.text {
width: 20em;
}
#notes-section tfoot td {
text-align: center;
font-style: italic;
color: #999;
}
#note-add-section {
margin-top: 2em;
}
Testing
Of course as all Python developers know, untested code is broken code. So here's
a useful tests module that unit-tests our application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75# NoteTaker/src/notetaker/tests.py
import unittest
from notetaker import models, views, main
from pyramid import testing
class ModelsTest(unittest.TestCase):
def testOwned(self):
class Mock(object): pass
m = Mock()
assert getattr(m, '__name__', None) is None
self.assertEqual(m, models._owned(m, 'foo', None))
assert getattr(m, '__name__', None) == 'foo'
def testRoot(self):
root = models.Root(None)
assert 'notes' in root
def testNote(self):
n = models.Note('foo', 'some text')
self.assertEqual(n.id, 'foo')
self.assertEqual(n.text, 'some text')
def testContainer(self):
c = models.Container()
n = models.Note('abc', '')
assert '__parent__' not in n.__dict__
c['foo'] = n
assert '__parent__' in n.__dict__
self.assertEqual(n.__name__, 'foo')
class ViewsTest(unittest.TestCase):
def testAddGlobals(self):
event = {'request': None}
views.add_globals(event)
assert 'context_url' in event
assert 'static_url' in event
def testContextUrl(self):
event = {'request': testing.DummyRequest()}
views.add_globals(event)
context_url = event['context_url']
self.assertEquals(context_url('foo'), 'http://example.com/foo')
def testMainView(self):
req = testing.DummyRequest()
req.context = {'notes': {}}
assert 'notes' in views.main_view(req)
def testAddNote(self):
req = testing.DummyRequest()
req.context = {}
req.params['text'] = 'testing'
views.add_note(req)
assert '1' in req.context
def testEditNote(self):
req = testing.DummyRequest()
req.context = models.Note('id', 'old text')
self.assertEquals(req.context.text, 'old text')
req.params['text'] = 'new text'
views.edit_note(req)
self.assertEquals(req.context.text, 'new text')
def testShowEditNote(self):
self.assertEquals(len(views.show_edit_note(None).keys()), 0)
class MainTest(unittest.TestCase):
def testMakeApp(self):
app = main.make_app()
assert app.registry is not None
Wrapping Up
We want to setup our egg in our virtualenv so using the python
from the virtualenv bin, we execute the following:
$ python setup.py develop
Now we can run the tests:
$ python setup.py test
And the actual server application itself can be run with:
$ python -m notetaker.main
At which point you should be able to visit your browser
at http://127.0.0.1:8080 to see the application in action
Technology Stack
Pyramid itself (as with Pylons) is actually quite small. It leverages
framework components from other projects that are mature and
easy to use. The stack used in this particular example is as follows:
Jinja2 - template language
SQLAlchemy - data persistence (relational)
repoze.tm2 - transaction management (commits request-scope relational db changes)
Conclusion
Even though Pyramid is in an alpha state and still rough around the edges,
it is able to benefit from repoze.bfg 's long period of maturity. As with
the Pylons philosophy we are able to pick and choose our favourite framework
pieces (Jinja2, SQLAlchemy, etc) to piece together a useful application.
The 0.1 release of NoteTaker with this source code can be downloaded at
http://dist.serverzen.com/pypi/d/notetaker/f/NoteTaker-0.1.tar.gz
[Less]
|
Posted
over 14 years
ago
by
gmane.comp.web.zope.announce
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Great work!
Cheers,
Andreas
Hanno Schlichting wrote:
- --
ZOPYX Limited | zopyx group
Charlottenstr. 37/1 | The full-service network for Zope & Plone
D-72070 Tübingen |
... [More]
Produce & Publish
www.zopyx.com | www.produce-and-publish.com
- ------------------------------------------------------------------------
E-Publishing, Python, Zope & Plone development, Consulting
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (Darwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iQGUBAEBAgAGBQJM1DUeAAoJEADcfz7u4AZj8skLwOGbh32LMr8dyQRtRvPpPzhT
7PRaD6mgWSYQyFS4u5qO2J9vVziNw6mbLANFLq2lBSxDGf6Gp5E8A3EpR/w51N+0
Y5S/iS/Kb1qMPxEV1TBf/Ai8dXio0tPZCE6dp5610Tfv3HPP4mFi/Owi3EbcFZ5J
tJMjFuQYYLhpX5mwp0yRY1WgKxIfDoE207pZW53+RjuUNTqthXIH6zRTeZjj2u04
ehnAQeD19mhML/gco5I04BQktU33WmQv8VgtJu2M8nMEvW2wxoCWjWepABVQ/mlZ
1w7JSzT3gsNnR9MCxuNLfXQ4lbKN8Mn3+RsIwOfnxYFoayaCO7AtWd2JvPDrIb6G
zc1tcsIbqmnfMG9ZeEsPwOgO16+rG4AaAlEfEv2LpKo2Qp0nZY3quIm [Less]
|
Posted
over 14 years
ago
by
BlueDynamics Alliance - Zope Related
First of all I was really amazed to see my talk titled "Plone is so semantic, isn't it?" accepted for Plone Conference 2010. I always thought Plone and Semantics is not a topic popular at the conference. Now I know better. My talk at the conference
... [More]
including the questions and answers afterwards was recorded, so watch it yourself. If you cant read the slides in the video, especially the code examples, please look at slidesshare or at the README at github.
Prior to the talk I got in contact with Mr. De Marinis from the European Environmental Agency (EEA). We had a very good conversation and I learned a lot about new semantic package eea.rdfmarshaller for Plone created by/for the EEA. This great work exports the whole structure and metadata of a Plone site or of a single content object to RDF using standards and common namespaces. EEA uses it to make its site with it tons of information available in the linked data cloud.
I also meet other interested people to talk, get ideas and use cases. The major fear about semantics is, people talking about it all the time - a big buzz - but theres no pragmatic, easy to use software out there. Starting with semantics usally increases the complexity of your software and software stack dramatically.
In my opinion FISE will decrease this complexity a lot. It bundles semantic analyzers, a triple store and a sparql-endpoint. Unfortunately quering a triple store with SPARQL brings a new query language and also thinking in triples is more complex than thinking in tables. So what we need here is a supporting tool. With RDFlib and SuRF Python has a good libraries to deal with triples, also making the creation of SPARQL queries pythonic.
FISE itself is a RESTful service. I created fise.client, a simple python client dealing with FISE engines and store based on restkit. It makes the work with FISE pythonic, hiding all the RESTful HTTP struggling. The client ships with tests against a running FISE server. I also provided an integration buildout stitching the Python fise.client and the Java FISE-Server together, making it easy for Python developers to try the stack.
Beside the Talk at Plone Conference I invited for an Open Space session on Plone and Semantics. We had about 8-10 interested people in the room talking and discussing about semantics in general, about possible use-cases and about existing software. I learned its difficult to construct use-cases, because in IT we dont think semantic. After the implications of having facts in triples in a triple store were explained, finding good real-world uses-cases improving the current situation were no longer a problem.
After the conference two days of sprint (aka hackathon) were held. At Saturday I worked together with Alexander Pilz from Munich on the Plone FISE story. While I fixed bugs in fise.client and helped bootstrapping FISE using fise-buildout on other computers, Alexander started a first version of a Plone Integration package with FISE. Finally we got Plone Content (the SearchableText) passed to FISE. Theres really a bunch of work to be done, but it shows how easy FISE and fise.client is to use in a CMS.
There is a lot more work to be done. First we need to use plone.app.async while pushing content to fise. Then we need to fetch the enhancements back and display them in Plone or expose them. Next we need to find a path to get the structural information of Plone offered by eea.rdfmarshaller into the FISE store, so we can include it in the sparql queries/results. And finally this all need to be used in a real world project.
However, next date on my schedule - and on everybodys whos seriosly interested in the IKS stack - is the workshop "Unleashing the Power of Semantic Data - Today" 9th and 10th of december 2010 in Amsterdam. Lets meet there!
Image by Wurz under a CC-License from Flickr [Less]
|
Posted
over 14 years
ago
by
Mock It!
Ever (used Plone and) wanted to stop worrying about whether your site content has proper image scales generated for them? Here's a bobo traverser snippet that'll generate them on-the-fly: image.py.Drop it in place of the ATImage traverser or use in your own content types.
|
Posted
over 14 years
ago
by
Weblog
Rob Gietema: Deco
I will talk about Deco and some more of Plone 4. I work for Four
Digits, who organized this PUN. Originally four people, now seven.
Since a few years we do only Plone. I myself do mostly front-end
development, like integration
... [More]
of the TinyMCE visual editor in Plone.
Plone is a CMS. One of the current problems is creating composite
pages. There are add-ons for that, but it is still crufty. Deco is
meant to improve that a lot. In 2008 several people came with a
proposal for that. Deco as front-end, Blocks as back-end.
Deco is a grid-based system. So you create multiple columns that you
can style with CSS. The columns can be filled with tiles. We have
structure tiles, like rich text. Also field tiles, like a title,
description, list of tags. And application tiles, which can be
anything: image, discussion forum, table of contents, multi-file
upload.
The idea was good, but there were not a lot of people who wanted to
write the javascript needed for that. But I do like to do that. :-)
We sprinted a few times on this. Four Digits hosted the Living
Statues Sprint, there was a sprint at the Plone Conference in Bristol
last week.
We created a demo website using Deco: http://decobrewery.com/. I will
demo that now.
This is mostly for Plone 5, but parts will be available in upcoming
Plone 4.x versions.
The parts involved in Deco are currently split over about 14 small
python packages.
Create your own tile: an interface class with a schema, a class with a
__call__ method that returns some html (can be in an html
template), and some zcml to register this and glue everything together.
To do: create more templates for content types, fix bugs.
Want to code on it? Use
https://svn.plone.org/svn/plone/plone.app.deco/buildouts/dev
It actually works in IE6 as well!
Reinout van Rees: checkoutmanager
checkoutmanager is a small tool to manage all your checkouts on your
whole file system. You have 20 personal projects, 30 client projects,
some dotfiles in your home dir. Just manage them all. I created
checkoutmanager when I switched to a new laptop. Use it to checkout
git, subversion, bazaard, mercurial. Every morning I do a
checkoutmanager up to update all checkouts. I do checkoutmanager
st to see which files in all my checkouts I forgot to commit. Also
checkoutmanager out to see which local commits from git or
mercurial I have not pushed to the central server yet. (To do:
mention this in the docs.)
See http://pypi.python.org/pypi/checkoutmanager
Maurits van Rees: Compiling po files
Just see http://pypi.python.org/pypi/zest.pocompile
Roel Bruggink: Subversion pre-commit hooks for pyflakes, pep8
I installed pre-commit hooks on our subversion to disallow commits
that go against pyflakes and pep8. I wrote some code that made it
possible to add a comment in a file to ignore one or more PEP8 style
checks. Hooked it up to TextMate to prevent you from saving a file
with PEP8 problems, except when you explicitly specify it with that
comment. Handy for those times where you really can't help it. If
you want the code, mail me.
Lars van de Kerkhof: Hudson and Fabric
Hudson is a Java tool for continuous integration. It basically just
runs a shell script for you and reports the results back. Usually
those are test results. We use it for deployment as well, but of
course only when the test requirements are met. We let it use Fabric
for that. It is tricky to get Fabric to use the correct virtualenv,
but we got that working. I also made a way to print a shell script
that looks like what Fabric would do for real.
Rob Gietema: XDV/Diaza/Deliverance for theming
XDV uses XSLT to get html from for example Plone and transform and
push it into an html template that your designer has created. Nice
alternative way to do theming. You can also combine multiple sources,
not just from Plone, but from other systems as well at the same time.
Varundev Kashimath: Why do certain products succeed?
Working at TU Delft. We did some research into how long it takes to
do a project with Rails and with Django. Rails took longer in this
case. Some discussion followed, hard to summarize.
[Less]
|