final test before release v1.0

This commit is contained in:
2023-01-21 16:43:29 +01:00
parent 59e2a50a3f
commit e8916c454f
15 changed files with 313 additions and 137 deletions

View File

@@ -0,0 +1,26 @@
"""init
Revision ID: a632e375e7dc
Revises:
Create Date: 2023-01-21 11:25:48.517435
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a632e375e7dc'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('entries', 'author')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('entries', sa.Column('author', sa.VARCHAR(length=50), nullable=True))
# ### end Alembic commands ###

View File

@@ -1,55 +0,0 @@
"""init
Revision ID: d335bb2cb9da
Revises:
Create Date: 2023-01-21 08:05:36.719719
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd335bb2cb9da'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tags',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('tag', sa.Unicode(length=25), nullable=True),
sa.Column('tag_name', sa.Unicode(length=25), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_tags'))
)
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'))
)
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('creator', sa.Unicode(length=50), nullable=True),
sa.Column('edited', sa.DateTime(), nullable=True),
sa.Column('editor', sa.Unicode(length=50), nullable=True),
sa.Column('tag', sa.Unicode(length=25), nullable=True),
sa.Column('author', sa.Unicode(length=50), nullable=True),
sa.Column('status', sa.Unicode(length=50), nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('pk_entries')),
sa.UniqueConstraint('title', name=op.f('uq_entries_title'))
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('entries')
op.drop_table('users')
op.drop_table('tags')
# ### end Alembic commands ###

View File

@@ -1,4 +1,4 @@
from wtforms import Form, StringField, TextAreaField, validators
from wtforms import Form, StringField, TextAreaField, SelectField, validators
from wtforms import IntegerField, PasswordField
from wtforms.validators import InputRequired, Length
from wtforms.widgets import HiddenInput
@@ -10,10 +10,8 @@ class BlogCreateForm(Form):
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])
tag = SelectField('Tag')
status = SelectField('Statut', choices=[('brouillon','Brouillon'),('privé','Privé'),('publié','Publié')])
class BlogUpdateForm(BlogCreateForm):
id = IntegerField(widget=HiddenInput())
@@ -22,6 +20,13 @@ class BlogSearchForm(Form):
criteria = StringField('Critère', validators=[InputRequired(), Length(min=3, max=45)],
filters=[strip_filter])
class TagForm(Form):
id = IntegerField(widget=HiddenInput())
tag = StringField('Tag', validators=[InputRequired(), Length(min=1, max=25)],
filters=[strip_filter])
class UserCreateForm(Form):
username = StringField('Nom', validators=[InputRequired(), Length(min=1, max=255)],
filters=[strip_filter])

View File

@@ -21,7 +21,6 @@ class BlogRecord(Base):
edited = Column(DateTime, default=datetime.datetime.now)
editor = Column(Unicode(50), default='')
tag = Column(Unicode(25))
author = Column(Unicode(50), default='')
status = Column(Unicode(50), default='brouillon')
@property
@@ -38,3 +37,4 @@ class Tags(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
tag = Column(Unicode(25))

View File

@@ -7,6 +7,8 @@ def includeme(config):
config.add_route('blog_search', '/blog_search')
config.add_route('login', '/login')
config.add_route('logout', '/logout')
config.add_route('tags', '/tags')
config.add_route('tag_edit', '/tag_edit/{id}')
config.add_route('users', '/users')
config.add_route('user_add', '/user_add/{name}')
config.add_route('user_pwd', '/user_pwd/{name}')

View File

@@ -2,22 +2,21 @@ import sqlalchemy as sa
import datetime #<- will be used to set default dates on models
from sqlalchemy import or_
from ..models.blog_record import BlogRecord
from ..models.blog_record import BlogRecord, Tags
class BlogRecordService(object):
@classmethod
def all(cls, request):
query = request.dbsession.query(BlogRecord)
return query.order_by(sa.desc(BlogRecord.created))
@classmethod
def by_criteria(cls, request, criteria):
search = "%{}%".format(criteria)
query = request.dbsession.query(BlogRecord).filter(or_(BlogRecord.title.like(search),
BlogRecord.body.like(search))).all()
query = request.dbsession.query(BlogRecord)
if request.authenticated_userid == None:
# if user is anonym, display only published posts
query = query.filter(BlogRecord.status == 'publié')
query = query.filter(or_(BlogRecord.title.like(search),
BlogRecord.body.like(search))).all()
return query
@classmethod
@@ -29,6 +28,30 @@ class BlogRecordService(object):
def get_last_created(cls, request):
# gest the 10 last created posts
query = request.dbsession.query(BlogRecord)
if request.authenticated_userid == None:
# if user is anonym, display only published posts
query = query.filter(BlogRecord.status == 'publié')
query = query.order_by(sa.desc(BlogRecord.created)).limit(10).all()
return query
@classmethod
def delete(cls, request, id):
request.dbsession.query(BlogRecord).filter(BlogRecord.id == id).delete(synchronize_session=False)
return
@classmethod
def get_tags(cls, request):
query = request.dbsession.query(Tags)
query = query.order_by(Tags.tag).all()
return query
@classmethod
def get_tags_byId(cls, request, id):
# gest the last 5 items modified
query = request.dbsession.query(Tags).filter(Tags.id == id).first()
return query
@classmethod
def tag_delete(cls, request, id):
request.dbsession.query(Tags).filter(Tags.id == id).delete(synchronize_session=False)
return

View File

@@ -12,20 +12,23 @@
<hr/>
<p>{{ body_html | safe }}</p>
<hr/>
{% if request.authenticated_userid %}
<p>
Topic : <strong>{{ entry.topic }}</strong>
&nbsp;|&nbsp;
Tag : <strong>{{ entry.tag }}</strong>
&nbsp;|&nbsp;
Créé le : <strong>{{ entry.created.strftime("%d-%m-%Y - %H:%M") }}</strong>
&nbsp;|&nbsp;
Modifié le : <strong>{{ entry.edited.strftime("%d-%m-%Y - %H:%M") }}</strong>
</p>
{% else %}
<p>
Créé : <strong title="{{ entry.created }}">{{ entry.created_in_words }}</strong>
</p>
{% endif %}
<p>
Auteur : <strong>{{ entry.author }}</strong><br>
Publié le : <strong>{{ entry.created.strftime("%d-%m-%Y - %H:%M") }}</strong><br>
{% if request.authenticated_userid %}
Modifié le : <strong>{{ entry.edited.strftime("%d-%m-%Y - %H:%M") }}</strong><br>
Tag : <strong>{{ entry.tag }}</strong><br>
Statut : <strong>{{ entry.status }}</strong>
{% endif %}
</p>
<script>
const anchors = document.querySelectorAll('a');
anchors.forEach((a) => {
a.setAttribute('target', '__blank');
a.setAttribute('rel', 'noopener noreferrer');
});
</script>
{% endblock %}

View File

@@ -19,30 +19,31 @@
{{ form.body(class_='form-control', cols="35", rows="20") }}
</div>
{% for error in form.topic.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="topic">{{ form.topic.label }}</label>
{{ form.topic(class_='form-control') }}
</div>
{% for error in form.tag.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="tag">{{ form.tag.label }}</label>
{{ form.tag(class_='form-control') }}
</div>
<div class="form-group">
<label class="required-field" for="status">{{ form.status.label }}</label>
{{ form.status(class_='form-control') }}
</div>
<p>
{% if blog_id != '0' %}
Créé le : <strong>{{ entry.created.strftime("%d-%m-%Y - %H:%M") }}</strong><br>
Modifié le : <strong>{{ entry.edited.strftime("%d-%m-%Y - %H:%M") }}</strong>
{% endif %}
</p>
<br />
<div class="form-group">
<a class="btn btn-default" href="{{ request.route_url('home') }}"><span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
{% if action == 'edit' %}
<button class="btn btn-warning" type="submit" name="form.deleted">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
{% if blog_id != '0' %}
<button class="btn btn-danger" type="button" data-toggle="modal" data-target="#confirmDelete">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
{% endif %}
</div>
@@ -50,4 +51,30 @@
</form>
<!-- Modal : Confirmation SUPRESSION -->
<div id="confirmDelete" class="modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Supprimer une page</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<p>Etes-vous certain(e) de vouloir supprimer <b><br>
{{ entry.title }}</b> ?</p>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="text-center">
<form id="confirmForm" method="post">
<button type="submit" class="btn btn-danger" name="form.deleted">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -3,21 +3,24 @@
{% block content %}
{% if request.authenticated_userid %}
<p><a href="{{ request.route_url('blog_edit', id='0') }}">
[Nouveau post]</a>
</p>
<p><a href="{{ request.route_url('blog_edit', id='0') }}" class="btn btn-success" role="button">
<span class="glyphicon glyphicon-plus"></span> Nouveau</a>
</p>
{% endif%}
<table id="posts_list" class="table table-condensed">
{% for entry in last_ten %}
<tr>
<td>{{ entry.created.strftime("%d.%m.%Y") }}</td>
<td>
<a href="{{ request.route_url('blog', id=entry.id, slug=entry.slug) }}">{{ entry.title }}</a>
</td>
<td>{{ entry.topic }}</td>
<td>{{ entry.tag }}</td>
</td>
<td>{{ entry.created.strftime("%d.%m.%Y") }}</td>
<td>
<a href="{{ request.route_url('blog', id=entry.id, slug=entry.slug) }}">{{ entry.title }}</a>
</td>
<td>{{ entry.tag }}</td>
{% if entry.status != 'publié' %}
<td><span class="label label-danger">{{ entry.status }}</span></td>
{% else %}
<td>&nbsp;</td>
{% endif%}
</tr>
{% else %}
<p class="text-danger">Aucun post trouvé</p>

View File

@@ -32,27 +32,28 @@
<a class="navbar-brand" href="{{ request.route_url('home') }}">CAO Blogr</a>
</div>
<div class="collapse navbar-collapse" id="myNavbar">
<ul class="nav navbar-nav navbar-right">
<li><a href="{{ request.route_url('blog_search') }}"><span class="glyphicon glyphicon-search"></span></a></li>
{% if request.authenticated_userid %}
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">{{request.authenticated_userid}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
{% if request.authenticated_userid == 'admin' %}
<li><a href="{{request.route_url('users')}}">Utilisateurs</a></li>
{% endif %}
<li><a href="{{ request.route_url('logout') }}">Se déconnecter</a></li>
</ul>
</li>
{% else %}
<!-- si anonyme, lien pour se connecter -->
<li><a href="{{ request.route_url('login') }}">
<span class="glyphicon glyphicon-user"></span></a></li>
{% endif %}
<ul class="nav navbar-nav navbar-right">
<li><a href="{{ request.route_url('blog_search') }}"><span class="glyphicon glyphicon-search"></span></a></li>
{% if request.authenticated_userid %}
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">{{request.authenticated_userid}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
{% if request.authenticated_userid == 'admin' %}
<li><a href="{{request.route_url('users')}}">Utilisateurs</a></li>
{% endif %}
<li><a href="{{ request.route_url('tags') }}"><span class="glyphicon glyphicon-tag"></span>&nbsp;&nbsp;Tags</a></li>
<li><a href="{{ request.route_url('logout') }}"><span class="glyphicon glyphicon-off"></span>&nbsp;&nbsp;Se déconnecter</a></li>
</ul>
</li>
{% else %}
<!-- si anonyme, lien pour se connecter -->
<li><a href="{{ request.route_url('login') }}">
<span class="glyphicon glyphicon-user"></span></a></li>
{% endif %}
</ul>
</ul>
</div>
</div>
</nav>

View File

@@ -0,0 +1,55 @@
{% extends "cao_blogr:templates/layout.jinja2" %}
{% block content %}
<form action="{{ url }}" method="post" class="form">
{% for error in form.tag.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
<div class="form-group">
<label class="required-field" for="tag">{{form.tag.label}}</label>
{{form.tag(class_='form-control')}}
</div>
<div class="form-group">
<a class="btn btn-default" href="{{ request.route_url('tags') }}">
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
{% if form.id.data %}
<button class="btn btn-danger" type="button" data-toggle="modal" data-target="#confirmDelete">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
{% endif %}
</div>
</form>
<!-- Modal : Confirmation SUPRESSION -->
<div id="confirmDelete" class="modal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Supprimer le Tag</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<p>Etes-vous certain(e) de vouloir supprimer le Tag <b>{{ form.tag.data }}</b> ?</p>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="text-center">
<form id="confirmForm" method="post">
<button type="submit" class="btn btn-danger" name="form.deleted">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends "cao_blogr:templates/layout.jinja2" %}
{% block content %}
<p><a href="{{ request.route_url('tag_edit', id='0') }}" class="btn btn-success" role="button">
<span class="glyphicon glyphicon-plus"></span> Nouveau</a>
</p>
<ul></ul>
{% for entry in tags %}
<li>
<a href="{{ request.route_url('tag_edit', id=entry.id) }}">{{ entry.tag }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}

View File

@@ -1,8 +1,8 @@
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPNotFound, HTTPFound
from ..models.blog_record import BlogRecord
from ..models.blog_record import BlogRecord, Tags
from ..services.blog_record import BlogRecordService
from ..forms import BlogCreateForm, BlogUpdateForm, BlogSearchForm
from ..forms import BlogCreateForm, BlogUpdateForm, BlogSearchForm, TagForm
import markdown
import datetime #<- will be used to set default dates on models
@@ -35,13 +35,16 @@ def blog_edit(request):
blog_id = request.matchdict['id']
url = request.route_url('blog_edit',id=blog_id)
# get the list of tags
tags = BlogRecordService.get_tags(request)
if blog_id == '0':
# create a new post
entry = BlogRecord()
# set default values
entry.tag = 'pyramid'
entry.topic = 'blog'
form = BlogCreateForm(request.POST, entry)
form.tag.choices = [(row.tag, row.tag) for row in tags]
page_title = 'Nouvelle page'
else:
# modify post
entry = BlogRecordService.by_id(request, blog_id)
@@ -49,26 +52,39 @@ def blog_edit(request):
request.session.flash(u"Page non trouvée : %s" % blog_id, 'warning')
return HTTPFound(location=request.route_url('home'))
form = BlogUpdateForm(request.POST, entry)
form.tag.choices = [(row.tag, row.tag) for row in tags]
page_title = 'Modifier : ' + entry.title
if 'form.submitted' in request.params and form.validate():
if blog_id == '0':
form.populate_obj(entry)
import pdb;pdb.set_trace()
# interdire le car '/' dans le titre à cause du slug
entry.title = entry.title.replace('/','.')
entry.creator = request.authenticated_userid
entry.editor = entry.creator
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)
# interdire le car '/' dans le titre à cause du slug
entry.title = entry.title.replace('/','.')
entry.edited = datetime.datetime.now()
entry.editor = request.authenticated_userid
return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug))
if 'form.deleted' in request.params:
BlogRecordService.delete(request, blog_id)
request.session.flash("La page a été supprimée avec succès.", 'success')
return HTTPFound(location=request.route_url('home'))
return {
'page_title': entry.title,
'page_title': page_title,
'url': url,
'form': form,
}
'blog_id': blog_id,
'entry': entry, }
@view_config(route_name='blog_search',
@@ -90,3 +106,55 @@ def blog_search(request):
'criteria': criteria,
}
@view_config(route_name='tags', renderer='cao_blogr:templates/tags.jinja2', permission='view')
def tags(request):
# get the list of tags of this topic
tags = BlogRecordService.get_tags(request)
return {
'page_title': 'Tags',
'tags': tags,
}
@view_config(route_name='tag_edit', renderer='cao_blogr:templates/tag_edit.jinja2', permission='view')
def tag_edit(request):
# get tag parameters from request
tag_id = request.matchdict['id']
url = request.route_url('tag_edit', id=tag_id)
if tag_id == '0':
# create a new tag
entry = Tags()
form = TagForm(request.POST, entry)
page_title = "Nouveau Tag"
else:
# modify post
entry = BlogRecordService.get_tags_byId(request, tag_id)
if not entry:
request.session.flash(u"Tag non trouvé : %s" % tag_id, 'warning')
return HTTPFound(location=request.route_url('tags'))
form = TagForm(request.POST, entry)
page_title = entry.tag
if 'form.submitted' in request.params and form.validate():
if tag_id == '0':
form.populate_obj(entry)
request.dbsession.add(entry)
return HTTPFound(location=request.route_url('tags'))
else:
del form.id # SECURITY: prevent overwriting of primary key
form.populate_obj(entry)
return HTTPFound(location=request.route_url('tags'))
if 'form.deleted' in request.params:
BlogRecordService.tag_delete(request, entry.id)
request.session.flash("La fiche a été supprimée avec succès.", 'success')
return HTTPFound(location=request.route_url('tags'))
return {
'page_title': page_title,
'url': url,
'form': form,
}

View File

@@ -62,8 +62,9 @@ def login(request):
@view_config(route_name='logout', renderer='string')
def logout(request):
username = request.authenticated_userid
headers = forget(request)
request.session.flash('Vous avez bien été déconnecté.', 'success')
request.session.flash('Au revoir ' + username + ' !', 'success')
return HTTPFound(location=request.route_url('home'), headers=headers)