A lot works!
This commit is contained in:
3
.idea/mvl.iml
generated
3
.idea/mvl.iml
generated
@@ -1,5 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="Flask">
|
||||
<option name="enabled" value="true" />
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Pipenv (mvl)" jdkType="Python SDK" />
|
||||
|
||||
69
app.py
69
app.py
@@ -1,22 +1,28 @@
|
||||
import os
|
||||
|
||||
from flask import Flask, render_template, jsonify, abort
|
||||
from flask import Flask, render_template, jsonify, abort, send_file, request, redirect
|
||||
from sqlalchemy import func
|
||||
from base64 import standard_b64decode, standard_b64encode
|
||||
from io import BytesIO
|
||||
|
||||
from model import Post, Category, db
|
||||
|
||||
app = Flask(__name__)
|
||||
from model import db, Post, Category, ImageBase64
|
||||
|
||||
# Check for environment variable
|
||||
if not os.getenv("DATABASE_URL"):
|
||||
raise RuntimeError("DATABASE_URL is not set")
|
||||
env_vars = ["DATABASE_URL", "PASSWORD"]
|
||||
for env_var in env_vars:
|
||||
if not os.getenv(env_var):
|
||||
raise RuntimeError(f"{env_var} is not set")
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv("DATABASE_URL")
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['JSON_SORT_KEYS'] = False
|
||||
|
||||
# Bind db to application
|
||||
db.init_app(app)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
posts = Post.get_posts()
|
||||
@@ -25,7 +31,6 @@ def index():
|
||||
|
||||
@app.route('/<string:category_name>')
|
||||
def category(category_name):
|
||||
|
||||
category_item = Category.query.filter(func.lower(Category.name) == category_name.replace('_', ' ')).first()
|
||||
if not category_item:
|
||||
return abort(404)
|
||||
@@ -35,6 +40,11 @@ def category(category_name):
|
||||
return render_template("main/index.html", posts=posts)
|
||||
|
||||
|
||||
@app.route('/contact')
|
||||
def contact():
|
||||
return render_template("main/contact.html")
|
||||
|
||||
|
||||
@app.route("/api/post/<int:post_id>", methods=["POST"])
|
||||
def get_post(post_id):
|
||||
post = Post.query.get(post_id)
|
||||
@@ -44,8 +54,47 @@ def get_post(post_id):
|
||||
return jsonify({"success": False})
|
||||
|
||||
|
||||
@app.route("/login", methods=["GET"])
|
||||
def login():
|
||||
return render_template("adm/login.html")
|
||||
@app.route('/images/<string:filename>')
|
||||
def get_image(filename):
|
||||
image_b64 = ImageBase64.query.filter(ImageBase64.filename == filename).first()
|
||||
if not image_b64:
|
||||
return abort(404)
|
||||
image = standard_b64decode(image_b64.data)
|
||||
return send_file(BytesIO(image), mimetype=image_b64.mimetype, attachment_filename=filename)
|
||||
|
||||
|
||||
@app.route('/adm/uploadfile', methods=['POST', 'GET'])
|
||||
def file_uploaded():
|
||||
if request.method == 'POST':
|
||||
if not request.form.get('password') == os.getenv('PASSWORD'):
|
||||
abort(401)
|
||||
# check if the post request has the file part
|
||||
if 'file' not in request.files:
|
||||
# flash('No file part')
|
||||
return abort(400)
|
||||
# return redirect(request.url)
|
||||
file = request.files['file']
|
||||
# if user does not select file, browser also
|
||||
# submit an empty part without filename
|
||||
if file.filename == '':
|
||||
# flash('No selected file')
|
||||
return redirect(request.url)
|
||||
|
||||
if file:
|
||||
data = standard_b64encode(file.read()).decode()
|
||||
print(file.filename, file.mimetype)
|
||||
print(data)
|
||||
database_object = ImageBase64(filename=file.filename, mimetype=file.mimetype, data=data)
|
||||
db.session.add(database_object)
|
||||
db.session.commit()
|
||||
# if file and allowed_file(file.filename):
|
||||
# filename = secure_filename(file.filename)
|
||||
# file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
||||
# return redirect(url_for('uploaded_file',
|
||||
# filename=filename))
|
||||
return render_template("adm/uploadfile.html")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.app_context().push()
|
||||
db.create_all()
|
||||
|
||||
10
model.py
10
model.py
@@ -26,7 +26,7 @@ class Post(db.Model):
|
||||
"title": self.title,
|
||||
"intro": self.intro,
|
||||
"description": self.description,
|
||||
"images": self.images}
|
||||
"images": [image.uri for image in self.images]}
|
||||
|
||||
@staticmethod
|
||||
def get_posts():
|
||||
@@ -44,6 +44,14 @@ class Image(db.Model):
|
||||
post_id = db.Column(db.Integer, db.ForeignKey("posts.id"), nullable=False)
|
||||
|
||||
|
||||
class ImageBase64(db.Model):
|
||||
__tablename__ = "image_store"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
filename = db.Column(db.String(30), unique=True, nullable=False)
|
||||
mimetype = db.Column(db.String(127), nullable=False)
|
||||
data = db.Column(db.Text, nullable=False)
|
||||
|
||||
|
||||
class Category(db.Model):
|
||||
__tablename__ = "category"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/*
|
||||
* Start of Custom CSS
|
||||
*/
|
||||
.nav-link, .navbar-brand {
|
||||
font-family: 'Amatic SC', cursive;
|
||||
font-size: 40px; }
|
||||
|
||||
.card-img-overlay {
|
||||
opacity: 0; }
|
||||
|
||||
@@ -17,8 +21,11 @@
|
||||
.card .card-img-overlay .card-title-bg .card-text {
|
||||
opacity: 1; }
|
||||
|
||||
.social-icon {
|
||||
margin: 10px; }
|
||||
|
||||
body {
|
||||
padding-top: 4.5rem; }
|
||||
padding-top: 6.5rem; }
|
||||
|
||||
footer {
|
||||
margin-top: 10px;
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
$margin: 20px;
|
||||
$half-margin: $margin / 2;
|
||||
|
||||
// Nav
|
||||
.nav-link, .navbar-brand {
|
||||
font-family: 'Amatic SC', cursive;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
|
||||
}
|
||||
|
||||
// Overlay is hidden
|
||||
.card-img-overlay {
|
||||
@@ -36,8 +45,13 @@ $half-margin: $margin / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Contact page
|
||||
.social-icon {
|
||||
margin: $half-margin;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 4.5rem;
|
||||
padding-top: 6.5rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
21
templates/adm/uploadfile.html
Normal file
21
templates/adm/uploadfile.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'adm/layout.html' %}
|
||||
{% block main %}
|
||||
<main class="container">
|
||||
<h1>Upload new Photo</h1>
|
||||
<form class="" method=post enctype=multipart/form-data>
|
||||
<div class="form-group">
|
||||
<label for=password>Password</label>
|
||||
<input class="form-control" id=password type=password name=password>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="file" name="file">
|
||||
<label class="custom-file-label" for="file">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input class="btn btn-primary form-control" type=submit value=Upload>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -5,8 +5,13 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Amatic+SC" rel="stylesheet">
|
||||
<!-- Bootstrap first, then custom style -->
|
||||
<link rel="stylesheet" href="/static/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
|
||||
integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
@@ -22,6 +27,7 @@
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
|
||||
integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{% block nav %}{% endblock %}
|
||||
|
||||
22
templates/main/contact.html
Normal file
22
templates/main/contact.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "main/layout.html" %}
|
||||
|
||||
{% block main %}
|
||||
<main class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 align-content-center">
|
||||
<h1 class="text-center">Hoi!</h1>
|
||||
<p>Na het behalen van mijn diploma 'Product Design' van de Hogeschool van Amsterdam in februari 2018 ben
|
||||
ik gaan werken bij een grafisch ontwerpbureau in Haarlem.
|
||||
Daarnaast ben ik steeds meer bezig met eigen opdrachten, zoals te zien is in dit portfolio.</p>
|
||||
<div class="text-center ">
|
||||
<a href="https://www.linkedin.com/in/marlousvanleeuwen/"><i
|
||||
class="social-icon fab fa-linkedin-in fa-3x fa-fw text-dark"></i></a>
|
||||
<a href="mailto:marlousvleeuwen@live.nl"><i
|
||||
class="social-icon far fa-envelope fa-3x fa-fw text-dark"></i></a>
|
||||
<a href="https://www.instagram.com/marlousvleeuwen/"><i
|
||||
class="social-icon fab fa-instagram fa-3x fa-fw text-dark"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -12,7 +12,7 @@
|
||||
data-post_id="{{ post.id }}"
|
||||
>
|
||||
<div class="card border-light">
|
||||
<img class="card-img" src="https://placeimg.com/400/400/any" alt="random image">
|
||||
<img class="card-img" src="{{ post.images[0].uri }}" alt="random image">
|
||||
|
||||
<div class="card-img-overlay d-flex align-items-center justify-content-center">
|
||||
<div class="card-title-bg bg-dark w-100 d-flex align-items-center justify-content-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="modal fade" id="detailModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title text-center" id="details-title">Modal title</h5>
|
||||
@@ -9,7 +9,8 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="details-intro"></div>
|
||||
<div id="details-intro" class="font-weight-bold"></div>
|
||||
<div id="details-images"></div>
|
||||
<div id="details-description"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,20 +20,25 @@
|
||||
<script>
|
||||
$('#detailModal').on('show.bs.modal', function (event) {
|
||||
let card = $(event.relatedTarget); // Button that triggered the modal
|
||||
{#let name = card.data('name'); // Extract info from data-* attributes#}
|
||||
let post_id = card.data('post_id');
|
||||
|
||||
$.post(`/api/post/${post_id}`, data => {
|
||||
if (!data.success) {
|
||||
console.error("No data for post");
|
||||
return;
|
||||
}
|
||||
let modal = $(this);
|
||||
console.log(data);
|
||||
modal.find('#details-title').text(data.post.title);
|
||||
modal.find('#details-intro').text(data.post.intro);
|
||||
modal.find('#details-description').text(data.post.description);
|
||||
|
||||
let post = data.post;
|
||||
console.log(post);
|
||||
|
||||
$('#details-title').text(post.title);
|
||||
$('#details-intro').text(post.intro);
|
||||
$('#details-description').text(post.description);
|
||||
|
||||
$('#details-images').text('');
|
||||
post.images.forEach(image => {
|
||||
$('#details-images').append(`<img src=${image}>`)
|
||||
});
|
||||
|
||||
});
|
||||
})
|
||||
.on('shown.bs.modal', function () {
|
||||
$('#detailModal').scrollTop(0);
|
||||
});
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<nav class="navbar navbar-expand-md navbar-light bg-light fixed-top">
|
||||
<a class="navbar-brand" href="{{ url_for("index") }}">Marlous</a>
|
||||
<a class="navbar-brand font-weight-bold" href="{{ url_for("index") }}">Marlous</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
@@ -8,8 +8,19 @@
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="#">Home<span class="sr-only">(current)</span></a>
|
||||
{% set items = [("Product Design", 'product_design'), ("Grafisch", "grafisch"), ("Fotografie", "fotografie")] %}
|
||||
{% for item in items %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if item[1] == request.path[1:] %}active{% endif %}"
|
||||
href={{ url_for("category", category_name=item[1]) }}>
|
||||
{{ item[0] }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
<li class="nav-item">
|
||||
{% set name = "contact" %}
|
||||
<a class="nav-link {% if name == request.path[1:] %}active{% endif %}" href="{{ url_for("contact") }}">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user