summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar cel <cel@blos.sm>2022-05-12 15:08:19 +0100
committerLibravatar cel <cel@blos.sm>2022-05-12 15:08:19 +0100
commit8e7527ae1045f49cd326a394e0f07d95e64a5b6f (patch)
treed12c8d9edae8916a49da554f488a2a6ee6a75294
parentd473ffe12f72109bc304ca341571a10cc4cf8f38 (diff)
downloadvictoria-flask.tar.gz
victoria-flask.tar.bz2
victoria-flask.zip
added routes to admin/routes.py and begun on admin formsflask
-rw-r--r--LICENSE.md0
-rw-r--r--docs/routes.txt (renamed from routes.txt)0
-rw-r--r--victoria/__pycache__/__init__.cpython-310.pycbin0 -> 1039 bytes
-rw-r--r--victoria/__pycache__/config.cpython-310.pycbin0 -> 594 bytes
-rw-r--r--victoria/__pycache__/models.cpython-310.pycbin0 -> 5330 bytes
-rw-r--r--victoria/admin/__pycache__/__init__.cpython-310.pycbin0 -> 156 bytes
-rw-r--r--victoria/admin/__pycache__/routes.cpython-310.pycbin0 -> 972 bytes
-rw-r--r--victoria/admin/forms.py41
-rw-r--r--victoria/admin/routes.py107
-rw-r--r--victoria/errors/__pycache__/__init__.cpython-310.pycbin0 -> 157 bytes
-rw-r--r--victoria/errors/__pycache__/handlers.cpython-310.pycbin0 -> 706 bytes
-rw-r--r--victoria/main/__pycache__/__init__.cpython-310.pycbin0 -> 155 bytes
-rw-r--r--victoria/main/__pycache__/routes.cpython-310.pycbin0 -> 582 bytes
-rw-r--r--victoria/main/routes.py5
-rw-r--r--victoria/models.py97
-rw-r--r--victoria/site.dbbin0 -> 77824 bytes
-rw-r--r--victoria/static/main.css188
-rw-r--r--victoria/templates/base.html41
18 files changed, 445 insertions, 34 deletions
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/LICENSE.md
diff --git a/routes.txt b/docs/routes.txt
index c95a2c4..c95a2c4 100644
--- a/routes.txt
+++ b/docs/routes.txt
diff --git a/victoria/__pycache__/__init__.cpython-310.pyc b/victoria/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..a2c4fba
--- /dev/null
+++ b/victoria/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/victoria/__pycache__/config.cpython-310.pyc b/victoria/__pycache__/config.cpython-310.pyc
new file mode 100644
index 0000000..f43dea0
--- /dev/null
+++ b/victoria/__pycache__/config.cpython-310.pyc
Binary files differ
diff --git a/victoria/__pycache__/models.cpython-310.pyc b/victoria/__pycache__/models.cpython-310.pyc
new file mode 100644
index 0000000..64004a6
--- /dev/null
+++ b/victoria/__pycache__/models.cpython-310.pyc
Binary files differ
diff --git a/victoria/admin/__pycache__/__init__.cpython-310.pyc b/victoria/admin/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..b58c293
--- /dev/null
+++ b/victoria/admin/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/victoria/admin/__pycache__/routes.cpython-310.pyc b/victoria/admin/__pycache__/routes.cpython-310.pyc
new file mode 100644
index 0000000..d87b7c0
--- /dev/null
+++ b/victoria/admin/__pycache__/routes.cpython-310.pyc
Binary files differ
diff --git a/victoria/admin/forms.py b/victoria/admin/forms.py
index 5faad3b..f5c1648 100644
--- a/victoria/admin/forms.py
+++ b/victoria/admin/forms.py
@@ -1,8 +1,47 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField
-from wtforms import StringField, SubmitField, BooleanField
+from wtforms import DateField, TextAreaField, StringField, SubmitField, BooleanField, PasswordField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flask_login import current_user
from victoria.models import Artist, Series, Work, Tag, License
+class LoginForm(FlaskForm):
+ email = StringField('Email',
+ validators=[DataRequired(), Email()])
+ submit = SubmitField('Send Login Code')
+
+class OTPForm(FlaskForm):
+ password = PasswordField('Password', validators=[DataRequired()])
+ submit = SubmitField('Login')
+
+class UpdateArtistForm(FlaskForm):
+ username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
+ email = StringField('Email', validators=[DataRequired(), Email()])
+ name = StringField('Name', validators=[DataRequired(), Length(min=2, max=100)])
+ picture = FileField('Update Picture', validators=[FileAllowed(['jpg', 'png', 'jpeg', 'gif'])])
+ bio = TextAreaField('Bio')
+ submit = SubmitField('Update')
+
+ def validate_username(self, username):
+ if username.data != current_user.username:
+ user = Artist.query.filter_by(username=username.data).first()
+ if user:
+ raise ValidationError('Username is taken, please choose a different one')
+ def validate_email(self, email):
+ if email.data != current_user.email:
+ user = Artist.query.filter_by(email=email.data).first()
+ if user:
+ raise ValidationError('Email is taken, please choose a different one')
+
+class WorkForm(FlaskForm):
+ title = StringField('Title', validators=[Length(min=2, max=120)])
+ date = DateField('Date')
+ description = TextAreaField('Bio')
+ tags = StringField('Title', validators=[Length(min=2, max=120)])
+ license = StringField('Title', validators=[Length(min=2, max=20)])
+
+class MediaForm(FlaskForm):
+ media = FileField('Update Picture', validators=[FileAllowed(['jpg', 'png', 'jpeg', 'gif'])])
+ alt_text = TextAreaField('Alt Text')
+
diff --git a/victoria/admin/routes.py b/victoria/admin/routes.py
index e721075..55dbe1b 100644
--- a/victoria/admin/routes.py
+++ b/victoria/admin/routes.py
@@ -2,29 +2,104 @@ from flask import render_template, url_for, redirect, request, Blueprint
from flask_login import login_user, current_user, logout_user, login_required
from victoria import db
from victoria.models import Artist, Work, Series, Tag, License
-from victoria.admin.forms import
-from victoria.admin.utils import send_otp_email
+from victoria.admin.forms import LoginForm
+#from victoria.admin.utils import send_otp_email
admin = Blueprint('admin', __name__)
@admin.route("/admin/login", methods=['GET', 'POST'])
-@login_required
-def admin_dashboard():
+def login():
+ form = LoginForm()
return render_template('admin.html', title='Admin Dashboard')
-@admin.route("/admin", methods=['GET', 'POST'])
-@admin.route("/admin/dashboard", methods=['GET', 'POST'])
+@admin.route("/admin/login/<string:otp>")
+def otp_login(otp):
+ return redirect(url_for('admin.dashboard'))
+
+@admin.route("/admin")
+@admin.route("/admin/dashboard")
@login_required
-def admin_dashboard():
+def dashboard():
return render_template('admin.html', title='Admin Dashboard')
-
-@admin.route("/admin", methods=['GET', 'POST'])
+
+@admin.route("/admin/works")
@login_required
-def admin_dashboard():
- return render_template('admin.html', title='Admin Dashboard')
-
-@admin.route("/admin", methods=['GET', 'POST'])
+def works():
+ return render_template('admin/works.html', title='Works')
+
+@admin.route("/admin/work/new")
@login_required
-def admin_dashboard():
- return render_template('admin.html', title='Admin Dashboard')
-
+def new_work():
+ return render_template('admin/new_work.html', title='New work')
+
+@admin.route("/admin/work/<int:work_id>")
+@login_required
+def edit_work(work_id):
+ return render_template('admin/work.html', title=work_title)
+
+@admin.route("/admin/work/<int:work_id>/delete", methods=['POST'])
+@login_required
+def delete_work(work_id):
+ flash('The work has been deleted.', 'success')
+ return redirect(url_for('admin.works'))
+
+@admin.route("/admin/series")
+@login_required
+def series():
+ return render_template('admin/series.html', title='Series')
+
+@admin.route("/admin/series/new")
+@login_required
+def new_series():
+ return render_template('admin/new_series.html', title='New Series')
+
+@admin.route("/admin/series/<int:series_id>")
+@login_required
+def edit_series(series_id):
+ return render_template('admin/edit_series.html', title=series_title)
+
+@admin.route("/admin/series/<int:series_id>/delete", methods=['POST'])
+@login_required
+def delete_series(series_id):
+ flash('The series has been deleted.', 'success')
+ return redirect(url_for('admin.series'))
+
+@admin.route("/admin/artist")
+@login_required
+def artist():
+ return render_template('admin/edit_artist.html', title='Edit Artist Profile')
+
+@admin.route("/admin/tags")
+@login_required
+def tags():
+ #list tags
+ return render_template('admin/list_tags.html', title='Tags')
+
+@admin.route("/admin/licenses")
+@login_required
+def licenses():
+ return render_template('admin/licenses.html', title='Licences')
+
+@admin.route("/admin/license/new")
+@login_required
+def new_license():
+ return render_template('admin/new_license.html', title='New License')
+
+@admin.route("/admin/license/<int:license_id>")
+@login_required
+def edit_license(license_id):
+ return render_template('admin/edit_license.html', title=license_title)
+
+@admin.route("/admin/license/<int:license_id>/delete", methods=['POST'])
+@login_required
+def delete_license(license_id):
+ flash('The license has been deleted.', 'success')
+ return redirect(url_for('admin.licenses'))
+
+@admin.route("/admin/settings")
+@login_required
+def settings():
+ return render_template('admin/settings.html', title='Settings')
+
+
+
diff --git a/victoria/errors/__pycache__/__init__.cpython-310.pyc b/victoria/errors/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..c371450
--- /dev/null
+++ b/victoria/errors/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/victoria/errors/__pycache__/handlers.cpython-310.pyc b/victoria/errors/__pycache__/handlers.cpython-310.pyc
new file mode 100644
index 0000000..cb64952
--- /dev/null
+++ b/victoria/errors/__pycache__/handlers.cpython-310.pyc
Binary files differ
diff --git a/victoria/main/__pycache__/__init__.cpython-310.pyc b/victoria/main/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..1a73e77
--- /dev/null
+++ b/victoria/main/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/victoria/main/__pycache__/routes.cpython-310.pyc b/victoria/main/__pycache__/routes.cpython-310.pyc
new file mode 100644
index 0000000..b6c0329
--- /dev/null
+++ b/victoria/main/__pycache__/routes.cpython-310.pyc
Binary files differ
diff --git a/victoria/main/routes.py b/victoria/main/routes.py
index 52eafa8..8272e63 100644
--- a/victoria/main/routes.py
+++ b/victoria/main/routes.py
@@ -1,14 +1,11 @@
from flask import Blueprint, request, render_template
-from flaskblog.models import Post
main = Blueprint('main', __name__)
@main.route("/")
@main.route("/home")
def home():
- page = request.args.get('page', 1, type=int)
- posts = Post.query.order_by(Post.date_posted.desc()).paginate(page=page, per_page=5)
- return render_template('home.html', posts=posts)
+ return render_template('home.html')
@main.route("/about")
def about():
diff --git a/victoria/models.py b/victoria/models.py
index a37724a..6a8dc89 100644
--- a/victoria/models.py
+++ b/victoria/models.py
@@ -2,6 +2,68 @@ from victoria import db, login_manager
from flask_login import UserMixin
from datetime import datetime
+# for sqlalchemy_media
+import json
+import functools
+from os import path, getcwd
+from sqlalchemy import TypeDecorator, Unicode
+from sqlalchemy_media import File, Image, ImageValidator, ImageProcessor, ImageAnalyzer, StoreManager, \
+ FileSystemStore
+from sqlalchemy_media.constants import MB, KB
+
+WORKING_DIR = path.abspath(getcwd())
+TEMP_PATH = path.join(WORKING_DIR, 'static', 'media')
+
+StoreManager.register(
+ 'fs',
+ functools.partial(FileSystemStore, TEMP_PATH, 'http://localhost:5000/static/media'),
+ default=True
+)
+
+# JSON mutable type for SQLite
+class Json(TypeDecorator):
+ impl = Unicode
+
+ def process_bind_param(self, value, engine):
+ return json.dumps(value)
+
+ def process_result_value(self, value, engine):
+ if value is None:
+ return None
+ return json.loads(value)
+
+class ProfilePicture(Image):
+ __pre_processors__ = [
+ ImageAnalyzer(),
+ ImageValidator(
+ minimum=(10, 10),
+ maximum=(3840, 3840),
+ content_types=('image/jpeg', 'image/png', 'image/gif')
+ ),
+ ImageProcessor(
+ fmt='jpeg'
+ )
+ ]
+ __max_length__ = 10 * MB
+ __min_length__ = 10 * KB
+
+class MediaFile(Image):
+ __pre_processors__ = [
+ ImageAnalyzer(),
+ ImageValidator(
+ minimum=(10, 10),
+ maximum=(3840, 3840),
+ content_types=('image/jpeg', 'image/png', 'image/gif')
+ ),
+ ImageProcessor(
+ fmt='jpeg'
+ )
+ ]
+ __max_length__ = 20 * MB
+ __min_length__ = 10 * KB
+
+
+# login_manager
@login_manager.user_loader
def load_user(artist_id):
return Artist.query.get(int(artist_id))
@@ -30,12 +92,12 @@ class Artist(db.Model, UserMixin):
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
name = db.Column(db.String(120))
- profile_picture = db.Column(db.String(20))
+ profile_picture = db.Column(ProfilePicture.as_mutable(Json))
bio = db.Column(db.Text)
- works = db.relationship('Work', secondary=tags, lazy='subquery',
+ works = db.relationship('Work', secondary=artist_work, lazy='subquery',
backref=db.backref('artist', lazy=True))
- series = db.relationship('Series', secondary=tags, lazy='subquery',
+ series = db.relationship('Series', secondary=artist_series, lazy='subquery',
backref=db.backref('artist', lazy=True))
def __repr__(self):
@@ -43,37 +105,45 @@ class Artist(db.Model, UserMixin):
class Work(db.Model):
id = db.Column(db.Integer, primary_key=True)
- image_file = db.Column(db.String(20), unique=True, nullable=False)
+
+ media = db.relationship('Media', backref='work', lazy=True)
+
title = db.Column(db.String(120), nullable=False, default='Untitled')
- date = db.Column(db.DateTime),
+ date = db.Column(db.DateTime)
date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
description = db.Column(db.Text)
- alt_text = db.Column(db.Text)
# tags
tags = db.relationship('Tag', secondary=tags, lazy='subquery',
- backref=db.backref('works', lazy=True))
+ backref=db.backref('work', lazy=True))
# license
- license = db.Column(db.String(20), db.ForeignKey('license.license'))
+ license_id = db.Column(db.String(20), db.ForeignKey('license.id'))
def __repr__(self):
return f"Work('{self.title}', '{self.image_file}', '{self.alt_text}')"
+# Media table, as each work can have multiple images.
+class Media(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ media = db.Column(MediaFile.as_mutable(Json), nullable=False)
+ alt_text = db.Column(db.Text)
+ work_id = db.Column(db.Integer, db.ForeignKey('work.id'), nullable=False)
+
class Series(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False, default='Untitled')
- date = db.Column(db.DateTime),
+ date = db.Column(db.DateTime)
date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
location = db.Column(db.String(120))
description = db.Column(db.Text)
# tree self-relationship
- children = db.relationship('Series', backref='parent', lazy=True)
- parent_id = db.Column(db.Integer, db.ForeignKey('parent.id'), nullable=False)
+ children = db.relationship('Series', backref='parent', lazy=True, remote_side=[id])
+ parent_id = db.Column(db.Integer, db.ForeignKey('series.id'))
# many-to-many relationships
# works
- works = db.relationship('Work', secondary=tags, lazy='subquery',
+ works = db.relationship('Work', secondary=series_work, lazy='subquery',
backref=db.backref('series', lazy=True))
@@ -81,7 +151,8 @@ class Tag(db.Model):
tag = db.Column(db.String(20), primary_key=True)
class License(db.Model):
- license = db.Column(db.String(20), primary_key=True)
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(20), nullable=False)
text = db.Column(db.Text)
works = db.relationship('Work', backref='license', lazy=True)
diff --git a/victoria/site.db b/victoria/site.db
new file mode 100644
index 0000000..2504ed1
--- /dev/null
+++ b/victoria/site.db
Binary files differ
diff --git a/victoria/static/main.css b/victoria/static/main.css
new file mode 100644
index 0000000..2790857
--- /dev/null
+++ b/victoria/static/main.css
@@ -0,0 +1,188 @@
+h1 {
+ color: #9561bd;
+}
+
+bottom {
+ float: bottom;
+}
+
+* {
+ overflow-wrap:break-word;
+ cursor: var(--default), auto;
+
+}
+
+header {
+ padding: 20px;
+}
+
+p, a {
+ font-family: sans-serif;
+}
+
+a {
+ cursor: var(--pointer), auto;
+ display: inline-block;
+ color: inherit;
+ position: relative;
+ text-decoration: none;
+ transition: linear 0.2s;
+ color: var(--purple);
+}
+
+a:before {
+ background: var(--rainbowright);
+ content: '';
+ height: 2px;
+ position: absolute;
+ bottom: -1.5px;
+ width: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ transition: width 0.2s ease-in-out;
+}
+
+a:hover{
+ color: var(--white);
+}
+
+a:hover:before{
+ width: 0;
+}
+
+
+
+html{
+ min-height: 100%;
+}
+
+body {
+ color: var(--lightblue);
+ font-family: serif;
+ font-weight: normal;
+
+ margin: 0;
+ padding: 0;
+
+ background-color: #ffffff;
+
+
+ min-height: 100%;
+ height: 100%;
+
+ background-size: auto;
+ background-position: center;
+}
+
+.flexbox{
+ padding-top: 1.5%;
+ padding-bottom: 4%;
+ display:flex;
+ justify-content: space-between;
+}
+
+.in-flexbox {
+ box-sizing: border-box;
+ height: 100%;
+ padding: 0;
+}
+
+.container{
+ padding: 1% 2%;
+ width: 74%;
+ height: 100%;
+}
+
+.sidebar{
+ position: sticky;
+ top: 1.5%;
+ width: 24%;
+}
+
+.sidebar div{
+ margin-bottom: 13px;
+ padding: 2% 10%;
+}
+
+.nav a, .subpage a{
+ color: var(--lightblue);
+}
+
+.nav a:hover, .subpage a:hover{
+ color: var(--white);
+ transition: linear 0.2s;
+}
+
+.nav p, .subpage p{
+ line-height: 1em;
+}
+
+
+@media (max-width:1024px)
+{
+ .flexbox{
+ flex-direction: column;
+ justify-content: space-around;
+ align-items: center;
+
+ }
+ .sidebar {
+ display: flex;
+ position: relative;
+ flex-direction: row;
+ width: 74%;
+ justify-content: space-between;
+ box-sizing: border-box;
+ gap: 2%;
+ }
+
+ .sidebar div{
+ margin-bottom: 13px;
+ padding: 1% 2%;
+ flex-grow: 1;
+ }
+}
+
+@media (max-width: 720px)
+{
+ .container{
+ width: 90%;
+ }
+ .sidebar{
+ width: 90%;
+ }
+
+ .nav h2{
+ font-size: 1.4em;
+ }
+ .nav p{
+ font-size: 11px;
+ }
+
+ a:before {
+ height: 2px;
+ bottom: -2px;
+ }
+
+}
+
+.body {
+ width: 60%;
+ height: 95%;
+
+ margin: auto;
+ margin-top: 2.5%;
+
+ padding: 0;
+
+}
+
+@media (max-width:1024px)
+{
+ .body {
+ width: 100%;
+ padding: 0;
+ margin: auto;
+ }
+}
+
diff --git a/victoria/templates/base.html b/victoria/templates/base.html
new file mode 100644
index 0000000..9b8186c
--- /dev/null
+++ b/victoria/templates/base.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <!-- Required meta tags -->
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+ <!-- Bootstrap CSS -->
+ <link rel="stylesheet" type="text/css" href="static/main.css">
+
+ <title>VICTORIA KENNAUGH</title>
+</head>
+<body>
+ <div class="body">
+ <header class="header top">
+ <h1>VICTORIA KENNAUGH</h1>
+ </header>
+ <div class="flexbox">
+ <nav class="sidebar in-flexbox">
+ <div class="nav outline">
+ <p><a class="accordion" href="#">series 1</a></p>
+ <p><a class="accordion" href="#">series 2</a></p>
+ <p><a class="accordion" href="#">series 3</a></p>
+ </div>
+ <div class="nav outline bottom">
+ <p><a href="#">works</a></p>
+ <p><a href="#">about</a></p>
+ <p><a href="#">contact</a></p>
+ <p><a href="#">insta</a></p>
+ </div>
+ </nav>
+
+ <main class="container outline in-flexbox">
+ {% block content %}{% endblock %}
+ </main>
+ </div>
+ <footer>
+ </footer>
+ </div>
+</body>
+</html>