Bläddra i källkod

first blueprints

master
Johannnes Bürst 5 år sedan
förälder
incheckning
14ebf71f2f
16 ändrade filer med 472 tillägg och 1 borttagningar
  1. +14
    -0
      .gitignore
  2. +3
    -1
      .idea/.gitignore
  3. +11
    -0
      .idea/dataSources.xml
  4. +8
    -0
      .idea/sqldialects.xml
  5. +42
    -0
      calender/__init__.py
  6. +95
    -0
      calender/auth.py
  7. +99
    -0
      calender/calender.py
  8. +41
    -0
      calender/db.py
  9. +17
    -0
      calender/schema.sql
  10. +26
    -0
      calender/static/style.css
  11. +15
    -0
      calender/templates/auth/login.html
  12. +15
    -0
      calender/templates/auth/register.html
  13. +24
    -0
      calender/templates/base.html
  14. +15
    -0
      calender/templates/calender/create.html
  15. +28
    -0
      calender/templates/calender/index.html
  16. +19
    -0
      calender/templates/calender/update.html

+ 14
- 0
.gitignore Visa fil

@@ -0,0 +1,14 @@
venv/

*.pyc
__pycache__/

instance/

.pytest_cache/
.coverage
htmlcov/

dist/
build/
*.egg-info/

+ 3
- 1
.idea/.gitignore Visa fil

@@ -1,2 +1,4 @@
# Default ignored files
/workspace.xml
/workspace.xml
# Datasource local storage ignored files
/dataSources.local.xml

+ 11
- 0
.idea/dataSources.xml Visa fil

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="@localhost" uuid="1d925457-0ec6-4e05-8d64-06939788523b">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306</jdbc-url>
</data-source>
</component>
</project>

+ 8
- 0
.idea/sqldialects.xml Visa fil

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/calender/auth.py" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/calender/calender.py" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/calender/schema.sql" dialect="SQLite" />
</component>
</project>

+ 42
- 0
calender/__init__.py Visa fil

@@ -0,0 +1,42 @@
import os

from flask import Flask


def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
DATABASE=os.path.join(app.instance_path, 'calender.sqlite'),
)

if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)

# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass

# a simple page that says hello
@app.route('/hello')
def hello():
return 'Hello, World!'

from . import db
db.init_app(app)

from . import auth
app.register_blueprint(auth.bp)

from . import calender
app.register_blueprint(calender.bp)
app.add_url_rule('/', endpoint='index')

return app

+ 95
- 0
calender/auth.py Visa fil

@@ -0,0 +1,95 @@
import functools

from flask import (
Blueprint, flash, redirect, render_template, request, session, url_for,
g)
from werkzeug.security import check_password_hash, generate_password_hash

from calender.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')


@bp.route('/register', methods=('GET', 'POST'))
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None

if not username:
error = 'Username is required.'
elif not password:
error = 'Password is required.'
elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None:
error = 'User {} is already registered.'.format(username)

if error is None:
db.execute(
'INSERT INTO user (username, password) VALUES (?, ?)',
(username, generate_password_hash(password))
)
db.commit()
return redirect(url_for('auth.login'))

flash(error)

return render_template('auth/register.html')


@bp.route('/login', methods=('GET', 'POST'))
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM user WHERE username = ?', (username,)
).fetchone()

if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
error = 'Incorrect password.'

if error is None:
session.clear()
session['user_id'] = user['id']
return redirect(url_for('index'))

flash(error)

return render_template('auth/login.html')


@bp.before_app_request
def load_logged_in_user():
user_id = session.get('user_id')

if user_id is None:
g.user = None
else:
g.user = get_db().execute(
'SELECT * FROM user WHERE id = ?', (user_id,)
).fetchone()


@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))


def login_required(view):
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for('auth.login'))

return view(**kwargs)

return wrapped_view

+ 99
- 0
calender/calender.py Visa fil

@@ -0,0 +1,99 @@
from flask import (
Blueprint, render_template,
flash, request, url_for, redirect, abort, g)

from calender.auth import login_required
from calender.db import get_db

bp = Blueprint('calender', __name__)


@bp.route('/')
def index():
db = get_db()
posts = db.execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' ORDER BY created DESC'
).fetchall()
return render_template('calender/index.html', posts=posts)


@bp.route('/create', methods=('GET', 'POST'))
@login_required
def create():
if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None

if not title:
error = 'Title is required.'

if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'INSERT INTO post (title, body, author_id)'
' VALUES (?, ?, ?)',
(title, body, g.user['id'])
)
db.commit()
return redirect(url_for('calender.index'))

return render_template('calender/create.html')


def get_post(id, check_author=True):
post = get_db().execute(
'SELECT p.id, title, body, created, author_id, username'
' FROM post p JOIN user u ON p.author_id = u.id'
' WHERE p.id = ?',
(id,)
).fetchone()

if post is None:
abort(404, "Post id {0} doesn't exist.".format(id))

if check_author and post['author_id'] != g.user['id']:
abort(403)

return post


@bp.route('/<int:id>/update', methods=('GET', 'POST'))
@login_required
def update(id):
post = get_post(id)

if request.method == 'POST':
title = request.form['title']
body = request.form['body']
error = None

if not title:
error = 'Title is required.'

if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'UPDATE post SET title = ?, body = ?'
' WHERE id = ?',
(title, body, id)
)
db.commit()
return redirect(url_for('calender.index'))

return render_template('calender/update.html', post=post)

@bp.route('/<int:id>/delete', methods=('POST',))
@login_required
def delete(id):
get_post(id)
db = get_db()
db.execute('DELETE FROM post WHERE id = ?', (id,))
db.commit()
return redirect(url_for('calender.index'))

+ 41
- 0
calender/db.py Visa fil

@@ -0,0 +1,41 @@
import sqlite3

import click
from flask import current_app, g
from flask.cli import with_appcontext


def get_db():
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row

return g.db


def close_db(e=None):
db = g.pop('db', None)

if db is not None:
db.close()

def init_db():
db = get_db()

with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))


@click.command('init-db')
@with_appcontext
def init_db_command():
"""Clear the existing data and create new tables."""
init_db()
click.echo('Initialized the database.')

def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)

+ 17
- 0
calender/schema.sql Visa fil

@@ -0,0 +1,17 @@
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS post;

CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
);

CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
body TEXT NOT NULL,
FOREIGN KEY (author_id) REFERENCES user (id)
);

+ 26
- 0
calender/static/style.css Visa fil

@@ -0,0 +1,26 @@
html { font-family: sans-serif; background: #eee; padding: 1rem; }
body { max-width: 960px; margin: 0 auto; background: white; }
h1 { font-family: serif; color: #377ba8; margin: 1rem 0; }
a { color: #377ba8; }
hr { border: none; border-top: 1px solid lightgray; }
nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; }
nav h1 { flex: auto; margin: 0; }
nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; }
nav ul { display: flex; list-style: none; margin: 0; padding: 0; }
nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; }
.content { padding: 0 1rem 1rem; }
.content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; }
.content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; }
.flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; }
.post > header { display: flex; align-items: flex-end; font-size: 0.85em; }
.post > header > div:first-of-type { flex: auto; }
.post > header h1 { font-size: 1.5em; margin-bottom: 0; }
.post .about { color: slategray; font-style: italic; }
.post .body { white-space: pre-line; }
.content:last-child { margin-bottom: 0; }
.content form { margin: 1em 0; display: flex; flex-direction: column; }
.content label { font-weight: bold; margin-bottom: 0.5em; }
.content input, .content textarea { margin-bottom: 1em; }
.content textarea { min-height: 12em; resize: vertical; }
input.danger { color: #cc2f2e; }
input[type=submit] { align-self: start; min-width: 10em; }

+ 15
- 0
calender/templates/auth/login.html Visa fil

@@ -0,0 +1,15 @@
{% extends 'base.html' %}

{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}

{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% endblock %}

+ 15
- 0
calender/templates/auth/register.html Visa fil

@@ -0,0 +1,15 @@
{% extends 'base.html' %}

{% block header %}
<h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}

{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Register">
</form>
{% endblock %}

+ 24
- 0
calender/templates/base.html Visa fil

@@ -0,0 +1,24 @@
<!doctype html>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1>Flaskr</h1>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</ul>
</nav>
<section class="content">
<header>
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</section>

+ 15
- 0
calender/templates/calender/create.html Visa fil

@@ -0,0 +1,15 @@
{% extends 'base.html' %}

{% block header %}
<h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}

{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title" value="{{ request.form['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] }}</textarea>
<input type="submit" value="Save">
</form>
{% endblock %}

+ 28
- 0
calender/templates/calender/index.html Visa fil

@@ -0,0 +1,28 @@
{% extends 'base.html' %}

{% block header %}
<h1>{% block title %}Posts{% endblock %}</h1>
{% if g.user %}
<a class="action" href="{{ url_for('calender.create') }}">New</a>
{% endif %}
{% endblock %}

{% block content %}
{% for post in posts %}
<article class="post">
<header>
<div>
<h1>{{ post['title'] }}</h1>
<div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
</div>
{% if g.user['id'] == post['author_id'] %}
<a class="action" href="{{ url_for('calender.update', id=post['id']) }}">Edit</a>
{% endif %}
</header>
<p class="body">{{ post['body'] }}</p>
</article>
{% if not loop.last %}
<hr>
{% endif %}
{% endfor %}
{% endblock %}

+ 19
- 0
calender/templates/calender/update.html Visa fil

@@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}

{% block content %}
<form method="post">
<label for="title">Title</label>
<input name="title" id="title"
value="{{ request.form['title'] or post['title'] }}" required>
<label for="body">Body</label>
<textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
<input type="submit" value="Save">
</form>
<hr>
<form action="{{ url_for('calender.delete', id=post['id']) }}" method="post">
<input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
</form>
{% endblock %}

Laddar…
Avbryt
Spara