dongwook-chan commited on
Commit
95fd3af
·
1 Parent(s): cdf4e4e

Create README.md

Browse files
README.md CHANGED
@@ -1,14 +1,67 @@
1
- ## Getting Started
2
- ```bash
3
- # git clone
4
- $ git clone {repo URL}
5
- $ cd {repo name}
6
- # create & activate venv
7
- $ python3 -m venv venv
8
- $ source venv/bin/activate
9
- (venv) $ python3 -m pip install -r requirements.txt
10
- # activate pre-commit hooks
11
- (venv) $ pre-commit install
12
- ```
13
- ## PyCharm Black Integration
14
- https://black.readthedocs.io/en/stable/integrations/editors.html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ice Breaking Challenge
2
+ ```mermaid
3
+ sequenceDiagram
4
+ actor user as 사용자
5
+ participant browser as 브라우저
6
+ participant flask as Flask
7
+ %% participant sheets as Google Sheets
8
+ %% participant gemma as Fine-Tuned Gemma 2
9
+
10
+
11
+ autonumber
12
+ critical 팀 정보 입력
13
+ user ->> browser: Hugging Face Space 접근
14
+ browser ->> flask: `팀 정보 입력 페이지` 요청 (json)
15
+ flask ->> browser: `팀 정보 입력 페이지` 응답 (html & js)
16
+ browser ->> user: `팀 정보 입력 페이지` 렌더링
17
+ end
18
+
19
+ critical 설문 QR
20
+ user ->> browser: `팀 정보 입력 페이지`의 `다음` 버튼 클릭
21
+ browser ->> flask: `설문 QR 페이지` 요청 (json)
22
+ flask ->> browser: `설문 QR 페이지` 응답 (html & js)
23
+ browser ->> user: `설문 QR 페이지` 렌더링
24
+ end
25
+
26
+ critical 자기소개
27
+ user ->> browser: `팀 정보 입력 페이지`의 `다음` 버튼 클릭
28
+ browser ->> flask: `자기소개 페이지` 요청 (json)
29
+ flask ->> browser: `자기소개 페이지` 응답 (html & js)
30
+ browser ->> user: `자기소개 페이지` 렌더링
31
+ Note right of user: 질문 생성 완료될 때까지 `다음` 버튼 렌더링 X
32
+ end
33
+
34
+ critical 설문 및 질문 생성 완료 확인
35
+ browser ->> flask: 설문 완료 확인 요청 (json)
36
+ create participant sheets as Google Sheets
37
+ flask ->> sheets: 설문 요청
38
+ destroy sheets
39
+ sheets ->> flask: 설문 응답
40
+ flask ->> flask: 팀 정보와 설문 내역 대조
41
+ create participant gemma
42
+ flask ->> gemma: 설문 완료되었다면, 질문 생성 요청
43
+ destroy gemma
44
+ gemma ->> flask: 질문 생성 응답
45
+ flask ->> browser: 질문 생성 완료된 경우 `다음` 버튼 응답
46
+ browser ->> user: `다음` 버튼 렌더링
47
+ end
48
+
49
+ critical 젬마 생성 질문 1번
50
+ user ->> browser: `자기소개 페이지`의 `다음` 버튼 클릭
51
+ browser ->> flask: `생성 질문 1번 페이지 요청` (json)
52
+ flask ->> browser: `생성 질문 1번 페이지 응답` (html & js)
53
+ browser ->> user: `생성 질문 1번 페이지 응답` 렌더링
54
+ end
55
+
56
+ critical 젬마 생성 질문 2번
57
+ user ->> browser: `생성 질문 1번 페이지`의 `다음` 버튼 클릭 (json)
58
+ Note left of flask: 이하 동일
59
+ end
60
+
61
+ critical 친해지셨나요
62
+ user ->> browser: `생성 질문 마지막 페이지`의 `다음` 버튼 클릭 (json)
63
+ browser ->> flask: `생성 질문 마지막 페이지` 요청 (json)
64
+ flask ->> browser: `생성 질문 마지막 페이지` 응답 (html & js)
65
+ browser ->> user: `생성 질문 마지막 페이지` 렌더링
66
+ end
67
+ ```
ice_breaking_challenge/__init__.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from flask import Flask
4
+
5
+
6
+ def create_app(test_config=None):
7
+ """Create and configure an instance of the Flask application."""
8
+ app = Flask(__name__, instance_relative_config=True)
9
+ app.config.from_mapping(
10
+ # a default secret that should be overridden by instance config
11
+ SECRET_KEY="dev",
12
+ # store the database in the instance folder
13
+ DATABASE=os.path.join(app.instance_path, "flaskr.sqlite"),
14
+ )
15
+
16
+ if test_config is None:
17
+ # load the instance config, if it exists, when not testing
18
+ app.config.from_pyfile("config.py", silent=True)
19
+ else:
20
+ # load the test config if passed in
21
+ app.config.update(test_config)
22
+
23
+ # ensure the instance folder exists
24
+ try:
25
+ os.makedirs(app.instance_path)
26
+ except OSError:
27
+ pass
28
+
29
+ @app.route("/hello")
30
+ def hello():
31
+ return "Hello, World!"
32
+
33
+ # register the database commands
34
+ from . import db
35
+
36
+ db.init_app(app)
37
+
38
+ # apply the blueprints to the app
39
+ from . import auth
40
+ from . import blog
41
+
42
+ app.register_blueprint(auth.bp)
43
+ app.register_blueprint(blog.bp)
44
+
45
+ # make url_for('index') == url_for('blog.index')
46
+ # in another app, you might define a separate main index here with
47
+ # app.route, while giving the blog blueprint a url_prefix, but for
48
+ # the tutorial the blog will be the main index
49
+ app.add_url_rule("/", endpoint="index")
50
+
51
+ return app
ice_breaking_challenge/auth.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from .db import get_db
15
+
16
+ bp = Blueprint("auth", __name__, url_prefix="/auth")
17
+
18
+
19
+ def login_required(view):
20
+ """View decorator that redirects anonymous users to the login page."""
21
+
22
+ @functools.wraps(view)
23
+ def wrapped_view(**kwargs):
24
+ if g.user is None:
25
+ return redirect(url_for("auth.login"))
26
+
27
+ return view(**kwargs)
28
+
29
+ return wrapped_view
30
+
31
+
32
+ @bp.before_app_request
33
+ def load_logged_in_user():
34
+ """If a user id is stored in the session, load the user object from
35
+ the database into ``g.user``."""
36
+ user_id = session.get("user_id")
37
+
38
+ if user_id is None:
39
+ g.user = None
40
+ else:
41
+ g.user = (
42
+ get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone()
43
+ )
44
+
45
+
46
+ @bp.route("/register", methods=("GET", "POST"))
47
+ def register():
48
+ """Register a new user.
49
+
50
+ Validates that the username is not already taken. Hashes the
51
+ password for security.
52
+ """
53
+ if request.method == "POST":
54
+ username = request.form["username"]
55
+ password = request.form["password"]
56
+ db = get_db()
57
+ error = None
58
+
59
+ if not username:
60
+ error = "Username is required."
61
+ elif not password:
62
+ error = "Password is required."
63
+
64
+ if error is None:
65
+ try:
66
+ db.execute(
67
+ "INSERT INTO user (username, password) VALUES (?, ?)",
68
+ (username, generate_password_hash(password)),
69
+ )
70
+ db.commit()
71
+ except db.IntegrityError:
72
+ # The username was already taken, which caused the
73
+ # commit to fail. Show a validation error.
74
+ error = f"User {username} is already registered."
75
+ else:
76
+ # Success, go to the login page.
77
+ return redirect(url_for("auth.login"))
78
+
79
+ flash(error)
80
+
81
+ return render_template("auth/register.html")
82
+
83
+
84
+ @bp.route("/login", methods=("GET", "POST"))
85
+ def login():
86
+ """Log in a registered user by adding the user id to the session."""
87
+ if request.method == "POST":
88
+ username = request.form["username"]
89
+ password = request.form["password"]
90
+ db = get_db()
91
+ error = None
92
+ user = db.execute(
93
+ "SELECT * FROM user WHERE username = ?", (username,)
94
+ ).fetchone()
95
+
96
+ if user is None:
97
+ error = "Incorrect username."
98
+ elif not check_password_hash(user["password"], password):
99
+ error = "Incorrect password."
100
+
101
+ if error is None:
102
+ # store the user id in a new session and return to the index
103
+ session.clear()
104
+ session["user_id"] = user["id"]
105
+ return redirect(url_for("index"))
106
+
107
+ flash(error)
108
+
109
+ return render_template("auth/login.html")
110
+
111
+
112
+ @bp.route("/logout")
113
+ def logout():
114
+ """Clear the current session, including the stored user id."""
115
+ session.clear()
116
+ 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/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,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <title>{% block title %}{% endblock %} - Flaskr</title>
3
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
4
+ <nav>
5
+ <h1><a href="{{ url_for('index') }}">Flaskr</a></h1>
6
+ <ul>
7
+ {% if g.user %}
8
+ <li><span>{{ g.user['username'] }}</span>
9
+ <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
10
+ {% else %}
11
+ <li><a href="{{ url_for('auth.register') }}">Register</a>
12
+ <li><a href="{{ url_for('auth.login') }}">Log In</a>
13
+ {% endif %}
14
+ </ul>
15
+ </nav>
16
+ <section class="content">
17
+ <header>
18
+ {% block header %}{% endblock %}
19
+ </header>
20
+ {% for message in get_flashed_messages() %}
21
+ <div class="flash">{{ message }}</div>
22
+ {% endfor %}
23
+ {% block content %}{% endblock %}
24
+ </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 %}