Pyramid Starter project
+404 Page Not Found
+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 0000000..b34b329 Binary files /dev/null and b/cao_blogr.sqlite differ 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 0000000..1f2210f Binary files /dev/null and b/cao_blogr/static/favicon.ico differ diff --git a/cao_blogr/static/pyramid-16x16.png b/cao_blogr/static/pyramid-16x16.png new file mode 100644 index 0000000..9792031 Binary files /dev/null and b/cao_blogr/static/pyramid-16x16.png differ diff --git a/cao_blogr/static/pyramid.png b/cao_blogr/static/pyramid.png new file mode 100644 index 0000000..4ab837b Binary files /dev/null and b/cao_blogr/static/pyramid.png differ diff --git a/cao_blogr/static/theme.css b/cao_blogr/static/theme.css new file mode 100644 index 0000000..04217fb --- /dev/null +++ b/cao_blogr/static/theme.css @@ -0,0 +1,132 @@ +/* Theme inspired from Bootstrap Theme "The Band" */ +/* https://www.w3schools.com/bootstrap/bootstrap_theme_band.asp */ + +body { + font: 400 19px/1.8 Georgia, serif; + color: #777; +} +h3, h4 { + margin: 10px 0 30px 0; + letter-spacing: 10px; + font-size: 20px; + color: #111; +} +.container { + padding: 80px 120px; +} +.person { + border: 10px solid transparent; + margin-bottom: 25px; + width: 80%; + height: 80%; + opacity: 0.7; +} +.person:hover { + border-color: #f1f1f1; +} +.carousel-inner img { + -webkit-filter: grayscale(90%); + filter: grayscale(90%); /* make all photos black and white */ + width: 100%; /* Set width to 100% */ + margin: auto; +} +.carousel-caption h3 { + color: #fff !important; +} +@media (max-width: 600px) { + .carousel-caption { + display: none; /* Hide the carousel text when the screen is less than 600 pixels wide */ + } +} +.bg-1 { + background: #bc2131; + color: #bdbdbd; +} +.bg-1 h3 {color: #fff;} +.bg-1 p {font-style: italic;} +.list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.list-group-item:last-child { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.thumbnail { + padding: 0 0 15px 0; + border: none; + border-radius: 0; +} +.thumbnail p { + margin-top: 15px; + color: #555; +} + +.modal-header, h4, .close { + background-color: #333; + color: #fff !important; + text-align: center; + font-size: 30px; +} +.modal-header, .modal-body { + padding: 40px 50px; +} +.nav-tabs li a { + color: #777; +} +#googleMap { + width: 100%; + height: 400px; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} +.navbar { + font-family: Montserrat, sans-serif; + margin-bottom: 0; + background-color: #bc2131; + border: 0; + font-size: 14px !important; + letter-spacing: 4px; + opacity: 0.9; +} +.navbar li a, .navbar .navbar-brand { + color: #ffffff !important; +} +.navbar-nav li a:hover { + color: #fff !important; +} +.navbar-nav li.active a { + color: #fff !important; + background-color: #29292c !important; +} +.navbar-default .navbar-toggle { + border-color: transparent; +} +.open .dropdown-toggle { + color: #fff; + background-color: #555 !important; +} +.dropdown-menu li a { + color: #000 !important; +} +.dropdown-menu li a:hover { + background-color: red !important; +} +footer { + background-color: #bc2131; + color: #f5f5f5; + padding: 32px; +} +footer a { + color: #f5f5f5; +} +footer a:hover { + color: #777; + text-decoration: none; +} +.form-control { + border-radius: 0; +} +textarea { + resize: none; +} \ No newline at end of file diff --git a/cao_blogr/templates/404.jinja2 b/cao_blogr/templates/404.jinja2 new file mode 100644 index 0000000..aaf1241 --- /dev/null +++ b/cao_blogr/templates/404.jinja2 @@ -0,0 +1,8 @@ +{% extends "layout.jinja2" %} + +{% block content %} +
404 Page Not Found
++ 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. ++
+
+ [ Retour ] + [ Modifier ] + +
+ {% endif %} + +{{ entry.body_html | safe }}
++ 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 %} + + + {% 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 %} + + {% endif%} + + {% if paginator.items %} + + {% for entry in paginator.items %} +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 @@ + + + + + + + + + +No content
+ {% endblock content %} ++ + Retour + + Nouvel utilisateur +
+ +| No Id | +Nom | +Dernière connexion | +
|---|---|---|
| {{ entry.id }} | ++ + {{ entry.name }} + + | +{{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }} | +