Compare commits
49 Commits
release-1.
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
181d8d0d21 | ||
|
|
ebfe0ba995 | ||
| 91ee548b52 | |||
| 3c620d6142 | |||
| dcb8ac5b30 | |||
| ac4e073e49 | |||
| e12e7e7543 | |||
| 8eb55385ad | |||
| 46a7f30bae | |||
| c6c14938e4 | |||
| 63e776e370 | |||
| 93833d527a | |||
| d3c66e4240 | |||
| f90649d3cd | |||
| 11318a32e3 | |||
| 6af19b99c1 | |||
| 985354d532 | |||
| 2963f8e5c5 | |||
| dd231c0d64 | |||
| 3efce74696 | |||
| 51169bb96b | |||
| 9fb5ea0e0d | |||
| 85f8423a39 | |||
| f4d603750e | |||
| 8b2fcb1a76 | |||
| 5ce9066824 | |||
| 92142854df | |||
| 6c146e66c9 | |||
| 2d5e8fc20e | |||
| 7e73994acf | |||
| b714c6138a | |||
| 4103d08e26 | |||
| fdcf474bde | |||
| b7a8c64333 | |||
| 7a2ba16e2f | |||
| 05c3d83ae0 | |||
| e03b2ea353 | |||
| 1e9cb12565 | |||
| d712e5e4db | |||
| 1e6ec919e0 | |||
| f1287c3fad | |||
| fcd83c48d7 | |||
| d95ae8eeeb | |||
| 7059553a03 | |||
| e60212239e | |||
| f8023701a4 | |||
| 4a55f94551 | |||
| 060b796636 | |||
| 25c6ad3cc0 |
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
*.pyc
|
||||
*$py.class
|
||||
*~
|
||||
*.sqlite
|
||||
.coverage
|
||||
coverage.xml
|
||||
build/
|
||||
|
||||
BIN
cao_blogr.sqlite
@@ -1,26 +0,0 @@
|
||||
"""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 ###
|
||||
@@ -1,34 +0,0 @@
|
||||
from wtforms import Form, StringField, TextAreaField, SelectField, 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])
|
||||
tag = SelectField('Tag')
|
||||
status = SelectField('Statut', choices=[('brouillon','Brouillon'),('privé','Privé'),('publié','Publié')])
|
||||
|
||||
class BlogUpdateForm(BlogCreateForm):
|
||||
id = IntegerField(widget=HiddenInput())
|
||||
|
||||
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])
|
||||
password = PasswordField('Mot de passe', validators=[InputRequired(), Length(min=6)])
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if request.authenticated_userid %}
|
||||
<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.tag }}</td>
|
||||
{% if entry.status != 'publié' %}
|
||||
<td><span class="label label-danger">{{ entry.status }}</span></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif%}
|
||||
</tr>
|
||||
{% else %}
|
||||
<p class="text-danger">Aucun post trouvé</p>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,34 +0,0 @@
|
||||
{% extends "cao_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="{{request.route_url('user_add', name=name)}}" method="post" class="form">
|
||||
|
||||
{% for error in form.username.errors %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="username">{{form.username.label}}</label>
|
||||
{{form.username(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
{% for error in form.password.errors %}
|
||||
<div class="error">{{error}}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="password">{{form.password.label}}</label>
|
||||
{{form.password(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<a class="btn btn-default" href="{{ request.route_url('users') }}"><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>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,30 +0,0 @@
|
||||
{% extends "cao_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="{{ request.route_url('user_pwd', name=entry.name) }}" method="post" class="form">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Nouveau mot de passe</label></label>
|
||||
<input type="password" name="new_password" class="form-control" placeholder="Optionel">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-control-static text-success">
|
||||
<strong>Dernière connexion</strong> :
|
||||
{{ entry.last_logged.strftime("%d-%m-%Y - %H:%M") }}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<a class="btn btn-default" href="{{ request.route_url('users') }}"><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 name != 'new' %}
|
||||
<button class="btn btn-warning" type="submit" name="form.deleted">
|
||||
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,127 +0,0 @@
|
||||
from pyramid.view import (
|
||||
view_config,
|
||||
forbidden_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):
|
||||
# get the last created posts
|
||||
last_ten = BlogRecordService.get_last_created(request)
|
||||
return {
|
||||
'page_title': "Bienvenue sur mon blog",
|
||||
'last_ten': last_ten,
|
||||
}
|
||||
|
||||
|
||||
@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')
|
||||
@forbidden_view_config(renderer='cao_blogr:templates/login.jinja2')
|
||||
def login(request):
|
||||
username = ''
|
||||
login_url = request.route_url('login')
|
||||
|
||||
referrer = request.url
|
||||
if referrer == login_url:
|
||||
referrer = '/' # never use the login form itself as came_from
|
||||
|
||||
came_from = request.params.get('came_from', referrer)
|
||||
username = request.POST.get('username')
|
||||
userpwd = request.POST.get('password')
|
||||
if username:
|
||||
user = UserService.by_name(request, username)
|
||||
if user and user.verify_password(userpwd):
|
||||
headers = remember(request, username)
|
||||
request.session.flash("Bienvenue %s !" % username, 'success')
|
||||
return HTTPFound(location=came_from, headers=headers)
|
||||
else:
|
||||
headers = forget(request)
|
||||
request.session.flash("Login et mot de passe invalides. La connexion a échoué.", "danger")
|
||||
|
||||
return {
|
||||
'page_title': "",
|
||||
'came_from': came_from,
|
||||
'login_url': login_url,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='logout', renderer='string')
|
||||
def logout(request):
|
||||
username = request.authenticated_userid
|
||||
headers = forget(request)
|
||||
request.session.flash('Au revoir ' + username + ' !', '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,
|
||||
}
|
||||
BIN
ctp_blogr.sqlite
Normal file
@@ -10,13 +10,13 @@ def main(global_config, **settings):
|
||||
""" This function returns a Pyramid WSGI application.
|
||||
"""
|
||||
# session factory
|
||||
my_session_factory = SignedCookieSessionFactory('mGcAJn2HmNH6Hc')
|
||||
my_session_factory = SignedCookieSessionFactory('hZug2zPt7hT2MZ')
|
||||
|
||||
authentication_policy = AuthTktAuthenticationPolicy('wMWvAWMZnp6Lch',
|
||||
authentication_policy = AuthTktAuthenticationPolicy('J2wv322aL5DTn2',
|
||||
callback=groupfinder, hashalg='sha512', timeout=36000)
|
||||
authorization_policy = ACLAuthorizationPolicy()
|
||||
with Configurator(settings=settings,
|
||||
root_factory='cao_blogr.security.RootFactory',
|
||||
root_factory='ctp_blogr.security.RootFactory',
|
||||
authentication_policy=authentication_policy,
|
||||
authorization_policy=authorization_policy) as config:
|
||||
config.include('pyramid_jinja2')
|
||||
@@ -3,7 +3,7 @@ from alembic import context
|
||||
from pyramid.paster import get_appsettings, setup_logging
|
||||
from sqlalchemy import engine_from_config
|
||||
|
||||
from cao_blogr.models.meta import Base
|
||||
from ctp_blogr.models.meta import Base
|
||||
|
||||
config = context.config
|
||||
|
||||
42
ctp_blogr/alembic/versions/20230129_b8f8216d72c7.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""added foreign key to allocation
|
||||
|
||||
Revision ID: b8f8216d72c7
|
||||
Revises:
|
||||
Create Date: 2023-01-29 12:20:48.097107
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b8f8216d72c7'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('allocation',
|
||||
sa.Column('no_cat', sa.Integer(), nullable=False),
|
||||
sa.Column('classe', sa.Unicode(length=45), nullable=True),
|
||||
sa.Column('pc_cible', sa.Integer(), nullable=True),
|
||||
sa.Column('pc_atteint', sa.Float(), nullable=True),
|
||||
sa.Column('pc_ecart', sa.Float(), nullable=True),
|
||||
sa.Column('valeur', sa.Float(), nullable=True),
|
||||
sa.Column('type', sa.Unicode(length=45), nullable=True),
|
||||
sa.Column('ordre', sa.Integer(), nullable=True),
|
||||
sa.Column('bg_color', sa.Unicode(length=45), nullable=True),
|
||||
sa.ForeignKeyConstraint(['classe'], ['actifs.classe'], name=op.f('fk_allocation_classe_actifs')),
|
||||
sa.PrimaryKeyConstraint('no_cat', name=op.f('pk_allocation'))
|
||||
)
|
||||
op.create_index('classe_index', 'allocation', ['classe'], unique=False)
|
||||
op.create_index('ordre_index', 'allocation', ['ordre'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index('ordre_index', table_name='allocation')
|
||||
op.drop_index('classe_index', table_name='allocation')
|
||||
op.drop_table('allocation')
|
||||
# ### end Alembic commands ###
|
||||
66
ctp_blogr/forms.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from wtforms import Form, StringField, TextAreaField, SelectField, DecimalField
|
||||
from wtforms import IntegerField, PasswordField
|
||||
from wtforms.validators import InputRequired, Length, EqualTo
|
||||
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])
|
||||
tag = SelectField('Tag')
|
||||
status = SelectField('Statut', choices=[('brouillon','Brouillon'),('privé','Privé'),('publié','Publié')])
|
||||
|
||||
class BlogUpdateForm(BlogCreateForm):
|
||||
id = IntegerField(widget=HiddenInput())
|
||||
|
||||
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):
|
||||
id = IntegerField(widget=HiddenInput())
|
||||
name = StringField('Nom', validators=[InputRequired(), Length(min=1, max=255)],
|
||||
filters=[strip_filter])
|
||||
password = PasswordField('Mot de passe')
|
||||
confirm = PasswordField('Confirmer', validators=[EqualTo('password', message='Les 2 Passwords doivent être identiques')])
|
||||
|
||||
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()])
|
||||
|
||||
@@ -7,6 +7,7 @@ import zope.sqlalchemy
|
||||
# Base.metadata prior to any initialization routines
|
||||
from .user import User
|
||||
from .blog_record import BlogRecord
|
||||
from .portfolio import Actifs
|
||||
|
||||
# run configure_mappers after defining all of the models to ensure
|
||||
# all relationships can be setup
|
||||
@@ -54,7 +55,7 @@ def includeme(config):
|
||||
"""
|
||||
Initialize the model for a Pyramid app.
|
||||
|
||||
Activate this setup using ``config.include('cao_blogr.models')``.
|
||||
Activate this setup using ``config.include('ctp_blogr.models')``.
|
||||
|
||||
"""
|
||||
settings = config.get_settings()
|
||||
@@ -1,5 +1,5 @@
|
||||
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 ctp_blogr.models.meta import Base #<- we need to import our sqlalchemy metadata from which model classes will inherit
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
63
ctp_blogr/models/portfolio.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import datetime #<- will be used to set default dates on models
|
||||
from ctp_blogr.models.meta import Base #<- we need to import our sqlalchemy metadata from which model classes will inherit
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
Float,
|
||||
Unicode, #<- will provide Unicode field
|
||||
UnicodeText, #<- will provide Unicode text field
|
||||
DateTime, #<- time abstraction field
|
||||
Index,
|
||||
ForeignKey,
|
||||
)
|
||||
|
||||
|
||||
class Actifs(Base):
|
||||
__tablename__ = 'actifs'
|
||||
no_id = Column(Integer, primary_key=True)
|
||||
symbole = Column(Unicode(45), unique=True, nullable=False)
|
||||
libelle = Column(Unicode(45), nullable=False)
|
||||
classe = Column(Unicode(45), nullable=False)
|
||||
nombre = Column(Integer)
|
||||
cours = Column(Float)
|
||||
pru = Column(Float)
|
||||
valeur = Column(Float)
|
||||
plus_value = Column(Float)
|
||||
pc_plusvalue = Column(Float)
|
||||
rendement = Column(Float)
|
||||
pc_rdt = Column(Float)
|
||||
pc_allocation = Column(Float)
|
||||
ter = Column(Float)
|
||||
ter_pondere = Column(Float)
|
||||
devise = Column(Unicode(45), default='EUR')
|
||||
parite = Column(Float)
|
||||
website = Column(Unicode(100))
|
||||
modif_le = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
__table_args__ = (Index('symbole_index', 'symbole'),)
|
||||
|
||||
|
||||
class Allocation(Base):
|
||||
__tablename__ = 'allocation'
|
||||
no_cat = Column(Integer, primary_key=True)
|
||||
classe = Column(Unicode(45), ForeignKey('actifs.classe'))
|
||||
pc_cible = Column(Integer)
|
||||
pc_atteint = Column(Float)
|
||||
pc_ecart = Column(Float)
|
||||
valeur = Column(Float)
|
||||
type = Column(Unicode(45), default='ACTION')
|
||||
ordre = Column(Integer)
|
||||
bg_color = Column(Unicode(45))
|
||||
__table_args__ = (Index('classe_index', 'classe'), Index('ordre_index', 'ordre'),)
|
||||
|
||||
|
||||
class Histo(Base):
|
||||
__tablename__ = 'histo'
|
||||
no_id = Column(Integer, primary_key=True)
|
||||
date = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
mvt_cash = Column(Float)
|
||||
valeur_pf = Column(Float)
|
||||
nb_part = Column(Float)
|
||||
val_part = Column(Float)
|
||||
cours_ref = Column(Float)
|
||||
val_part_ref = Column(Float)
|
||||
@@ -1,5 +1,5 @@
|
||||
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 ctp_blogr.models.meta import Base #<- we need to import our sqlalchemy metadata from which model classes will inherit
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
Integer,
|
||||
@@ -3,6 +3,7 @@ def includeme(config):
|
||||
config.add_route('home', '/')
|
||||
config.add_route('apropos', '/apropos')
|
||||
config.add_route('blog', '/blog/{id:\d+}/{slug}')
|
||||
config.add_route('blog_bytag', '/blog_bytag/{tag}/{retour}')
|
||||
config.add_route('blog_edit', '/blog_edit/{id}')
|
||||
config.add_route('blog_search', '/blog_search')
|
||||
config.add_route('login', '/login')
|
||||
@@ -10,5 +11,13 @@ def includeme(config):
|
||||
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}')
|
||||
config.add_route('user_edit', '/user_edit/{name}')
|
||||
# portfolio
|
||||
config.add_route('actif_edit', '/actif_edit/{no_id}')
|
||||
config.add_route('actif2_edit', '/actif2_edit/{no_id}')
|
||||
config.add_route('allocation_edit', '/allocation_edit/{no_cat}')
|
||||
config.add_route('histo_list', '/histo_list')
|
||||
config.add_route('histo_edit', '/histo_edit/{no_id}')
|
||||
config.add_route('portfolio', '/portfolio')
|
||||
# personal
|
||||
config.add_route('portal', '/portal')
|
||||
@@ -12,18 +12,26 @@ class BlogRecordService(object):
|
||||
def by_criteria(cls, request, criteria):
|
||||
search = "%{}%".format(criteria)
|
||||
query = request.dbsession.query(BlogRecord)
|
||||
query = query.filter(or_(BlogRecord.title.like(search), BlogRecord.body.like(search)))
|
||||
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
|
||||
return query.all()
|
||||
|
||||
@classmethod
|
||||
def by_id(cls, request, _id):
|
||||
query = request.dbsession.query(BlogRecord)
|
||||
return query.get(_id)
|
||||
|
||||
@classmethod
|
||||
def by_tag(cls, request, tag):
|
||||
query = request.dbsession.query(BlogRecord)
|
||||
query = query.filter(BlogRecord.tag == tag)
|
||||
if request.authenticated_userid == None:
|
||||
# if user is anonym, display only published posts
|
||||
query = query.filter(BlogRecord.status == 'publié')
|
||||
return query.order_by(sa.desc(BlogRecord.created)).all()
|
||||
|
||||
@classmethod
|
||||
def get_last_created(cls, request):
|
||||
# gest the 10 last created posts
|
||||
@@ -31,7 +39,7 @@ class BlogRecordService(object):
|
||||
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()
|
||||
query = query.order_by(sa.desc(BlogRecord.created)).limit(15).all()
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
106
ctp_blogr/services/portfolio.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import func
|
||||
from ..models.portfolio import Actifs, Allocation, Histo
|
||||
|
||||
class PFService(object):
|
||||
|
||||
@classmethod
|
||||
def get_actifs(cls, request, no_id):
|
||||
if no_id == '0':
|
||||
items = request.dbsession.query(Actifs, Allocation).join(Allocation).order_by(Allocation.ordre, Actifs.libelle).all()
|
||||
else:
|
||||
# lire une allocation par le no_id
|
||||
items = request.dbsession.query(Actifs).filter(Actifs.no_id == no_id).first()
|
||||
return items
|
||||
|
||||
@classmethod
|
||||
def get_allocation(cls, request, no_cat):
|
||||
if no_cat == '0':
|
||||
query = request.dbsession.query(Allocation)
|
||||
query = query.order_by(sa.asc(Allocation.ordre)).all()
|
||||
else:
|
||||
# lire une allocation par le no_id
|
||||
query = request.dbsession.query(Allocation).filter(Allocation.no_cat == no_cat).first()
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
def get_allocation_byType(cls, request, type):
|
||||
# lire une allocation par le no_id
|
||||
query = request.dbsession.query(Allocation).filter(Allocation.type == type).all()
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
def get_histo(cls, request, no_id):
|
||||
if no_id == '0':
|
||||
items = request.dbsession.query(Histo).order_by(sa.asc(Histo.date)).all()
|
||||
elif no_id == '-1':
|
||||
items = request.dbsession.query(Histo).order_by(sa.desc(Histo.date)).all()
|
||||
else:
|
||||
# lire le histo par le no_id
|
||||
items = request.dbsession.query(Histo).filter(Histo.no_id == no_id).first()
|
||||
return items
|
||||
|
||||
@classmethod
|
||||
def get_last_histo(cls, request):
|
||||
# lire le dernier histo créé
|
||||
items = request.dbsession.query(Histo).order_by(sa.desc(Histo.date)).first()
|
||||
return items
|
||||
|
||||
@classmethod
|
||||
def delete_allocation(cls, request, no_cat):
|
||||
request.dbsession.query(Allocation).filter(Allocation.no_cat == no_cat).delete(synchronize_session=False)
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def delete_histo(cls, request, no_id):
|
||||
request.dbsession.query(Histo).filter(Histo.no_id == no_id).delete(synchronize_session=False)
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def update_actif_devise(cls, request, devise, taux):
|
||||
request.dbsession.query(Actifs).filter(Actifs.devise == devise).update({'parite': taux})
|
||||
return
|
||||
|
||||
def update_actif_valeur(request, symbole, cours, valeur, plus_value, pc_plusvalue):
|
||||
request.dbsession.query(Actifs).filter(Actifs.symbole == symbole
|
||||
).update({'cours': cours,
|
||||
'valeur': valeur,
|
||||
'plus_value':plus_value,
|
||||
'pc_plusvalue': pc_plusvalue,
|
||||
})
|
||||
return
|
||||
|
||||
def update_portefeuille(request, today):
|
||||
# cumuler la valeur totale des actifs
|
||||
result = request.dbsession.query(Actifs, func.sum(Actifs.valeur).label("TotalValue")).first()
|
||||
TotalValue = result.TotalValue
|
||||
# maj du pourcentage d'allocation des lignes du portefeuille
|
||||
items = PFService.get_actifs(request, '0')
|
||||
for item in items:
|
||||
pc_allocation = round(item.Actifs.valeur / TotalValue * 100, 3)
|
||||
request.dbsession.query(Actifs).filter(Actifs.no_id == item.Actifs.no_id).update({'pc_allocation': pc_allocation,
|
||||
'modif_le': today})
|
||||
|
||||
# maj des allocations
|
||||
items = PFService.get_allocation(request, '0')
|
||||
for item in items:
|
||||
# cumuler la valeur totale des actifs de cette classe
|
||||
result = request.dbsession.query(Actifs, func.sum(Actifs.valeur).label("TotalClasse")
|
||||
).filter(Actifs.classe == item.classe).first()
|
||||
TotalClasse = result.TotalClasse
|
||||
pc_atteint = round(TotalClasse / TotalValue * 100, 3)
|
||||
# maj du pourcentage d'allocation de cette classe
|
||||
request.dbsession.query(Allocation).filter(Allocation.classe == item.classe
|
||||
).update({'valeur': TotalClasse,
|
||||
'pc_atteint': pc_atteint})
|
||||
|
||||
return
|
||||
|
||||
def delete_actif(request, no_id):
|
||||
request.dbsession.query(Actifs).filter(Actifs.no_id == no_id).delete(synchronize_session=False)
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
ctp_blogr/static/blog/MacBook_mid_2014.png
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
ctp_blogr/static/blog/MacBook_mid_2014_sonoma.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
ctp_blogr/static/blog/my-home-network.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
ctp_blogr/static/blog/nginx_proxy_reverse.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
ctp_blogr/static/blog/proxy_certificat.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
ctp_blogr/static/blog/proxy_ddns.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
ctp_blogr/static/blog/proxy_firewall.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
ctp_blogr/static/blog/proxy_inverse.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
ctp_blogr/static/blog/proxy_regle.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
ctp_blogr/static/blog/proxy_reverse.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
ctp_blogr/static/blog/vpn_1.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
ctp_blogr/static/blog/vpn_2.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
ctp_blogr/static/blog/vpn_3.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
ctp_blogr/static/blog/wonders_cao.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
ctp_blogr/static/mythosaur_skull_480x480.webp
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
ctp_blogr/static/mythosaur_skull_60x150.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
ctp_blogr/static/mythosaur_skull_80x80.jpg
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
ctp_blogr/static/mythosaur_skull_80x80.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
ctp_blogr/static/mythosaur_skull_red.ico
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
ctp_blogr/static/mythosaur_skull_white.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -2,18 +2,18 @@
|
||||
/* https://www.w3schools.com/bootstrap/bootstrap_theme_band.asp */
|
||||
|
||||
body {
|
||||
font: 400 19px/1.8 Georgia, serif;
|
||||
color: #777;
|
||||
font: 100 18px/24px Times New Roman, serif;
|
||||
color: rgb(90, 89, 89);
|
||||
}
|
||||
h3, h4 {
|
||||
.container h2, h3{
|
||||
color: #df4937 !important;
|
||||
}
|
||||
h5 {
|
||||
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;
|
||||
@@ -38,6 +38,21 @@ h3, h4 {
|
||||
display: none; /* Hide the carousel text when the screen is less than 600 pixels wide */
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container{
|
||||
max-width: 950px;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
/* ne pas affichier l'url after the link */
|
||||
a[href]:after {
|
||||
content: none !important;
|
||||
}
|
||||
* {
|
||||
color: inherit !important;
|
||||
background-color: inherit !important;
|
||||
}
|
||||
}
|
||||
.bg-1 {
|
||||
background: #bc2131;
|
||||
color: #bdbdbd;
|
||||
@@ -57,6 +72,9 @@ h3, h4 {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.monospace-font {
|
||||
font-family: Monaco, "Courier New", Courier, monospace;
|
||||
}
|
||||
.thumbnail p {
|
||||
margin-top: 15px;
|
||||
color: #555;
|
||||
@@ -102,6 +120,7 @@ h3, h4 {
|
||||
.navbar-default .navbar-toggle {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #bc2131;
|
||||
color: #f5f5f5;
|
||||
@@ -141,3 +160,4 @@ textarea {
|
||||
.dropdown-menu li a:hover {
|
||||
background-color: red !important;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
|
||||
<h1><span class="font-semi-bold">CTP Blog</span></h1>
|
||||
<p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "cao_blogr:templates/layout.jinja2" %}
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
{% if request.authenticated_userid %}
|
||||
<p>
|
||||
<a href="{{ request.route_url('home') }}">[ Retour ]</a>
|
||||
<a href="{{ return_url }}">[ Retour ]</a>
|
||||
<a href="{{ request.route_url('blog_edit', id=entry.id) }}">[ Modifier ]</a>
|
||||
|
||||
</p>
|
||||
@@ -13,8 +13,7 @@
|
||||
<p>{{ body_html | safe }}</p>
|
||||
<hr/>
|
||||
<p>
|
||||
Auteur : <strong>{{ entry.author }}</strong><br>
|
||||
Publié le : <strong>{{ entry.created.strftime("%d-%m-%Y - %H:%M") }}</strong><br>
|
||||
Publié le : <strong>{{ entry.edited.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>
|
||||
26
ctp_blogr/templates/blog_bytag.jinja2
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p><a href="{{ url_retour }}">[ retour ]</a></p>
|
||||
|
||||
<table class="table table-condensed">
|
||||
{% for entry in items %}
|
||||
<tr>
|
||||
<td>{{ entry.created.strftime("%d %b %y") }}</td>
|
||||
<td>
|
||||
<a href="{{ request.route_url('blog', id=entry.id, slug=entry.slug) }}">{{ entry.title }}</a>
|
||||
</td>
|
||||
{% if entry.status != 'publié' %}
|
||||
<td><span class="label label-danger">{{ entry.status }}</span></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif%}
|
||||
</tr>
|
||||
{% else %}
|
||||
<p class="text-danger">Aucun post trouvé !</p>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<p class="text-center">[ {{nb_items}} billets ]</p>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "cao_blogr:templates/layout.jinja2" %}
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
<form action="{{ url }}" method="post" class="form">
|
||||
@@ -16,7 +16,7 @@
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="body">{{ form.body.label }}</label>
|
||||
{{ form.body(class_='form-control', cols="35", rows="20") }}
|
||||
{{ form.body(class_='form-control monospace-font', cols="35", rows="20") }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -31,7 +31,9 @@
|
||||
|
||||
<p>
|
||||
{% if blog_id != '0' %}
|
||||
Créé le : <strong>{{ entry.created.strftime("%d-%m-%Y - %H:%M") }}</strong><br>
|
||||
Créé le : <strong>{{ entry.created.strftime("%d-%m-%Y - %H:%M") }}</strong>
|
||||
<button class="btn btn-link" type="submit" name="form.update_created">[ Mettre à jour ]</button>
|
||||
<br>
|
||||
Modifié le : <strong>{{ entry.edited.strftime("%d-%m-%Y - %H:%M") }}</strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "cao_blogr:templates/layout.jinja2" %}
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
<form id="search-form" class="form-horizontal" role="form" action="/blog_search" method="post">
|
||||
@@ -46,6 +46,12 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<h2 class="text-center">Index</h2>
|
||||
<p class="text-center">
|
||||
|
|
||||
{% for tag in tags %}
|
||||
<a href="{{ request.route_url('blog_bytag', tag=tag.tag, retour='blog_search') }}">{{ tag.tag }}</a> |
|
||||
{% endfor %}
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
43
ctp_blogr/templates/home.jinja2
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div>
|
||||
<h3 class="text-center">{{ cartoon_title }}</h3>
|
||||
<p class="text-center">
|
||||
<img src="{{ cartoon_url }}"
|
||||
class="img_responsive" style="width:440px" /><br>
|
||||
<a href="https://www.chappatte.com/fr/" target="_blank">© Chappatte</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
{% if request.authenticated_userid %}
|
||||
<p>
|
||||
<a href="{{ request.route_url('blog_edit', id='0') }}">[ Nouveau ]</a>
|
||||
<a href="{{ request.route_url('blog_edit', id='95') }}">[ Sélectionner un autre le dessin ]</a>
|
||||
</p>
|
||||
{% endif%}
|
||||
|
||||
<table id="posts_list" class="table table-condensed">
|
||||
{% for entry in last_ten %}
|
||||
<tr>
|
||||
<td>{{ entry.created.strftime("%d %b %y") }}</td>
|
||||
<td>
|
||||
<a href="{{ request.route_url('blog', id=entry.id, slug=entry.slug) }}">{{ entry.title }}</a>
|
||||
</td>
|
||||
<td>
|
||||
[ <a href="{{ request.route_url('blog_bytag', tag=entry.tag, retour='home') }}">{{ entry.tag }}</a> ]
|
||||
</td>
|
||||
{% if entry.status != 'publié' %}
|
||||
<td><span class="label label-danger">{{ entry.status }}</span></td>
|
||||
{% else %}
|
||||
<td> </td>
|
||||
{% endif%}
|
||||
</tr>
|
||||
{% else %}
|
||||
<p class="text-danger">Aucun post trouvé</p>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="pyramid web application">
|
||||
<link rel="shortcut icon" href="{{request.static_url('cao_blogr:static/pyramid-16x16.png')}}">
|
||||
<link rel="shortcut icon" href="{{request.static_url('ctp_blogr:static/mythosaur_skull_red.ico')}}">
|
||||
|
||||
<title>{{page_title}}</title>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
|
||||
|
||||
<!-- Custom styles for this scaffold -->
|
||||
<link href="{{request.static_url('cao_blogr:static/theme.css')}}" rel="stylesheet">
|
||||
<link href="{{request.static_url('ctp_blogr:static/theme.css')}}" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{{ request.route_url('home') }}">CAO Blogr</a>
|
||||
<a class="navbar-brand" href="{{ request.route_url('home') }}"><img src="{{request.static_url('ctp_blogr:static/mythosaur_skull_60x150.png')}}" /></a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="myNavbar">
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
@@ -42,7 +42,12 @@
|
||||
<ul class="dropdown-menu">
|
||||
{% if request.authenticated_userid == 'admin' %}
|
||||
<li><a href="{{request.route_url('users')}}"><span class="glyphicon glyphicon-user"></span> Utilisateurs</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{request.route_url('user_edit', name=request.authenticated_userid)}}">
|
||||
<span class="glyphicon glyphicon-user"></span> Modifier le mot de passe</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li><a href="{{ request.route_url('portfolio') }}"><span class="glyphicon glyphicon-briefcase"></span> Portfolio</a></li>
|
||||
<li><a href="{{ request.route_url('tags') }}"><span class="glyphicon glyphicon-tag"></span> Tags</a></li>
|
||||
<li><a href="{{ request.route_url('logout') }}"><span class="glyphicon glyphicon-off"></span> Se déconnecter</a></li>
|
||||
</ul>
|
||||
@@ -58,14 +63,10 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% if request.path == '/' %}
|
||||
{# -- display carousel -- #}
|
||||
{% block carousel %}
|
||||
{% endblock carousel %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Container (The Page Template Section) -->
|
||||
<div class="container">
|
||||
<br>
|
||||
<br>
|
||||
<!-- Display Page Title -->
|
||||
{% if page_title %}
|
||||
<h1>{{ page_title }}</h1>
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
<form action="{{ login_url }}" method="post">
|
||||
<h2>Se connecter</h2>
|
||||
<input type="hidden" name="came_from" value="{{ came_from }}" />
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" name="username" class="form-control" placeholder="Identifiant">
|
||||
</div>
|
||||
@@ -14,7 +16,7 @@
|
||||
<input type="password" name="password" class="form-control" placeholder="Mot de passe">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Se connecter" class="btn btn-primary">
|
||||
<input type="submit" name="form.submitted" value="Se connecter" class="btn btn-primary">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
88
ctp_blogr/templates/portfolio/actif2_edit.jinja2
Normal file
@@ -0,0 +1,88 @@
|
||||
{% extends "ctp_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="{{ url }}" method="post" class="form">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field">{{ form.classe.label }}</label>
|
||||
{{ form.classe(class_='form-control') }}
|
||||
</div>
|
||||
|
||||
{% for error in form.symbole.errors %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="symbole">{{form.symbole.label}}</label>
|
||||
{{form.symbole(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
{% for error in form.libelle.errors %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="libelle">{{form.libelle.label}}</label>
|
||||
{{form.libelle(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="pru">{{form.pru.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{ item.devise }}</div>
|
||||
{{form.pru(class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="cours">{{form.cours.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{ item.devise }}</div>
|
||||
{{form.cours(class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<p class="form-control-static"><b>Dernière modif. :</b> {{ item.modif_le }}</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
|
||||
<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.no_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">×</button>
|
||||
<h4 class="modal-title">Supprimer cette allocation</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- The form is placed inside the body of modal -->
|
||||
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.symbole.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 %}
|
||||
111
ctp_blogr/templates/portfolio/actif_edit.jinja2
Normal file
@@ -0,0 +1,111 @@
|
||||
{% extends "ctp_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="{{ url }}" method="post" class="form">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field">{{ form.classe.label }}</label>
|
||||
{{ form.classe(class_='form-control') }}
|
||||
</div>
|
||||
|
||||
{% for error in form.symbole.errors %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="symbole">{{form.symbole.label}}</label>
|
||||
{{form.symbole(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
{% for error in form.libelle.errors %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="libelle">{{form.libelle.label}}</label>
|
||||
{{form.libelle(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="nombre">{{form.nombre.label}}</label>
|
||||
{{form.nombre(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label required-field" for="pru">{{form.pru.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">{{ item.devise }}</div>
|
||||
{{form.pru(class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="ter">{{form.ter.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">%</div>
|
||||
{{form.ter(class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="pc_rdt">{{form.pc_rdt.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">%</div>
|
||||
{{form.pc_rdt(class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="website">{{form.website.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">
|
||||
<a href="{{ item.website }}" target="_blank"><span class="glyphicon glyphicon-globe"></span></a>
|
||||
</div>
|
||||
{{form.website(class_='form-control')}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<p class="form-control-static"><b>Dernière modif. :</b> {{ item.modif_le }}</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
|
||||
<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.no_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">×</button>
|
||||
<h4 class="modal-title">Supprimer cette allocation</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- The form is placed inside the body of modal -->
|
||||
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.symbole.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 %}
|
||||
69
ctp_blogr/templates/portfolio/allocation_edit.jinja2
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends "ctp_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="{{ url }}" method="post" class="form">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field">{{ form.classe.label }}</label>
|
||||
{{ form.classe(class_='form-control') }}
|
||||
</div>
|
||||
|
||||
{% for error in form.pc_cible.errors %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="tag">{{form.pc_cible.label}}</label>
|
||||
{{form.pc_cible(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="tag">{{form.type.label}}</label>
|
||||
{{form.type(class_='form-control')}}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="tag">{{form.bg_color.label}}</label>
|
||||
{{form.bg_color(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
|
||||
<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.no_cat.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">×</button>
|
||||
<h4 class="modal-title">Supprimer cette allocation</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- The form is placed inside the body of modal -->
|
||||
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.classe.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 %}
|
||||
64
ctp_blogr/templates/portfolio/histo_edit.jinja2
Normal file
@@ -0,0 +1,64 @@
|
||||
{% extends "ctp_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form action="{{ url }}" method="post" class="form">
|
||||
|
||||
<div class="form-group">
|
||||
<p class="form-control-static">Date : <b>{{ item.date.strftime('%d-%m-%Y') }}</b></p>
|
||||
</div>
|
||||
|
||||
{% for error in form.mvt_cash.errors %}
|
||||
<div class="error">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="tag">{{form.mvt_cash.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon">€</div>
|
||||
{{form.mvt_cash(class_='form-control')}}
|
||||
</div>
|
||||
<p class="form-control-static">(si le montant est négatif, le montant est retiré du portefeuille)</b></p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<a class="btn btn-default" href="{{ request.route_url('histo_list') }}">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
|
||||
{% if form.no_id.data %}
|
||||
<button class="btn btn-danger" type="button" data-toggle="modal" data-target="#confirmDelete">
|
||||
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary" type="submit" name="form.submitted">
|
||||
<span class="glyphicon glyphicon-ok"></span> Enregistrer</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">×</button>
|
||||
<h4 class="modal-title">Supprimer cet historique</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- The form is placed inside the body of modal -->
|
||||
<p>Etes-vous certain(e) de vouloir supprimer la ligne <b>{{ form.no_id.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 %}
|
||||
49
ctp_blogr/templates/portfolio/histo_list.jinja2
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "ctp_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>
|
||||
<a class="btn btn-default" href="{{ request.route_url('portfolio') }}">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
|
||||
<a href="{{ request.route_url('histo_edit', no_id='0') }}" class="btn btn-success" role="button">
|
||||
<span class="glyphicon glyphicon-plus"></span> Entrée / Sortie cash</a>
|
||||
</p>
|
||||
|
||||
<table id="histo_list" class="table table-striped table-bordered table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th align='right'>E/S Cash</th>
|
||||
<th align='right'>Valeur Pf</th>
|
||||
<th align='right'>Nb Part</th>
|
||||
<th align='right'>Valeur Part</th>
|
||||
<th align='right'>Cours ref</th>
|
||||
<th align='right'>Nb Part ref</th>
|
||||
<th align='center'>No Id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>{{ item.date.strftime('%d/%m/%Y') }}</td>
|
||||
<td align='right'>{{ '{0:0.2f} €'.format(item.mvt_cash) }}</td>
|
||||
<td align='right'>{{ '{0:0.2f} €'.format(item.valeur_pf) }}</td>
|
||||
<td align='right'>{{ '{0:0.2f}'.format(item.nb_part) }}</td>
|
||||
<td align='right'>{{ '{0:0.2f} €'.format(item.val_part) }}</td>
|
||||
<td align='right'>{{ '{0:0.2f} €'.format(item.cours_ref) }}</td>
|
||||
<td align='right'>{{ '{0:0.2f}'.format(item.val_part_ref) }}</td>
|
||||
<td align='center'>
|
||||
<a href="{{ request.route_url('histo_edit', no_id=item.no_id) }}">{{ item.no_id }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
245
ctp_blogr/templates/portfolio/portfolio.jinja2
Normal file
@@ -0,0 +1,245 @@
|
||||
{% extends "ctp_blogr:templates/layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>
|
||||
<a href="allocation_edit/0">[ Nouvelle classe ]</a>
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<table class="table table-condensed table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Classe</th>
|
||||
<th class="text-right">% cible</th>
|
||||
<th class="text-right">% actuel</th>
|
||||
<th class="text-right">Ecart</th>
|
||||
<th class="text-right">Valeur</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td class="{{ item.bg_color }}"><a href="allocation_edit/{{ item.no_cat }}">{{ item.classe }}</a></td>
|
||||
<td class="text-right">{{ item.pc_cible }} %</td>
|
||||
<td class="text-right">{{ '{0:0.1f}'.format(item.pc_atteint) }} %</td>
|
||||
{% if (item.pc_atteint - item.pc_cible) >= 0 %}
|
||||
<td class="text-right" style="color: green;">{{ '{0:0.1f}'.format(item.pc_ecart) }}</td>
|
||||
{% else %}
|
||||
<td class="text-right" style="color: red;">{{ '{0:0.1f}'.format(item.pc_ecart) }}</td>
|
||||
{% endif %}
|
||||
<td class="text-right">{{ '{0:0,.0f} €'.format(item.valeur).replace(',',' ') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<table id="portfolio" class="table table-condensed table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Portefeuille</th>
|
||||
<th class="text-right">Montant</th>
|
||||
<th class="text-right">%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Valorisation</td>
|
||||
<td class="text-right">{{ '{0:0.2f} €'.format(total_valeur).replace(',',' ') }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Plus value</td>
|
||||
<td class="text-right">{{ '{0:0,.0f} €'.format(total_pv).replace(',',' ') }}</td>
|
||||
<td class="text-right">{{ '{0:0.1f} %'.format(total_pc_value) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Safe Withdrawal Rate</td>
|
||||
<td class="text-right text-success"><b>{{ '{0:0,.0f} €'.format(swr_amount) }}</b></td>
|
||||
<td class="text-right text-success"><b>{{ '{0:0.1f} %'.format(swr_rate) }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<b>Allocation globale</b> : 70% actions + 30% obligations<br />
|
||||
[Inspirée du
|
||||
<a href="https://www.nbim.no/en/the-fund/how-we-invest/benchmark-index/" target="_blank">Fond souverain Norvégien</a>]<br />
|
||||
<b>Allocation actions</b> : 80% Monde (56%) + 20% Croissance (14%)<br />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<!-- graphique donut cible -->
|
||||
<div id="donutchart_cible" style="width: 100%; height: 500px;"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<!-- graphique donut actuel -->
|
||||
<div id="donutchart_actuel" style="width: 100%; height: 500px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<h2>Mes actifs</h2>
|
||||
<p>"<i>Diversification is not determined by the number of securities held.</i>"
|
||||
<a href="http://www.etf.com/sections/index-investor-corner" target="_blank">Larry Swedroe</a></p>
|
||||
|
||||
<form id="actif_list-form" action="{{ url }}" method="post">
|
||||
<div class="form-group">
|
||||
<button id="updateButton" class="btn btn-primary" type="submit" name="form.submitted">
|
||||
<i class="glyphicon glyphicon-refresh"></i> MAJ du portefeuille</button>
|
||||
<a href="#" data-toggle="modal" data-target="#choixTypeActif">[ Nouvel actif ]</a>
|
||||
<a href="/histo_list">[ Historique ]</a>
|
||||
<a href="/blog/2/mouvements-du-portefeuille">[ Mouvements ]</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table table-condensed table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Classe</th>
|
||||
<th>Libellé</th>
|
||||
<th class="text-right">Cours</th>
|
||||
<th class="text-right">Nb</th>
|
||||
<th class="text-right">Valeur</th>
|
||||
<th class="text-right">+/- Valeur</th>
|
||||
<th class="text-right">% de +/-</th>
|
||||
<th class="text-right">% TER</th>
|
||||
<th class="text-right">% PF</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ligne in actifs %}
|
||||
<tr>
|
||||
<td class="{{ ligne.Allocation.bg_color }}">{{ ligne.Allocation.classe }}</td>
|
||||
{% if ligne.Allocation.type=='ACTION' %}
|
||||
<td><a href="actif_edit/{{ ligne.Actifs.no_id }}">{{ ligne.Actifs.libelle }}</a></td>
|
||||
{% else %}
|
||||
<td><a href="actif2_edit/{{ ligne.Actifs.no_id }}">{{ ligne.Actifs.libelle }}</a></td>
|
||||
{% endif %}
|
||||
{% if ligne.Actifs.devise=='EUR' %}
|
||||
<td class="text-right">{{ '{0:0.2f} €'.format(ligne.Actifs.cours) }}</td>
|
||||
{% else %}
|
||||
<td class="text-right">{{ '{0:0.2f} $'.format(ligne.Actifs.cours) }}</td>
|
||||
{% endif %}
|
||||
<td class="text-right">{{ ligne.Actifs.nombre }}</td>
|
||||
<td class="text-right">{{ '{0:0,.2f} €'.format(ligne.Actifs.valeur).replace(',',' ') }}</td>
|
||||
{% if ligne.Actifs.plus_value >= 0 %}
|
||||
<td class="text-right" style="color: green;">{{ '{0:0,.2f} €'.format(ligne.Actifs.plus_value).replace(',',' ') }}</td>
|
||||
<td class="text-right" style="color: green;">{{ '{0:0.1f}'.format(ligne.Actifs.pc_plusvalue) }}</td>
|
||||
{% else %}
|
||||
<td class="text-right" style="color: red;">{{ '{0:0,.2f} €'.format(ligne.Actifs.plus_value).replace(',',' ') }}</td>
|
||||
<td class="text-right" style="color: red;">{{ '{0:0.1f}'.format(ligne.Actifs.pc_plusvalue) }}</td>
|
||||
{% endif %}
|
||||
<td class="text-right">{{ '{0:0.1f}'.format(ligne.Actifs.ter) }}</td>
|
||||
<td class="text-right">{{ '{0:0.1f}'.format(ligne.Actifs.pc_allocation) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
<tr>
|
||||
<td class="text-right" colspan="4"><b>Total au {{ maj_pf_le.strftime('%d/%m/%Y') }}</b></td>
|
||||
<td>{{ '{0:0,.2f} €'.format(total_valeur).replace(',',' ') }}</td>
|
||||
{% if total_pv >= 0 %}
|
||||
<td class="text-right" style="color: green;">{{ '{0:0,.2f} €'.format(total_pv).replace(',',' ') }}</td>
|
||||
<td class="text-right" style="color: green;">{{ '{0:0.1f}'.format(total_pc_value) }}</td>
|
||||
{% else %}
|
||||
<td class="text-right" style="color: red;">{{ '{0:0,.2f} €'.format(total_pv).replace(',',' ') }}</td>
|
||||
<td class="text-right" style="color: red;">{{ '{0:0.1f}'.format(total_pc_value) }}</td>
|
||||
{% endif %}
|
||||
<td></td>
|
||||
<td class="text-right"><b>100.0</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>
|
||||
<!-- graphique evolution -->
|
||||
<div id="curve_chart" style="width: 100%; height: 500px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODAL POPUP : Affichage de la FAQ -->
|
||||
<div class="modal fade" id="choixTypeActif" tabindex="-1" role="dialog" aria-labelledby="choixTypeActif" aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span class="glyphicon glyphicon-remove"></span></button>
|
||||
<h4 class="modal-title" id="choixTypeActif">Choix du type</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Voulez-vous créer un actif de type <br />
|
||||
<br />
|
||||
<a href="/actif_edit/0" class="btn btn-primary" role="button">
|
||||
ACTION</a> ou
|
||||
<a href="/actif2_edit/0" class="btn btn-warning" role="button">
|
||||
AUTRE</a>
|
||||
<br />
|
||||
<br />
|
||||
<p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$('#updateButton').on('click', function(){
|
||||
$('i.gly-spin').removeClass('gly-spin');
|
||||
$('i').addClass('gly-spin');
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script type="text/javascript">
|
||||
google.charts.load("current", {packages:["corechart"]});
|
||||
google.charts.setOnLoadCallback(drawChart);
|
||||
var dataSet_cible = {{ donut_cible | safe }};
|
||||
var dataSet_actuel = {{ donut_actuel | safe }};
|
||||
var dataSet_evoln = {{ courbe_evoln | safe }};
|
||||
|
||||
function drawChart() {
|
||||
var data_cible = google.visualization.arrayToDataTable(dataSet_cible);
|
||||
var data_actuel = google.visualization.arrayToDataTable(dataSet_actuel);
|
||||
var data_evoln = google.visualization.arrayToDataTable(dataSet_evoln);
|
||||
|
||||
var options_cible = {
|
||||
title: 'Allocation cible',
|
||||
pieHole: 0.4,
|
||||
slices: {
|
||||
0: {color: 'SteelBlue'}, 1: {color: 'LightSteelBlue'},
|
||||
2: {color: 'Maroon'}, 3: {color: 'Brown'},
|
||||
5: {offset: 0.2, color: 'DarkGreen'}, 6: {offset: 0.3, color: 'Green'},
|
||||
},
|
||||
};
|
||||
var options_actuel = {
|
||||
title: 'Allocation actuelle',
|
||||
pieHole: 0.4,
|
||||
slices: {
|
||||
0: {color: 'SteelBlue'}, 1: {color: 'LightSteelBlue'},
|
||||
2: {color: 'Maroon'}, 3: {color: 'Brown'},
|
||||
5: {offset: 0.2, color: 'DarkGreen'}, 6: {offset: 0.3, color: 'Green'},
|
||||
},
|
||||
};
|
||||
var options_evoln = {
|
||||
title: 'Evolution du portefeuille VS fond Carmignac Investissement',
|
||||
curveType: 'function',
|
||||
legend: { position: 'top' }
|
||||
};
|
||||
|
||||
var chart_cible = new google.visualization.PieChart(document.getElementById('donutchart_cible'));
|
||||
chart_cible.draw(data_cible, options_cible);
|
||||
var chart_actuel = new google.visualization.PieChart(document.getElementById('donutchart_actuel'));
|
||||
chart_actuel.draw(data_actuel, options_actuel);
|
||||
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
|
||||
chart.draw(data_evoln, options_evoln);
|
||||
}
|
||||
</script>
|
||||
|
||||
</div><!-- content -->
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "cao_blogr:templates/layout.jinja2" %}
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "cao_blogr:templates/layout.jinja2" %}
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
55
ctp_blogr/templates/user_edit.jinja2
Normal file
@@ -0,0 +1,55 @@
|
||||
{% extends "layout.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if message %}
|
||||
<div class="alert alert-danger">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="{{ url }}" method="post" class="form">
|
||||
|
||||
{% for error in form.name.errors %}
|
||||
<div class="label label-warning">{{ error }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="name">{{form.name.label}}</label>
|
||||
{% if form.id.data %}
|
||||
<input class="form-control" name="name" readonly type="text" value="{{form.name.data}}">
|
||||
{% else %}
|
||||
{{form.name(class_='form-control')}}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="password">{{form.password.label}}</label>
|
||||
{{form.password(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
{% for error in form.confirm.errors %}
|
||||
<div class="label label-danger">{{error}}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required-field" for="confirm">{{form.confirm.label}}</label>
|
||||
{{form.confirm(class_='form-control')}}
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<a class="btn btn-default" href="{{ url_retour }}"><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 and request.authenticated_userid == 'admin' %}
|
||||
<button class="btn btn-warning" type="submit" name="form.deleted">
|
||||
<span class="glyphicon glyphicon-remove"></span> Supprimer</button>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -4,7 +4,7 @@
|
||||
<p>
|
||||
<a href="{{ request.route_url('home' ) }}" class="btn btn-default" role="button">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span> Retour</a>
|
||||
<a href="{{ request.route_url('user_add', name='new') }}" class="btn btn-success" role="button">
|
||||
<a href="{{ request.route_url('user_edit', name='0') }}" class="btn btn-success" role="button">
|
||||
<span class="glyphicon glyphicon-plus"></span> Nouvel utilisateur</a>
|
||||
</p>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<tr>
|
||||
<td>{{ entry.id }}</td>
|
||||
<td>
|
||||
<a href="{{ request.route_url('user_pwd', name=entry.name) }}">
|
||||
<a href="{{ request.route_url('user_edit', name=entry.name) }}">
|
||||
{{ entry.name }}
|
||||
</a>
|
||||
</td>
|
||||
@@ -6,8 +6,7 @@ from ..forms import BlogCreateForm, BlogUpdateForm, BlogSearchForm, TagForm
|
||||
import markdown
|
||||
import datetime #<- will be used to set default dates on models
|
||||
|
||||
@view_config(route_name='blog',
|
||||
renderer='cao_blogr:templates/blog.jinja2')
|
||||
@view_config(route_name='blog', renderer='ctp_blogr:templates/blog.jinja2')
|
||||
def blog(request):
|
||||
# get post id from request
|
||||
blog_id = request.matchdict['id']
|
||||
@@ -22,14 +21,21 @@ def blog(request):
|
||||
# convertir de markdown en HTML
|
||||
body_html = markdown.markdown(body, extensions=['footnotes'])
|
||||
|
||||
# si page mouvement de portfolio
|
||||
if blog_id == '2':
|
||||
return_url = "/portfolio"
|
||||
else:
|
||||
return_url = "/"
|
||||
|
||||
return {
|
||||
'page_title': entry.title,
|
||||
'entry': entry,
|
||||
'body_html': body_html,
|
||||
'return_url': return_url,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='blog_edit', renderer='cao_blogr:templates/blog_edit.jinja2', permission='view')
|
||||
@view_config(route_name='blog_edit', renderer='ctp_blogr:templates/blog_edit.jinja2', permission='view')
|
||||
def blog_edit(request):
|
||||
# get post id from request
|
||||
blog_id = request.matchdict['id']
|
||||
@@ -74,6 +80,12 @@ def blog_edit(request):
|
||||
entry.editor = request.authenticated_userid
|
||||
return HTTPFound(location=request.route_url('blog', id=entry.id, slug=entry.slug))
|
||||
|
||||
if 'form.update_created' in request.params and form.validate():
|
||||
del form.id # SECURITY: prevent overwriting of primary key
|
||||
form.populate_obj(entry)
|
||||
entry.created = datetime.datetime.now()
|
||||
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')
|
||||
@@ -86,12 +98,32 @@ def blog_edit(request):
|
||||
'blog_id': blog_id,
|
||||
'entry': entry, }
|
||||
|
||||
@view_config(route_name='blog_bytag', renderer='ctp_blogr:templates/blog_bytag.jinja2')
|
||||
def blog_bytag(request):
|
||||
# get tag parameters from request
|
||||
tag = request.matchdict['tag']
|
||||
retour = request.matchdict['retour']
|
||||
url_retour = request.route_url(retour)
|
||||
|
||||
@view_config(route_name='blog_search',
|
||||
renderer='cao_blogr:templates/blog_search.jinja2')
|
||||
# get the posts by tag
|
||||
items = BlogRecordService.by_tag(request, tag)
|
||||
nb_items = len(items)
|
||||
|
||||
return {
|
||||
'page_title': "Billets avec tag : " + tag,
|
||||
'items': items,
|
||||
'nb_items': nb_items,
|
||||
'url_retour': url_retour,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='blog_search', renderer='ctp_blogr:templates/blog_search.jinja2')
|
||||
def blog_search(request):
|
||||
|
||||
criteria = ''
|
||||
# get the list of tags
|
||||
tags = BlogRecordService.get_tags(request)
|
||||
|
||||
form = BlogSearchForm(request.POST)
|
||||
items = []
|
||||
if 'form.submitted' in request.params and form.validate():
|
||||
@@ -104,12 +136,13 @@ def blog_search(request):
|
||||
'form': form,
|
||||
'items': items,
|
||||
'criteria': criteria,
|
||||
'tags': tags,
|
||||
}
|
||||
|
||||
@view_config(route_name='tags', renderer='cao_blogr:templates/tags.jinja2', permission='view')
|
||||
@view_config(route_name='tags', renderer='ctp_blogr:templates/tags.jinja2', permission='view')
|
||||
def tags(request):
|
||||
|
||||
# get the list of tags of this topic
|
||||
# get the list of tags
|
||||
tags = BlogRecordService.get_tags(request)
|
||||
|
||||
return {
|
||||
@@ -117,7 +150,7 @@ def tags(request):
|
||||
'tags': tags,
|
||||
}
|
||||
|
||||
@view_config(route_name='tag_edit', renderer='cao_blogr:templates/tag_edit.jinja2', permission='view')
|
||||
@view_config(route_name='tag_edit', renderer='ctp_blogr:templates/tag_edit.jinja2', permission='view')
|
||||
def tag_edit(request):
|
||||
# get tag parameters from request
|
||||
tag_id = request.matchdict['id']
|
||||
160
ctp_blogr/views/default.py
Normal file
@@ -0,0 +1,160 @@
|
||||
from pyramid.view import (
|
||||
view_config,
|
||||
forbidden_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='../templates/home.jinja2')
|
||||
def home(request):
|
||||
# get the last created posts
|
||||
last_ten = BlogRecordService.get_last_created(request)
|
||||
# get Chappatte cartoon
|
||||
entry = BlogRecordService.by_id(request, 95)
|
||||
|
||||
return {
|
||||
'page_title': "This is the Way",
|
||||
'last_ten': last_ten,
|
||||
'cartoon_title': entry.title,
|
||||
'cartoon_url': entry.body,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='apropos',
|
||||
renderer='../templates/apropos.jinja2')
|
||||
def apropos(request):
|
||||
|
||||
return {
|
||||
'page_title': "A propos",
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='login', renderer='../templates/login.jinja2')
|
||||
@forbidden_view_config(renderer='../templates/login.jinja2')
|
||||
def login(request):
|
||||
login_url = request.route_url('login')
|
||||
|
||||
referrer = request.url
|
||||
if referrer == login_url:
|
||||
referrer = '/' # never use the login form itself as came_from
|
||||
|
||||
came_from = request.params.get('came_from', referrer)
|
||||
username = ''
|
||||
userpwd = ''
|
||||
if 'form.submitted' in request.params:
|
||||
username = request.POST.get('username')
|
||||
userpwd = request.POST.get('password')
|
||||
user = UserService.by_name(request, username)
|
||||
if user and user.verify_password(userpwd):
|
||||
headers = remember(request, username)
|
||||
request.session.flash("Bienvenue %s !" % username, 'success')
|
||||
return HTTPFound(location=came_from, headers=headers)
|
||||
else:
|
||||
headers = forget(request)
|
||||
request.session.flash("Login et mot de passe invalides. La connexion a échoué.", "danger")
|
||||
|
||||
return {
|
||||
'page_title': "",
|
||||
'came_from': came_from,
|
||||
'login_url': login_url,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='logout', renderer='string')
|
||||
def logout(request):
|
||||
username = request.authenticated_userid
|
||||
if username == None:
|
||||
username = ''
|
||||
|
||||
headers = forget(request)
|
||||
request.session.flash('Au revoir ' + username + ' !', 'success')
|
||||
return HTTPFound(location=request.route_url('home'), headers=headers)
|
||||
|
||||
|
||||
@view_config(route_name='users', renderer='../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_edit', renderer='ctp_blogr:templates/user_edit.jinja2', permission='view')
|
||||
def user_edit(request):
|
||||
message = ''
|
||||
name = request.matchdict['name']
|
||||
url = request.route_url('user_edit', name=name)
|
||||
if request.authenticated_userid == 'admin':
|
||||
url_retour = request.route_url('users')
|
||||
else:
|
||||
url_retour = request.route_url('home')
|
||||
|
||||
if name == '0':
|
||||
# nouvel utilisateur
|
||||
user = User()
|
||||
form = UserCreateForm(request.POST, user)
|
||||
page_title = "Nouvel utilisateur"
|
||||
else:
|
||||
# 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)
|
||||
|
||||
form = UserCreateForm(request.POST, user)
|
||||
page_title = "Modification utilisateur"
|
||||
|
||||
|
||||
if 'form.submitted' in request.params and form.validate():
|
||||
# controle que le password a moins 6 car
|
||||
if len(form.password.data) < 6 :
|
||||
message = "Le mot de passe doit avoir au moins 6 caractères"
|
||||
else:
|
||||
if name == '0':
|
||||
# création user
|
||||
# controler que le nouvel user n'existe pas dans la BD
|
||||
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)
|
||||
user.set_password(form.password.data.encode('utf8'))
|
||||
# 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)
|
||||
|
||||
else:
|
||||
# modification user
|
||||
del form.name # SECURITY: prevent overwriting of primary key
|
||||
form.populate_obj(user)
|
||||
user.set_password(form.password.data.encode('utf8'))
|
||||
request.session.flash("La fiche a été modifiée avec succès.", 'success')
|
||||
return HTTPFound(location=url_retour)
|
||||
|
||||
if 'form.deleted' in request.params:
|
||||
delete(request, user.id)
|
||||
request.session.flash("La fiche a été supprimée avec succès.", 'success')
|
||||
return HTTPFound(location=url_retour)
|
||||
|
||||
return {
|
||||
'page_title': page_title,
|
||||
'message': message,
|
||||
'form': form,
|
||||
'url': url,
|
||||
'url_retour': url_retour,
|
||||
'name': name,
|
||||
}
|
||||
|
||||
@view_config(route_name='portal', renderer='../templates/portal.jinja2')
|
||||
def portal(request):
|
||||
|
||||
return {
|
||||
'page_title': "A propos",
|
||||
}
|
||||
373
ctp_blogr/views/portfolio.py
Normal file
@@ -0,0 +1,373 @@
|
||||
from pyramid.view import (
|
||||
view_config,
|
||||
)
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
from ..services.portfolio import PFService
|
||||
from ..forms import ActifForm, Actif2Form, AllocationForm, HistoForm
|
||||
from ..models.portfolio import Actifs, Histo, Allocation
|
||||
|
||||
import datetime #<- will be used to set default dates on models
|
||||
import yfinance as yf
|
||||
import json
|
||||
import html
|
||||
|
||||
@view_config(route_name='portfolio', renderer='../templates/portfolio/portfolio.jinja2', permission='view')
|
||||
def portfolio(request):
|
||||
logged_in = request.authenticated_userid
|
||||
url = request.route_url('portfolio')
|
||||
|
||||
# lire les categories
|
||||
items = PFService.get_allocation(request, '0')
|
||||
# construire la liste pour donut
|
||||
donut_cible=[]
|
||||
donut_cible.append(('Allocation cible', 'Pourcent'))
|
||||
donut_actuel=[]
|
||||
donut_actuel.append(('Allocation actuelle', 'Pourcent'))
|
||||
|
||||
# calculer % total
|
||||
total = 0
|
||||
for item in items:
|
||||
# construire la liste pour donut cible
|
||||
d = (item.classe, item.pc_cible)
|
||||
donut_cible.append(d)
|
||||
# construire la liste pour donut actuel
|
||||
d = (item.classe, int(item.pc_atteint * 10))
|
||||
donut_actuel.append(d)
|
||||
# totaliser les pourcentages
|
||||
total += item.pc_cible
|
||||
|
||||
swr_rate = 3.5
|
||||
if total != 100:
|
||||
request.session.flash('Attention, le total de votre répartition cible dépasse 100% : ' + str(total), 'warning')
|
||||
|
||||
# lire les actifs
|
||||
actifs = PFService.get_actifs(request, '0')
|
||||
|
||||
# MAJ du prtefeuille
|
||||
if 'form.submitted' in request.params:
|
||||
# lire le cours de EURUSD
|
||||
ticker = yf.Ticker('EUR=X')
|
||||
# maj des parités des devises
|
||||
taux = ticker.fast_info.get('lastPrice')
|
||||
PFService.update_actif_devise(request, 'USD', taux)
|
||||
|
||||
for item in actifs:
|
||||
if item.Allocation.type == 'ACTION':
|
||||
symbole = item.Actifs.symbole
|
||||
# lire le cours de l'action
|
||||
ticker = yf.Ticker(symbole)
|
||||
# ticker delisted ?
|
||||
if symbole == 'SHLDQ':
|
||||
cours = 0.1
|
||||
else:
|
||||
cours = round(ticker.fast_info.get('lastPrice'), 3)
|
||||
|
||||
valeur = round(cours * item.Actifs.parite * item.Actifs.nombre, 3)
|
||||
plus_value = round(valeur - (item.Actifs.pru * item.Actifs.nombre),2)
|
||||
pc_plusvalue = round(valeur * 100 / (item.Actifs.pru * item.Actifs.nombre) - 100, 3)
|
||||
PFService.update_actif_valeur(request, symbole, cours, valeur, plus_value, pc_plusvalue)
|
||||
# time.sleep(1) # attendre 2 secondes
|
||||
|
||||
# update du portefeuille
|
||||
today = datetime.datetime.now()
|
||||
PFService.update_portefeuille(request, today)
|
||||
# relire les actifs
|
||||
actifs = PFService.get_actifs(request, '0')
|
||||
request.session.flash('Le portefeuille est mis à jour avec succès.', 'success')
|
||||
|
||||
total_valeur = 0
|
||||
total_pv = 0
|
||||
total_rdt = 0
|
||||
maj_pf_le = None
|
||||
for item in actifs:
|
||||
maj_pf_le = item.Actifs.modif_le
|
||||
total_valeur += item.Actifs.valeur
|
||||
total_pv += item.Actifs.plus_value
|
||||
if total_valeur == 0:
|
||||
total_pc_value = 0
|
||||
else:
|
||||
total_pc_value = total_pv / total_valeur * 100
|
||||
total_rdt += item.Actifs.rendement
|
||||
|
||||
# lire l'historique
|
||||
histos = PFService.get_histo(request,'0')
|
||||
courbe_evoln=[]
|
||||
courbe_evoln.append(('Date', 'Valeur part PF', 'Valeur part CARINVT:FP'))
|
||||
|
||||
for item in histos:
|
||||
# construire la liste pour donut cible
|
||||
d = (item.date.strftime('%d/%m/%Y'), int(item.val_part * 1000), int(item.val_part_ref * 1000))
|
||||
courbe_evoln.append(d)
|
||||
|
||||
return {
|
||||
'page_title': "Portefeuille",
|
||||
'url': url,
|
||||
'items': items,
|
||||
'donut_cible': json.dumps(donut_cible),
|
||||
'donut_actuel': json.dumps(donut_actuel),
|
||||
'courbe_evoln': json.dumps(courbe_evoln),
|
||||
'actifs': actifs,
|
||||
'total_valeur': total_valeur,
|
||||
'total_pv': total_pv,
|
||||
'total_pc_value': total_pc_value,
|
||||
'total_rdt': total_rdt,
|
||||
'maj_pf_le' : maj_pf_le,
|
||||
'swr_rate' : swr_rate,
|
||||
'swr_amount': float(total_valeur) * swr_rate / 100,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='histo_list', renderer='../templates/portfolio/histo_list.jinja2', permission='view')
|
||||
def histo_list(request):
|
||||
# lire l historique
|
||||
items = PFService.get_histo(request, '-1')
|
||||
|
||||
return {
|
||||
'page_title': 'Historique des parts',
|
||||
'items': items,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='allocation_edit', renderer='../templates/portfolio/allocation_edit.jinja2', permission='view')
|
||||
def allocation_edit(request):
|
||||
no_cat = request.matchdict['no_cat']
|
||||
url = request.route_url('allocation_edit', no_cat=no_cat)
|
||||
|
||||
if no_cat == '0':
|
||||
# create a new allocation
|
||||
entry = Allocation()
|
||||
form = AllocationForm(request.POST, entry)
|
||||
page_title = "Nouvelle allocation"
|
||||
|
||||
else:
|
||||
# modify post
|
||||
entry = PFService.get_allocation(request, no_cat)
|
||||
if not entry:
|
||||
request.session.flash(u"Allocation non trouvée : %s" % no_cat, 'warning')
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
form = AllocationForm(request.POST, entry)
|
||||
page_title = "Modifier Allocation : " + str(entry.no_cat)
|
||||
|
||||
if 'form.submitted' in request.params :
|
||||
if no_cat == '0':
|
||||
form.populate_obj(entry)
|
||||
entry.pc_atteint = 0
|
||||
entry.pc_ecart = 0
|
||||
entry.valeur = 0
|
||||
request.dbsession.add(entry)
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
else:
|
||||
del form.no_cat # SECURITY: prevent overwriting of primary key
|
||||
form.populate_obj(entry)
|
||||
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
|
||||
if 'form.deleted' in request.params:
|
||||
PFService.delete_allocation(request, entry.no_cat)
|
||||
request.session.flash("La fiche a été supprimée avec succès.", 'success')
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
|
||||
return {
|
||||
'page_title': page_title,
|
||||
'url': url,
|
||||
'form': form,
|
||||
'item': entry,
|
||||
}
|
||||
|
||||
|
||||
@view_config(route_name='histo_edit', renderer='../templates/portfolio/histo_edit.jinja2', permission='view')
|
||||
def histo_edit(request):
|
||||
no_id = request.matchdict['no_id']
|
||||
url = request.route_url('histo_edit', no_id=no_id)
|
||||
|
||||
if no_id == '0':
|
||||
# create a new tag
|
||||
entry = Histo()
|
||||
entry.mvt_cash = 0
|
||||
entry.date = datetime.datetime.now()
|
||||
form = HistoForm(request.POST, entry)
|
||||
page_title = "Nouveau Histo"
|
||||
|
||||
else:
|
||||
# modify post
|
||||
entry = PFService.get_histo(request, no_id)
|
||||
if not entry:
|
||||
request.session.flash(u"Histo non trouvé : %s" % no_id, 'warning')
|
||||
return HTTPFound(location=request.route_url('histo_list'))
|
||||
form = HistoForm(request.POST, entry)
|
||||
page_title = "Modifier histo no " + str(entry.no_id)
|
||||
|
||||
if 'form.submitted' in request.params and form.validate():
|
||||
form.populate_obj(entry)
|
||||
|
||||
# lire le cours de l'indice de réfence : Carmignac Investissement A EUR Acc
|
||||
ticker = yf.Ticker('0P00000FB2.F')
|
||||
entry.cours_ref = round(ticker.fast_info.last_price, 3)
|
||||
|
||||
# lire le dernier histo
|
||||
last = PFService.get_last_histo(request)
|
||||
|
||||
# lire les actifs et cumuler leurs valeurs
|
||||
actifs = PFService.get_actifs(request, '0')
|
||||
valeur_pf = 0
|
||||
for item in actifs:
|
||||
valeur_pf += item.Actifs.valeur
|
||||
|
||||
entry.valeur_pf = valeur_pf
|
||||
# nlle valeur part = ancienne + nouvelle ratio
|
||||
entry.nb_part = round(last.nb_part + (float(entry.mvt_cash) / (valeur_pf - float(entry.mvt_cash))/last.nb_part), 3)
|
||||
entry.val_part = round(entry.valeur_pf / entry.nb_part, 3)
|
||||
entry.val_part_ref = round(float(entry.cours_ref) * last.val_part_ref / last.cours_ref, 3)
|
||||
|
||||
request.dbsession.add(entry)
|
||||
|
||||
return HTTPFound(location=request.route_url('histo_list'))
|
||||
|
||||
if 'form.deleted' in request.params:
|
||||
PFService.delete_histo(request, entry.no_id)
|
||||
request.session.flash("La fiche a été supprimée avec succès.", 'success')
|
||||
return HTTPFound(location=request.route_url('histo_list'))
|
||||
|
||||
return {
|
||||
'page_title': page_title,
|
||||
'url': url,
|
||||
'form': form,
|
||||
'item': entry,
|
||||
}
|
||||
|
||||
@view_config(route_name='actif_edit', renderer='../templates/portfolio/actif_edit.jinja2', permission='view')
|
||||
def actif_edit(request):
|
||||
no_id = request.matchdict['no_id']
|
||||
url = request.route_url('actif_edit', no_id=no_id)
|
||||
|
||||
# get the list of classes
|
||||
classes = PFService.get_allocation_byType(request, 'ACTION')
|
||||
|
||||
if no_id == '0':
|
||||
# create a new allocation
|
||||
entry = Actifs()
|
||||
entry.pru = 0
|
||||
entry.cours = 0
|
||||
entry.pc_rdt = 0
|
||||
entry.ter = 0
|
||||
form = ActifForm(request.POST, entry)
|
||||
form.classe.choices = [(row.classe, row.classe) for row in classes]
|
||||
page_title = "Nouvel actif"
|
||||
|
||||
else:
|
||||
# modify post
|
||||
entry = PFService.get_actifs(request, no_id)
|
||||
if not entry:
|
||||
request.session.flash(u"Actif non trouvé : %s" % no_id, 'warning')
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
form = ActifForm(request.POST, entry)
|
||||
form.classe.choices = [(row.classe, row.classe) for row in classes]
|
||||
page_title = "Modifier Actif : " + entry.symbole
|
||||
|
||||
if 'form.submitted' in request.params :
|
||||
if no_id == '0':
|
||||
form.populate_obj(entry)
|
||||
# récupérer le cours du symbole de Yahoo finance
|
||||
ticker = yf.Ticker(entry.symbole)
|
||||
entry.cours = ticker.fast_info.get('lastPrice')
|
||||
entry.devise = ticker.fast_info.get('currency')
|
||||
# raccourcir le libelle
|
||||
entry.pc_allocation = 1.0
|
||||
entry.valeur = round(float(entry.cours) * entry.parite * entry.nombre, 3)
|
||||
entry.plus_value = round(entry.valeur - float(entry.pru * entry.nombre), 3)
|
||||
entry.pc_plusvalue = round(entry.plus_value / entry.valeur * 100, 3)
|
||||
entry.rendement = 0 # round(entry.valeur * float(entry.pc_rdt) / 100, 3)
|
||||
request.dbsession.add(entry)
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
else:
|
||||
if entry.symbole != 'SHLDQ':
|
||||
del form.no_id # SECURITY: prevent overwriting of primary key
|
||||
form.populate_obj(entry)
|
||||
# récupérer le cours du symbole de Yahoo finance
|
||||
ticker = yf.Ticker(entry.symbole)
|
||||
entry.cours = ticker.fast_info.get('lastPrice')
|
||||
entry.devise = ticker.fast_info.get('currency')
|
||||
# raccourcir le libelle
|
||||
entry.valeur = round(float(entry.cours) * entry.parite * entry.nombre, 3)
|
||||
entry.plus_value = round(entry.valeur - float(entry.pru * entry.nombre), 3)
|
||||
entry.pc_plusvalue = round(entry.plus_value / entry.valeur * 100, 3)
|
||||
entry.rendement = 0 # round(entry.valeur * float(entry.pc_rdt) / 100, 3)
|
||||
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
|
||||
if 'form.deleted' in request.params:
|
||||
PFService.delete_actif(request, entry.no_id)
|
||||
request.session.flash("La fiche a été supprimée avec succès.", 'success')
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
|
||||
return {
|
||||
'page_title': page_title,
|
||||
'url': url,
|
||||
'form': form,
|
||||
'item': entry,
|
||||
}
|
||||
|
||||
@view_config(route_name='actif2_edit', renderer='../templates/portfolio/actif2_edit.jinja2', permission='view')
|
||||
def actif2_edit(request):
|
||||
no_id = request.matchdict['no_id']
|
||||
url = request.route_url('actif2_edit', no_id=no_id)
|
||||
|
||||
# get the list of classes
|
||||
classes = PFService.get_allocation_byType(request, 'AUTRE')
|
||||
|
||||
if no_id == '0':
|
||||
# create a new allocation
|
||||
entry = Actifs()
|
||||
entry.pru = 0
|
||||
entry.cours = 0
|
||||
entry.pc_rdt = 0
|
||||
entry.ter = 0
|
||||
entry.devise = 'EUR'
|
||||
form = Actif2Form(request.POST, entry)
|
||||
form.classe.choices = [(row.classe, row.classe) for row in classes]
|
||||
page_title = "Nouvel actif"
|
||||
|
||||
else:
|
||||
# modify post
|
||||
entry = PFService.get_actifs(request, no_id)
|
||||
if not entry:
|
||||
request.session.flash(u"Actif non trouvé : %s" % no_id, 'warning')
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
form = Actif2Form(request.POST, entry)
|
||||
form.classe.choices = [(row.classe, row.classe) for row in classes]
|
||||
page_title = "Modifier Actif : " + entry.symbole
|
||||
|
||||
if 'form.submitted' in request.params :
|
||||
if no_id == '0':
|
||||
form.populate_obj(entry)
|
||||
entry.nombre = 1000
|
||||
entry.devise = 'EUR'
|
||||
entry.parite = 1.0
|
||||
entry.pc_allocation = 1.0
|
||||
entry.valeur = float(entry.cours) * entry.parite * entry.nombre;
|
||||
entry.plus_value = entry.valeur - float(entry.pru * entry.nombre);
|
||||
entry.pc_plusvalue = entry.plus_value / entry.valeur * 100;
|
||||
entry.rendement = entry.valeur * float(entry.pc_rdt) / 100;
|
||||
request.dbsession.add(entry)
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
else:
|
||||
del form.no_id # SECURITY: prevent overwriting of primary key
|
||||
form.populate_obj(entry)
|
||||
entry.valeur = float(entry.cours) * entry.parite * entry.nombre;
|
||||
entry.plus_value = entry.valeur - float(entry.pru * entry.nombre);
|
||||
entry.pc_plusvalue = entry.plus_value / entry.valeur * 100;
|
||||
entry.rendement = entry.valeur * float(entry.pc_rdt) / 100;
|
||||
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
|
||||
if 'form.deleted' in request.params:
|
||||
PFService.delete_actif(request, entry.no_id)
|
||||
request.session.flash("La fiche a été supprimée avec succès.", 'success')
|
||||
return HTTPFound(location=request.route_url('portfolio'))
|
||||
|
||||
return {
|
||||
'page_title': page_title,
|
||||
'url': url,
|
||||
'form': form,
|
||||
'item': entry,
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
###
|
||||
|
||||
[app:main]
|
||||
use = egg:cao_blogr
|
||||
use = egg:ctp_blogr
|
||||
|
||||
pyramid.reload_templates = true
|
||||
pyramid.debug_authorization = false
|
||||
@@ -14,7 +14,7 @@ pyramid.default_locale_name = en
|
||||
pyramid.includes =
|
||||
pyramid_debugtoolbar
|
||||
|
||||
sqlalchemy.url = sqlite:///%(here)s/cao_blogr.sqlite
|
||||
sqlalchemy.url = sqlite:///%(here)s/ctp_blogr.sqlite
|
||||
|
||||
retry.attempts = 3
|
||||
|
||||
@@ -23,7 +23,7 @@ retry.attempts = 3
|
||||
# debugtoolbar.hosts = 127.0.0.1 ::1
|
||||
|
||||
[pshell]
|
||||
setup = cao_blogr.pshell.setup
|
||||
setup = ctp_blogr.pshell.setup
|
||||
|
||||
###
|
||||
# wsgi server configuration
|
||||
@@ -31,13 +31,13 @@ setup = cao_blogr.pshell.setup
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = cao_blogr/alembic
|
||||
script_location = ctp_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
|
||||
listen = localhost:9280
|
||||
|
||||
###
|
||||
# logging configuration
|
||||
@@ -45,7 +45,7 @@ listen = localhost:6543
|
||||
###
|
||||
|
||||
[loggers]
|
||||
keys = root, cao_blogr, sqlalchemy
|
||||
keys = root, ctp_blogr, sqlalchemy
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
@@ -57,10 +57,10 @@ keys = generic
|
||||
level = INFO
|
||||
handlers = console
|
||||
|
||||
[logger_cao_blogr]
|
||||
[logger_ctp_blogr]
|
||||
level = DEBUG
|
||||
handlers =
|
||||
qualname = cao_blogr
|
||||
qualname = ctp_blogr
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
|
||||
50
email_avnes_purge.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# Compter les emails BEFORE DATE
|
||||
#
|
||||
|
||||
from pprint import pprint
|
||||
import datetime
|
||||
import imaplib
|
||||
|
||||
# connecter au serveur IMAP
|
||||
conn = imaplib.IMAP4_SSL('mail.gandi.net')
|
||||
conn.login('ytranhong@free.fr', 'minhy116')
|
||||
# lister les dossiers
|
||||
typ, data = conn.list()
|
||||
print('Liste des dossiers :')
|
||||
pprint(data)
|
||||
|
||||
# delete mails before 14 years
|
||||
before_date = (datetime.date.today() - datetime.timedelta(365.25 * 13)).strftime("%d-%b-%Y")
|
||||
print("Delete emails before " + before_date)
|
||||
|
||||
# select ALL
|
||||
conn.select('INBOX')
|
||||
|
||||
rv, data = conn.search(None, '(BEFORE {0})'.format(before_date))
|
||||
nb_mails = str(len(data[0]))
|
||||
print(nb_mails + " emails founded")
|
||||
|
||||
resp = input ("Enter 'c' to continue, or 'a' to abort : ")
|
||||
if resp=="c":
|
||||
print("Moving " + nb_mails + " emails to Trash")
|
||||
messages = data[0].split(b' ')
|
||||
for mail in messages:
|
||||
# move to trash
|
||||
conn.store(mail, '+X-GM-LABELS', '\\Trash')
|
||||
|
||||
#This block empties trash, remove if you want to keep, Gmail auto purges trash after 30 days.
|
||||
print("Emptying Trash & Expunge...")
|
||||
conn.select('[Gmail]/Corbeille')
|
||||
conn.store("1:*", '+FLAGS', '\\Deleted')
|
||||
# delete all the selected messages
|
||||
conn.expunge()
|
||||
print("Script completed")
|
||||
else:
|
||||
print("Script aborted")
|
||||
|
||||
# deconnexion du serveur
|
||||
conn.close()
|
||||
conn.logout()
|
||||
|
||||
50
gmail_caotek_purge.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# Compter les emails BEFORE DATE
|
||||
#
|
||||
|
||||
from pprint import pprint
|
||||
import datetime
|
||||
import imaplib
|
||||
|
||||
# connecter au serveur IMAP
|
||||
conn = imaplib.IMAP4_SSL('imap.gmail.com')
|
||||
conn.login('phuoc@caotek.fr', 'pcao.8211')
|
||||
# lister les dossiers
|
||||
typ, data = conn.list()
|
||||
print('Liste des dossiers :')
|
||||
pprint(data)
|
||||
|
||||
# delete mails before 14 years
|
||||
before_date = (datetime.date.today() - datetime.timedelta(365.25 * 13)).strftime("%d-%b-%Y")
|
||||
print("Delete emails before " + before_date)
|
||||
|
||||
# select ALL
|
||||
conn.select('INBOX')
|
||||
|
||||
rv, data = conn.search(None, '(BEFORE {0})'.format(before_date))
|
||||
nb_mails = str(len(data[0]))
|
||||
print(nb_mails + " emails founded")
|
||||
|
||||
resp = input ("Enter 'c' to continue, or 'a' to abort : ")
|
||||
if resp=="c":
|
||||
print("Moving " + nb_mails + " emails to Trash")
|
||||
messages = data[0].split(b' ')
|
||||
for mail in messages:
|
||||
# move to trash
|
||||
conn.store(mail, '+X-GM-LABELS', '\\Trash')
|
||||
|
||||
#This block empties trash, remove if you want to keep, Gmail auto purges trash after 30 days.
|
||||
print("Emptying Trash & Expunge...")
|
||||
conn.select('[Gmail]/Corbeille')
|
||||
conn.store("1:*", '+FLAGS', '\\Deleted')
|
||||
# delete all the selected messages
|
||||
conn.expunge()
|
||||
print("Script completed")
|
||||
else:
|
||||
print("Script aborted")
|
||||
|
||||
# deconnexion du serveur
|
||||
conn.close()
|
||||
conn.logout()
|
||||
|
||||
53
gmail_ctphuoc_purge.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# Compter les emails BEFORE DATE et les supprimer
|
||||
#
|
||||
|
||||
from pprint import pprint
|
||||
import datetime
|
||||
import imaplib
|
||||
|
||||
# connecter au serveur IMAP
|
||||
# Gmail avec authentification à deux facteurs (2FA),
|
||||
# demander un mot de passe spécifique du compte Gmail
|
||||
conn = imaplib.IMAP4_SSL('imap.gmail.com')
|
||||
conn.login('ctphuoc@gmail.com', 'ztwciswzhxxogcfv')
|
||||
|
||||
# lister les dossiers
|
||||
typ, data = conn.list()
|
||||
print('Liste des dossiers :')
|
||||
pprint(data)
|
||||
|
||||
# supprimer les mails au-dela de 14 ans
|
||||
before_date = (datetime.date.today() - datetime.timedelta(365.25 * 13)).strftime("%d-%b-%Y")
|
||||
print("Supprimer les emails avant " + before_date)
|
||||
|
||||
# select ALL
|
||||
conn.select('INBOX')
|
||||
|
||||
rv, data = conn.search(None, '(BEFORE {0})'.format(before_date))
|
||||
nb_mails = str(len(data[0]))
|
||||
print(nb_mails + " emails trouvés")
|
||||
|
||||
resp = input ("Entrer 'c' pour continuer, or 'a' to annuler : ")
|
||||
if resp=="c":
|
||||
print("Déplacer " + nb_mails + " emails à la Corbeille")
|
||||
messages = data[0].split(b' ')
|
||||
for mail in messages:
|
||||
# move to trash
|
||||
conn.store(mail, '+X-GM-LABELS', '\\Trash')
|
||||
|
||||
#This block empties trash, remove if you want to keep, Gmail auto purges trash after 30 days.
|
||||
print("Vider la Corbeille & Expurger...")
|
||||
conn.select('[Gmail]/Corbeille')
|
||||
conn.store("1:*", '+FLAGS', '\\Deleted')
|
||||
# supprimer tous les messages selectionnés
|
||||
conn.expunge()
|
||||
print("Script terminé")
|
||||
else:
|
||||
print("Script annulé")
|
||||
|
||||
# deconnexion du serveur
|
||||
conn.close()
|
||||
conn.logout()
|
||||
|
||||
45
imap-APP-office365-dumas.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
import imaplib
|
||||
import msal
|
||||
import pprint
|
||||
|
||||
conf = {
|
||||
"authority": "https://login.microsoftonline.com/47f02128-acf0-47a7-a3bf-27a3619d4a4f",
|
||||
"client_id": "4fdf2634-a260-4576-a442-684998f0187b", #AppID
|
||||
"scope": ['https://outlook.office365.com/.default'],
|
||||
"secret": "dZD8Q~GmG_PFi.t_uRiyBEzwLeMZeemQGHUFta~J", #Key-Value
|
||||
}
|
||||
|
||||
def generate_auth_string(user, token):
|
||||
return f"user={user}\x01auth=Bearer {token}\x01\x01"
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = msal.ConfidentialClientApplication(conf['client_id'], authority=conf['authority'],
|
||||
client_credential=conf['secret'])
|
||||
|
||||
result = app.acquire_token_silent(conf['scope'], account=None)
|
||||
|
||||
if not result:
|
||||
print("No suitable token in cache. Get new one.")
|
||||
result = app.acquire_token_for_client(scopes=conf['scope'])
|
||||
|
||||
if "access_token" in result:
|
||||
print(result['token_type'])
|
||||
pprint.pprint(result)
|
||||
else:
|
||||
print(result.get("error"))
|
||||
print(result.get("error_description"))
|
||||
print(result.get("correlation_id"))
|
||||
|
||||
imap = imaplib.IMAP4('outlook.office365.com')
|
||||
imap.starttls()
|
||||
imap.authenticate("XOAUTH2", lambda x: generate_auth_string("peinture-dumas@entreprise-dumas.com", result['access_token']).encode("utf-8"))
|
||||
|
||||
# Print list of mailboxes on server
|
||||
code, mailboxes = imap.list()
|
||||
for mailbox in mailboxes:
|
||||
print(mailbox.decode("utf-8"))
|
||||
# Select mailbox
|
||||
imap.select("INBOX")
|
||||
# Cleanup
|
||||
imap.close()
|
||||
20
imap-SSL-alinto-dumas.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import imaplib
|
||||
|
||||
if __name__ == "__main__":
|
||||
# connecter au serveur IMAP avec SSL
|
||||
# authentification basique (email / password)
|
||||
imap = imaplib.IMAP4_SSL('imap.entreprise-dumas.com')
|
||||
# se connecter à la mailbox
|
||||
mbx_name = 'peinture-dumas@entreprise-dumas.com'
|
||||
mbx_pwd = 'S@sdumas69'
|
||||
imap.login(mbx_name, mbx_pwd)
|
||||
|
||||
# Print list of mailboxes on server
|
||||
code, mailboxes = imap.list()
|
||||
for mailbox in mailboxes:
|
||||
print(mailbox.decode("unicode"))
|
||||
# Select mailbox
|
||||
imap.select("INBOX")
|
||||
# Cleanup
|
||||
imap.close()
|
||||
@@ -4,7 +4,7 @@
|
||||
###
|
||||
|
||||
[app:main]
|
||||
use = egg:cao_blogr
|
||||
use = egg:ctp_blogr
|
||||
|
||||
pyramid.reload_templates = false
|
||||
pyramid.debug_authorization = false
|
||||
@@ -12,12 +12,12 @@ pyramid.debug_notfound = false
|
||||
pyramid.debug_routematch = false
|
||||
pyramid.default_locale_name = en
|
||||
|
||||
sqlalchemy.url = sqlite:///%(here)s/cao_blogr.sqlite
|
||||
sqlalchemy.url = sqlite:///%(here)s/ctp_blogr.sqlite
|
||||
|
||||
retry.attempts = 3
|
||||
|
||||
[pshell]
|
||||
setup = cao_blogr.pshell.setup
|
||||
setup = ctp_blogr.pshell.setup
|
||||
|
||||
###
|
||||
# wsgi server configuration
|
||||
@@ -25,13 +25,14 @@ setup = cao_blogr.pshell.setup
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = cao_blogr/alembic
|
||||
script_location = ctp_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
|
||||
listen = *:9280
|
||||
url_scheme = https
|
||||
|
||||
###
|
||||
# logging configuration
|
||||
@@ -39,7 +40,7 @@ listen = *:6543
|
||||
###
|
||||
|
||||
[loggers]
|
||||
keys = root, cao_blogr, sqlalchemy
|
||||
keys = root, ctp_blogr, sqlalchemy
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
@@ -51,10 +52,10 @@ keys = generic
|
||||
level = WARN
|
||||
handlers = console
|
||||
|
||||
[logger_cao_blogr]
|
||||
[logger_ctp_blogr]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = cao_blogr
|
||||
qualname = ctp_blogr
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[pytest]
|
||||
testpaths = cao_blogr
|
||||
testpaths = ctp_blogr
|
||||
python_files = test*.py
|
||||
|
||||
15
setup.py
@@ -18,13 +18,16 @@ requires = [
|
||||
'pyramid_retry',
|
||||
'pyramid_layout',
|
||||
'pyramid_tm',
|
||||
'SQLAlchemy',
|
||||
'SQLAlchemy==1.4.54',
|
||||
'transaction',
|
||||
'zope.sqlalchemy',
|
||||
'zope.sqlalchemy==2.0',
|
||||
'wtforms', # form library 2.2.1
|
||||
'webhelpers2', # various web building related helpers 2.0
|
||||
'passlib',
|
||||
'python-magic',
|
||||
'markdown',
|
||||
'urllib3==1.26',
|
||||
'yfinance',
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
@@ -34,9 +37,9 @@ tests_require = [
|
||||
]
|
||||
|
||||
setup(
|
||||
name='cao_blogr',
|
||||
name='ctp_blogr',
|
||||
version='1.0',
|
||||
description='cao_blogr',
|
||||
description='ctp_blogr',
|
||||
long_description=README + '\n\n' + CHANGES,
|
||||
classifiers=[
|
||||
'Programming Language :: Python',
|
||||
@@ -57,10 +60,10 @@ setup(
|
||||
install_requires=requires,
|
||||
entry_points={
|
||||
'paste.app_factory': [
|
||||
'main = cao_blogr:main',
|
||||
'main = ctp_blogr:main',
|
||||
],
|
||||
'console_scripts': [
|
||||
'initialize_cao_blogr_db=cao_blogr.scripts.initialize_db:main',
|
||||
'initialize_ctp_blogr_db=ctp_blogr.scripts.initialize_db:main',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
33
smtp-SSL-alinto-dumas.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
import smtplib
|
||||
import ssl
|
||||
|
||||
# Fabrication du corps du email_passwordMessage
|
||||
sender = "sasdumas@entreprise-dumas.com"
|
||||
receiver = "phuoc@caotek.fr"
|
||||
message = """\
|
||||
Subject: Envoi d'un message en SSL
|
||||
|
||||
Voici le contenu du message. Phuoc"""
|
||||
|
||||
# Create a secure SSL context
|
||||
context = ssl.create_default_context()
|
||||
smtp_server = "gatewayxl.alinto.net"
|
||||
smtp_port = 465 # For SSL
|
||||
smtp_user = "smtpsasdumas@entreprise-dumas.com"
|
||||
smtp_pass = "S@sdumas69"
|
||||
|
||||
# Try to log in to server and send email
|
||||
try:
|
||||
server = smtplib.SMTP_SSL(smtp_server, smtp_port, context=context)
|
||||
server.login(smtp_user, smtp_pass)
|
||||
# envoyer l'email
|
||||
server.sendmail(sender, receiver, message)
|
||||
print("sendmail -> OK")
|
||||
|
||||
except Exception as e:
|
||||
# Print any error messages to stdout
|
||||
print(e)
|
||||
finally:
|
||||
server.quit()
|
||||
|
||||
35
smtp-TLS-office365-dumas.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
import smtplib
|
||||
import ssl
|
||||
|
||||
# Fabrication du corps du email_passwordMessage
|
||||
sender = "peinture-dumas@entreprise-dumas.com"
|
||||
receiver = "phuoc@caotek.fr"
|
||||
message = """\
|
||||
Subject: Envoi d'un message en TLS
|
||||
|
||||
Voici le contenu du message. Phuoc"""
|
||||
|
||||
# Create a secure SSL context
|
||||
context = ssl.create_default_context()
|
||||
smtp_server = "smtp.office365.com"
|
||||
smtp_port = 587 # For TLS
|
||||
smtp_user = "peinture-dumas@entreprise-dumas.com"
|
||||
smtp_pass = "Nar50611"
|
||||
|
||||
# Try to log in to server and send email
|
||||
try:
|
||||
server = smtplib.SMTP(smtp_server,smtp_port)
|
||||
# server.ehlo() # Can be omitted
|
||||
server.starttls(context=context) # Secure the connection
|
||||
# server.ehlo() # Can be omitted
|
||||
server.login(smtp_user, smtp_pass)
|
||||
# envoyer l'email
|
||||
server.sendmail(sender, receiver, message)
|
||||
print("sendmail -> OK")
|
||||
|
||||
except Exception as e:
|
||||
# Print any error messages to stdout
|
||||
print(e)
|
||||
finally:
|
||||
server.quit()
|
||||