One of the new additions in TG2.1.4 has been the support for the so called pluggable applications, this is a really powerful and convenient feature that probably not enough TurboGears users started embracing.
For people that never used them, pluggable applications provide a python package that can be installed and “plugged” inside any existing TurboGears application to add new features. Django has been probably the first framework to bring this feature to Python world and TurboGears implementation tries to be as convenient by making pluggable applications identical to plain TurboGears applications and providing a “quickstart-pluggable” command that creates the application skeleton for you. Pluggable applications can be installed using easy_install or pip and they can off course depend on any other pluggable application they need.
This year, at EuroPython 2012, I have been pleased to present a talk about using TurboGears for rapid prototyping (both in Italian and English, you should be able to find the videos on EuroPython youtube channel), so I decided to dedicate a part of it to pluggable applications as they are actually the fastest way to rapidly prototype a project. With my surprise most the questions I received were about the EasyCrudRestController and not about pluggable applications.
While the EasyCrudRestController is definitively a powerful tool, it’s far from being the answer to all the web developments needs. In most of the applications you are going to develop, users will probably prefer consulting content from something more engaging than an administration table of database entries.
This month, to create a set of utilities that can help people with their everyday needs, I decided to ask guys that work with me to make every part of the web sites that they were writing as pluggable applications. The result of this experiment has been that most of the pluggable apps that I did in my spare time (tgapp-smallpress, tgapp-photos, tgapp-tgcomments, tgext.tagging and so on) ended being used in real world projects and started to improve exposing hooks and ways to customize their behavior for the project they were going to be used.
After a few weeks, new pluggables like tgapp-fbauth, tgapp-userprofile, tgapp-calendarevents, tgapp-fbcontest, tgapp-youtubevideo has seen light and developing the target application started becoming blazing fast: Just plug what you need and customize it.
Embracing this philosophy the last project I’m working on has an app_cfg.py file that looks like:
plug(base_config, 'tgext.debugbar', inventing=True) plug(base_config, 'tgext.scss') plug(base_config, 'tgext.browserlimit') plug(base_config, 'registration') plug(base_config, 'photos') plug(base_config, 'smallpress', 'press', form='XXX.lib.forms.ArticleForm') plug(base_config, 'tgcomments', allow_anonymous=False) from XXX.lib.matches import MyKindOfEvent plug(base_config, 'calendarevents', 'eventi', event_types=[MyKindOfEvent()]) replace_template(base_config, 'smallpress.templates.article', 'XXX.templates.press.article') replace_template(base_config, 'smallpress.templates.excerpt', 'XXX.templates.press.excerpt')
Thanks to this our development process has really improved: whenever a developer finds a bug he just has to propose a patch for the target pluggable, whenever someone notices a missing index on a query he has just to add it to the given pluggable. All the websites under development improved like people were working on the same project.
While existing pluggables might be limited, buggy or slow I’m getting confident that they will continue to improve, and some day they will surpass whatever custom implementation I can think of. I think I’m going to heavily rely on pluggable applications for any future project sticking to only one rule: “make it opensource”. This way, apart from probably helping other people, I’m also improving my own projects through other people feedbacks, bug reports and patches to the pluggables I used.
So, next time you have to start a new project give a look at the TurboGears CogBin and check if there is a pluggable application that looks like what you need. If you find any issue or find space for improvements just fork it and send a pull request, or send an email on the TurboGears Mailing List I’ll do my best to address any reported issue thanking you for your feedbacks as I’m aware that you are actually improving any past and future project that relies on that pluggable.
by amol at September 08, 2012 09:30 PM
by Michael Pedersen (noreply@blogger.com) at August 23, 2012 11:15 PM
Today Sprox 0.8 got released, it is the first release to add ToscaWidgets2 support. Depending on which version of ToscaWidgets is available inside your environment Sprox will either use TW1 or TW2 to generate its forms.
Being mostly a TW2 oriented release it might seem that not a lot changed since the previous version, but a little gem is hidden between all the TW2 changes as Sprox now supports setting default behavior for models themselves using the __sprox__ attribute inside model declaration.
class Parent(DeclarativeBase): __tablename__ = 'parents' uid = Column(Integer, primary_key=True) data = Column(String(100)) class Child(DeclarativeBase): __tablename__ = 'children' class __sprox__(object): dropdown_field_names = {'owner': ['data']} uid = Column(Integer, primary_key=True) data = Column(String(100)) owner_id = Column(Integer, ForeignKey(Parent.uid)) owner = relation('Parent')
The previous code example makes Sprox use the Parent data field for selection fields when choosing the parent of Child entities.
Apart from making easier to share options between your AddRecordForm and EditableForm __sprox__ attribute opens a great way to customize the TurboGears admin.
By adding a __sprox__ attribute inside your models you will be able to change the TurboGears admin behavior without having to create a custom admin configuration. Setting __sprox__ attribute makes possible to change most sprox properties changing CrudRestController behavior, the same properties that are documented on sprox.org can be specified inside the __sprox__ attribute by simply removing the underscores.
by amol at August 16, 2012 07:53 PM
I have been a bit busy and thus off from turbogears development for a while, but I see 2.2 release shaping up really good, props for Michael and Alessandro for their work.
Now, looking at the stuff that I’m most familiar with (jinja2 support code), I have been thinking for a while that jinja2 does not have bytecode caching enable by default, in fact, there is no support at all in tg2 code for that, while Genshi does have caching available jinja2 is lacking in this area, where enabling the cache is actually very simple.
I created a few tests with the latest 2.2rc2 turbogears with jinja bytecode caching enabled, I used the included filesystem based cache and a custom (simple) in-memory cache, as we can see on the graphics from the benchmark the gains from enabling the cache a bit small, around 4 request per second more, but if you are looking to squeeze performance out of tg2 this could be a good option as its practically free, the cache is not really intrusive, if I make a change to the template it is reloaded automatically thus it doesn’t affect the normal development workflow.
Jinja2 Bytecode Benchmark: Cached vs Uncached
I should, however, do some additional test under gunicorn or uwsgi to see if there are even more substantial benefits to enabling the bytecode cache.
by Carlos Daniel at July 21, 2012 04:43 PM
by Michael Pedersen (noreply@blogger.com) at July 16, 2012 09:32 PM
from genshi.template import TemplateLoader, NewTextTemplate
class VCardTemplateLoader(TemplateLoader):
template_extension = '.vcf'
def get_dotted_filename(self, filename):
if not filename.endswith(self.template_extension):
finder = config['pylons.app_globals'].dotted_filename_finder
filename = finder.get_dotted_filename(
template_name=filename,
template_extension=self.template_extension)
return filename
def load(self, filename, relative_to=None, cls=None, encoding=None):
"""Actual loader function."""
return TemplateLoader.load(
self, self.get_dotted_filename(filename),
relative_to=relative_to, cls=NewTextTemplate, encoding=encoding)
from pylons import templating
class RenderVCard(object):
"""Singleton that can be called as the vcard render function."""
genshi_functions = {} # auxiliary Genshi functions loaded on demand
def __init__(self, loader):
if not self.genshi_functions:
from genshi import HTML, XML
self.genshi_functions.update(HTML=HTML, XML=XML)
self.load_template = loader.load
def __call__(self, template_name, template_vars, **kwargs):
"""Render the template_vars with the Genshi template."""
template_vars.update(self.genshi_functions)
doctype = 'text/x-vcard'
method='vcf'
kwargs['doctype'] = doctype
kwargs['method'] = method
def render_template():
template = self.load_template(template_name)
return template.generate(**template_vars).render(encoding=None)
return templating.cached_template(
template_name, render_template,
**kwargs)
from tg.configuration import AppConfig, config
class MyProjectConfig(AppConfig):
def setup_vcard_renderer(self):
loader = VCardTemplateLoader(search_path=self.paths.templates,
auto_reload=self.auto_reload_templates)
self.render_functions.vcard = RenderVCard(loader)
base_config = MyProjectConfig()
base_config.renderers = []
#Set the default renderer
base_config.default_renderer = 'genshi'
# Add vcard rendering (genshi plain text renderer)
base_config.renderers.append('vcard')
{% python from datetime import datetime; now = datetime.now(); urls = user.links_to_dict(); phones=user.phones_to_dict() %}BEGIN:VCARD
VERSION:2.1
FN:${user.display_name}
{% if user.title %}TITLE:${user.title}{% end %}
{% for phone in phones %}TEL;${phone.upper()};VOICE;${phones[phone]}{% end %}
ADR;HOME:;;${user.streetaddress.replace('\n','=0D=0A')};${user.city};${user.state_province if user.state_province else ""};${user.postal_code};${user.country if user.country else ""}
LABEL;HOME;ENCODING=QUOTED-PRINTABLE:${user.streetaddress.replace('\n','=0D=0A')}=0D=0A${user.city}, ${user.state_province if user.state_province else ""} ${user.postal_code}=0D=0A${user.country if
user.country else ""}
{% if user.email_address %}EMAIL;PREF;INTERNET:${user.email_address}{% end %}
{% if 'homepage' in urls %}URL:${urls['homepage']}{% end %}
REV:${'%04d%02d%02dT%02d%02d%02dZ' % (now.year, now.month, now.day, now.hour, now.minute, now.second)}
END:VCARD
by Michael Pedersen (noreply@blogger.com) at July 14, 2012 09:48 PM
Last updated:
September 10, 2012 01:45 PM
All times are UTC.
Powered by: