Alessandro Molina avatar Alessandro Molina committed 2863931

first import

Comments (0)

Files changed (18)

+.pyc
+.swp
+^build
+^dist
+.egg-info
+.idea
+.DS_Store
+recursive-include tgcomments/public *
+recursive-include tgcomments/templates *.html
+global-exclude *.pyc
+
+About tgcomments
+-------------------------
+
+tgcomments is a Pluggable application for TurboGears2.
+
+Installing
+-------------------------------
+
+tgcomments can be installed both from pypi or from bitbucket::
+
+    easy_install tgcomments
+
+should just work for most of the users
+
+Plugging tgcomments
+----------------------------
+
+In your application *config/app_cfg.py* import **plug**::
+
+    from tgext.pluggable import plug
+
+Then at the *end of the file* call plug with tgcomments::
+
+    plug(base_config, 'tgcomments')
+
+You will be able to access the registration process at
+*http://localhost:8080/tgcomments*.
+
+Available Hooks
+----------------------
+
+tgcomments makes available a some hooks which will be
+called during some actions to alter the default
+behavior of the appplications:
+
+Exposed Partials
+----------------------
+
+tgcomments exposes a bunch of partials which can be used
+to render pieces of the blogging system anywhere in your
+application:
+
+Exposed Templates
+--------------------
+
+The templates used by registration and that can be replaced with
+*tgext.pluggable.replace_template* are:
+
+# -*- coding: utf-8 -*-
+import sys, os
+
+try:
+    from setuptools import setup, find_packages
+except ImportError:
+    from ez_setup import use_setuptools
+    use_setuptools()
+    from setuptools import setup, find_packages
+
+install_requires=[
+    "TurboGears2 >= 2.1.4",
+    "tgext.pluggable"
+]
+
+here = os.path.abspath(os.path.dirname(__file__))
+try:
+    README = open(os.path.join(here, 'README.rst')).read()
+except IOError:
+    README = ''
+
+setup(
+    name='tgapp-tgcomments',
+    version='0.0.1',
+    description='TurboGears2 pluggable application for comments to entities with facebook sharing',
+    long_description=README,
+    author='Alessandro Molina',
+    author_email='alessandro.molina@axant.it',
+    #url='',
+    keywords='turbogears2.application',
+    setup_requires=["PasteScript >= 1.7"],
+    paster_plugins=[],
+    packages=find_packages(exclude=['ez_setup']),
+    install_requires=install_requires,
+    include_package_data=True,
+    package_data={'tgapp.tgcomments': ['i18n/*/LC_MESSAGES/*.mo',
+                                 'templates/*/*',
+                                 'public/*/*']},
+    entry_points="""
+    """,
+    zip_safe=False
+)

tgcomments/__init__.py

+# -*- coding: utf-8 -*-
+"""The tgapp-tgcomments package"""
+
+def plugme(app_config, options):
+    app_config['_pluggable_tgcomments_config'] = options
+    return dict(appid='tgcomments', global_helpers=False)

tgcomments/bootstrap.py

+# -*- coding: utf-8 -*-
+"""Setup the tgcomments application"""
+
+from tgcomments import model
+from tgext.pluggable import app_model
+
+def bootstrap(command, conf, vars):
+    print 'Bootstrapping tgcomments...'

tgcomments/controllers/__init__.py

+# -*- coding: utf-8 -*-
+"""Controllers for the tgapp-tgcomments pluggable application."""
+
+from root import RootController

tgcomments/controllers/root.py

+# -*- coding: utf-8 -*-
+"""Main Controller"""
+
+from tg import TGController
+from tg import expose, flash, require, url, lurl, request, redirect, validate
+from tg.exceptions import HTTPForbidden, HTTPRedirection
+from tg.i18n import ugettext as _, lazy_ugettext as l_
+
+from tgcomments import model
+from tgcomments.model import DBSession, Comment
+from tgcomments.lib import get_user_gravatar, notify_comment_on_facebook
+
+from tgext.pluggable import app_model
+
+from formencode.validators import Email, String, Invalid
+
+def back_to_referer(*args, **kw):
+    if not kw.get('success'):
+        flash('Failed to post comment', 'error')
+    raise redirect(request.referer)
+
+class RootController(TGController):
+    @expose()
+    @validate({'entity_type':String(not_empty=True),
+               'entity_id':String(not_empty=True),
+               'body':String(not_empty=True)},
+              error_handler=back_to_referer)
+    def new(self, **kw):
+        entity_type = getattr(app_model, kw['entity_type'], None)
+        entity_id = kw['entity_id']
+        if entity_type is None or entity_id is None:
+            return back_to_referer()
+
+        entity = DBSession.query(entity_type).get(entity_id)
+        if not entity:
+            return back_to_referer()
+
+        if not request.identity:
+            try:
+                user = {'name':String(not_empty=True).to_python(kw.get('author')),
+                        'avatar':get_user_gravatar(Email(not_empty=True).to_python(kw.get('email')))}
+            except Invalid, e:
+                print e
+                return back_to_referer()
+        else:
+            user = request.identity['user']
+
+        c = Comment.add_comment(entity, user, kw['body'])
+        notify_comment_on_facebook(request.referer, c)
+        flash('Comment Added')
+        return back_to_referer(success=True)
+

tgcomments/helpers.py

+# -*- coding: utf-8 -*-
+
+"""WebHelpers used in tgapp-tgcomments."""
+
+from tgcomments.lib import get_user_avatar

tgcomments/lib/__init__.py

+# -*- coding: utf-8 -*-
+from datetime import datetime
+from hashlib import md5
+from tg import url, config
+import json
+from urllib import urlopen, urlencode
+from contextlib import closing
+
+def get_user_gravatar(user):
+    if not isinstance(user, basestring):
+        user = user.email_address
+    mhash = md5(user).hexdigest()
+    return url('http://www.gravatar.com/avatar/'+mhash, params=dict(s=32))
+
+def get_user_avatar(user):
+    author_pic = getattr(user, 'avatar', None)
+    if author_pic is None:
+        author_pic = get_user_gravatar(user)
+        fbauth = getattr(user, 'fbauth', None)
+        if fbauth:
+            author_pic = fbauth.profile_picture + '?type=large'
+    return author_pic
+
+def notify_comment_on_facebook(url, comment):
+    notify_faceook = config['_pluggable_tgcomments_config'].get('notify_facebook', True)
+    if not notify_faceook:
+        return
+
+    user = comment.user
+    if not user:
+        return
+
+    fbauth = getattr(user, 'fbauth', None)
+    if not fbauth:
+        return
+
+    #check if facebook token has expired
+    if not fbauth.access_token_expiry or datetime.now() >= fbauth.access_token_expiry:
+        return
+
+    url = 'https://graph.facebook.com/me/feed?access_token=%s' % fbauth.access_token
+    data = {'link':url,
+            'title':'%s posted a new comment' % comment.author_name,
+            'message':comment.body}
+
+    with closing(urlopen(url, urlencode(data))) as fbanswer:
+        return json.loads(fbanswer.read())
+

tgcomments/model/__init__.py

+# -*- coding: utf-8 -*-
+from sqlalchemy.ext.declarative import declarative_base
+from tgext.pluggable import PluggableSession
+
+DBSession = PluggableSession()
+DeclarativeBase = declarative_base()
+
+def init_model(app_session):
+    DBSession.configure(app_session)
+
+from models import Comment
+

tgcomments/model/models.py

+import tg
+
+from sqlalchemy import Table, ForeignKey, Column
+from sqlalchemy.types import Unicode, Integer, DateTime, UnicodeText
+from sqlalchemy.orm import backref, relation
+
+from tgcomments.model import DeclarativeBase, DBSession
+from tgcomments.lib import get_user_avatar
+from tgext.pluggable import app_model, primary_key
+
+from datetime import datetime
+
+class Comment(DeclarativeBase):
+    __tablename__ = 'tgcomments_comments'
+
+    uid = Column(Integer, autoincrement=True, primary_key=True)
+
+    body = Column(UnicodeText, nullable=False)
+    created_at = Column(DateTime, default=datetime.now, nullable=False)
+
+    user_id = Column(Integer, ForeignKey(primary_key(app_model.User)), nullable=True)
+    user = relation(app_model.User, backref=backref('comments'))
+
+    author_name = Column(Unicode(2048), nullable=False)
+    author_pic = Column(Unicode(2048), nullable=True)
+
+    entity_id = Column(Integer, nullable=False, index=True)
+    entity_type = Column(Unicode(255), nullable=False, index=True)
+
+    @classmethod
+    def get_entity_descriptor(cls, entity):
+        Type = entity.__class__
+        type_primary_key = primary_key(Type)
+        return Type.__name__, getattr(entity, type_primary_key.key)
+
+    @classmethod
+    def comments_for(cls, entity):
+        entity_type, entity_id = cls.get_entity_descriptor(entity)
+
+        return DBSession.query(cls).filter_by(entity_type=entity_type)\
+                                   .filter_by(entity_id=entity_id)\
+                                   .order_by(cls.created_at.desc()).all()
+
+    @classmethod
+    def add_comment(cls, entity, user, body):
+        entity_type, entity_id = cls.get_entity_descriptor(entity)
+        c = Comment(body=body, entity_type=entity_type, entity_id=entity_id)
+
+        if isinstance(user, dict):
+            c.author_name = user['name']
+            c.author_pic = user.get('avatar')
+        else:
+            c.user = user
+            c.author_name = user.display_name
+            c.author_pic = get_user_avatar(user)
+
+        DBSession.add(c)
+        return c
+
+

tgcomments/partials.py

+from tg import expose, config
+from tgcomments.model import Comment
+
+@expose('tgcomments.templates.comments_partial')
+def comments_for(entity):
+    comments = Comment.comments_for(entity)
+    allow_anonymous = config['_pluggable_tgcomments_config'].get('allow_anonymous', True)
+    entity_type, entity_id = Comment.get_entity_descriptor(entity)
+    return dict(entity_type=entity_type, entity_id=entity_id,
+                comments=comments, allow_anonymous=allow_anonymous)

tgcomments/public/__init__.py

+# -*- coding: utf-8 -*-
+"""Enable statics for the tgapp-tgcomments package"""
Add a comment to this file

tgcomments/public/css/style.css

Empty file added.

tgcomments/templates/__init__.py

+# -*- coding: utf-8 -*-
+"""Templates package for the application."""

tgcomments/templates/comments_partial.html

+<div xmlns:py="http://genshi.edgewall.org/"
+     xmlns:xi="http://www.w3.org/2001/XInclude"
+     class="tgcomments_box">
+    <div class="tgcomments_form" py:if="request.identity or allow_anonymous">
+        <form action="${h.plug_url('tgcomments', '/new')}" method="POST">
+            <input type="hidden" name="entity_type" value="${entity_type}"/>
+            <input type="hidden" name="entity_id" value="${entity_id}"/>
+            <table>
+                <tr py:if="not request.identity">
+                    <td class="tgcomments_form_label">Author:</td>
+                    <td><input type="text" name="author"/></td>
+                </tr>
+                <tr py:if="not request.identity">
+                    <td class="tgcomments_form_label">Email:</td>
+                    <td><input type="text" name="email"/></td>
+                </tr>
+                <tr py:if="request.identity">
+                    <td><img style="width:32px;"
+                             src="${h.tgcomments.get_user_avatar(request.identity['user'])}"/></td>
+                    <td>${request.identity['user'].display_name}</td>
+                </tr>
+                <tr>
+                    <td colspan="2">
+                        <textarea name="body" placeholder="post your comment here"></textarea>
+                    </td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td><input type="submit" value="Submit"/></td>
+                </tr>
+            </table>
+        </form>
+    </div>
+    <div class="tgcomments_comments">
+        <div class="tgcomments_comment" py:for="comment in comments">
+            <div class="tgcomments_comment_head">
+                <div class="tgcomments_comment_avatar">
+                    <img style="width:32px;" src="${comment.author_pic}"/>
+                </div>
+                <div class="tgcomments_comment_author">
+                    ${comment.author_name}
+                </div>
+            </div>
+            <div class="tgcomments_comment_body">${comment.body}</div>
+        </div>
+    </div>
+</div>

tgcomments/templates/index.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <xi:include href="master.html" />
+
+<head>
+  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" py:if="False"/>
+  <title>Welcome to TurboGears pluggable Application</title>
+  <link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/_pluggable/tgcomments/css/style.css')}" />
+</head>
+
+<body>
+    <div py:if="request.identity">Hi ${request.identity['user']}</div>
+    <div id="hello_box">
+        <img src="${tg.url('/_pluggable/tgcomments/images/star.png')}"/>
+        Hello from, ${h.tgcomments.bold(sample.name)}
+        owned by ${sample.user}
+    </div>
+    <div>${h.call_partial('tgcomments.partials:something', name='Partial')}</div>
+</body>
+</html>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.