~sirn/fanboi2

10a31f585b3ad9060a3f210004dd878bf090a02d — Kridsada Thanabulpong 8 years ago
Initial version.
A  => .gitignore +18 -0
@@ 1,18 @@
# System caches
.DS_Store
Thumbs.db

# Application runtime
static/
*.egg-info/
*.sqlite
*.pyc
*.log

# IDE stuff
fanboi2.iml
.idea

# Testing leftovers
.coverage
.noseids
\ No newline at end of file

A  => CHANGES.rst +4 -0
@@ 1,4 @@
0.0
---

-  Initial version

A  => MANIFEST.in +2 -0
@@ 1,2 @@
include *.txt *.ini *.cfg *.rst
recursive-include fanboi2 *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml

A  => README.rst +26 -0
@@ 1,26 @@
Fanboi2
===============

Board engine behind `Fanboi Channel <http://fanboi.ch/>`_ written in Python.

Getting Started
---------------

::

    $ python setup.py develop
    $ alembic upgrade head


License
---------------

Copyright (c) 2013, Kridsada Thanabulpong
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file

A  => alembic.ini +13 -0
@@ 1,13 @@
[alembic]
# path to migration scripts
script_location = migration

# path to Pyramid configuration to initialize
pyramid_configuration = development.ini

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

A  => development.ini +60 -0
@@ 1,60 @@
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###

[app:main]
use = egg:fanboi2

pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
    pyramid_debugtoolbar
    pyramid_tm

sqlalchemy.url = sqlite:///%(here)s/fanboi2.sqlite

[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543

###
# logging configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###

[loggers]
keys = root, fanboi2, sqlalchemy

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console

[logger_fanboi2]
level = DEBUG
handlers =
qualname = fanboi2

[logger_sqlalchemy]
level = INFO
handlers =
qualname = sqlalchemy.engine

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s

A  => fanboi2/__init__.py +16 -0
@@ 1,16 @@
from pyramid.config import Configurator
from sqlalchemy import engine_from_config
from .models import DBSession, Base


def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    config = Configurator(settings=settings)
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('index', '/')
    config.scan()
    return config.make_wsgi_app()

A  => fanboi2/models.py +46 -0
@@ 1,46 @@
import re
from sqlalchemy import func, Column, Integer, String, DateTime, Unicode,\
    ForeignKey
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
from zope.sqlalchemy import ZopeTransactionExtension

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()


RE_FIRST_CAP = re.compile('(.)([A-Z][a-z]+)')
RE_ALL_CAP = re.compile('([a-z0-9])([A-Z])')


class BaseModel(object):
    id = Column(Integer, primary_key=True)
    created_at = Column(DateTime, default=func.now())
    updated_at = Column(DateTime, onupdate=func.now())

    @declared_attr
    def __tablename__(self):
        name = RE_FIRST_CAP.sub(r'\1_\2', self.__name__)
        return RE_ALL_CAP.sub(r'\1_\2', name).lower()

    def __init__(self, **kwargs):
        for key, value in kwargs.iteritems():
            setattr(self, key, value)


class Board(BaseModel, Base):
    slug = Column(String(64), unique=True, nullable=False)
    title = Column(Unicode(255), nullable=False)


class Topic(BaseModel, Base):
    board_id = Column(Integer, ForeignKey('board.id'), nullable=False)
    topic = Column(Unicode(255), nullable=False)
    board = relationship('Board', backref=backref('topics'))


class Post(BaseModel, Base):
    topic_id = Column(Integer, ForeignKey('topic.id'), nullable=False)
    body = Column(Unicode, nullable=False)
    topic = relationship('Topic', backref=backref('posts',
                                                  order_by='Post.id'))

A  => fanboi2/scripts/__init__.py +1 -0
@@ 1,1 @@
# package

A  => fanboi2/tests.py +33 -0
@@ 1,33 @@
import unittest
import transaction

from pyramid import testing

from .models import DBSession


class TestMyView(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
        from sqlalchemy import create_engine
        engine = create_engine('sqlite://')
        from .models import (
            Base,
            MyModel,
            )
        DBSession.configure(bind=engine)
        Base.metadata.create_all(engine)
        with transaction.manager:
            model = MyModel(name='one', value=55)
            DBSession.add(model)

    def tearDown(self):
        DBSession.remove()
        testing.tearDown()

    def test_it(self):
        from .views import my_view
        request = testing.DummyRequest()
        info = my_view(request)
        self.assertEqual(info['one'].name, 'one')
        self.assertEqual(info['project'], 'fanboi2')

A  => fanboi2/views.py +7 -0
@@ 1,7 @@
from pyramid.response import Response
from pyramid.view import view_config


@view_config(route_name='index')
def index_view(request):
    return Response("Hello, world!")

A  => migration/README +1 -0
@@ 1,1 @@
Generic single-database configuration.
\ No newline at end of file

A  => migration/env.py +78 -0
@@ 1,78 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
from pyramid.paster import get_appsettings
from fanboi2.models import Base


def get_pyramid_sa_url(config):
    pyramid_config = config.get_main_option('pyramid_configuration')
    return get_appsettings(pyramid_config)['sqlalchemy.url']

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
pyramid_config = config.get_main_option('pyramid_configuration')
config.set_main_option('sqlalchemy.url', get_pyramid_sa_url(config))

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(pyramid_config)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(url=url)

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online():
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    engine = engine_from_config(
        config.get_section(config.config_ini_section),
        prefix='sqlalchemy.',
        poolclass=pool.NullPool)

    connection = engine.connect()
    context.configure(connection=connection, target_metadata=target_metadata)

    try:
        with context.begin_transaction():
            context.run_migrations()
    finally:
        connection.close()

if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

A  => migration/script.py.mako +22 -0
@@ 1,22 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}

"""

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}

from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

def upgrade():
    ${upgrades if upgrades else "pass"}


def downgrade():
    ${downgrades if downgrades else "pass"}

A  => migration/versions/38f5ad30fe6f_create_initial_table.py +50 -0
@@ 1,50 @@
"""Create initial tables

Revision ID: 38f5ad30fe6f
Revises: None
Create Date: 2013-01-11 06:13:22.487819

"""

# revision identifiers, used by Alembic.
revision = '38f5ad30fe6f'
down_revision = None

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.create_table('board',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('created_at', sa.DateTime(), nullable=True),
        sa.Column('updated_at', sa.DateTime(), nullable=True),
        sa.Column('slug', sa.String(length=64), nullable=False),
        sa.Column('title', sa.Unicode(length=255), nullable=False),
        sa.PrimaryKeyConstraint('id'),
        sa.UniqueConstraint('slug')
    )
    op.create_table('topic',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('created_at', sa.DateTime(), nullable=True),
        sa.Column('updated_at', sa.DateTime(), nullable=True),
        sa.Column('board_id', sa.Integer(), nullable=False),
        sa.Column('topic', sa.Unicode(length=255), nullable=False),
        sa.ForeignKeyConstraint(['board_id'], ['board.id'], ),
        sa.PrimaryKeyConstraint('id')
    )
    op.create_table('post',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('created_at', sa.DateTime(), nullable=True),
        sa.Column('updated_at', sa.DateTime(), nullable=True),
        sa.Column('topic_id', sa.Integer(), nullable=False),
        sa.Column('body', sa.Unicode(), nullable=False),
        sa.ForeignKeyConstraint(['topic_id'], ['topic.id'], ),
        sa.PrimaryKeyConstraint('id')
    )


def downgrade():
    op.drop_table('post')
    op.drop_table('topic')
    op.drop_table('board')

A  => production.ini +59 -0
@@ 1,59 @@
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###

[app:main]
use = egg:fanboi2

pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
    pyramid_tm

sqlalchemy.url = sqlite:///%(here)s/fanboi2.sqlite

[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543

###
# logging configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###

[loggers]
keys = root, fanboi2, sqlalchemy

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console

[logger_fanboi2]
level = WARN
handlers =
qualname = fanboi2

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s

A  => setup.cfg +27 -0
@@ 1,27 @@
[nosetests]
match=^test
nocapture=1
cover-package=fanboi2
with-coverage=1
cover-erase=1

[compile_catalog]
directory = fanboi2/locale
domain = fanboi2
statistics = true

[extract_messages]
add_comments = TRANSLATORS:
output_file = fanboi2/locale/fanboi2.pot
width = 80

[init_catalog]
domain = fanboi2
input_file = fanboi2/locale/fanboi2.pot
output_dir = fanboi2/locale

[update_catalog]
domain = fanboi2
input_file = fanboi2/locale/fanboi2.pot
output_dir = fanboi2/locale
previous = true

A  => setup.py +45 -0
@@ 1,45 @@
import os

from setuptools import setup, find_packages

here = os.path.abspath(os.path.dirname(__file__))
readme = open(os.path.join(here, 'README.rst')).read()
changes = open(os.path.join(here, 'CHANGES.rst')).read()

requires = [
    'pyramid',
    'sqlalchemy',
    'transaction',
    'pyramid_tm',
    'pyramid_debugtoolbar',
    'zope.sqlalchemy',
    'waitress',
    'alembic',
    'wtforms',
    'webtest',
    ]

setup(name='fanboi2',
      version='0.0',
      description='fanboi2',
      long_description=readme + '\n\n' + changes,
      classifiers=[
        "programming language :: python",
        "framework :: pyramid",
        "topic :: internet :: www/http",
        "topic :: internet :: www/http :: wsgi :: application",
        ],
      author='',
      author_email='',
      url='',
      keywords='web wsgi bfg pylons pyramid',
      packages=find_packages(),
      include_package_data=True,
      zip_safe=False,
      test_suite='fanboi2',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = fanboi2:main
      """,
      )