This package contains Django's generic class based views adapted to be used with Tornado along with SQLAlchemy and WTForms.
Note that implementation might differ a bit in some of the cases.
The features included:
- generic handlers
- pagination
The only requirement is SQLAlchemy's session stored in application's db attribute.
```python #app.py from sqlalchemy.orm import scoped_session, sessionmakerclass Application(tornado.web.Application): def init(self): self.db = scoped_session(sessionmaker(bind=engine))
<h2>Basic usage</h2>
```python
from torgen.base import TemplateHandler
from torgen.list import ListHandler
from torgen.detail import DetailHandler
from torgen.edit import FormHandler, DeleteHandler
class HomeHandler(TemplateHandler):
template_name = 'home.html'
class BlogHandler(ListHandler):
template_name = 'blog.html'
paginate_by = 10
context_object_name = 'post_list'
model = Post
class PostHandler(DetailHandler):
template_name = 'post.html'
model = Post
context_object_name = 'post'
class LoginHandler(FormHandler):
template_name = 'login.html'
form_class = LoginForm
success_url = '/'
def form_valid(self, form):
self.set_secure_cookie('user', form.data['username'])
return super(LoginHandler, self).form_valid(form)
class DeletePostHandler(DeleteHandler):
template_name = 'confirm_delete.html'
model = Post
success_url = '/blog/'
You'd like to override handlers methods to customize their behaviour.
A handler that displays a form. On error, redisplays the form with validation errors; on success, redirects to a new URL.
```python class CreatePostHandler(FormHandler): template_name = 'create_post.html' form_class = CreatePostForm initial = {'title': 'Default title'} #initial value for the form fielddef get_initial(self):
"""
Returns the copy of provided initial dictionary.
If you need, return the whole new dictionary from here instead of updating it.
"""
dummy_text = self.db.query(DummyTexts).first()
self.initial.update({'text': dummy_text})
return super(CreatePostHandler, self).get_initial()
def form_valid(self, form):
"""
Called if the form was valid. Redirect user to success_url.
"""
post = Post(title=form.data['title'], text=form.data['text'])
self.db.add(post)
self.db.commit()
self.db.refresh(post)
self.post_id = post.id
return super(CreatePostHandler, self).form_valid(form)
def form_invalid(self, form):
"""
Called if the form was invalid.
By default it rerenders template with the form holding error messages.
Here you can add new context variables or do anythin you'd like, i.e.
redirect user somewhere.
"""
new_var = 'brand new variable to the template context'
return self.render(self.get_context_data(form=form, new_var=new_var))
def get_success_url(self):
"""
Returns success_url attribute by default.
"""
return self.reverse_url('post', self.post_id)
def get_form_kwargs(self):
"""
Returns kwargs that will be passed to your form's constructor.
"""
kwargs = super(CreatePostHandler, self).get_form_kwargs()
kwargs['variable'] = 'some variable to be here'
return kwargs
<h3>DetailHandler</h3>
<p>While this handler is executing, self.object will contain the object that the handler is operating upon.</p>
```python
class PostDetailHandler(DetailHandler):
"""
Displays the object with provided id.
"""
template_name = 'post.html'
model = Post
#name by which the object will be accessible in template
context_object_name = 'post'
def get_context_data(self, **kwargs):
"""
A place to append any necessary context variables.
"""
context = super(PostDetailHandler, self).get_context_data(**kwargs)
context['now'] = datetime.now()
return context
class Application(tornado.web.Application):
def __init__(self):
handlers = [
url(r'/post/(?P<id>\d+)/', PostDetailHandler, name='post_detail'),
]
class PostDetailHandler(DetailHandler):
"""
The same, but with modified url kwarg name.
"""
template_name = 'post.html'
model = Post
context_object_name = 'post'
pk_url_kwarg = 'super_id'
class Application(tornado.web.Application):
def __init__(self):
handlers = [
url(r'/post/(?P<super_id>\d+)/', PostDetailHandler, name='post_detail'),
]
class PostDetailHandler(DetailHandler):
"""
Displays the object by its slug.
Surely, the slug field doesn't need to be a slug really.
"""
template_name = 'post.html'
model = Post
context_object_name = 'post'
slug_url_kwarg = 'mega_slug' #defaults to slug
slug_field = Post.slug_field_name
class Application(tornado.web.Application):
def __init__(self):
handlers = [
url(r'/post/(?P<mega_slug>[-_\w]+)/', PostDetailHandler, name='post_detail'),
]
A page representing a list of objects.
While this handler is executing, self.object_list will contain the list of objects
#An integer specifying the number of “overflow” objects the last page can contain.
#This extends the paginate_by limit on the last page by up to paginate_orphans,
#in order to keep the last page from having a very small number of objects.
paginate_orphans = 0
class Application(tornado.web.Application): def init(self): handlers = [ url(r'/blog/', BlogHandler, name='blog_index'), url(r'/blog/(?P<the_page>\d+)/', BlogHandler, name='blog_page'), ]
<h3>DeleteHandler</h3>
<p>Displays a confirmation page and deletes an existing object. The given object will only be deleted if the request method is POST. If this handler is fetched via GET, it will display a confirmation page that should contain a form that POSTs to the same URL.</p>
```python
class DeletePostHandler(DeleteHandler):
template_name = 'confirm_delete.html'
model = Post
success_url = '/blog/'
<form action="" method="post">
<p>Are you sure you want to delete "{{ object }}"?</p>
<input type="submit" value="Confirm" />
</form>
Pagination can be used separately from ListView in any handler.
```python from torgen.pagination import Paginator, EmptyPage, PageNotAnIntegerclass BlogHandler(tornado.web.RequestHandler): @property def db(self): return self.application.db
def get(self, page):
post_list = self.db.query(Post).all()
paginator = Paginator(posts, 15)
try:
posts = paginator.page(page)
except PageNotAnInteger:
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
self.render('blog.html', posts=posts)
<p>In the template:</p>
```html
{% for post in posts %}
{{ post.title }}<br />
{% end %}
<div class="pagination">
<span class="step-links">
{% if posts.has_previous %}
<a href="/blog/{{ posts.previous_page_number }}/">previous</a>
{% end %}
<span class="current">
Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
</span>
{% if posts.has_next %}
<a href="/blog/{{ posts.next_page_number }}/">next</a>
{% end %}
</span>
</div>
In order to use Tornado's built-in decorators, simply override the http method.
```python class HomeHandler(TemplateHandler): template_name = 'home.html'@tornado.web.authenticated
def get(self, *args, **kwargs):
return super(HomeHandler, self).get(*args, **kwargs)
<h3>Extending handlers with custom methods</h3>
<p>One of the valid approaches would be to create a mixin. <br/></p>
```python
class BaseMixin(object):
def get_current_user(self):
username = self.get_secure_cookie("user")
if username:
return username
else:
return None
def get_context_data(self, **kwargs):
"""
The variables declared here will be available in any template that uses this mixin.
Note that a 'handler' variable is already available in any template,
and represents a current handler's object.
"""
kwargs['logged_in'] = self.get_current_user()
return super(BaseMixin, self).get_context_data(**kwargs)
class HomeHandler(BaseMixin, TemplateHandler):
template_name = 'home.html'
{% if logged_in %}
<li><a href="{{handler.reverse_url('create_post')}}">Create post</a></li>
{% end %}