Skip to content

Commit 93fe86b

Browse files
Refactor database config and error reporting and updating doc
1 parent 570da9e commit 93fe86b

59 files changed

Lines changed: 2024 additions & 1746 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# custom
2+
.venv_3.12
3+
sql-dumps
4+
.vscode
5+
.gitignore
6+
7+
18
# Byte-compiled / optimized / DLL files
29
__pycache__/
310
*.py[cod]
@@ -136,7 +143,7 @@ venv.bak/
136143
.ropeproject
137144

138145
# mkdocs documentation
139-
/site
146+
**/doc/site/
140147

141148
# mypy
142149
.mypy_cache/

chrono_des_vignes/__init__.py

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -23,59 +23,48 @@
2323
from flask_sqlalchemy import SQLAlchemy
2424
from flask_migrate import Migrate
2525
from functools import wraps
26-
from flask_colorpicker import colorpicker
2726
from icecream import install
2827
from flask_babel import Babel, lazy_gettext, gettext, _
2928
from flask_socketio import SocketIO
30-
from sqlalchemy import URL
3129
from flask_bcrypt import Bcrypt
32-
import os
30+
import os, logging, json
31+
from logging.handlers import SMTPHandler
3332
from dotenv import load_dotenv
3433
from datetime import datetime
3534
install()
3635
load_dotenv()
37-
from sqlalchemy import make_url
36+
from urllib.parse import quote
3837
from werkzeug import exceptions
39-
from sentry_sdk import init
40-
from sentry_sdk.integrations.flask import FlaskIntegration
38+
#from sentry_sdk import init
39+
#from sentry_sdk.integrations.flask import FlaskIntegration
4140
# met la langue en francais pour le formatage des dates
4241
import locale
4342
locale.setlocale(locale.LC_TIME,'')
4443

4544
app = Flask(__name__)
46-
app.config['SERVER_NAME'] = 'localhost:5000'
47-
password= os.environ.get('db_password')
45+
app.subdomain_matching = True
46+
app.config['SERVER_NAME'] = os.getenv('SERVER_NAME')
4847
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') #! change that for deployment
49-
url_object = URL.create(
50-
"mysql+pymysql",
51-
username="root",
52-
password=password, # plain (unescaped) text
53-
host="localhost",
54-
database="site",
55-
)
56-
app.config["SQLALCHEMY_DATABASE_URI"] = url_object
48+
49+
password= os.environ.get('db_password')
50+
username= os.environ.get('db_user')
51+
hostname= os.environ.get('db_host')
52+
databasename= os.environ.get('db_name')
53+
54+
url = f"mysql+pymysql://{username}:{quote(password)}@{hostname}/{databasename}"
55+
app.config["SQLALCHEMY_DATABASE_URI"] = url
5756
app.config['BABEL_TRANSLATION_DIRECTORIES'] = f'{app.root_path}/translations'
5857
app.jinja_env.add_extension('jinja2.ext.loopcontrols')
5958
app.url_map.default_subdomain = ''
59+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
60+
app.config["SQLALCHEMY_POOL_RECYCLE"] = 280
6061

6162
DEFAULT_PROFIL_PIC = 'icone.png'
6263
LANGAGES = ['de', 'fr', 'en']
6364
if app.debug:
6465
LANGAGES += ['ids', 'pseudo']
6566
PICTURE_SIZE = (200, 200)
6667

67-
if not app.debug:
68-
ic('init sentry')
69-
init(
70-
dsn=os.environ.get('SANTRY_DSN'),
71-
send_default_pii=True,
72-
traces_sample_rate=1.0,
73-
profiles_sample_rate=1.0,
74-
integrations=[FlaskIntegration(
75-
transaction_style='endpoint'
76-
)],
77-
)
78-
7968
db = SQLAlchemy(app)
8069

8170
migrate = Migrate(app, db)
@@ -89,22 +78,68 @@
8978
login_manager.login_view = 'users.login'
9079
login_manager.login_message_category = 'info'
9180

92-
colorpicker(app)
81+
# ? error report
82+
mail_host= tuple(json.loads(os.getenv('mail_host')))
83+
from_addr= os.getenv('from_addr')
84+
mail_token= os.getenv('mail_token')
85+
to_addrs= json.loads(os.getenv('to_addrs'))
86+
ic(mail_host, from_addr, mail_token, to_addrs)
87+
88+
class MailFormatter(logging.Formatter):
89+
def format(self, record):
90+
#region
91+
try: post_data = "\n\t".join([f'"{k}": "{v}"' if v else f'"{k}"' for k, v in request.get_json().items()])
92+
except: post_data = request.get_data() if request.get_data()!=b'' else ''
93+
args = "\n\t".join([f'"{k}": "{v}"' if v else f'"{k}"' for k, v in request.args.to_dict().items()])
94+
user = f"""\
95+
username: {current_user.username}
96+
id: {current_user.id}
97+
name: {current_user.name}""" if current_user.is_authenticated else "anonymous"
98+
message = f'''\
99+
an error occurred in the chrono des vignes:
100+
101+
{record.message} - {record.levelname}
102+
it occured on the {self.formatTime(record, "%A, %d %B %Y %H:%M:%S")}
103+
[user]
104+
{user}
105+
106+
[request]
107+
url: {request.url}
108+
endpoint:{request.endpoint}
109+
route: {request.url_rule}
110+
method: {request.method}
111+
args: {args}
112+
post: {post_data}
113+
114+
[traceback]
115+
{record.exc_text}
116+
117+
'''
118+
return message
119+
#endregion
120+
121+
smtp_handeler = SMTPHandler(mailhost=mail_host, fromaddr=to_addrs, toaddrs=to_addrs, subject='server error', credentials=(from_addr, mail_token))
122+
smtp_handeler.setFormatter(MailFormatter())
123+
smtp_handeler.setLevel(logging.WARNING)
124+
125+
app.logger.addHandler(smtp_handeler)
93126

94127
#? instansiate flask babel
95-
old = '.venv/Lib/site-packages/babel/locale-data/fr_CH.dat'
96-
new = ('.venv/Lib/site-packages/babel/locale-data/pseudo.dat', '.venv/Lib/site-packages/babel/locale-data/ids.dat')
97-
for file in new:
98-
if not os.path.exists(file):
99-
with open(old, 'rb') as file1:
100-
with open(file, '+wb') as file2:
101-
file2.write(file1.read())
102-
103-
from babel.core import LOCALE_ALIASES
104-
LOCALE_ALIASES['pseudo'] ='pseudo'
105-
LOCALE_ALIASES['ids'] ='ids'
106-
107-
128+
if app.debug:
129+
old = '.venv/Lib/site-packages/babel/locale-data/fr_CH.dat'
130+
new = ('.venv/Lib/site-packages/babel/locale-data/pseudo.dat', '.venv/Lib/site-packages/babel/locale-data/ids.dat')
131+
for file in new:
132+
if not os.path.exists(file):
133+
with open(old, 'rb') as file1:
134+
with open(file, '+wb') as file2:
135+
file2.write(file1.read())
136+
137+
from babel.core import LOCALE_ALIASES
138+
LOCALE_ALIASES['pseudo'] ='pseudo'
139+
LOCALE_ALIASES['ids'] ='ids'
140+
141+
babel = Babel(app)
142+
@babel.localeselector
108143
def get_locale():
109144
# if a user is logged in, use the locale from the user settings
110145
if session.get('lang') :
@@ -114,11 +149,6 @@ def get_locale():
114149
# example. The best match wins.
115150
return request.accept_languages.best_match(LANGAGES)
116151

117-
def get_timezone():
118-
pass
119-
120-
babel = Babel(app, locale_selector=get_locale, timezone_selector=get_timezone)
121-
122152
from chrono_des_vignes.models import User
123153
@login_manager.user_loader
124154
def load_user(user_id:str ):

chrono_des_vignes/admin/parcours/form.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Etape_modif_form(FlaskForm):
4444

4545
class New_parcours_form(FlaskForm):
4646
name = StringField(_('form.parcoursname'), validators=[DataRequired(), Length(max=40)])
47-
start_lat = FloatField(_('form.startlat'), validators=[InputRequired()])
48-
start_lng = FloatField(_('form.startlng'), validators=[InputRequired()])
47+
start_lat = FloatField(_('form.startlat'), validators=[InputRequired()], default=46.54685605692591)
48+
start_lng = FloatField(_('form.startlng'), validators=[InputRequired()], default=6.449900437449806)
4949

5050
submit_btn = SubmitField(_('form.create'))

chrono_des_vignes/admin/parcours/templates/parcours.html

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1+
<!--
2+
* Chrono Des Vignes
3+
* a timing system for sports events
4+
*
5+
* Copyright © 2024-2025 Romain Maurer
6+
* This file is part of Chrono Des Vignes
7+
*
8+
* Chrono Des Vignes is free software: you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License as published by the Free Software Foundation,
10+
* either version 3 of the License, or (at your option) any later version.
11+
*
12+
* Chrono Des Vignes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13+
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14+
* See the GNU General Public License for more details.
15+
* You should have received a copy of the GNU General Public License along with Foobar.
16+
* If not, see <https://www.gnu.org/licenses/>.
17+
*
18+
* You may contact me at chrono-des-vignes@ikmail.com
19+
-->
20+
121
{% extends 'layout.html' %}
2-
{% from 'macro.html' import form_field, submit, map_field %}
22+
{% from 'macro.html' import render_form %}
323
{% block content %}
424
<h3>{{ _('admin.parcours.parcours') }}</h3>
525
<div class="d-flex justify-content-center flex-wrap">
@@ -61,11 +81,7 @@ <h5 class="modal-title" id="new_edition_label">{{ _('admin.parcours.createnewpar
6181
</div>
6282
<div class="modal-body">
6383
<form action="" method="post" id="new_edition_form">
64-
{{ form.hidden_tag() }}
65-
{{ form_field(form.name) }}
66-
{{ form_field(form.start_lat, input_id='start_lat', div_class='d-none') }}
67-
{{ form_field(form.start_lng, input_id='start_lng', div_class='d-none') }}
68-
{{ map_field(_('admin.parcours.startpoint'), 'start_lat', 'start_lng', 0,0) }}
84+
{{ render_form(form) }}
6985
</form>
7086
</div>
7187
<div class="modal-footer">

chrono_des_vignes/babel.cfg

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,24 @@
5050
[jinja2: chrono_des_vignes/templates/sidebar.html]
5151
[jinja2: chrono_des_vignes/templates/doc/site/404.html]
5252
[jinja2: chrono_des_vignes/templates/doc/site/index.html]
53-
[jinja2: chrono_des_vignes/templates/doc/site/about/index.html]
5453
[jinja2: chrono_des_vignes/templates/doc/site/de/index.html]
55-
[jinja2: chrono_des_vignes/templates/doc/site/de/about/index.html]
54+
[jinja2: chrono_des_vignes/templates/doc/site/de/organisateurs/index.html]
55+
[jinja2: chrono_des_vignes/templates/doc/site/de/organisateurs/coureurs/index.html]
56+
[jinja2: chrono_des_vignes/templates/doc/site/de/organisateurs/edition/index.html]
57+
[jinja2: chrono_des_vignes/templates/doc/site/de/organisateurs/parcours/index.html]
58+
[jinja2: chrono_des_vignes/templates/doc/site/de/users/index.html]
5659
[jinja2: chrono_des_vignes/templates/doc/site/en/index.html]
57-
[jinja2: chrono_des_vignes/templates/doc/site/en/about/index.html]
60+
[jinja2: chrono_des_vignes/templates/doc/site/en/organisateurs/index.html]
61+
[jinja2: chrono_des_vignes/templates/doc/site/en/organisateurs/coureurs/index.html]
62+
[jinja2: chrono_des_vignes/templates/doc/site/en/organisateurs/edition/index.html]
63+
[jinja2: chrono_des_vignes/templates/doc/site/en/organisateurs/parcours/index.html]
64+
[jinja2: chrono_des_vignes/templates/doc/site/en/users/index.html]
65+
[jinja2: chrono_des_vignes/templates/doc/site/organisateurs/index.html]
66+
[jinja2: chrono_des_vignes/templates/doc/site/organisateurs/coureurs/index.html]
67+
[jinja2: chrono_des_vignes/templates/doc/site/organisateurs/edition/index.html]
68+
[jinja2: chrono_des_vignes/templates/doc/site/organisateurs/parcours/index.html]
69+
[jinja2: chrono_des_vignes/templates/doc/site/users/index.html]
70+
[jinja2: chrono_des_vignes/templates/error/simple_error.html]
5871
[jinja2: chrono_des_vignes/users/templates/inscription.html]
5972
[jinja2: chrono_des_vignes/users/templates/login.html]
6073
[jinja2: chrono_des_vignes/users/templates/modify_password.html]

chrono_des_vignes/lib.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
'''
2+
# Chrono Des Vignes
3+
# a timing system for sports events
4+
#
5+
# Copyright © 2024-2025 Romain Maurer
6+
# This file is part of Chrono Des Vignes
7+
#
8+
# Chrono Des Vignes is free software: you can redistribute it and/or modify it under
9+
# the terms of the GNU General Public License as published by the Free Software Foundation,
10+
# either version 3 of the License, or (at your option) any later version.
11+
#
12+
# Chrono Des Vignes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13+
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14+
# See the GNU General Public License for more details.
15+
# You should have received a copy of the GNU General Public License along with Foobar.
16+
# If not, see <https://www.gnu.org/licenses/>.
17+
#
18+
# You may contact me at chrono-des-vignes@ikmail.com
19+
'''
20+
121
import requests
222
from math import acos, sin, radians, cos
323
from time import time

chrono_des_vignes/models.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@
2929
from typing import Iterator, Iterable, Literal
3030
from collections import namedtuple
3131
from markdown import markdown
32-
from markdown.extensions.tables import TableExtension
3332
from sqlalchemy import func, not_
33+
from flask_sqlalchemy.model import Model
3434

35-
md_extentions = ['admonition', 'markdown.extensions.tables']
36-
def get_html_from_markdown(markdown_text):
35+
md_extentions:list[str] = ['admonition', 'markdown.extensions.tables']
36+
def get_html_from_markdown(markdown_text: str) -> str:
3737
return markdown(
3838
escape(markdown_text),
3939
extensions=md_extentions,
4040
extension_configs={},
4141
output_format='html'
4242
)
4343

44-
def get_column_max_length(table, column_name):
44+
def get_column_max_length(table:db.Model, column_name:str) -> int |None:
4545
for column in table.__table__.columns:
4646
if column.name == column_name:
4747
return column.type.length
@@ -50,15 +50,15 @@ def get_column_max_length(table, column_name):
5050
class ColorType(ColorType_sql_utils):
5151
STORE_FORMAT='hex_l'
5252

53-
TracePoint = namedtuple('TracePoint', ['lat', 'lng', 'alt'], defaults=[None])
53+
TracePoint: namedtuple = namedtuple('TracePoint', ['lat', 'lng', 'alt'], defaults=[None])
5454

55-
editions_parcours = db.Table(
55+
editions_parcours: db.Table = db.Table(
5656
'editions_parcours',
5757
db.Column('edition_id', db.Integer, db.ForeignKey('edition.id')),
5858
db.Column('parcours_id', db.Integer, db.ForeignKey('parcours.id')),
5959
)
6060

61-
passagekey_stand = db.Table(
61+
passagekey_stand:db.Table = db.Table(
6262
'passagekey_stand',
6363
db.Column('passage_key_id', db.Integer, db.ForeignKey('passage_key.id')),
6464
db.Column('stand_id', db.Integer, db.ForeignKey('stand.id')),
@@ -404,7 +404,7 @@ class PassageKey(db.Model):
404404
name=db.Column(db.String(40), nullable=False)
405405

406406
def __repr__(self) -> str:
407-
return f'<PassageKey edition:{self.edition.name} stands={', '.join([str(stand) for stand in self.stands.all()])}'
407+
return f"<PassageKey edition:{self.edition.name} stands={', '.join(str(stand) for stand in self.stands.all())}>"
408408

409409
class Passage(db.Model):
410410
__allow_unmapped__ = True

0 commit comments

Comments
 (0)