Compare commits

..

2 Commits

Author SHA1 Message Date
Remi Rampin 9aa42e5ffb Point out which codes are too close 2022-05-10 23:34:24 -04:00
Remi Rampin e2b711c3a6 Correct codes that are off by one 2022-05-10 23:25:13 -04:00
25 changed files with 564 additions and 20394 deletions

35
france/.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
.env
# python
__pycache__
*.py[co]
.ipynb_checkpoints
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
# Eclipse PyDev
.project
.pydevproject
# PyCharm
.idea
# ViM
.*.swp
# Emacs
\#*#
# OS files
.DS_Store
desktop.ini
# Archives
*.tar
*.tar.gz
*.tar.bz2
*.zip
*.whl

File diff suppressed because it is too large Load Diff

11527
france/css/bootstrap.css vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +0,0 @@
/* timeline from https://mdbootstrap.com/docs/standard/extended/timeline/ */
.timeline-with-icons {
border-left: 3px solid hsl(0, 0%, 90%);
position: relative;
list-style: none;
margin-top:1.3rem;
}
.timeline-with-icons .timeline-item {
position: relative;
}
.timeline-with-icons .timeline-item:after {
position: absolute;
display: block;
top: 0;
}
.timeline-with-icons .timeline-icon {
position: absolute;
left: -48px;
background-color: hsl(217, 88.2%, 90%);
color: hsl(217, 88.8%, 35.1%);
border-radius: 60%;
height: 38px;
width: 38px;
display: flex;
align-items: center;
justify-content: center;
}
body {
font-size: 1.5rem;
background-image: url("../imgs/background.png");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
body > nav > div {
font-size:1.5rem;
}

View File

View File

@ -0,0 +1,92 @@
import logging
import random
import sys
from .database import get_db
ALPHABET = '0123456789bcdefghjkmnpqrstuvwxyz'
assert len(ALPHABET) == 32
LENGTH = 4
def list_errors(code):
for place in range(LENGTH):
for replacement in ALPHABET:
if replacement == code[place]:
continue
new_code = code[:place] + replacement + code[place + 1:]
yield new_code
correct_codes = set()
errored_codes = {}
def _load_codes():
global correct_codes
global errored_codes
# Get the current codes
with get_db() as db:
correct_codes = set(row[0] for row in db.execute(
'''\
SELECT code FROM families;
''',
))
# Augment them with possible errored codes
errored_codes = {}
for code in correct_codes:
for error in list_errors(code):
if error in errored_codes:
raise ValueError("%s too close to %s" % (
code,
errored_codes[error],
))
errored_codes[error] = code
assert (
len(errored_codes)
== LENGTH * (len(ALPHABET) - 1) * len(correct_codes)
)
_load_codes()
CORRECT = {
'i': '1',
'l': '1',
'o': '0',
}
def correct_code(code):
code = code.lower()
fixed_code = ''.join(CORRECT.get(c, c) for c in code)
fixed_code = errored_codes.get(fixed_code, None)
return fixed_code or code
def main():
logging.basicConfig(level=logging.INFO)
if len(sys.argv) <= 1:
number = 1
elif len(sys.argv) == 2:
number = int(sys.argv[1], 10)
else:
raise ValueError
# Generate new codes
generated = 0
while generated < number:
code = ''.join(random.choice(ALPHABET) for _ in range(LENGTH))
if code in correct_codes or code in errored_codes:
continue
correct_codes.add(code)
for error in list_errors(code):
errored_codes[error] = code
print(code)
generated += 1

View File

@ -0,0 +1,77 @@
import contextlib
from datetime import datetime
import logging
import os
import sqlite3
logger = logging.getLogger(__name__)
DATABASE_FILE = os.environ['DATABASE']
assert os.path.isfile(DATABASE_FILE)
@contextlib.contextmanager
def get_db():
db = sqlite3.connect(DATABASE_FILE)
try:
yield db
finally:
db.close()
def get_name_and_replies(code):
with get_db() as db:
rows = db.execute(
'''\
WITH latest_reply AS (
SELECT
adults, children
FROM replies
WHERE family_code = :code
ORDER BY date DESC
LIMIT 1
)
SELECT
families.name,
(SELECT adults FROM latest_reply) AS adults,
(SELECT children FROM latest_reply) AS children
FROM families
WHERE families.code = :code
LIMIT 1;
''',
dict(code=code),
)
try:
return next(rows)
except StopIteration:
raise ValueError("Invalid code")
def record_reply(code, adults, children):
assert isinstance(adults, int)
assert isinstance(children, int)
assert 0 <= adults and 0 <= children
assert adults > 0 or children == 0
with get_db() as db:
# Insert update
cursor = db.cursor()
date = datetime.utcnow()
cursor.execute(
'''\
INSERT INTO replies(family_code, date, adults, children)
VALUES(:code, :date, :adults, :children);
''',
dict(code=code, date=date, adults=adults, children=children),
)
cursor.execute('COMMIT;')
logger.info(
"Recorded update for code=%r date=%r adults=%d children=%d",
code,
date,
adults,
children,
)

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>RSVP - Vicky &amp; Remi</title>
</head>
<body>
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
<form action="{{ url_for('form') }}" method="POST">
<p>
Code sur l'invitation:
<input type="text" name="code" placeholder="ABCD" />
</p>
<p><input type="submit" value="RSVP" /></p>
</form>
</body>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>RSVP - Vicky &amp; Remi</title>
</head>
<body>
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
<form action="" method="POST">
<p>
<label for="adults">Adultes :</label>
<input type="number" name="adults" id="adults" min="1" max="5" />
</p>
<p>
<label for="children">Enfants :</label>
<input type="number" name="children" id="children" min="0" max="5" />
</p>
<p><input type="submit" value="RSVP" /></p>
</form>
</body>
</html>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>RSVP - Vicky &amp; Remi</title>
</head>
<body>
<h1>Merci !</h1>
</body>
</html>

66
france/france_rsvp/web.py Normal file
View File

@ -0,0 +1,66 @@
from flask import Flask, render_template, request, url_for
import logging
from werkzeug.utils import redirect
from .codes import correct_code
from .database import get_name_and_replies, record_reply
logger = logging.getLogger(__name__)
app = Flask('france_rsvp')
@app.route('/france/', methods=['GET', 'POST'])
def form():
error = None
if request.method == 'POST':
if (
'code' in request.form
and len(request.form['code']) == 4
):
code = correct_code(request.form['code'])
try:
get_name_and_replies(code)
except ValueError:
error = "Code invalide"
else:
return redirect(url_for('rsvp', code=code))
else:
error = "Code invalide"
return render_template('form.html', error=error)
@app.route('/france/thanks')
def thanks():
return render_template('thanks.html')
@app.route('/france/<code>', methods=['GET', 'POST'])
def rsvp(code):
error = None
if request.method == 'POST':
try:
adults = int(request.form['adults'], 10)
children = int(request.form['children'], 10)
except (KeyError, ValueError, OverflowError):
logger.warning("Invalid reply", exc_info=True)
error = "Nombres invalides"
else:
record_reply(code, adults, children)
return redirect(url_for('thanks'))
# Lookup guests from code
adults = None
kids = None
try:
name, adults, kids = get_name_and_replies(code)
except ValueError:
error = "Code invalide"
return render_template(
'rsvp.html',
adults=adults,
kids=kids,
error=error,
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 KiB

View File

@ -1,44 +0,0 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/france/css/bootstrap.css">
<link rel="stylesheet" href="/france/css/bootstrap-icons.css">
<link rel="stylesheet" href="/france/css/custom.css">
<title>Rémi &amp; Vicky, France, 2022</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<span class="navbar-text pe-3">Rémi &amp; Vicky, France, 2022</span>
</div>
</nav>
<div id="content" class="container mt-2">
<h1>Rémi &amp; Vicky renouvellent leurs voeux de mariage ! </h1>
<p class="text-dark">Rémi &amp; Vicky se sont mariés lors d'une petite cérémonie au Massachusetts le 30 janvier 2021. Exactement un an et demi plus tard, nous avons pu enfin venir en France pour célébrer avec tout le monde et renouveler nos voeux ! Merci à tous ceux qui sont venus partager ce moment avec nous.</p>
<a class="btn btn-primary btn-lg mb-3" href="https://www.zola.com/registry/vicky-remi" role="button">Faire un cadeau <i class="bi bi-heart-arrow"></i></a>
<h4><i class="bi bi-geo-alt"></i> La Chapelle-Taillefert</h4>
<h4><i class="bi bi-calendar-heart"></i> 30 juillet 2022</h4>
<div class="container mt-4">
<a href="https://cloud.rampin.org/s/6d2jHbFPcTw7dx9" class="row">
<img class="col-md-6 mb-4" src="/france/imgs/france01.jpg">
<img class="col-md-6 mb-4" src="/france/imgs/france02.jpg">
<img class="col-md-6 mb-4" src="/france/imgs/france03.jpg">
<img class="col-md-6 mb-4" src="/france/imgs/france04.jpg">
</a>
</div>
</div>
<script src="/france/js/bootstrap.bundle.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

201
france/poetry.lock generated Normal file
View File

@ -0,0 +1,201 @@
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "flask"
version = "2.1.2"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
click = ">=8.0"
importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
itsdangerous = ">=2.0"
Jinja2 = ">=3.0"
Werkzeug = ">=2.0"
[package.extras]
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "importlib-metadata"
version = "4.11.3"
description = "Read metadata from Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
perf = ["ipython"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
[[package]]
name = "itsdangerous"
version = "2.1.2"
description = "Safely pass data to untrusted environments and back."
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "markupsafe"
version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "uwsgi"
version = "2.0.20"
description = "The uWSGI server"
category = "main"
optional = true
python-versions = "*"
[[package]]
name = "werkzeug"
version = "2.1.2"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
watchdog = ["watchdog"]
[[package]]
name = "zipp"
version = "3.8.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
[extras]
uwsgi = ["uWSGI"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "22dc31ed2f24fc96f3cd412113fe05f63b85c9bbc73c92b74c8d4d915d3cd092"
[metadata.files]
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
flask = [
{file = "Flask-2.1.2-py3-none-any.whl", hash = "sha256:fad5b446feb0d6db6aec0c3184d16a8c1f6c3e464b511649c8918a9be100b4fe"},
{file = "Flask-2.1.2.tar.gz", hash = "sha256:315ded2ddf8a6281567edb27393010fe3406188bafbfe65a3339d5787d89e477"},
]
importlib-metadata = [
{file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"},
{file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"},
]
itsdangerous = [
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
]
jinja2 = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
markupsafe = [
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
uwsgi = [
{file = "uwsgi-2.0.20.tar.gz", hash = "sha256:88ab9867d8973d8ae84719cf233b7dafc54326fcaec89683c3f9f77c002cdff9"},
]
werkzeug = [
{file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"},
{file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"},
]
zipp = [
{file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
{file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
]

23
france/pyproject.toml Normal file
View File

@ -0,0 +1,23 @@
[tool.poetry]
name = "france-rsvp"
version = "1.0.0"
description = ""
authors = ["Remi Rampin <remi@rampin.org>"]
[tool.poetry.dependencies]
python = "^3.8"
Flask = "^2.1.2"
uWSGI = {version = "*", optional = true}
[tool.poetry.extras]
uwsgi = ["uWSGI"]
[tool.poetry.dev-dependencies]
[tool.poetry.scripts]
generate-codes = "france_rsvp.codes:main"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

12
france/schema.sql Normal file
View File

@ -0,0 +1,12 @@
CREATE TABLE families(
code VARCHAR(8) PRIMARY KEY,
name TEXT
);
CREATE TABLE replies(
family_code VARCHAR(8) NOT NULL,
date DATETIME NOT NULL,
adults INTEGER NOT NULL,
children INTEGER NOT NULL,
FOREIGN KEY(family_code) REFERENCES families(code)
);

9
france/test-data.sql Normal file
View File

@ -0,0 +1,9 @@
INSERT INTO families(code, name) VALUES
('bbbb', 'one'),
('cccc', 'two'),
('dddd', 'three');
INSERT INTO replies(family_code, date, adults, children) VALUES
('bbbb', '2022-05-10 20:37:39', 2, 0),
('bbbb', '2022-05-10 23:55:55', 0, 0),
('cccc', '2022-05-10 20:22:11', 2, 1);

Binary file not shown.