From e3d4616e624c4356c4233e184108bc3144deccc9 Mon Sep 17 00:00:00 2001 From: Phuoc CAO Date: Thu, 21 Apr 2022 13:49:31 +0200 Subject: [PATCH] initial upload --- .coveragerc | 3 + .gitignore | 67 +++------ CHANGES.txt | 4 + MANIFEST.in | 2 + README.md | 44 +++++- README.txt | 43 ++++++ cao_blogr.sqlite | Bin 0 -> 28672 bytes cao_blogr/__init__.py | 27 ++++ cao_blogr/alembic/env.py | 58 ++++++++ cao_blogr/alembic/script.py.mako | 22 +++ .../alembic/versions/20181223_5899f27f265f.py | 43 ++++++ .../alembic/versions/20220419_bbacde35234d.py | 26 ++++ .../alembic/versions/20220419_e7889eab89c0.py | 28 ++++ cao_blogr/alembic/versions/README.txt | 1 + cao_blogr/forms.py | 26 ++++ cao_blogr/models/__init__.py | 78 +++++++++++ cao_blogr/models/blog_record.py | 33 +++++ cao_blogr/models/meta.py | 16 +++ cao_blogr/models/user.py | 34 +++++ cao_blogr/pshell.py | 13 ++ cao_blogr/routes.py | 12 ++ cao_blogr/scripts/__init__.py | 1 + cao_blogr/scripts/initialize_db.py | 49 +++++++ cao_blogr/security.py | 15 ++ cao_blogr/services/__init__.py | 0 cao_blogr/services/blog_record.py | 56 ++++++++ cao_blogr/services/user.py | 32 +++++ cao_blogr/static/favicon.ico | Bin 0 -> 1150 bytes cao_blogr/static/pyramid-16x16.png | Bin 0 -> 1319 bytes cao_blogr/static/pyramid.png | Bin 0 -> 12901 bytes cao_blogr/static/theme.css | 132 ++++++++++++++++++ cao_blogr/templates/404.jinja2 | 8 ++ cao_blogr/templates/apropos.jinja2 | 33 +++++ cao_blogr/templates/blog.jinja2 | 31 ++++ cao_blogr/templates/blog_edit.jinja2 | 51 +++++++ cao_blogr/templates/home.jinja2 | 34 +++++ cao_blogr/templates/layout.jinja2 | 104 ++++++++++++++ cao_blogr/templates/login.jinja2 | 28 ++++ cao_blogr/templates/user_add.jinja2 | 34 +++++ cao_blogr/templates/user_pwd.jinja2 | 30 ++++ cao_blogr/templates/users.jinja2 | 32 +++++ cao_blogr/tests.py | 66 +++++++++ cao_blogr/views/__init__.py | 0 cao_blogr/views/blog.py | 68 +++++++++ cao_blogr/views/default.py | 116 +++++++++++++++ cao_blogr/views/notfound.py | 7 + development.ini | 80 +++++++++++ production.ini | 74 ++++++++++ pytest.ini | 3 + rtd.txt | 1 + setup.py | 66 +++++++++ 51 files changed, 1677 insertions(+), 54 deletions(-) create mode 100644 .coveragerc create mode 100644 CHANGES.txt create mode 100644 MANIFEST.in create mode 100644 README.txt create mode 100644 cao_blogr.sqlite create mode 100644 cao_blogr/__init__.py create mode 100644 cao_blogr/alembic/env.py create mode 100644 cao_blogr/alembic/script.py.mako create mode 100644 cao_blogr/alembic/versions/20181223_5899f27f265f.py create mode 100644 cao_blogr/alembic/versions/20220419_bbacde35234d.py create mode 100644 cao_blogr/alembic/versions/20220419_e7889eab89c0.py create mode 100644 cao_blogr/alembic/versions/README.txt create mode 100644 cao_blogr/forms.py create mode 100644 cao_blogr/models/__init__.py create mode 100644 cao_blogr/models/blog_record.py create mode 100644 cao_blogr/models/meta.py create mode 100644 cao_blogr/models/user.py create mode 100644 cao_blogr/pshell.py create mode 100644 cao_blogr/routes.py create mode 100644 cao_blogr/scripts/__init__.py create mode 100644 cao_blogr/scripts/initialize_db.py create mode 100644 cao_blogr/security.py create mode 100644 cao_blogr/services/__init__.py create mode 100644 cao_blogr/services/blog_record.py create mode 100644 cao_blogr/services/user.py create mode 100644 cao_blogr/static/favicon.ico create mode 100644 cao_blogr/static/pyramid-16x16.png create mode 100644 cao_blogr/static/pyramid.png create mode 100644 cao_blogr/static/theme.css create mode 100644 cao_blogr/templates/404.jinja2 create mode 100644 cao_blogr/templates/apropos.jinja2 create mode 100644 cao_blogr/templates/blog.jinja2 create mode 100644 cao_blogr/templates/blog_edit.jinja2 create mode 100644 cao_blogr/templates/home.jinja2 create mode 100644 cao_blogr/templates/layout.jinja2 create mode 100644 cao_blogr/templates/login.jinja2 create mode 100644 cao_blogr/templates/user_add.jinja2 create mode 100644 cao_blogr/templates/user_pwd.jinja2 create mode 100644 cao_blogr/templates/users.jinja2 create mode 100644 cao_blogr/tests.py create mode 100644 cao_blogr/views/__init__.py create mode 100644 cao_blogr/views/blog.py create mode 100644 cao_blogr/views/default.py create mode 100644 cao_blogr/views/notfound.py create mode 100644 development.ini create mode 100644 production.ini create mode 100644 pytest.ini create mode 100644 rtd.txt create mode 100644 setup.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..9ece22e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = pyramid_blogr +omit = pyramid_blogr/test* diff --git a/.gitignore b/.gitignore index b24d71e..1853d98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,21 @@ -# These are some examples of commonly ignored file patterns. -# You should customize this list as applicable to your project. -# Learn more about .gitignore: -# https://www.atlassian.com/git/tutorials/saving-changes/gitignore - -# Node artifact files -node_modules/ +*.egg +*.egg-info +*.pyc +*$py.class +*~ +.coverage +coverage.xml +build/ dist/ - -# Compiled Java class files -*.class - -# Compiled Python bytecode -*.py[cod] - -# Log files -*.log - -# Package files -*.jar - -# Maven -target/ -dist/ - -# JetBrains IDE -.idea/ - -# Unit test reports -TEST*.xml - -# Generated by MacOS +.tox/ +nosetests.xml +env*/ +tmp/ +Data.fs* +*.sublime-project +*.sublime-workspace +.*.sw? +.sw? .DS_Store - -# Generated by Windows -Thumbs.db - -# Applications -*.app -*.exe -*.war - -# Large media files -*.mp4 -*.tiff -*.avi -*.flv -*.mov -*.wmv - +coverage +test diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..c9f9b0f --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,4 @@ +0.1 +--- + +- Initial version. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..168dcff --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include cao_blogr *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/README.md b/README.md index 39af52c..dd66181 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,44 @@ This README would normally document whatever steps are necessary to get your app ### How do I get set up? ### -* Summary of set up -* Configuration -* Dependencies -* Database configuration -* How to run tests -* Deployment instructions +- Change directory into your newly created Pyramid project. + + cd cao_blogr + +- Create a Python virtual environment. + + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Initialize and upgrade the database using Alembic. + + - Generate your first revision. + + env/bin/alembic -c development.ini revision --autogenerate -m "init" + + - Upgrade to that revision. + + env/bin/alembic -c development.ini upgrade head + +- Load default data into the database using a script. + + env/bin/initialize_cao_blogr_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini + ### Contribution guidelines ### diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..99bb47a --- /dev/null +++ b/README.txt @@ -0,0 +1,43 @@ +cao_blogr +========= + +Getting Started +--------------- + +- Change directory into your newly created project. + + cd cao_blogr + +- Create a Python virtual environment. + + python3 -m venv env + +- Upgrade packaging tools. + + env/bin/pip install --upgrade pip setuptools + +- Install the project in editable mode with its testing requirements. + + env/bin/pip install -e ".[testing]" + +- Initialize and upgrade the database using Alembic. + + - Generate your first revision. + + env/bin/alembic -c development.ini revision --autogenerate -m "init" + + - Upgrade to that revision. + + env/bin/alembic -c development.ini upgrade head + +- Load default data into the database using a script. + + env/bin/initialize_cao_blogr_db development.ini + +- Run your project's tests. + + env/bin/pytest + +- Run your project. + + env/bin/pserve development.ini diff --git a/cao_blogr.sqlite b/cao_blogr.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..b34b3290881ccf2d8b5e25bef12bb6b67e7dbb16 GIT binary patch literal 28672 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 literal 0 HcmV?d00001 diff --git a/cao_blogr/__init__.py b/cao_blogr/__init__.py new file mode 100644 index 0000000..24c5693 --- /dev/null +++ b/cao_blogr/__init__.py @@ -0,0 +1,27 @@ +from pyramid.config import Configurator +from pyramid.authentication import AuthTktAuthenticationPolicy +from pyramid.authorization import ACLAuthorizationPolicy +from pyramid.session import SignedCookieSessionFactory + +from .services.user import groupfinder + + +def main(global_config, **settings): + """ This function returns a Pyramid WSGI application. + """ + # session factory + my_session_factory = SignedCookieSessionFactory('mGcAJn2HmNH6Hc') + + authentication_policy = AuthTktAuthenticationPolicy('wMWvAWMZnp6Lch', + callback=groupfinder, hashalg='sha512', timeout=36000) + authorization_policy = ACLAuthorizationPolicy() + with Configurator(settings=settings, + root_factory='cao_blogr.security.RootFactory', + authentication_policy=authentication_policy, + authorization_policy=authorization_policy) as config: + config.include('pyramid_jinja2') + config.include('.models') + config.include('.routes') + config.set_session_factory(my_session_factory) + config.scan() + return config.make_wsgi_app() diff --git a/cao_blogr/alembic/env.py b/cao_blogr/alembic/env.py new file mode 100644 index 0000000..9546957 --- /dev/null +++ b/cao_blogr/alembic/env.py @@ -0,0 +1,58 @@ +"""Pyramid bootstrap environment. """ +from alembic import context +from pyramid.paster import get_appsettings, setup_logging +from sqlalchemy import engine_from_config + +from pyramid_blogr.models.meta import Base + +config = context.config + +setup_logging(config.config_file_name) + +settings = get_appsettings(config.config_file_name) +target_metadata = Base.metadata + + +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. + + """ + context.configure(url=settings['sqlalchemy.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(settings, prefix='sqlalchemy.') + + 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() diff --git a/cao_blogr/alembic/script.py.mako b/cao_blogr/alembic/script.py.mako new file mode 100644 index 0000000..535780d --- /dev/null +++ b/cao_blogr/alembic/script.py.mako @@ -0,0 +1,22 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + +def upgrade(): + ${upgrades if upgrades else "pass"} + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/cao_blogr/alembic/versions/20181223_5899f27f265f.py b/cao_blogr/alembic/versions/20181223_5899f27f265f.py new file mode 100644 index 0000000..0b31f9a --- /dev/null +++ b/cao_blogr/alembic/versions/20181223_5899f27f265f.py @@ -0,0 +1,43 @@ +"""init + +Revision ID: 5899f27f265f +Revises: +Create Date: 2018-12-23 16:39:13.677058 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5899f27f265f' +down_revision = None +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('entries', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.Unicode(length=255), nullable=False), + sa.Column('body', sa.UnicodeText(), nullable=True), + sa.Column('created', sa.DateTime(), nullable=True), + sa.Column('edited', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('pk_entries')), + sa.UniqueConstraint('title', name=op.f('uq_entries_title')) + ) + op.create_table('users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.Unicode(length=255), nullable=False), + sa.Column('password', sa.Unicode(length=255), nullable=False), + sa.Column('last_logged', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('pk_users')), + sa.UniqueConstraint('name', name=op.f('uq_users_name')) + ) + # ### end Alembic commands ### + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('users') + op.drop_table('entries') + # ### end Alembic commands ### diff --git a/cao_blogr/alembic/versions/20220419_bbacde35234d.py b/cao_blogr/alembic/versions/20220419_bbacde35234d.py new file mode 100644 index 0000000..47b04ea --- /dev/null +++ b/cao_blogr/alembic/versions/20220419_bbacde35234d.py @@ -0,0 +1,26 @@ +"""init + +Revision ID: bbacde35234d +Revises: e7889eab89c0 +Create Date: 2022-04-19 17:09:50.728285 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bbacde35234d' +down_revision = 'e7889eab89c0' +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('entries', sa.Column('body_html', sa.UnicodeText(), nullable=True)) + # ### end Alembic commands ### + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('entries', 'body_html') + # ### end Alembic commands ### diff --git a/cao_blogr/alembic/versions/20220419_e7889eab89c0.py b/cao_blogr/alembic/versions/20220419_e7889eab89c0.py new file mode 100644 index 0000000..a0ac311 --- /dev/null +++ b/cao_blogr/alembic/versions/20220419_e7889eab89c0.py @@ -0,0 +1,28 @@ +"""init + +Revision ID: e7889eab89c0 +Revises: 5899f27f265f +Create Date: 2022-04-19 16:21:57.531003 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e7889eab89c0' +down_revision = '5899f27f265f' +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('entries', sa.Column('tag', sa.Unicode(), nullable=True)) + op.add_column('entries', sa.Column('topic', sa.Unicode(), nullable=True)) + # ### end Alembic commands ### + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('entries', 'topic') + op.drop_column('entries', 'tag') + # ### end Alembic commands ### diff --git a/cao_blogr/alembic/versions/README.txt b/cao_blogr/alembic/versions/README.txt new file mode 100644 index 0000000..b0d704d --- /dev/null +++ b/cao_blogr/alembic/versions/README.txt @@ -0,0 +1 @@ +Placeholder for alembic versions diff --git a/cao_blogr/forms.py b/cao_blogr/forms.py new file mode 100644 index 0000000..f442b34 --- /dev/null +++ b/cao_blogr/forms.py @@ -0,0 +1,26 @@ +from wtforms import Form, StringField, TextAreaField, validators +from wtforms import IntegerField, PasswordField +from wtforms.validators import InputRequired, Length +from wtforms.widgets import HiddenInput + +strip_filter = lambda x: x.strip() if x else None + +class BlogCreateForm(Form): + title = StringField('Titre', validators=[InputRequired(), Length(min=1, max=255)], + filters=[strip_filter]) + body = TextAreaField('Corps du texte', validators=[InputRequired(), Length(min=1)], + filters=[strip_filter]) + topic = StringField('Topic', validators=[InputRequired(), Length(min=1, max=255)], + filters=[strip_filter]) + tag = StringField('Tag', validators=[InputRequired(), Length(min=1, max=20)], + filters=[strip_filter]) + +class BlogUpdateForm(BlogCreateForm): + id = IntegerField(widget=HiddenInput()) + + +class UserCreateForm(Form): + username = StringField('Nom', [validators.required(), validators.Length(min=1, max=255)], + filters=[strip_filter]) + password = PasswordField('Mot de passe', validators.required(), [validators.Length(min=6)]) + diff --git a/cao_blogr/models/__init__.py b/cao_blogr/models/__init__.py new file mode 100644 index 0000000..c61b38d --- /dev/null +++ b/cao_blogr/models/__init__.py @@ -0,0 +1,78 @@ +from sqlalchemy import engine_from_config +from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import configure_mappers +import zope.sqlalchemy + +# import or define all models here to ensure they are attached to the +# Base.metadata prior to any initialization routines +from .user import User +from .blog_record import BlogRecord + +# run configure_mappers after defining all of the models to ensure +# all relationships can be setup +configure_mappers() + + +def get_engine(settings, prefix='sqlalchemy.'): + return engine_from_config(settings, prefix) + + +def get_session_factory(engine): + factory = sessionmaker() + factory.configure(bind=engine) + return factory + + +def get_tm_session(session_factory, transaction_manager): + """ + Get a ``sqlalchemy.orm.Session`` instance backed by a transaction. + + This function will hook the session to the transaction manager which + will take care of committing any changes. + + - When using pyramid_tm it will automatically be committed or aborted + depending on whether an exception is raised. + + - When using scripts you should wrap the session in a manager yourself. + For example:: + + import transaction + + engine = get_engine(settings) + session_factory = get_session_factory(engine) + with transaction.manager: + dbsession = get_tm_session(session_factory, transaction.manager) + + """ + dbsession = session_factory() + zope.sqlalchemy.register( + dbsession, transaction_manager=transaction_manager) + return dbsession + + +def includeme(config): + """ + Initialize the model for a Pyramid app. + + Activate this setup using ``config.include('cao_blogr.models')``. + + """ + settings = config.get_settings() + settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' + + # use pyramid_tm to hook the transaction lifecycle to the request + config.include('pyramid_tm') + + # use pyramid_retry to retry a request when transient exceptions occur + config.include('pyramid_retry') + + session_factory = get_session_factory(get_engine(settings)) + config.registry['dbsession_factory'] = session_factory + + # make request.dbsession available for use in Pyramid + config.add_request_method( + # r.tm is the transaction manager used by pyramid_tm + lambda r: get_tm_session(session_factory, r.tm), + 'dbsession', + reify=True + ) diff --git a/cao_blogr/models/blog_record.py b/cao_blogr/models/blog_record.py new file mode 100644 index 0000000..cdb7251 --- /dev/null +++ b/cao_blogr/models/blog_record.py @@ -0,0 +1,33 @@ +import datetime #<- will be used to set default dates on models +from cao_blogr.models.meta import Base #<- we need to import our sqlalchemy metadata from which model classes will inherit +from sqlalchemy import ( + Column, + Integer, + Unicode, #<- will provide Unicode field + UnicodeText, #<- will provide Unicode text field + DateTime, #<- time abstraction field +) +from webhelpers2.text import urlify #<- will generate slugs +from webhelpers2.date import distance_of_time_in_words #<- human friendly dates + + +class BlogRecord(Base): + __tablename__ = 'entries' + id = Column(Integer, primary_key=True) + title = Column(Unicode(255), unique=True, nullable=False) + body = Column(UnicodeText, default=u'') + body_html = Column(UnicodeText, default=u'') + tag = Column(Unicode, default=u'pyramid') + topic = Column(Unicode, default=u'blog') + created = Column(DateTime, default=datetime.datetime.utcnow) + edited = Column(DateTime, default=datetime.datetime.utcnow) + + @property + def slug(self): + return urlify(self.title) + + @property + def created_in_words(self): + return distance_of_time_in_words(self.created, + datetime.datetime.utcnow()) + diff --git a/cao_blogr/models/meta.py b/cao_blogr/models/meta.py new file mode 100644 index 0000000..02285b3 --- /dev/null +++ b/cao_blogr/models/meta.py @@ -0,0 +1,16 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.schema import MetaData + +# Recommended naming convention used by Alembic, as various different database +# providers will autogenerate vastly different names making migrations more +# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html +NAMING_CONVENTION = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s" +} + +metadata = MetaData(naming_convention=NAMING_CONVENTION) +Base = declarative_base(metadata=metadata) diff --git a/cao_blogr/models/user.py b/cao_blogr/models/user.py new file mode 100644 index 0000000..2784dd7 --- /dev/null +++ b/cao_blogr/models/user.py @@ -0,0 +1,34 @@ +import datetime #<- will be used to set default dates on models +from cao_blogr.models.meta import Base #<- we need to import our sqlalchemy metadata from which model classes will inherit +from sqlalchemy import ( + Column, + Integer, + Unicode, #<- will provide Unicode field + UnicodeText, #<- will provide Unicode text field + DateTime, #<- time abstraction field +) + +from passlib.apps import custom_app_context as blogger_pwd_context + + +class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + name = Column(Unicode(255), unique=True, nullable=False) + password = Column(Unicode(255), nullable=False) + last_logged = Column(DateTime, default=datetime.datetime.utcnow) + + def verify_password(self, password): + # is it cleartext? + if password == self.password: + self.set_password(password) + # verify password + result = blogger_pwd_context.verify(password, self.password) + if result: + # pwd OK, set last login date + self.last_logged = datetime.datetime.now() + return result + + def set_password(self, password): + password_hash = blogger_pwd_context.encrypt(password) + self.password = password_hash diff --git a/cao_blogr/pshell.py b/cao_blogr/pshell.py new file mode 100644 index 0000000..b0847ee --- /dev/null +++ b/cao_blogr/pshell.py @@ -0,0 +1,13 @@ +from . import models + + +def setup(env): + request = env['request'] + + # start a transaction + request.tm.begin() + + # inject some vars into the shell builtins + env['tm'] = request.tm + env['dbsession'] = request.dbsession + env['models'] = models diff --git a/cao_blogr/routes.py b/cao_blogr/routes.py new file mode 100644 index 0000000..6dd30ac --- /dev/null +++ b/cao_blogr/routes.py @@ -0,0 +1,12 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_route('home', '/') + config.add_route('apropos', '/apropos') + config.add_route('blog', '/blog/{id:\d+}/{slug}') + config.add_route('blog_edit', '/blog_edit/{id}') + config.add_route('page_search', '/page_search') + config.add_route('login', '/login') + config.add_route('logout', '/logout') + config.add_route('users', '/users') + config.add_route('user_add', '/user_add/{name}') + config.add_route('user_pwd', '/user_pwd/{name}') diff --git a/cao_blogr/scripts/__init__.py b/cao_blogr/scripts/__init__.py new file mode 100644 index 0000000..5bb534f --- /dev/null +++ b/cao_blogr/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/cao_blogr/scripts/initialize_db.py b/cao_blogr/scripts/initialize_db.py new file mode 100644 index 0000000..2820508 --- /dev/null +++ b/cao_blogr/scripts/initialize_db.py @@ -0,0 +1,49 @@ +import argparse +import sys + +from pyramid.paster import bootstrap, setup_logging +from sqlalchemy.exc import OperationalError + +from .. import models + + +def setup_models(dbsession): + """ + Add or update models / fixtures in the database. + + """ + + model = models.user.User(name=u'admin', password=u'pcao.7513') + dbsession.add(model) + + +def parse_args(argv): + parser = argparse.ArgumentParser() + parser.add_argument( + 'config_uri', + help='Configuration file, e.g., development.ini', + ) + return parser.parse_args(argv[1:]) + + +def main(argv=sys.argv): + args = parse_args(argv) + setup_logging(args.config_uri) + env = bootstrap(args.config_uri) + + try: + with env['request'].tm: + dbsession = env['request'].dbsession + setup_models(dbsession) + except OperationalError: + print(''' +Pyramid is having a problem using your SQL database. The problem +might be caused by one of the following things: + +1. You may need to initialize your database tables with `alembic`. + Check your README.txt for description and try to run it. + +2. Your database server may not be running. Check that the + database server referred to by the "sqlalchemy.url" setting in + your "development.ini" file is running. + ''') diff --git a/cao_blogr/security.py b/cao_blogr/security.py new file mode 100644 index 0000000..c3c2ab1 --- /dev/null +++ b/cao_blogr/security.py @@ -0,0 +1,15 @@ +from pyramid.security import ( + Allow, + Authenticated, + DENY_ALL, + ) + +class RootFactory(object): + """Defines an ACL for groups/permissions mapping""" + __acl__ = [ (Allow, Authenticated, 'view'), + (Allow, 'group:administrators', 'manage'), + DENY_ALL, + ] + def __init__(self, request): + pass + diff --git a/cao_blogr/services/__init__.py b/cao_blogr/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cao_blogr/services/blog_record.py b/cao_blogr/services/blog_record.py new file mode 100644 index 0000000..5a60560 --- /dev/null +++ b/cao_blogr/services/blog_record.py @@ -0,0 +1,56 @@ +import sqlalchemy as sa +import datetime #<- will be used to set default dates on models + +from paginate_sqlalchemy import SqlalchemyOrmPage #<- provides pagination +from ..models.blog_record import BlogRecord +from markdown2 import Markdown + + +class BlogRecordService(object): + + @classmethod + def all(cls, request): + query = request.dbsession.query(BlogRecord) + return query.order_by(sa.desc(BlogRecord.created)) + + @classmethod + def by_id(cls, request, _id): + query = request.dbsession.query(BlogRecord) + return query.get(_id) + + @classmethod + def get_paginator(cls, request, page=1): + query = request.dbsession.query(BlogRecord) + query = query.order_by(sa.desc(BlogRecord.created)) + query_params = request.GET.mixed() + + def url_maker(link_page): + # replace page param with values generated by paginator + query_params['page'] = link_page + return request.current_route_url(_query=query_params) + + return SqlalchemyOrmPage(query, page, items_per_page=5, + url_maker=url_maker) + + @classmethod + def proc_after_create(cls, request, _id): + entry = request.dbsession.query(BlogRecord).get(_id) + # set default values + if entry.tag == '': + entry.tag = 'pyramid' + if entry.topic == '': + entry.topic = 'blog' + # convertir mardown en HTML + markdowner = Markdown() + entry.body_html = markdowner.convert(entry.body) + return + + @classmethod + def proc_after_update(cls, request, _id): + entry = request.dbsession.query(BlogRecord).get(_id) + entry.edited = datetime.datetime.now() + # convertir mardown en HTML + markdowner = Markdown() + entry.body_html = markdowner.convert(entry.body) + return + diff --git a/cao_blogr/services/user.py b/cao_blogr/services/user.py new file mode 100644 index 0000000..2d771f1 --- /dev/null +++ b/cao_blogr/services/user.py @@ -0,0 +1,32 @@ +import sqlalchemy as sa +from ..models.user import User + + +class UserService(object): + + @classmethod + def all(cls, request): + items = request.dbsession.query(User).order_by(sa.asc(User.name)).all() + return items + + @classmethod + def by_name(cls, request, name ): + item = request.dbsession.query(User).filter(User.name == name).first() + return item + + @classmethod + def delete(cls, request, id): + request.dbsession.query(User).filter(User.id == id).delete(synchronize_session=False) + return + +def groupfinder(userid, request): + + if userid: + # user name is 'admin' ? + if userid == 'admin': + return ['group:administrators'] + else: + return [] # it means that userid is logged in + else: + # it returns None if userid isn't logged in + return None diff --git a/cao_blogr/static/favicon.ico b/cao_blogr/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1f2210fd91c7b0afb15329a8151510107cf52c88 GIT binary patch literal 1150 zcmcIiPlyt66duL@{~=hUgU!u62)dDzEpO8jwM#%f8{wd++<+_vX!ShN1*`>2ws{Y3f8o zQ3n)7-9<+Tkv}*F)@~^&iug@hwOV!i{r*BA5O@;~htHzX=;>fExc{$b4u?aV$z=M9 zeZY6QTz=(rI^Wmpb=ST*e{m3t#n-7+>RF@FaJAd*qjI@??D2Ts5uoiQGd z$#H>Fsr)&ePU&5a%gO#$tMyzU5U{)D^Z5YbHk*yeX0v_96gr*GBd&j6T&-3*v)TLw zcLamMFk3E{2fbd8o#${kKJ3%*u|lEXM+_7S1>jDQOeR^q&*v-hd-v7dZucR+Cx}EM zs8lMD&1PA>-EQyh>*r(S8>!W5R+GtO0MD1rH5$$3Xf!(H_w(yuF!)+16oOu_2keK* zWJ0S{sviWiSS-wZKEKV^aCNiUd@PYj*7z-foa1)86%sC;PCrHcRw|W((P;cuDwU2G zi-n$W>-Ac~Fw9{j5_yRwyDN#gT<(PMoO!+8BIbbeL95lWeThWEM)+2%b>w!t--SY< zvv@rIyjH6{;dHx^a5PO%5CfS^cJ)91{H~tlv)Rnxa=GgLe*XcVyNMdmHunH7v;d1! f0OmVD@e<(9uN}Vtc!k*cCxDL(0H$zFglNA3G#>~c literal 0 HcmV?d00001 diff --git a/cao_blogr/static/pyramid-16x16.png b/cao_blogr/static/pyramid-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..979203112e76ba4cfdb8cd6f108f4275e987d99a GIT binary patch literal 1319 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xd_B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxR5#hc&_u!9QqR!T z(8R(}N5ROz&{*HVSl`fC*U-qyz|zXlQ~?TIxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7KMf`)Hma%LWg zuL;)R>ucqiS6q^qmz?V9Vygr+LN7Bj#mdRl*wM}0&C<--)xyxw)!5S9(bdAp)YZVy zz`)Yd%><^`B|o_|H#M&WrZ)wl*Ab^)P+G_>0NU)5T9jFqn&MWJpQ`}&vsET;x0vHJ z52`l>w_7Z5>eUB2MjsTjNHGl)0wy026P|8?9C*r4%>yR)B4E1CQ{KVEz`!`m)5S5Q z;#SXOUk#T)k>lxKj4~&v95M;sSJp8lNikLV)bX~~P1`qaNLZPZ<6^QgASli8jmk<% zZdJ~1%}JBkHhu^^q;ghbe{lMjv_0X)ug#yI+xz_S{hiM(g*saf_f6Pt#!wis>2u+! z{;RjXUlmP?^Kq|yJMn}2uJ~!L3EU-*{+zn6Ya6$jYueOB4G#Lx+=R;oM4sqe*Sq0$ zQ2Nf{-BFQxlX@Dog;`l=SkyQh`W)n22F}BoCTQYYC=p8g*kiK(1`FEnD$cY`NZX8<>GJicU)2$Vj5iRl-7k*od z3K)m{Gsp@4JM)eDo7z=gtG;>hu{QvcXxLU8eD@D+$BKJ@RM`zyYKvG z-8a3ur|aAMt1Vr-yH|CEDauQLkO+_f002lzQdIf%fB4Ui0QY*V)U3(^0FZ<%L_`#& zL`1-fj&^1i)}{b}Bq%eIA9uoG!laCfFcwoR zb`OTl9xm%u?u}UK6Z+-0LfvF1uNzRlu;BSs+a-xXQEAzvn#Z125}lrEE$o@!cYog? z@lko^ANF`uyQDsu%o2*s(%P^-sbKEJ1>90;Svb?qHr@sbgo4>hFv21pO(baNe1U?G_am z$%uaYhJupCKc=2k-Lpftu1m0%A~@dHZKRf6W*s6Qm&D`7K|3 zP8#?(KABe7=AZNd-k*6CTcqHJ?f3yA6ws8mf*wHc;}7VpNW)zn=9RJ4PSI>0zxN+V zk#)jtw`7ILRrYRCqD>sB@)+LaZvtH~TpCmeT z5;T(}&;kNeCnT`+Is{plpj-ki?E!QC9#b�i5=5IxreNAbVsKKM4p@aIXvt)VjX~ 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 +

Pyramid Starter project

+

404 Page Not Found

+ +{% endblock content %} diff --git a/cao_blogr/templates/apropos.jinja2 b/cao_blogr/templates/apropos.jinja2 new file mode 100644 index 0000000..bc08d18 --- /dev/null +++ b/cao_blogr/templates/apropos.jinja2 @@ -0,0 +1,33 @@ +{% extends "layout.jinja2" %} + +{% block content %} +
+
+
+
+
+
+ L'argent qu'on possède est l'instrument de la liberté; celui qu'on pourchasse est celui de la servitude. +
+
+ +
+
+
+
+
+
+ L'intelligence ce n'est pas ce que l'on sait mais ce que l'on fait quand on ne sait pas. +
+
+
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/cao_blogr/templates/blog.jinja2 b/cao_blogr/templates/blog.jinja2 new file mode 100644 index 0000000..cc3ca06 --- /dev/null +++ b/cao_blogr/templates/blog.jinja2 @@ -0,0 +1,31 @@ +{% extends "cao_blogr:templates/layout.jinja2" %} + +{% block content %} + {% if request.authenticated_userid %} +

+ [ Retour ] + [ Modifier ] + +

+ {% endif %} + +
+

{{ entry.body_html | safe }}

+
+ {% if request.authenticated_userid %} +

+ Topic : {{ entry.topic }} +  |  + Tag : {{ entry.tag }} +  |  + Créé le : {{ entry.created.strftime("%d-%m-%Y - %H:%M") }} +  |  + Modifié le : {{ entry.edited.strftime("%d-%m-%Y - %H:%M") }} +

+ {% else %} +

+ Créé : {{ entry.created_in_words }} +

+ {% endif %} + +{% endblock %} diff --git a/cao_blogr/templates/blog_edit.jinja2 b/cao_blogr/templates/blog_edit.jinja2 new file mode 100644 index 0000000..17e6d30 --- /dev/null +++ b/cao_blogr/templates/blog_edit.jinja2 @@ -0,0 +1,51 @@ +{% extends "cao_blogr:templates/layout.jinja2" %} + +{% block content %} +
+ + {% for error in form.title.errors %} +
{{ error }}
+ {% endfor %} +
+ + {{ form.title(class_='form-control') }} +
+ + {% for error in form.body.errors %} +
{{ error }}
+ {% endfor %} +
+ + {{ form.body(class_='form-control', cols="35", rows="20") }} +
+ + {% for error in form.topic.errors %} +
{{ error }}
+ {% endfor %} +
+ + {{ form.topic(class_='form-control') }} +
+ + {% for error in form.tag.errors %} +
{{ error }}
+ {% endfor %} +
+ + {{ form.tag(class_='form-control') }} +
+ +
+ Retour + + {% if action == 'edit' %} + + {% endif %} +
+ + +
+ + {% endblock %} diff --git a/cao_blogr/templates/home.jinja2 b/cao_blogr/templates/home.jinja2 new file mode 100644 index 0000000..c4e5797 --- /dev/null +++ b/cao_blogr/templates/home.jinja2 @@ -0,0 +1,34 @@ +{% extends "layout.jinja2" %} + +{% block content %} + + {% if request.authenticated_userid %} +

+ [Nouveau post] +

+ {% endif%} + + {% if paginator.items %} + + {% for entry in paginator.items %} +
+ {{ entry.created.strftime("%d-%m-%Y") }}   + +  {{ entry.title }} + +
+
+  {{ entry.tag }} +
+ + {% endfor %} + + {{ paginator.pager() |safe }} + + {% else %} + +

No blog entries found.

+ + {% endif %} + +{% endblock %} diff --git a/cao_blogr/templates/layout.jinja2 b/cao_blogr/templates/layout.jinja2 new file mode 100644 index 0000000..490ef9b --- /dev/null +++ b/cao_blogr/templates/layout.jinja2 @@ -0,0 +1,104 @@ + + + + + + + + + + {{page_title}} + + + + + + + + + + + + + + + + + +
+
+ + {% if page_title %} +

{{ page_title }}

+ {% endif %} +
+
+ {% for queue in ['', 'info', 'success', 'warning', 'danger'] %} + {% for message in request.session.pop_flash(queue) %} +
+ + {{ message }} +
+ {% endfor %} + {% endfor %} +
+ + + {% block content %} +

No content

+ {% endblock content %} +
+
+
+
+ + +
+
+

© 2017 - Phuoc Cao + + {% if request.authenticated_userid == 'admin' %} +  |  + + {% endif %} + +

+
+
+ + + + + + + diff --git a/cao_blogr/templates/login.jinja2 b/cao_blogr/templates/login.jinja2 new file mode 100644 index 0000000..62fef50 --- /dev/null +++ b/cao_blogr/templates/login.jinja2 @@ -0,0 +1,28 @@ +{% extends "layout.jinja2" %} + +{% block content %} + +
+
+ +
+

Se connecter

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +{% endblock %} diff --git a/cao_blogr/templates/user_add.jinja2 b/cao_blogr/templates/user_add.jinja2 new file mode 100644 index 0000000..b6e082b --- /dev/null +++ b/cao_blogr/templates/user_add.jinja2 @@ -0,0 +1,34 @@ +{% extends "cao_blogr:templates/layout.jinja2" %} + +{% block content %} + +
+ + {% for error in form.username.errors %} +
{{ error }}
+ {% endfor %} + +
+ + {{form.username(class_='form-control')}} +
+ + {% for error in form.password.errors %} +
{{error}}
+ {% endfor %} + +
+ + {{form.password(class_='form-control')}} +
+ +
+ Retour + +
+ + +
+ +{% endblock %} diff --git a/cao_blogr/templates/user_pwd.jinja2 b/cao_blogr/templates/user_pwd.jinja2 new file mode 100644 index 0000000..fb7df81 --- /dev/null +++ b/cao_blogr/templates/user_pwd.jinja2 @@ -0,0 +1,30 @@ +{% extends "cao_blogr:templates/layout.jinja2" %} + +{% block content %} + +
+ +
+ + +
+ +
+
+ Dernière connexion : + {{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }}
+
+ +
+ Retour + + {% if name != 'new' %} + + {% endif %} +
+ +
+ +{% endblock %} diff --git a/cao_blogr/templates/users.jinja2 b/cao_blogr/templates/users.jinja2 new file mode 100644 index 0000000..22ef3f5 --- /dev/null +++ b/cao_blogr/templates/users.jinja2 @@ -0,0 +1,32 @@ +{% extends "layout.jinja2" %} + +{% block content %} +

+ + Retour + + Nouvel utilisateur +

+ + + + + + + + + + {% for entry in users %} + + + + + + {% endfor %} +
No IdNomDernière connexion
{{ entry.id }} + + {{ entry.name }} + + {{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }}
+ +{% endblock %} diff --git a/cao_blogr/tests.py b/cao_blogr/tests.py new file mode 100644 index 0000000..2f1ec9e --- /dev/null +++ b/cao_blogr/tests.py @@ -0,0 +1,66 @@ +import unittest + +from pyramid import testing + +import transaction + + +def dummy_request(dbsession): + return testing.DummyRequest(dbsession=dbsession) + + +class BaseTest(unittest.TestCase): + def setUp(self): + self.config = testing.setUp(settings={ + 'sqlalchemy.url': 'sqlite:///:memory:' + }) + self.config.include('.models') + settings = self.config.get_settings() + + from .models import ( + get_engine, + get_session_factory, + get_tm_session, + ) + + self.engine = get_engine(settings) + session_factory = get_session_factory(self.engine) + + self.session = get_tm_session(session_factory, transaction.manager) + + def init_database(self): + from .models.meta import Base + Base.metadata.create_all(self.engine) + + def tearDown(self): + from .models.meta import Base + + testing.tearDown() + transaction.abort() + Base.metadata.drop_all(self.engine) + + +class TestMyViewSuccessCondition(BaseTest): + + def setUp(self): + super(TestMyViewSuccessCondition, self).setUp() + self.init_database() + + from .models import MyModel + + model = MyModel(name='one', value=55) + self.session.add(model) + + def test_passing_view(self): + from .views.default import my_view + info = my_view(dummy_request(self.session)) + self.assertEqual(info['one'].name, 'one') + self.assertEqual(info['project'], 'cao_blogr') + + +class TestMyViewFailureCondition(BaseTest): + + def test_failing_view(self): + from .views.default import my_view + info = my_view(dummy_request(self.session)) + self.assertEqual(info.status_int, 500) diff --git a/cao_blogr/views/__init__.py b/cao_blogr/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cao_blogr/views/blog.py b/cao_blogr/views/blog.py new file mode 100644 index 0000000..6639d82 --- /dev/null +++ b/cao_blogr/views/blog.py @@ -0,0 +1,68 @@ +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPNotFound, HTTPFound +from ..models.blog_record import BlogRecord +from ..services.blog_record import BlogRecordService +from ..forms import BlogCreateForm, BlogUpdateForm + + +@view_config(route_name='blog', + renderer='cao_blogr:templates/blog.jinja2') +def blog(request): + # get post id from request + blog_id = request.matchdict['id'] + entry = BlogRecordService.by_id(request, blog_id) + + # just created ? convert body to html + if entry.body_html == '': + BlogRecordService.proc_after_create(request, blog_id) + + if not entry: + request.session.flash(u"Page non trouvée : %s" % blog_id, 'warning') + return HTTPFound(location=request.route_url('home')) + return { + 'page_title': entry.title, + 'entry': entry + } + + +@view_config(route_name='blog_edit', + renderer='cao_blogr:templates/blog_edit.jinja2', + permission='view') +def blog_edit(request): + # get post id from request + blog_id = request.matchdict['id'] + url = request.route_url('blog_edit',id=blog_id) + + if blog_id == '0': + # create a new post + entry = BlogRecord() + form = BlogCreateForm(request.POST) + else: + # modify post + entry = BlogRecordService.by_id(request, blog_id) + if not entry: + request.session.flash(u"Page non trouvée : %s" % blog_id, 'warning') + return HTTPFound(location=request.route_url('home')) + form = BlogUpdateForm(request.POST, entry) + + if 'form.submitted' in request.params and form.validate(): + if blog_id == '0': + form.populate_obj(entry) + request.dbsession.add(entry) + + return HTTPFound(location=request.route_url('home')) + else: + del form.id # SECURITY: prevent overwriting of primary key + form.populate_obj(entry) + + # after update procedure + BlogRecordService.proc_after_update(request, blog_id) + + return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug)) + + return { + 'page_title': entry.title, + 'url': url, + 'form': form, + } + diff --git a/cao_blogr/views/default.py b/cao_blogr/views/default.py new file mode 100644 index 0000000..f7d3a26 --- /dev/null +++ b/cao_blogr/views/default.py @@ -0,0 +1,116 @@ +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPFound +from pyramid.security import remember, forget +from ..services.user import UserService +from ..services.blog_record import BlogRecordService +from ..forms import UserCreateForm +from ..models.user import User + + +@view_config(route_name='home', + renderer='cao_blogr:templates/home.jinja2') +def home(request): + page = int(request.params.get('page', 1)) + paginator = BlogRecordService.get_paginator(request, page) + return { + 'page_title': "Bienvenue sur mon blog", + 'paginator': paginator + } + + +@view_config(route_name='apropos', + renderer='cao_blogr:templates/apropos.jinja2') +def apropos(request): + + return { + 'page_title': "A propos", + } + + +@view_config(route_name='login', + renderer='cao_blogr:templates/login.jinja2') +def login(request): + username = request.POST.get('username') + + if username: + user = UserService.by_name(request, username) + if user and user.verify_password(request.POST.get('password')): + headers = remember(request, user.name) + request.session.flash("Bienvenue %s !" % username, 'success') + return HTTPFound(location=request.route_url('home'), headers=headers) + else: + headers = forget(request) + request.session.flash("Login et mot de passe invalides. La connexion a échoué.", "danger") + + return { + 'page_title': "", + } + + +@view_config(route_name='logout', renderer='string') +def logout(request): + headers = forget(request) + request.session.flash('Vous avez bien été déconnecté.', 'success') + return HTTPFound(location=request.route_url('home'), headers=headers) + + +@view_config(route_name='users', + renderer='cao_blogr:templates/users.jinja2', permission='manage') +def users(request): + # get all users + users = UserService.all(request) + return { + 'page_title': "Liste des utilisateurs", + 'users': users + } + + +@view_config(route_name='user_add', + renderer='cao_blogr:templates/user_add.jinja2', permission='manage') +def user_add(request): + name = request.matchdict['name'] + + # nouveau + form = UserCreateForm(request.POST) + + if 'form.submitted' in request.params and form.validate(): + # créer nouveau + new_user = User(name=form.username.data) + new_user.set_password(form.password.data.encode('utf8')) + request.dbsession.add(new_user) + return HTTPFound(location=request.route_url('users')) + + return { + 'page_title': 'Nouvel utilsateur', + 'form': form, + 'name': name, + } + + +@view_config(route_name='user_pwd', + renderer='cao_blogr:templates/user_pwd.jinja2', permission='manage') +def user_pwd(request): + # reset password or delete user + name = request.matchdict['name'] + + # lire la fiche du membre + entry = UserService.by_name(request, name) + if not entry: + request.session.flash(u"Utilisateur non trouvé : %s" % name, 'warning') + return HTTPFound(location=request.route_url('users')) + + if 'form.submitted' in request.params: + mdp = request.params["new_password"] + entry.set_password(mdp.encode('utf8')) + return HTTPFound(location=request.route_url('users')) + + if 'form.deleted' in request.params: + UserService.delete(request, entry.id) + request.session.flash("La fiche a été supprimée avec succès.", 'success') + return HTTPFound(location=request.route_url('users')) + + + return { + 'page_title': "Utilisateur : %s" %(entry.name), + 'entry': entry, + } diff --git a/cao_blogr/views/notfound.py b/cao_blogr/views/notfound.py new file mode 100644 index 0000000..69d6e28 --- /dev/null +++ b/cao_blogr/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.jinja2') +def notfound_view(request): + request.response.status = 404 + return {} diff --git a/development.ini b/development.ini new file mode 100644 index 0000000..6f3803b --- /dev/null +++ b/development.ini @@ -0,0 +1,80 @@ +### +# app configuration +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:cao_blogr + +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 + +sqlalchemy.url = sqlite:///%(here)s/cao_blogr.sqlite + +retry.attempts = 3 + +# By default, the toolbar only appears for clients from IP addresses +# '127.0.0.1' and '::1'. +# debugtoolbar.hosts = 127.0.0.1 ::1 + +[pshell] +setup = cao_blogr.pshell.setup + +### +# wsgi server configuration +### + +[alembic] +# path to migration scripts +script_location = cao_blogr/alembic +file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s +# file_template = %%(rev)s_%%(slug)s + +[server:main] +use = egg:waitress#main +listen = localhost:6543 + +### +# logging configuration +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, cao_blogr, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_cao_blogr] +level = DEBUG +handlers = +qualname = cao_blogr + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/production.ini b/production.ini new file mode 100644 index 0000000..5fbf1e4 --- /dev/null +++ b/production.ini @@ -0,0 +1,74 @@ +### +# app configuration +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:cao_blogr + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en + +sqlalchemy.url = sqlite:///%(here)s/cao_blogr.sqlite + +retry.attempts = 3 + +[pshell] +setup = cao_blogr.pshell.setup + +### +# wsgi server configuration +### + +[alembic] +# path to migration scripts +script_location = cao_blogr/alembic +file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s +# file_template = %%(rev)s_%%(slug)s + +[server:main] +use = egg:waitress#main +listen = *:6543 + +### +# logging configuration +# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, cao_blogr, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_cao_blogr] +level = WARN +handlers = +qualname = cao_blogr + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..0f5a4a4 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +testpaths = cao_blogr +python_files = test*.py diff --git a/rtd.txt b/rtd.txt new file mode 100644 index 0000000..93b76e4 --- /dev/null +++ b/rtd.txt @@ -0,0 +1 @@ +pylons-sphinx-themes diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9b8c1e1 --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.txt')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.txt')) as f: + CHANGES = f.read() + +requires = [ + 'plaster_pastedeploy', + 'pyramid', + 'pyramid_jinja2', + 'pyramid_debugtoolbar', + 'waitress', + 'alembic', + 'pyramid_retry', + 'pyramid_tm', + 'SQLAlchemy', + 'transaction', + 'zope.sqlalchemy', + 'wtforms==2.2.1', # form library + 'webhelpers2==2.0', # various web building related helpers + 'paginate==0.5.6', # pagination helpers + 'paginate_sqlalchemy==0.3.0', + 'passlib', +] + +tests_require = [ + 'WebTest >= 1.3.1', # py3 compat + 'pytest >= 3.7.4', + 'pytest-cov', +] + +setup( + name='cao_blogr', + version='0.0', + description='cao_blogr', + 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 pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + entry_points={ + 'paste.app_factory': [ + 'main = cao_blogr:main', + ], + 'console_scripts': [ + 'initialize_cao_blogr_db=cao_blogr.scripts.initialize_db:main', + ], + }, +)