PageRenderTime 61ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/README.md

https://gitlab.com/BeardyBear/tornado-generic-handlers
Markdown | 290 lines | 264 code | 26 blank | 0 comment | 0 complexity | 5d987c9180dacf41e4d55eba7448d4ba MD5 | raw file
  1. <h1>tornado-generic-handlers</h1>
  2. <p>This package contains Django's generic class based views adapted to be used with Tornado along with SQLAlchemy and WTForms.
  3. Note that implementation might differ a bit in some of the cases.<br/> The features included:
  4. <ul>
  5. <li>generic handlers</li>
  6. <li>pagination</li>
  7. </ul>
  8. </p>
  9. <h2>Installation</h2>
  10. ```
  11. pip install torgen
  12. ```
  13. <h2>Configuration</h2>
  14. <p>The only requirement is SQLAlchemy's session stored in application's db attribute.</p>
  15. ```python
  16. #app.py
  17. from sqlalchemy.orm import scoped_session, sessionmaker
  18. class Application(tornado.web.Application):
  19. def __init__(self):
  20. self.db = scoped_session(sessionmaker(bind=engine))
  21. ```
  22. <h2>Basic usage</h2>
  23. ```python
  24. from torgen.base import TemplateHandler
  25. from torgen.list import ListHandler
  26. from torgen.detail import DetailHandler
  27. from torgen.edit import FormHandler, DeleteHandler
  28. class HomeHandler(TemplateHandler):
  29. template_name = 'home.html'
  30. class BlogHandler(ListHandler):
  31. template_name = 'blog.html'
  32. paginate_by = 10
  33. context_object_name = 'post_list'
  34. model = Post
  35. class PostHandler(DetailHandler):
  36. template_name = 'post.html'
  37. model = Post
  38. context_object_name = 'post'
  39. class LoginHandler(FormHandler):
  40. template_name = 'login.html'
  41. form_class = LoginForm
  42. success_url = '/'
  43. def form_valid(self, form):
  44. self.set_secure_cookie('user', form.data['username'])
  45. return super(LoginHandler, self).form_valid(form)
  46. class DeletePostHandler(DeleteHandler):
  47. template_name = 'confirm_delete.html'
  48. model = Post
  49. success_url = '/blog/'
  50. ```
  51. <h2>More on handlers</h2>
  52. <p>You'd like to override handlers methods to customize their behaviour. </p>
  53. <h3>FormHandler</h3>
  54. <p>A handler that displays a form. On error, redisplays the form with validation errors; on success, redirects to a new URL.</p>
  55. ```python
  56. class CreatePostHandler(FormHandler):
  57. template_name = 'create_post.html'
  58. form_class = CreatePostForm
  59. initial = {'title': 'Default title'} #initial value for the form field
  60. def get_initial(self):
  61. """
  62. Returns the copy of provided initial dictionary.
  63. If you need, return the whole new dictionary from here instead of updating it.
  64. """
  65. dummy_text = self.db.query(DummyTexts).first()
  66. self.initial.update({'text': dummy_text})
  67. return super(CreatePostHandler, self).get_initial()
  68. def form_valid(self, form):
  69. """
  70. Called if the form was valid. Redirect user to success_url.
  71. """
  72. post = Post(title=form.data['title'], text=form.data['text'])
  73. self.db.add(post)
  74. self.db.commit()
  75. self.db.refresh(post)
  76. self.post_id = post.id
  77. return super(CreatePostHandler, self).form_valid(form)
  78. def form_invalid(self, form):
  79. """
  80. Called if the form was invalid.
  81. By default it rerenders template with the form holding error messages.
  82. Here you can add new context variables or do anythin you'd like, i.e.
  83. redirect user somewhere.
  84. """
  85. new_var = 'brand new variable to the template context'
  86. return self.render(self.get_context_data(form=form, new_var=new_var))
  87. def get_success_url(self):
  88. """
  89. Returns success_url attribute by default.
  90. """
  91. return self.reverse_url('post', self.post_id)
  92. def get_form_kwargs(self):
  93. """
  94. Returns kwargs that will be passed to your form's constructor.
  95. """
  96. kwargs = super(CreatePostHandler, self).get_form_kwargs()
  97. kwargs['variable'] = 'some variable to be here'
  98. return kwargs
  99. ```
  100. <h3>DetailHandler</h3>
  101. <p>While this handler is executing, self.object will contain the object that the handler is operating upon.</p>
  102. ```python
  103. class PostDetailHandler(DetailHandler):
  104. """
  105. Displays the object with provided id.
  106. """
  107. template_name = 'post.html'
  108. model = Post
  109. #name by which the object will be accessible in template
  110. context_object_name = 'post'
  111. def get_context_data(self, **kwargs):
  112. """
  113. A place to append any necessary context variables.
  114. """
  115. context = super(PostDetailHandler, self).get_context_data(**kwargs)
  116. context['now'] = datetime.now()
  117. return context
  118. class Application(tornado.web.Application):
  119. def __init__(self):
  120. handlers = [
  121. url(r'/post/(?P<id>\d+)/', PostDetailHandler, name='post_detail'),
  122. ]
  123. ```
  124. ```python
  125. class PostDetailHandler(DetailHandler):
  126. """
  127. The same, but with modified url kwarg name.
  128. """
  129. template_name = 'post.html'
  130. model = Post
  131. context_object_name = 'post'
  132. pk_url_kwarg = 'super_id'
  133. class Application(tornado.web.Application):
  134. def __init__(self):
  135. handlers = [
  136. url(r'/post/(?P<super_id>\d+)/', PostDetailHandler, name='post_detail'),
  137. ]
  138. ```
  139. ```python
  140. class PostDetailHandler(DetailHandler):
  141. """
  142. Displays the object by its slug.
  143. Surely, the slug field doesn't need to be a slug really.
  144. """
  145. template_name = 'post.html'
  146. model = Post
  147. context_object_name = 'post'
  148. slug_url_kwarg = 'mega_slug' #defaults to slug
  149. slug_field = Post.slug_field_name
  150. class Application(tornado.web.Application):
  151. def __init__(self):
  152. handlers = [
  153. url(r'/post/(?P<mega_slug>[-_\w]+)/', PostDetailHandler, name='post_detail'),
  154. ]
  155. ```
  156. <h3>ListHandler</h3>
  157. <p>A page representing a list of objects.<br/>While this handler is executing, self.object_list will contain the list of objects </p>
  158. ```python
  159. class BlogHandler(ListHandler):
  160. template_name = 'blog.html'
  161. paginate_by = 10
  162. context_object_name = 'post_list'
  163. model = Post
  164. queryset = Post.query.order_by(desc(Post.created))
  165. allow_empty = False #raises 404 if no objects found, defaults to True
  166. page_kwarg = 'the_page' #defauls to 'page'
  167. #An integer specifying the number of “overflow” objects the last page can contain.
  168. #This extends the paginate_by limit on the last page by up to paginate_orphans,
  169. #in order to keep the last page from having a very small number of objects.
  170. paginate_orphans = 0
  171. class Application(tornado.web.Application):
  172. def __init__(self):
  173. handlers = [
  174. url(r'/blog/', BlogHandler, name='blog_index'),
  175. url(r'/blog/(?P<the_page>\d+)/', BlogHandler, name='blog_page'),
  176. ]
  177. ```
  178. <h3>DeleteHandler</h3>
  179. <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>
  180. ```python
  181. class DeletePostHandler(DeleteHandler):
  182. template_name = 'confirm_delete.html'
  183. model = Post
  184. success_url = '/blog/'
  185. ```
  186. ```html
  187. <form action="" method="post">
  188. <p>Are you sure you want to delete "{{ object }}"?</p>
  189. <input type="submit" value="Confirm" />
  190. </form>
  191. ```
  192. <h3>Pagination</h3>
  193. <p>Pagination can be used separately from ListView in any handler.</p>
  194. ```python
  195. from torgen.pagination import Paginator, EmptyPage, PageNotAnInteger
  196. class BlogHandler(tornado.web.RequestHandler):
  197. @property
  198. def db(self):
  199. return self.application.db
  200. def get(self, page):
  201. post_list = self.db.query(Post).all()
  202. paginator = Paginator(posts, 15)
  203. try:
  204. posts = paginator.page(page)
  205. except PageNotAnInteger:
  206. posts = paginator.page(1)
  207. except EmptyPage:
  208. posts = paginator.page(paginator.num_pages)
  209. self.render('blog.html', posts=posts)
  210. ```
  211. <p>In the template:</p>
  212. ```html
  213. {% for post in posts %}
  214. {{ post.title }}<br />
  215. {% end %}
  216. <div class="pagination">
  217. <span class="step-links">
  218. {% if posts.has_previous %}
  219. <a href="/blog/{{ posts.previous_page_number }}/">previous</a>
  220. {% end %}
  221. <span class="current">
  222. Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
  223. </span>
  224. {% if posts.has_next %}
  225. <a href="/blog/{{ posts.next_page_number }}/">next</a>
  226. {% end %}
  227. </span>
  228. </div>
  229. ```
  230. <h3>Decorator usage</h3>
  231. <p>In order to use Tornado's built-in decorators, simply override the http method. </p>
  232. ```python
  233. class HomeHandler(TemplateHandler):
  234. template_name = 'home.html'
  235. @tornado.web.authenticated
  236. def get(self, *args, **kwargs):
  237. return super(HomeHandler, self).get(*args, **kwargs)
  238. ```
  239. <h3>Extending handlers with custom methods</h3>
  240. <p>One of the valid approaches would be to create a mixin. <br/></p>
  241. ```python
  242. class BaseMixin(object):
  243. def get_current_user(self):
  244. username = self.get_secure_cookie("user")
  245. if username:
  246. return username
  247. else:
  248. return None
  249. def get_context_data(self, **kwargs):
  250. """
  251. The variables declared here will be available in any template that uses this mixin.
  252. Note that a 'handler' variable is already available in any template,
  253. and represents a current handler's object.
  254. """
  255. kwargs['logged_in'] = self.get_current_user()
  256. return super(BaseMixin, self).get_context_data(**kwargs)
  257. class HomeHandler(BaseMixin, TemplateHandler):
  258. template_name = 'home.html'
  259. ```
  260. ```html
  261. {% if logged_in %}
  262. <li><a href="{{handler.reverse_url('create_post')}}">Create post</a></li>
  263. {% end %}
  264. ```