Spaces:
Runtime error
Runtime error
Feat: upload project
Browse files- Dockerfile +17 -0
- README.md +73 -5
- app.py +5 -0
- basic_test.py +2 -0
- flask_session/2029240f6d1128be89ddc32729463129 +0 -0
- flask_session/4b3a5c21ed5e4f46a96be9fe6a0bfb18 +0 -0
- ice_breaking_challenge/__init__.py +54 -0
- ice_breaking_challenge/auth.py +110 -0
- ice_breaking_challenge/blog.py +125 -0
- ice_breaking_challenge/db.py +52 -0
- ice_breaking_challenge/index.py +26 -0
- ice_breaking_challenge/introduction.py +24 -0
- ice_breaking_challenge/qr.py +24 -0
- ice_breaking_challenge/schema.sql +20 -0
- ice_breaking_challenge/static/style.css +134 -0
- ice_breaking_challenge/templates/auth/login.html +15 -0
- ice_breaking_challenge/templates/auth/register.html +15 -0
- ice_breaking_challenge/templates/base.html +15 -0
- ice_breaking_challenge/templates/blog/create.html +15 -0
- ice_breaking_challenge/templates/blog/index_.html +28 -0
- ice_breaking_challenge/templates/blog/update.html +19 -0
- ice_breaking_challenge/templates/index.html +12 -0
- ice_breaking_challenge/templates/introduction.html +8 -0
- ice_breaking_challenge/templates/qr.html +11 -0
- requirements.txt +2 -0
- run.sh +2 -0
- setup.sh +4 -0
Dockerfile
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use an official Python runtime as a parent image
|
2 |
+
FROM python:3.10-slim
|
3 |
+
|
4 |
+
# Set the working directory to /app
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Copy the current directory contents into the container at /app
|
8 |
+
COPY . /app
|
9 |
+
|
10 |
+
# Install any needed packages specified in requirements.txt
|
11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
12 |
+
|
13 |
+
# Make port 7860 available to the world outside this container
|
14 |
+
EXPOSE 7860
|
15 |
+
|
16 |
+
# Run the Flask app directly using Python
|
17 |
+
CMD ["python", "app.py"]
|
README.md
CHANGED
@@ -1,10 +1,78 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: docker
|
7 |
pinned: false
|
|
|
8 |
---
|
9 |
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: Ice Breaking Challenge
|
3 |
+
emoji: π
|
4 |
+
colorFrom: purple
|
5 |
+
colorTo: yellow
|
6 |
sdk: docker
|
7 |
pinned: false
|
8 |
+
short_description: ice_breaking_challenge
|
9 |
---
|
10 |
|
11 |
+
# Ice Breaking Challenge
|
12 |
+
## μ¬μ© μλ리μ€
|
13 |
+
```mermaid
|
14 |
+
sequenceDiagram
|
15 |
+
actor user as μ¬μ©μ
|
16 |
+
participant browser as λΈλΌμ°μ
|
17 |
+
participant flask as Flask
|
18 |
+
%% participant sheets as Google Sheets
|
19 |
+
%% participant gemma as Fine-Tuned Gemma 2
|
20 |
+
|
21 |
+
|
22 |
+
autonumber
|
23 |
+
critical ν μ 보 μ
λ ₯
|
24 |
+
user ->> browser: Hugging Face Space μ κ·Ό
|
25 |
+
browser ->> flask: `ν μ 보 μ
λ ₯ νμ΄μ§` μμ² (json)
|
26 |
+
flask ->> browser: `ν μ 보 μ
λ ₯ νμ΄μ§` μλ΅ (html & js)
|
27 |
+
browser ->> user: `ν μ 보 μ
λ ₯ νμ΄μ§` λ λλ§
|
28 |
+
end
|
29 |
+
|
30 |
+
critical μ€λ¬Έ QR
|
31 |
+
user ->> browser: `ν μ 보 μ
λ ₯ νμ΄μ§`μ `λ€μ` λ²νΌ ν΄λ¦
|
32 |
+
browser ->> flask: `μ€λ¬Έ QR νμ΄μ§` μμ² (json)
|
33 |
+
flask ->> browser: `μ€λ¬Έ QR νμ΄μ§` μλ΅ (html & js)
|
34 |
+
browser ->> user: `μ€λ¬Έ QR νμ΄μ§` λ λλ§
|
35 |
+
end
|
36 |
+
|
37 |
+
critical μκΈ°μκ°
|
38 |
+
user ->> browser: `ν μ 보 μ
λ ₯ νμ΄μ§`μ `λ€μ` λ²νΌ ν΄λ¦
|
39 |
+
browser ->> flask: `μκΈ°μκ° νμ΄μ§` μμ² (json)
|
40 |
+
flask ->> browser: `μκΈ°μκ° νμ΄μ§` μλ΅ (html & js)
|
41 |
+
browser ->> user: `μκΈ°μκ° νμ΄μ§` λ λλ§
|
42 |
+
Note right of user: μ§λ¬Έ μμ± μλ£λ λκΉμ§ `λ€μ` λ²νΌ λ λλ§ X
|
43 |
+
end
|
44 |
+
|
45 |
+
critical μ€λ¬Έ λ° μ§λ¬Έ μμ± μλ£ νμΈ
|
46 |
+
browser ->> flask: μ€λ¬Έ μλ£ νμΈ μμ² (json)
|
47 |
+
create participant sheets as Google Sheets
|
48 |
+
flask ->> sheets: μ€λ¬Έ μμ²
|
49 |
+
destroy sheets
|
50 |
+
sheets ->> flask: μ€λ¬Έ μλ΅
|
51 |
+
flask ->> flask: ν μ 보μ μ€λ¬Έ λ΄μ λμ‘°
|
52 |
+
create participant gemma as Fine-Tuned Gemma 2
|
53 |
+
flask ->> gemma: μ€λ¬Έ μλ£λμλ€λ©΄, μ§λ¬Έ μμ± μμ²
|
54 |
+
destroy gemma
|
55 |
+
gemma ->> flask: μ§λ¬Έ μμ± μλ΅
|
56 |
+
flask ->> browser: μ§λ¬Έ μμ± μλ£λ κ²½μ° `λ€μ` λ²νΌ μλ΅
|
57 |
+
browser ->> user: `λ€μ` λ²νΌ λ λλ§
|
58 |
+
end
|
59 |
+
|
60 |
+
critical μ ¬λ§ μμ± μ§λ¬Έ 1λ²
|
61 |
+
user ->> browser: `μκΈ°μκ° νμ΄μ§`μ `λ€μ` λ²νΌ ν΄λ¦
|
62 |
+
browser ->> flask: `μμ± μ§λ¬Έ 1λ² νμ΄μ§ μμ²` (json)
|
63 |
+
flask ->> browser: `μμ± μ§λ¬Έ 1λ² νμ΄μ§ μλ΅` (html & js)
|
64 |
+
browser ->> user: `μμ± μ§λ¬Έ 1λ² νμ΄μ§ μλ΅` λ λλ§
|
65 |
+
end
|
66 |
+
|
67 |
+
critical μ ¬λ§ μμ± μ§λ¬Έ 2λ²
|
68 |
+
user ->> browser: `μμ± μ§λ¬Έ 1λ² νμ΄μ§`μ `λ€μ` λ²νΌ ν΄λ¦ (json)
|
69 |
+
Note left of flask: μ΄ν λμΌ
|
70 |
+
end
|
71 |
+
|
72 |
+
critical μΉν΄μ§μ
¨λμ
|
73 |
+
user ->> browser: `μμ± μ§λ¬Έ λ§μ§λ§ νμ΄μ§`μ `λ€μ` λ²νΌ ν΄λ¦ (json)
|
74 |
+
browser ->> flask: `μμ± μ§λ¬Έ λ§μ§λ§ νμ΄μ§` μμ² (json)
|
75 |
+
flask ->> browser: `μμ± μ§λ¬Έ λ§μ§λ§ νμ΄μ§` μλ΅ (html & js)
|
76 |
+
browser ->> user: `μμ± μ§λ¬Έ λ§μ§λ§ νμ΄μ§` λ λλ§
|
77 |
+
end
|
78 |
+
```
|
app.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ice_breaking_challenge import create_app
|
2 |
+
|
3 |
+
if __name__ == "__main__":
|
4 |
+
app = create_app()
|
5 |
+
app.run(host="0.0.0.0", port=7860)
|
basic_test.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
def test_dummy():
|
2 |
+
pass
|
flask_session/2029240f6d1128be89ddc32729463129
ADDED
Binary file (9 Bytes). View file
|
|
flask_session/4b3a5c21ed5e4f46a96be9fe6a0bfb18
ADDED
Binary file (68 Bytes). View file
|
|
ice_breaking_challenge/__init__.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
from flask import Flask, session
|
4 |
+
from flask_session import Session
|
5 |
+
|
6 |
+
|
7 |
+
def create_app(test_config=None):
|
8 |
+
"""Create and configure an instance of the Flask application."""
|
9 |
+
app = Flask(__name__, instance_relative_config=True)
|
10 |
+
app.config.from_mapping(
|
11 |
+
# a default secret that should be overridden by instance config
|
12 |
+
SECRET_KEY="dev",
|
13 |
+
# store the database in the instance folder
|
14 |
+
DATABASE=os.path.join(app.instance_path, "flaskr.sqlite"),
|
15 |
+
)
|
16 |
+
app.config['SESSION_TYPE'] = 'filesystem'
|
17 |
+
Session(app)
|
18 |
+
|
19 |
+
if test_config is None:
|
20 |
+
# load the instance config, if it exists, when not testing
|
21 |
+
app.config.from_pyfile("config.py", silent=True)
|
22 |
+
else:
|
23 |
+
# load the test config if passed in
|
24 |
+
app.config.update(test_config)
|
25 |
+
|
26 |
+
# ensure the instance folder exists
|
27 |
+
try:
|
28 |
+
os.makedirs(app.instance_path)
|
29 |
+
except OSError:
|
30 |
+
pass
|
31 |
+
|
32 |
+
@app.route("/hello")
|
33 |
+
def hello():
|
34 |
+
return "Hello, World!"
|
35 |
+
|
36 |
+
# register the database commands
|
37 |
+
# from . import db
|
38 |
+
|
39 |
+
# db.init_app(app)
|
40 |
+
|
41 |
+
# apply the blueprints to the app
|
42 |
+
# from . import auth
|
43 |
+
from . import index
|
44 |
+
|
45 |
+
# app.register_blueprint(auth.bp)
|
46 |
+
app.register_blueprint(index.bp)
|
47 |
+
|
48 |
+
# make url_for('index') == url_for('blog.index')
|
49 |
+
# in another app, you might define a separate main index here with
|
50 |
+
# app.route, while giving the blog blueprint a url_prefix, but for
|
51 |
+
# the tutorial the blog will be the main index
|
52 |
+
app.add_url_rule("/", endpoint="index")
|
53 |
+
|
54 |
+
return app
|
ice_breaking_challenge/auth.py
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import functools
|
2 |
+
|
3 |
+
from flask import Blueprint
|
4 |
+
from flask import flash
|
5 |
+
from flask import g
|
6 |
+
from flask import redirect
|
7 |
+
from flask import render_template
|
8 |
+
from flask import request
|
9 |
+
from flask import session
|
10 |
+
from flask import url_for
|
11 |
+
from werkzeug.security import check_password_hash
|
12 |
+
from werkzeug.security import generate_password_hash
|
13 |
+
|
14 |
+
|
15 |
+
bp = Blueprint("auth", __name__, url_prefix="/auth")
|
16 |
+
|
17 |
+
|
18 |
+
def login_required(view):
|
19 |
+
"""View decorator that redirects anonymous users to the login page."""
|
20 |
+
|
21 |
+
@functools.wraps(view)
|
22 |
+
def wrapped_view(**kwargs):
|
23 |
+
if g.user is None:
|
24 |
+
return redirect(url_for("auth.login"))
|
25 |
+
|
26 |
+
return view(**kwargs)
|
27 |
+
|
28 |
+
return wrapped_view
|
29 |
+
|
30 |
+
|
31 |
+
@bp.before_app_request
|
32 |
+
def load_logged_in_user():
|
33 |
+
"""If a user id is stored in the session, load the user object from
|
34 |
+
the database into ``g.user``."""
|
35 |
+
user_id = session.get("user_id")
|
36 |
+
|
37 |
+
if user_id is None:
|
38 |
+
g.user = None
|
39 |
+
else:
|
40 |
+
g.user = (
|
41 |
+
get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone()
|
42 |
+
)
|
43 |
+
|
44 |
+
|
45 |
+
@bp.route("/register", methods=("GET", "POST"))
|
46 |
+
def register():
|
47 |
+
"""Register a new user.
|
48 |
+
|
49 |
+
Validates that the username is not already taken. Hashes the
|
50 |
+
password for security.
|
51 |
+
"""
|
52 |
+
if request.method == "POST":
|
53 |
+
username = request.form["username"]
|
54 |
+
password = request.form["password"]
|
55 |
+
db = get_db()
|
56 |
+
error = None
|
57 |
+
|
58 |
+
if not username:
|
59 |
+
error = "Username is required."
|
60 |
+
elif not password:
|
61 |
+
error = "Password is required."
|
62 |
+
|
63 |
+
if error is None:
|
64 |
+
try:
|
65 |
+
db.execute(
|
66 |
+
"INSERT INTO user (username, password) VALUES (?, ?)",
|
67 |
+
(username, generate_password_hash(password)),
|
68 |
+
)
|
69 |
+
db.commit()
|
70 |
+
except db.IntegrityError:
|
71 |
+
# The username was already taken, which caused the
|
72 |
+
# commit to fail. Show a validation error.
|
73 |
+
error = f"User {username} is already registered."
|
74 |
+
else:
|
75 |
+
# Success, go to the login page.
|
76 |
+
return redirect(url_for("auth.login"))
|
77 |
+
|
78 |
+
flash(error)
|
79 |
+
|
80 |
+
return render_template("auth/register.html")
|
81 |
+
|
82 |
+
|
83 |
+
@bp.route("/login", methods=("GET", "POST"))
|
84 |
+
def login():
|
85 |
+
"""Log in a registered user by adding the user id to the session."""
|
86 |
+
if request.method == "POST":
|
87 |
+
team_number = request.form["team_number"]
|
88 |
+
team_size = request.form["team_size"]
|
89 |
+
|
90 |
+
if user is None:
|
91 |
+
error = "Incorrect username."
|
92 |
+
elif not check_password_hash(user["password"], password):
|
93 |
+
error = "Incorrect password."
|
94 |
+
|
95 |
+
if error is None:
|
96 |
+
# store the user id in a new session and return to the index
|
97 |
+
session.clear()
|
98 |
+
session["user_id"] = user["id"]
|
99 |
+
return redirect(url_for("index"))
|
100 |
+
|
101 |
+
flash(error)
|
102 |
+
|
103 |
+
return render_template("auth/login.html")
|
104 |
+
|
105 |
+
|
106 |
+
@bp.route("/logout")
|
107 |
+
def logout():
|
108 |
+
"""Clear the current session, including the stored user id."""
|
109 |
+
session.clear()
|
110 |
+
return redirect(url_for("index"))
|
ice_breaking_challenge/blog.py
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Blueprint
|
2 |
+
from flask import flash
|
3 |
+
from flask import g
|
4 |
+
from flask import redirect
|
5 |
+
from flask import render_template
|
6 |
+
from flask import request
|
7 |
+
from flask import url_for
|
8 |
+
from werkzeug.exceptions import abort
|
9 |
+
|
10 |
+
from .auth import login_required
|
11 |
+
from .db import get_db
|
12 |
+
|
13 |
+
bp = Blueprint("blog", __name__)
|
14 |
+
|
15 |
+
|
16 |
+
@bp.route("/")
|
17 |
+
def index():
|
18 |
+
"""Show all the posts, most recent first."""
|
19 |
+
db = get_db()
|
20 |
+
posts = db.execute(
|
21 |
+
"SELECT p.id, title, body, created, author_id, username"
|
22 |
+
" FROM post p JOIN user u ON p.author_id = u.id"
|
23 |
+
" ORDER BY created DESC"
|
24 |
+
).fetchall()
|
25 |
+
return render_template("blog/index.html", posts=posts)
|
26 |
+
|
27 |
+
|
28 |
+
def get_post(id, check_author=True):
|
29 |
+
"""Get a post and its author by id.
|
30 |
+
|
31 |
+
Checks that the id exists and optionally that the current user is
|
32 |
+
the author.
|
33 |
+
|
34 |
+
:param id: id of post to get
|
35 |
+
:param check_author: require the current user to be the author
|
36 |
+
:return: the post with author information
|
37 |
+
:raise 404: if a post with the given id doesn't exist
|
38 |
+
:raise 403: if the current user isn't the author
|
39 |
+
"""
|
40 |
+
post = (
|
41 |
+
get_db()
|
42 |
+
.execute(
|
43 |
+
"SELECT p.id, title, body, created, author_id, username"
|
44 |
+
" FROM post p JOIN user u ON p.author_id = u.id"
|
45 |
+
" WHERE p.id = ?",
|
46 |
+
(id,),
|
47 |
+
)
|
48 |
+
.fetchone()
|
49 |
+
)
|
50 |
+
|
51 |
+
if post is None:
|
52 |
+
abort(404, f"Post id {id} doesn't exist.")
|
53 |
+
|
54 |
+
if check_author and post["author_id"] != g.user["id"]:
|
55 |
+
abort(403)
|
56 |
+
|
57 |
+
return post
|
58 |
+
|
59 |
+
|
60 |
+
@bp.route("/create", methods=("GET", "POST"))
|
61 |
+
@login_required
|
62 |
+
def create():
|
63 |
+
"""Create a new post for the current user."""
|
64 |
+
if request.method == "POST":
|
65 |
+
title = request.form["title"]
|
66 |
+
body = request.form["body"]
|
67 |
+
error = None
|
68 |
+
|
69 |
+
if not title:
|
70 |
+
error = "Title is required."
|
71 |
+
|
72 |
+
if error is not None:
|
73 |
+
flash(error)
|
74 |
+
else:
|
75 |
+
db = get_db()
|
76 |
+
db.execute(
|
77 |
+
"INSERT INTO post (title, body, author_id) VALUES (?, ?, ?)",
|
78 |
+
(title, body, g.user["id"]),
|
79 |
+
)
|
80 |
+
db.commit()
|
81 |
+
return redirect(url_for("blog.index"))
|
82 |
+
|
83 |
+
return render_template("blog/create.html")
|
84 |
+
|
85 |
+
|
86 |
+
@bp.route("/<int:id>/update", methods=("GET", "POST"))
|
87 |
+
@login_required
|
88 |
+
def update(id):
|
89 |
+
"""Update a post if the current user is the author."""
|
90 |
+
post = get_post(id)
|
91 |
+
|
92 |
+
if request.method == "POST":
|
93 |
+
title = request.form["title"]
|
94 |
+
body = request.form["body"]
|
95 |
+
error = None
|
96 |
+
|
97 |
+
if not title:
|
98 |
+
error = "Title is required."
|
99 |
+
|
100 |
+
if error is not None:
|
101 |
+
flash(error)
|
102 |
+
else:
|
103 |
+
db = get_db()
|
104 |
+
db.execute(
|
105 |
+
"UPDATE post SET title = ?, body = ? WHERE id = ?", (title, body, id)
|
106 |
+
)
|
107 |
+
db.commit()
|
108 |
+
return redirect(url_for("blog.index"))
|
109 |
+
|
110 |
+
return render_template("blog/update.html", post=post)
|
111 |
+
|
112 |
+
|
113 |
+
@bp.route("/<int:id>/delete", methods=("POST",))
|
114 |
+
@login_required
|
115 |
+
def delete(id):
|
116 |
+
"""Delete a post.
|
117 |
+
|
118 |
+
Ensures that the post exists and that the logged in user is the
|
119 |
+
author of the post.
|
120 |
+
"""
|
121 |
+
get_post(id)
|
122 |
+
db = get_db()
|
123 |
+
db.execute("DELETE FROM post WHERE id = ?", (id,))
|
124 |
+
db.commit()
|
125 |
+
return redirect(url_for("blog.index"))
|
ice_breaking_challenge/db.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sqlite3
|
2 |
+
|
3 |
+
import click
|
4 |
+
from flask import current_app
|
5 |
+
from flask import g
|
6 |
+
|
7 |
+
|
8 |
+
def get_db():
|
9 |
+
"""Connect to the application's configured database. The connection
|
10 |
+
is unique for each request and will be reused if this is called
|
11 |
+
again.
|
12 |
+
"""
|
13 |
+
if "db" not in g:
|
14 |
+
g.db = sqlite3.connect(
|
15 |
+
current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES
|
16 |
+
)
|
17 |
+
g.db.row_factory = sqlite3.Row
|
18 |
+
|
19 |
+
return g.db
|
20 |
+
|
21 |
+
|
22 |
+
def close_db(e=None):
|
23 |
+
"""If this request connected to the database, close the
|
24 |
+
connection.
|
25 |
+
"""
|
26 |
+
db = g.pop("db", None)
|
27 |
+
|
28 |
+
if db is not None:
|
29 |
+
db.close()
|
30 |
+
|
31 |
+
|
32 |
+
def init_db():
|
33 |
+
"""Clear existing data and create new tables."""
|
34 |
+
db = get_db()
|
35 |
+
|
36 |
+
with current_app.open_resource("schema.sql") as f:
|
37 |
+
db.executescript(f.read().decode("utf8"))
|
38 |
+
|
39 |
+
|
40 |
+
@click.command("init-db")
|
41 |
+
def init_db_command():
|
42 |
+
"""Clear existing data and create new tables."""
|
43 |
+
init_db()
|
44 |
+
click.echo("Initialized the database.")
|
45 |
+
|
46 |
+
|
47 |
+
def init_app(app):
|
48 |
+
"""Register database functions with the Flask app. This is called by
|
49 |
+
the application factory.
|
50 |
+
"""
|
51 |
+
app.teardown_appcontext(close_db)
|
52 |
+
app.cli.add_command(init_db_command)
|
ice_breaking_challenge/index.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Blueprint
|
2 |
+
from flask import flash
|
3 |
+
from flask import g
|
4 |
+
from flask import redirect
|
5 |
+
from flask import render_template
|
6 |
+
from flask import request
|
7 |
+
from flask import url_for
|
8 |
+
from werkzeug.exceptions import abort
|
9 |
+
from flask import session
|
10 |
+
|
11 |
+
from .auth import login_required
|
12 |
+
from .db import get_db
|
13 |
+
|
14 |
+
|
15 |
+
bp = Blueprint("index", __name__)
|
16 |
+
|
17 |
+
|
18 |
+
@bp.route("/", methods=["GET", "POST"])
|
19 |
+
def index() -> None:
|
20 |
+
match request.method:
|
21 |
+
case "GET":
|
22 |
+
return render_template("index.html")
|
23 |
+
case "POST": # index.htmlμμ `λ€μ` λ²νΌ λλ μ λ
|
24 |
+
session["team_number"] = request.form.get("team_number")
|
25 |
+
session["team_size"] = request.form.get("team_size")
|
26 |
+
return render_template("qr.html")
|
ice_breaking_challenge/introduction.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Blueprint
|
2 |
+
from flask import flash
|
3 |
+
from flask import g
|
4 |
+
from flask import redirect
|
5 |
+
from flask import render_template
|
6 |
+
from flask import request
|
7 |
+
from flask import url_for
|
8 |
+
from werkzeug.exceptions import abort
|
9 |
+
from flask import session
|
10 |
+
|
11 |
+
from .auth import login_required
|
12 |
+
from .db import get_db
|
13 |
+
|
14 |
+
|
15 |
+
bp = Blueprint("qr", __name__)
|
16 |
+
|
17 |
+
|
18 |
+
@bp.route("/", methods=["GET", "POST"])
|
19 |
+
def qr() -> None:
|
20 |
+
match request.method:
|
21 |
+
case "GET":
|
22 |
+
return render_template("qr.html")
|
23 |
+
case "POST": # qr.htmlμμ `λ€μ` λ²νΌ λλ μ λ
|
24 |
+
return render_template("introduction.html")
|
ice_breaking_challenge/qr.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Blueprint
|
2 |
+
from flask import flash
|
3 |
+
from flask import g
|
4 |
+
from flask import redirect
|
5 |
+
from flask import render_template
|
6 |
+
from flask import request
|
7 |
+
from flask import url_for
|
8 |
+
from werkzeug.exceptions import abort
|
9 |
+
from flask import session
|
10 |
+
|
11 |
+
from .auth import login_required
|
12 |
+
from .db import get_db
|
13 |
+
|
14 |
+
|
15 |
+
bp = Blueprint("qr", __name__)
|
16 |
+
|
17 |
+
|
18 |
+
@bp.route("/", methods=["GET", "POST"])
|
19 |
+
def qr() -> None:
|
20 |
+
match request.method:
|
21 |
+
case "GET":
|
22 |
+
return render_template("qr.html")
|
23 |
+
case "POST": # qr.htmlμμ `λ€μ` λ²νΌ λλ μ λ
|
24 |
+
return render_template("qr.html")
|
ice_breaking_challenge/schema.sql
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
-- Initialize the database.
|
2 |
+
-- Drop any existing data and create empty tables.
|
3 |
+
|
4 |
+
DROP TABLE IF EXISTS user;
|
5 |
+
DROP TABLE IF EXISTS post;
|
6 |
+
|
7 |
+
CREATE TABLE user (
|
8 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
9 |
+
username TEXT UNIQUE NOT NULL,
|
10 |
+
password TEXT NOT NULL
|
11 |
+
);
|
12 |
+
|
13 |
+
CREATE TABLE post (
|
14 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
15 |
+
author_id INTEGER NOT NULL,
|
16 |
+
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
17 |
+
title TEXT NOT NULL,
|
18 |
+
body TEXT NOT NULL,
|
19 |
+
FOREIGN KEY (author_id) REFERENCES user (id)
|
20 |
+
);
|
ice_breaking_challenge/static/style.css
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
html {
|
2 |
+
font-family: sans-serif;
|
3 |
+
background: #eee;
|
4 |
+
padding: 1rem;
|
5 |
+
}
|
6 |
+
|
7 |
+
body {
|
8 |
+
max-width: 960px;
|
9 |
+
margin: 0 auto;
|
10 |
+
background: white;
|
11 |
+
}
|
12 |
+
|
13 |
+
h1, h2, h3, h4, h5, h6 {
|
14 |
+
font-family: serif;
|
15 |
+
color: #377ba8;
|
16 |
+
margin: 1rem 0;
|
17 |
+
}
|
18 |
+
|
19 |
+
a {
|
20 |
+
color: #377ba8;
|
21 |
+
}
|
22 |
+
|
23 |
+
hr {
|
24 |
+
border: none;
|
25 |
+
border-top: 1px solid lightgray;
|
26 |
+
}
|
27 |
+
|
28 |
+
nav {
|
29 |
+
background: lightgray;
|
30 |
+
display: flex;
|
31 |
+
align-items: center;
|
32 |
+
padding: 0 0.5rem;
|
33 |
+
}
|
34 |
+
|
35 |
+
nav h1 {
|
36 |
+
flex: auto;
|
37 |
+
margin: 0;
|
38 |
+
}
|
39 |
+
|
40 |
+
nav h1 a {
|
41 |
+
text-decoration: none;
|
42 |
+
padding: 0.25rem 0.5rem;
|
43 |
+
}
|
44 |
+
|
45 |
+
nav ul {
|
46 |
+
display: flex;
|
47 |
+
list-style: none;
|
48 |
+
margin: 0;
|
49 |
+
padding: 0;
|
50 |
+
}
|
51 |
+
|
52 |
+
nav ul li a, nav ul li span, header .action {
|
53 |
+
display: block;
|
54 |
+
padding: 0.5rem;
|
55 |
+
}
|
56 |
+
|
57 |
+
.content {
|
58 |
+
padding: 0 1rem 1rem;
|
59 |
+
}
|
60 |
+
|
61 |
+
.content > header {
|
62 |
+
border-bottom: 1px solid lightgray;
|
63 |
+
display: flex;
|
64 |
+
align-items: flex-end;
|
65 |
+
}
|
66 |
+
|
67 |
+
.content > header h1 {
|
68 |
+
flex: auto;
|
69 |
+
margin: 1rem 0 0.25rem 0;
|
70 |
+
}
|
71 |
+
|
72 |
+
.flash {
|
73 |
+
margin: 1em 0;
|
74 |
+
padding: 1em;
|
75 |
+
background: #cae6f6;
|
76 |
+
border: 1px solid #377ba8;
|
77 |
+
}
|
78 |
+
|
79 |
+
.post > header {
|
80 |
+
display: flex;
|
81 |
+
align-items: flex-end;
|
82 |
+
font-size: 0.85em;
|
83 |
+
}
|
84 |
+
|
85 |
+
.post > header > div:first-of-type {
|
86 |
+
flex: auto;
|
87 |
+
}
|
88 |
+
|
89 |
+
.post > header h1 {
|
90 |
+
font-size: 1.5em;
|
91 |
+
margin-bottom: 0;
|
92 |
+
}
|
93 |
+
|
94 |
+
.post .about {
|
95 |
+
color: slategray;
|
96 |
+
font-style: italic;
|
97 |
+
}
|
98 |
+
|
99 |
+
.post .body {
|
100 |
+
white-space: pre-line;
|
101 |
+
}
|
102 |
+
|
103 |
+
.content:last-child {
|
104 |
+
margin-bottom: 0;
|
105 |
+
}
|
106 |
+
|
107 |
+
.content form {
|
108 |
+
margin: 1em 0;
|
109 |
+
display: flex;
|
110 |
+
flex-direction: column;
|
111 |
+
}
|
112 |
+
|
113 |
+
.content label {
|
114 |
+
font-weight: bold;
|
115 |
+
margin-bottom: 0.5em;
|
116 |
+
}
|
117 |
+
|
118 |
+
.content input, .content textarea {
|
119 |
+
margin-bottom: 1em;
|
120 |
+
}
|
121 |
+
|
122 |
+
.content textarea {
|
123 |
+
min-height: 12em;
|
124 |
+
resize: vertical;
|
125 |
+
}
|
126 |
+
|
127 |
+
input.danger {
|
128 |
+
color: #cc2f2e;
|
129 |
+
}
|
130 |
+
|
131 |
+
input[type=submit] {
|
132 |
+
align-self: start;
|
133 |
+
min-width: 10em;
|
134 |
+
}
|
ice_breaking_challenge/templates/auth/login.html
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
{% block header %}
|
4 |
+
<h1>{% block title %}Log In{% endblock %}</h1>
|
5 |
+
{% endblock %}
|
6 |
+
|
7 |
+
{% block content %}
|
8 |
+
<form method="post">
|
9 |
+
<label for="username">Username</label>
|
10 |
+
<input name="username" id="username" required>
|
11 |
+
<label for="password">Password</label>
|
12 |
+
<input type="password" name="password" id="password" required>
|
13 |
+
<input type="submit" value="Log In">
|
14 |
+
</form>
|
15 |
+
{% endblock %}
|
ice_breaking_challenge/templates/auth/register.html
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
{% block header %}
|
4 |
+
<h1>{% block title %}Register{% endblock %}</h1>
|
5 |
+
{% endblock %}
|
6 |
+
|
7 |
+
{% block content %}
|
8 |
+
<form method="post">
|
9 |
+
<label for="username">Username</label>
|
10 |
+
<input name="username" id="username" required>
|
11 |
+
<label for="password">Password</label>
|
12 |
+
<input type="password" name="password" id="password" required>
|
13 |
+
<input type="submit" value="Register">
|
14 |
+
</form>
|
15 |
+
{% endblock %}
|
ice_breaking_challenge/templates/base.html
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!doctype html>
|
2 |
+
<title>{% block title %}{% endblock %} - Ice Breaking Challenge</title>
|
3 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
4 |
+
<nav>
|
5 |
+
<h1><a href="{{ url_for('index') }}">Ice Breaking Challenge</a></h1>
|
6 |
+
</nav>
|
7 |
+
<section class="content">
|
8 |
+
<header>
|
9 |
+
{% block header %}{% endblock %}
|
10 |
+
</header>
|
11 |
+
{% for message in get_flashed_messages() %}
|
12 |
+
<div class="flash">{{ message }}</div>
|
13 |
+
{% endfor %}
|
14 |
+
{% block content %}{% endblock %}
|
15 |
+
</section>
|
ice_breaking_challenge/templates/blog/create.html
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
{% block header %}
|
4 |
+
<h1>{% block title %}New Post{% endblock %}</h1>
|
5 |
+
{% endblock %}
|
6 |
+
|
7 |
+
{% block content %}
|
8 |
+
<form method="post">
|
9 |
+
<label for="title">Title</label>
|
10 |
+
<input name="title" id="title" value="{{ request.form['title'] }}" required>
|
11 |
+
<label for="body">Body</label>
|
12 |
+
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
|
13 |
+
<input type="submit" value="Save">
|
14 |
+
</form>
|
15 |
+
{% endblock %}
|
ice_breaking_challenge/templates/blog/index_.html
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
{% block header %}
|
4 |
+
<h1>{% block title %}Posts{% endblock %}</h1>
|
5 |
+
{% if g.user %}
|
6 |
+
<a class="action" href="{{ url_for('blog.create') }}">New</a>
|
7 |
+
{% endif %}
|
8 |
+
{% endblock %}
|
9 |
+
|
10 |
+
{% block content %}
|
11 |
+
{% for post in posts %}
|
12 |
+
<article class="post">
|
13 |
+
<header>
|
14 |
+
<div>
|
15 |
+
<h1>{{ post['title'] }}</h1>
|
16 |
+
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
|
17 |
+
</div>
|
18 |
+
{% if g.user['id'] == post['author_id'] %}
|
19 |
+
<a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
|
20 |
+
{% endif %}
|
21 |
+
</header>
|
22 |
+
<p class="body">{{ post['body'] }}</p>
|
23 |
+
</article>
|
24 |
+
{% if not loop.last %}
|
25 |
+
<hr>
|
26 |
+
{% endif %}
|
27 |
+
{% endfor %}
|
28 |
+
{% endblock %}
|
ice_breaking_challenge/templates/blog/update.html
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
{% block header %}
|
4 |
+
<h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
|
5 |
+
{% endblock %}
|
6 |
+
|
7 |
+
{% block content %}
|
8 |
+
<form method="post">
|
9 |
+
<label for="title">Title</label>
|
10 |
+
<input name="title" id="title" value="{{ request.form['title'] or post['title'] }}" required>
|
11 |
+
<label for="body">Body</label>
|
12 |
+
<textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
|
13 |
+
<input type="submit" value="Save">
|
14 |
+
</form>
|
15 |
+
<hr>
|
16 |
+
<form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
|
17 |
+
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
|
18 |
+
</form>
|
19 |
+
{% endblock %}
|
ice_breaking_challenge/templates/index.html
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
|
4 |
+
{% block content %}
|
5 |
+
<form method="post">
|
6 |
+
<label for="team_number">ν λ²νΈ</label>
|
7 |
+
<input name="team_number" id="team_number" required>
|
8 |
+
<label for="team_size">νμ μ</label>
|
9 |
+
<input name="team_size" id="team_size" required>
|
10 |
+
<input type="submit" value="λ€μ">
|
11 |
+
</form>
|
12 |
+
{% endblock %}
|
ice_breaking_challenge/templates/introduction.html
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
{% block content %}
|
4 |
+
μκΈ°μκ° ν΄λ³΄μΈμ
|
5 |
+
<form method="post">
|
6 |
+
<input type="submit" value="λ€μ">
|
7 |
+
</form>
|
8 |
+
{% endblock %}
|
ice_breaking_challenge/templates/qr.html
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends 'base.html' %}
|
2 |
+
|
3 |
+
|
4 |
+
{% block content %}
|
5 |
+
<button>qr</button>
|
6 |
+
<br>
|
7 |
+
λͺ¨λ νμλ€μ΄ μ€λ¬Έμ μλ£νλ©΄ μλ λ²νΌμ λλ¬μ£ΌμΈμ.
|
8 |
+
<form method="post">
|
9 |
+
<input type="submit" value="λ€μ">
|
10 |
+
</form>
|
11 |
+
{% endblock %}
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
flask
|
2 |
+
Flask-Session
|
run.sh
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
source venv/bin/activate
|
2 |
+
python3 app.py
|
setup.sh
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
python3 -m venv venv
|
2 |
+
source venv/bin/activate
|
3 |
+
python3 -m pip install -r requirements.txt
|
4 |
+
pre-commit install
|