Compare commits

..

4 Commits

Author SHA1 Message Date
phuoc f6d16b00db remove opencode files 2026-10-13 11:01:34 +02:00
phuoc 705ba49f12 added user creation and delete 2026-10-09 19:21:00 +02:00
phuoc e81a317b4b push local database to repo 2026-10-07 18:01:05 +02:00
phuoc ea7f12e8a3 store only the name in creator and editor 2026-10-07 17:52:44 +02:00
7 changed files with 79 additions and 82 deletions
BIN
View File
Binary file not shown.
+2 -34
View File
@@ -1,6 +1,6 @@
from wtforms import Form, StringField, TextAreaField, SelectField, DecimalField from wtforms import Form, StringField, TextAreaField, SelectField, DecimalField
from wtforms import IntegerField from wtforms import IntegerField
from wtforms.validators import InputRequired, Length from wtforms.validators import InputRequired, Length, Email
from wtforms.widgets import HiddenInput from wtforms.widgets import HiddenInput
strip_filter = lambda x: x.strip() if x else None strip_filter = lambda x: x.strip() if x else None
@@ -26,39 +26,7 @@ class TagForm(Form):
class UserEditForm(Form): class UserEditForm(Form):
id = IntegerField(widget=HiddenInput()) name = StringField('Email', validators=[Email(), InputRequired(), Length(min=1, max=255)],
name = StringField('Nom', validators=[InputRequired(), Length(min=1, max=255)],
filters=[strip_filter]) filters=[strip_filter])
class HistoForm(Form):
no_id = IntegerField(widget=HiddenInput())
mvt_cash = DecimalField('Montant à ajouter au portefeuille', places=2, validators=[InputRequired()])
class AllocationForm(Form):
no_cat = IntegerField(widget=HiddenInput())
classe = StringField("Classe d'actif", validators=[InputRequired(), Length(min=1, max=25)], filters=[strip_filter])
pc_cible = IntegerField(validators=[InputRequired()])
type = SelectField('Type', choices=[('ACTION','ACTION'),('AUTRE','AUTRE')])
ordre = IntegerField(validators=[InputRequired()])
bg_color = SelectField('Couleur de fond', choices=[('info','BLEU'),('danger','ROUGE'),('warning','ORANGE'),('success','VERT')])
class ActifForm(Form):
no_id = IntegerField(widget=HiddenInput())
classe = SelectField('Classe')
symbole = StringField('Symbole', validators=[InputRequired(), Length(min=1, max=15)], filters=[strip_filter])
libelle = StringField('Nom', validators=[InputRequired(), Length(min=1, max=45)], filters=[strip_filter])
nombre = IntegerField(validators=[InputRequired()])
pru = DecimalField('PRU', places=3, validators=[InputRequired()])
ter = DecimalField('TER', places=2, validators=[InputRequired()])
pc_rdt = DecimalField('% rendement', places=2, validators=[InputRequired()])
website = StringField('Web site', validators=[Length(min=1, max=100)], filters=[strip_filter])
class Actif2Form(Form):
no_id = IntegerField(widget=HiddenInput())
classe = SelectField('Classe')
symbole = StringField('Symbole', validators=[InputRequired(), Length(min=1, max=15)], filters=[strip_filter])
libelle = StringField('Libellé', validators=[InputRequired(), Length(min=1, max=45)], filters=[strip_filter])
pru = DecimalField('Total investi', places=3, validators=[InputRequired()])
cours = DecimalField('Total valeur', places=3, validators=[InputRequired()])
pc_rdt = DecimalField('% rendement', places=2, validators=[InputRequired()])
+18 -16
View File
@@ -8,10 +8,6 @@
</div> </div>
{% endif %} {% endif %}
<p>
<a class="btn btn-default" href="{{ url_retour }}"><span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
</p>
<form action="{{ url }}" method="post" class="form"> <form action="{{ url }}" method="post" class="form">
{% for error in form.name.errors %} {% for error in form.name.errors %}
@@ -19,24 +15,30 @@
{% endfor %} {% endfor %}
<div class="form-group"> <div class="form-group">
<label class="required-field" for="name">{{form.name.label}}</label> <label class="required-field" for="name">{{form.name.label}}</label>
<input class="form-control" name="name" readonly type="text" value="{{form.name.data}}"> {% if name == "0" %}
<input class="form-control" name="name" type="text" value="{{form.name.data}}">
{% else %}
<input class="form-control" name="name" readonly type="text" value="{{form.name.data}}">
{% endif %}
</div> </div>
<p class="help-block">
L'adresse {{ form.name.data }} est votre compte Google autorisé. Elle
n'est pas modifiable ici : supprimez-la de la base et ajoutez-la à
<code>auth.admin_emails</code> ou à la table <code>users</code> pour
changer d'identité.
</p>
<br> <br>
<div class="form-group"> <div class="form-group">
<button class="btn btn-primary" type="submit" name="form.submitted"> <a class="btn btn-default" href="{{ url_retour }}"><span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button> {% if name == "0" %}
{% if 'group:administrators' in request.effective_principals %} <button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span> Enregistrer</button>
{% else %}
<button class="btn btn-warning" type="submit" name="form.deleted"> <button class="btn btn-warning" type="submit" name="form.deleted">
<span class="glyphicon glyphicon-remove"></span> Supprimer</button> <span class="glyphicon glyphicon-remove"></span> Supprimer</button>
<br>
<br>
<p class="text-danger">
Attention : la suppression est définitive. Pas de retour possible.
</p>
{% endif %} {% endif %}
</div> </div>
+12 -8
View File
@@ -2,26 +2,30 @@
{% block content %} {% block content %}
<p> <p>
<a href="{{ request.route_url('home' ) }}" class="btn btn-default" role="button"> <a href="{{ request.route_url('home' ) }}">[ Retour ]</a>&nbsp;
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a> <a href="{{ request.route_url('user_edit', name='0') }}">[ Nouvel utilisateur ]</a>
</p> </p>
<table id="users_list" class="table table-striped table-bordered table-condensed"> <table id="users_list" class="table table-striped table-bordered table-condensed">
<thead> <thead>
<tr> <tr>
<th>No Id</th> <th>Email</th>
<th>Nom</th> <th>Google sub</th>
<th>Dernière connexion</th> <th>Dernière connexion</th>
</tr> </tr>
</thead> </thead>
{% for entry in users %} {% for entry in users %}
<tr> <tr>
<td>{{ entry.id }}</td>
<td> <td>
<a href="{{ request.route_url('user_edit', name=entry.name) }}"> {% if entry.name == 'phuoc@caotek.fr' %}
{{ entry.name }} {{ entry.name }} (admin)
</a> {% else%}
<a href="{{ request.route_url('user_edit', name=entry.name) }}">
{{ entry.name }}
</a>
{% endif %}
</td> </td>
<td>{{ entry.google_sub }}</td>
<td>{% if entry.last_logged %}{{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }}{% else %}jamais{% endif %}</td> <td>{% if entry.last_logged %}{{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }}{% else %}jamais{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
+2 -2
View File
@@ -61,7 +61,7 @@ def blog_edit(request):
form.populate_obj(entry) form.populate_obj(entry)
# interdire le car '/' dans le titre à cause du slug # interdire le car '/' dans le titre à cause du slug
entry.title = entry.title.replace('/','.') entry.title = entry.title.replace('/','.')
entry.creator = request.authenticated_userid entry.creator = request.authenticated_userid.split('@')[0]
entry.editor = entry.creator entry.editor = entry.creator
request.dbsession.add(entry) request.dbsession.add(entry)
@@ -72,7 +72,7 @@ def blog_edit(request):
# interdire le car '/' dans le titre à cause du slug # interdire le car '/' dans le titre à cause du slug
entry.title = entry.title.replace('/','.') entry.title = entry.title.replace('/','.')
entry.edited = datetime.datetime.now() entry.edited = datetime.datetime.now()
entry.editor = request.authenticated_userid entry.editor = request.authenticated_userid.split('@')[0]
return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug)) return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug))
if 'form.update_created' in request.params and form.validate(): if 'form.update_created' in request.params and form.validate():
+44 -22
View File
@@ -5,6 +5,7 @@ from pyramid.view import view_config
from pyramid.httpexceptions import HTTPForbidden, HTTPFound from pyramid.httpexceptions import HTTPForbidden, HTTPFound
from pyramid.security import remember, forget from pyramid.security import remember, forget
from ..services.google_oauth import GoogleOAuthError from ..services.google_oauth import GoogleOAuthError
from ..models.user import User
from ..services.user import UserService from ..services.user import UserService
from ..services.blog_record import BlogRecordService from ..services.blog_record import BlogRecordService
from ..forms import UserEditForm from ..forms import UserEditForm
@@ -16,12 +17,19 @@ def home(request):
last_ten = BlogRecordService.get_last_created(request) last_ten = BlogRecordService.get_last_created(request)
# get Chappatte cartoon # get Chappatte cartoon
entry = BlogRecordService.by_id(request, 95) entry = BlogRecordService.by_id(request, 95)
if entry:
cartoon_title = entry.title
cartoon_url = entry.body
else:
cartoon_title = "Cartoon non trouvé"
cartoon_url = ""
return { return {
'page_title': "This is the Way", 'page_title': "This is the Way",
'last_ten': last_ten, 'last_ten': last_ten,
'cartoon_title': entry.title, 'cartoon_title': cartoon_title,
'cartoon_url': entry.body, 'cartoon_url': cartoon_url,
} }
@@ -192,36 +200,50 @@ def users(request):
renderer='ctp_blogr:templates/user_edit.jinja2', renderer='ctp_blogr:templates/user_edit.jinja2',
permission='manage') permission='manage')
def user_edit(request): def user_edit(request):
# 'manage' et non 'view' : avec 'view', n'importe quel utilisateur
# connecte pouvait ouvrir /user_edit/<un-autre-email> et le modifier,
# ou /user_edit/0 pour creer une fiche.
message = '' message = ''
name = request.matchdict['name'] name = request.matchdict['name']
url = request.route_url('user_edit', name=name) url = request.route_url('user_edit', name=name)
if 'group:administrators' in request.effective_principals: url_retour = request.route_url('users')
url_retour = request.route_url('users')
if name == '0':
# nouvel utilisateur
user = User()
form = UserEditForm(request.POST, user)
page_title = "Nouvel utilisateur"
else: else:
url_retour = request.route_url('home') # lire la fiche du user
user = UserService.by_name(request, name)
if not user:
request.session.flash("Utilisateur non trouvé : %s" % name, 'danger')
return HTTPFound(location=url_retour)
user = UserService.by_name(request, name) form = UserEditForm(request.POST, user)
if not user: page_title = "Modification utilisateur"
# '0' n'existe plus : la création d'un utilisateur a disparu avec
# le mot de passe.
request.session.flash("Utilisateur non trouvé : %s" % name, 'danger')
return HTTPFound(location=url_retour)
form = UserEditForm(request.POST, user)
page_title = "Modification utilisateur"
if 'form.submitted' in request.params and form.validate(): if 'form.submitted' in request.params and form.validate():
del form.name # SECURITY: prevent overwriting of primary key # controle que name est bien un email
form.populate_obj(user) if name == '0':
request.session.flash("La fiche a été modifiée avec succès.", 'success') # It is obviously a user creation.
# check if the new user already exists in DB
new_user = UserService.by_name(request, form.name.data)
if new_user:
message = "Utilisateur déjà créé : %s" % form.name.data
else:
form.populate_obj(user)
# créer le nouveau
request.dbsession.add(user)
request.session.flash("La fiche a été créée avec succès.", 'success')
return HTTPFound(location=url_retour) return HTTPFound(location=url_retour)
if 'form.deleted' in request.params: if 'form.deleted' in request.params:
UserService.delete(request, user.id) # check whether name is an admin
request.session.flash("La fiche a été supprimée avec succès.", 'success') if name in 'group:administrators' in request.effective_principals:
request.session.flash("Il est interdit de supprimer un utisateur 'Admin'.", 'success')
else:
UserService.delete(request, user.id)
request.session.flash("La fiche a été supprimée avec succès.", 'success')
return HTTPFound(location=url_retour) return HTTPFound(location=url_retour)
return { return {
+1
View File
@@ -20,6 +20,7 @@ requires = [
'transaction', 'transaction',
'zope.sqlalchemy', 'zope.sqlalchemy',
'wtforms', # form library 2.2.1 'wtforms', # form library 2.2.1
'email_validator',
'webhelpers2', # various web building related helpers 2.0 'webhelpers2', # various web building related helpers 2.0
'markdown', 'markdown',
'authlib', # client OAuth 2.0 / OIDC pour l'authentification Google 'authlib', # client OAuth 2.0 / OIDC pour l'authentification Google