/tornado-generic-handlers

Port of Django's generic class based views to be used with Tornado framework.

Primary LanguagePython

tornado-generic-handlers

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

Installation

``` pip install torgen ```

Configuration

The only requirement is SQLAlchemy's session stored in application's db attribute.

```python #app.py from sqlalchemy.orm import scoped_session, sessionmaker

class 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/'

More on handlers

You'd like to override handlers methods to customize their behaviour.

FormHandler

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 field
def 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'),
        ]

ListHandler

A page representing a list of objects.
While this handler is executing, self.object_list will contain the list of objects

```python class BlogHandler(ListHandler): template_name = 'blog.html' paginate_by = 10 context_object_name = 'post_list' model = Post queryset = Post.query.order_by(desc(Post.created)) allow_empty = False #raises 404 if no objects found, defaults to True page_kwarg = 'the_page' #defauls to 'page'
#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

Pagination can be used separately from ListView in any handler.

```python from torgen.pagination import Paginator, EmptyPage, PageNotAnInteger

class 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>

Decorator usage

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 %}