Initial commit
This commit is contained in:
7
.devcontainer/Dockerfile
Normal file
7
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM python:3.11.8-alpine
|
||||
|
||||
# Dev depedencies
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Prod depedencies
|
||||
RUN pip install flask psycopg2-binary sqlalchemy
|
||||
19
.devcontainer/devcontainer.json
Normal file
19
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,19 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json
|
||||
{
|
||||
"name": "avsa-form devenv",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "devenv",
|
||||
"workspaceFolder": "/home/developer/workspace",
|
||||
|
||||
"remoteEnv": {
|
||||
"XDG_RUNTIME_DIR": "/tmp/runtime-developer"
|
||||
},
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"shardulm94.trailing-spaces"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
29
.devcontainer/docker-compose.yml
Normal file
29
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
devenv:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
||||
container_name: avsa_form-dev
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
- ..:/home/developer/workspace
|
||||
|
||||
command: sleep infinity
|
||||
networks:
|
||||
- avsa_form
|
||||
|
||||
db:
|
||||
image: postgres:13.1
|
||||
container_name: avsa_form_db
|
||||
restart: always
|
||||
volumes:
|
||||
- ../postgresql:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- ../db.env
|
||||
networks:
|
||||
- avsa_form
|
||||
|
||||
networks:
|
||||
avsa_form:
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.venv
|
||||
postgresql
|
||||
__pycache__
|
||||
10
create_table.sql
Normal file
10
create_table.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE accounts (
|
||||
user_id SERIAL PRIMARY KEY,
|
||||
first_name VARCHAR (50) NOT NULL,
|
||||
last_name VARCHAR (50) NOT NULL,
|
||||
phone_number VARCHAR (30) NOT NULL,
|
||||
request_at TIMESTAMP NOT NULL,
|
||||
start_availability TIMESTAMP NOT NULL,
|
||||
end_availability TIMESTAMP NOT NULL,
|
||||
user_type VARCHAR (10) NOT NULL
|
||||
);
|
||||
3
db.env
Normal file
3
db.env
Normal file
@@ -0,0 +1,3 @@
|
||||
POSTGRES_USER=avsa_form
|
||||
POSTGRES_PASSWORD=#4gvAwnUr5@MuZk9cYb!
|
||||
POSTGRES_DB=avsa_form_db
|
||||
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
#version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:13.1
|
||||
container_name: avsa_form_db
|
||||
restart: always
|
||||
volumes:
|
||||
- ./postgresql:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- db.env
|
||||
networks:
|
||||
- avsa_form
|
||||
|
||||
flask:
|
||||
image: python:3.11.8-alpine
|
||||
|
||||
networks:
|
||||
avsa_form:
|
||||
external: true
|
||||
#reverse-proxy:
|
||||
85
hello.py
Normal file
85
hello.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from datetime import datetime
|
||||
|
||||
from urllib.parse import quote_plus
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask import render_template
|
||||
|
||||
DB_USER = 'avsa_form'
|
||||
DB_PASSWORD = '#4gvAwnUr5@MuZk9cYb!'
|
||||
DB_HOST = 'avsa_form_db'
|
||||
DB_PORT = '5432'
|
||||
DB_NAME = 'avsa_form_db'
|
||||
DB_TABLE = 'accounts'
|
||||
|
||||
class user_data:
|
||||
def __init__(self, first_name, last_name, phone_number, request_at, start_availability, end_availability, user_type):
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.phone_number = phone_number
|
||||
self.request_at = request_at
|
||||
self.start_availability = start_availability
|
||||
self.end_availability = end_availability
|
||||
self.user_type = user_type
|
||||
|
||||
# new_data = current_data('lucas','royer','0612345678','2011-05-16 15:36:38','2011-05-16 15:36:38','2011-05-16 15:36:38','benevole')
|
||||
|
||||
def insert_db(current_data):
|
||||
# Connect to DB
|
||||
engine = create_engine(f"postgresql+psycopg2://{DB_USER}:%s@{DB_HOST}:{DB_PORT}/{DB_NAME}" % quote_plus(DB_PASSWORD))
|
||||
conn = engine.connect()
|
||||
|
||||
|
||||
|
||||
data = {
|
||||
'first_name': current_data.first_name,
|
||||
'last_name': current_data.last_name,
|
||||
'phone_number': current_data.phone_number,
|
||||
'request_at': current_data.request_at,
|
||||
'start_availability': current_data.start_availability,
|
||||
'end_availability': current_data.end_availability,
|
||||
'user_type': current_data.user_type,
|
||||
}
|
||||
|
||||
# SQL query
|
||||
query=text(f"INSERT INTO {DB_TABLE} (first_name, last_name, phone_number, request_at, start_availability, end_availability, user_type) VALUES (:first_name, :last_name, :phone_number, :request_at, :start_availability, :end_availability, :user_type);")
|
||||
|
||||
conn.execute(query, data)
|
||||
conn.commit()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def accueil():
|
||||
return render_template('accueil.html')
|
||||
|
||||
@app.route('/formulaire', methods=['GET'])
|
||||
def formulaire():
|
||||
return render_template('formulaire.html', retry=False)
|
||||
|
||||
@app.route('/resultat', methods=['POST'])
|
||||
def resultat():
|
||||
if request.method == 'POST':
|
||||
first_name = request.form['first_name']
|
||||
last_name = request.form['last_name']
|
||||
phone_number = request.form['phone_number']
|
||||
request_at = datetime.now().strftime("%m-%d-%Y %H:%M:%S")
|
||||
|
||||
availability_date = request.form['availability_date']
|
||||
start_availability_h = request.form['start_availability_h']
|
||||
start_availability_m = request.form['start_availability_m']
|
||||
end_availability_h = request.form['end_availability_h']
|
||||
end_availability_m = request.form['end_availability_m']
|
||||
|
||||
start_availability = f'{availability_date} {start_availability_h}:{start_availability_m}:00'
|
||||
end_availability = f'{availability_date} {end_availability_h}:{end_availability_m}:00'
|
||||
user_type = request.form['user_type']
|
||||
current_data = user_data(first_name, last_name, phone_number, request_at, start_availability, end_availability, user_type)
|
||||
|
||||
if any(value is None for value in current_data.__dict__.values()):
|
||||
return render_template('formulaire.html', retry=True)
|
||||
else:
|
||||
insert_db(current_data)
|
||||
return render_template('resultat.html', first_name=first_name, last_name=last_name, phone_number=phone_number, request_at=request_at, start_availability_h=start_availability_h, start_availability_m=start_availability_m, end_availability_h=end_availability_h, end_availability_m=end_availability_m, user_type=current_data.user_type)
|
||||
13
templates/accueil.html
Normal file
13
templates/accueil.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr-FR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>AVSA - Familles</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="formulaire">
|
||||
<button>S'inscrire</button>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
257
templates/formulaire.html
Normal file
257
templates/formulaire.html
Normal file
@@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[type=text], select, textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
label {
|
||||
padding: 12px 12px 12px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
background-color: #04AA6D;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
input[type=submit]:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-radius: 5px;
|
||||
background-color: #f2f2f2;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.col-5 {
|
||||
float: left;
|
||||
width: 5%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.col-25 {
|
||||
float: left;
|
||||
width: 25%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.col-75 {
|
||||
float: left;
|
||||
width: 75%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/* Clear floats after the columns */
|
||||
.row::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Responsive layout - when the screen is less than 600px wide, make the two columns stack on top of each other instead of next to each other */
|
||||
@media screen and (max-width: 600px) {
|
||||
.col-5 .col-25, .col-75, input[type=submit] {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* .button {
|
||||
display: inline-block;
|
||||
border-radius: 4px;
|
||||
background-color: #04AA6D;
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
padding: 20px;
|
||||
width: 200px;
|
||||
transition: all 0.5s;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.button span {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.button span:after {
|
||||
content: '\00bb';
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
top: 0;
|
||||
right: -20px;
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.button:hover span {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.button:hover span:after {
|
||||
opacity: 1;
|
||||
right: 0;
|
||||
} */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Proposition de créneau de balade - A Vélo Sans Age Nantes</h2>
|
||||
<p>Merci de préciser votre demande ci-dessous :</p>
|
||||
|
||||
<div class="container">
|
||||
<form action="/resultat" method="post">
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="utype">Vous êtes</label>
|
||||
</div>
|
||||
<div class="col-75">
|
||||
<select id="utype" name="user_type">
|
||||
<option value="usager">Bénéficiaire</option>
|
||||
<option value="famille">Famille du bénéficiaire</option>
|
||||
<option value="benevole">Bénévole pilote</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="fname">Prénom</label>
|
||||
</div>
|
||||
<div class="col-75">
|
||||
<input type="text" id="fname" name="first_name" placeholder="Votre prénom...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="lname">Nom</label>
|
||||
</div>
|
||||
<div class="col-75">
|
||||
<input type="text" id="lname" name="last_name" placeholder="Votre nom...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="phone">Téléphone</label>
|
||||
</div>
|
||||
<div class="col-75">
|
||||
<input type="text" id="phone" name="phone_number" placeholder="Votre numéro de téléphone...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="date">Disponible le </label>
|
||||
</div>
|
||||
<div class="col-75">
|
||||
<input type="date" id="date" name="availability_date" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="hstart">À</label>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<select id="hstart" name="start_availability_h" style="text-align: center">
|
||||
<option value=08>08h</option>
|
||||
<option value=09>09h</option>
|
||||
<option value=10>10h</option>
|
||||
<option value=11>11h</option>
|
||||
<option value=12>12h</option>
|
||||
<option value=13>13h</option>
|
||||
<option value=14>14h</option>
|
||||
<option value=15>15h</option>
|
||||
<option value=16>16h</option>
|
||||
<option value=17>17h</option>
|
||||
<option value=18>18h</option>
|
||||
<option value=19>19h</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<select id="mstart" name="start_availability_m" style="text-align: center">
|
||||
<option value=00>00</option>
|
||||
<option value=15>15</option>
|
||||
<option value=30>30</option>
|
||||
<option value=45>45</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="hend">À</label>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<select id="hend" name="end_availability_h" style="text-align: center">
|
||||
<option value=08>08h</option>
|
||||
<option value=09>09h</option>
|
||||
<option value=10>10h</option>
|
||||
<option value=11>11h</option>
|
||||
<option value=12>12h</option>
|
||||
<option value=13>13h</option>
|
||||
<option value=14>14h</option>
|
||||
<option value=15>15h</option>
|
||||
<option value=16>16h</option>
|
||||
<option value=17>17h</option>
|
||||
<option value=18>18h</option>
|
||||
<option value=19>19h</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<select id="mend" name="end_availability_m" style="text-align: center">
|
||||
<option value=00>00</option>
|
||||
<option value=15>15</option>
|
||||
<option value=30>30</option>
|
||||
<option value=45>45</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="row">
|
||||
<div class="col-25">
|
||||
<label for="tstart">De </label>
|
||||
</div>
|
||||
<div class="col-75">
|
||||
<input type="time" id="tstart" name="start_availability_time" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-25">
|
||||
<label for="tend">Jusqu'à</label>
|
||||
</div>
|
||||
<div class="col-75">
|
||||
<input type="time" id="tend" name="end_availability_time" placeholder="">
|
||||
</div>
|
||||
</div> -->
|
||||
<br>
|
||||
<div class="row">
|
||||
<input type="submit" value="Envoyer">
|
||||
</div>
|
||||
<!-- <div style="text-align: center">
|
||||
<button type="submit" class="button" style="vertical-align:middle"><span>Envoyer</span></button>
|
||||
</div> -->
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/">
|
||||
<button>Retour</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
20
templates/resultat.html
Normal file
20
templates/resultat.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr-FR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>AVSA - Familles</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Votre demande a bien été prise en compte :</h1>
|
||||
<p>Vous êtes : {{ user_type }}</p>
|
||||
<p>Prenom : {{ first_name }}</p>
|
||||
<p>Nom : {{ last_name }}</p>
|
||||
<p>Téléphone : {{ user_message }}</p>
|
||||
<p>Date : {{ availability_date }}</p>
|
||||
<p>Horaire : de {{ start_availability_h }}h{{ start_availability_m }} à {{ end_availability_h }}h{{ end_availability_m }}</p>
|
||||
<a href="/">
|
||||
<button>Retour</button>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user