forked from crunk/distribusi-verse
first commit of distribusi-verse
This commit is contained in:
commit
876a44633f
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/.venv/
|
||||||
|
/__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
|
||||||
|
*.db
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "distribusi"]
|
||||||
|
path = distribusi
|
||||||
|
url = https://git.vvvvvvaria.org/varia/distribusi
|
30
README.md
Normal file
30
README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#distribusi verse
|
||||||
|
|
||||||
|
Distribusi is a content management system for the web that produces static index pages based on folders in the files system. It is inspired by the automatic index functions featured in several popular web servers. Distribusi works by traversing the file system and directory hierarchy to automatically list all the files in the directory, detect the file types and providing them with relevant html classes and tags for easy styling.
|
||||||
|
|
||||||
|
Distribusi was first conceptualized as a tool which supported a contribution by Dennis de Bel, Danny van der Kleij and Roel Roscam Abbing to the [ruru house](http://ruruhuis.nl/) organized by [Reinaart Vanhoe](http://vanhoe.org/) and the [ruangrupa](http://ruru.ruangrupa.org/) collective during 2016 Sonsbeek Biennale in Arnhem. During the biennale time the ruru house was a lively meeting place with a programme of discussions, workshops, lectures, culinary activities, performances, pop-up markets and even karaoke evenings, where curators and Arnhemmers met.
|
||||||
|
|
||||||
|
The contribution consisted of setting up distribusi. ruruhuis.nl (distribusi is bahasa Indonesian for 'distribution') which was a website connected to a server in the space. Rather than a hidden administrative interface, the server was present and visible and an invitation was extended to visitors to use it to publish material online. This was done by inserting a USB-drive into any of the ports. The distribusi script would then turn the contents of that stick it into a website. Once the USB-drive was removed that website was no longer on-line. Over time `distribusi.ruruhuis.nl` hosted photos, books and movies. The website is now off-line but the tool that was used to make it is still used in [Varia](https://varia.zone).
|
||||||
|
|
||||||
|
This particular work in progress project is an attempt to make distribusi into a webinterface that can be operated remotely without any knowlegde of CLI. Trying to somehow combine the ideas of distribusi with the ideas of a [tildeverse](https://tildeverse.org/) or [Tilde club ](https://tilde.club/), but also be neither of these ideas.
|
||||||
|
|
||||||
|
This project is made for Autonomous Practices at the WDKA in Rotterdam.
|
||||||
|
|
||||||
|
## Start your engines!
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3 -m venv .venv
|
||||||
|
$ source .venv/bin/activate
|
||||||
|
$ pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database, Databass
|
||||||
|
The git doesn't come with a database of users, you have to make one.
|
||||||
|
make sure you have the virtual environment enabled.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python deploydb.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Distribusi
|
||||||
|
for now I cloned it into the distribusi folder from the varia git.
|
0
__init.py__
Normal file
0
__init.py__
Normal file
1
distribusi
Submodule
1
distribusi
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 63becedaf51d069744b4088253d883d08e883aa1
|
25
pyproject.toml
Normal file
25
pyproject.toml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[tool.black]
|
||||||
|
line-length = 79
|
||||||
|
target-version = ['py37', 'py38', 'py39']
|
||||||
|
include = '\.pyi?$'
|
||||||
|
exclude = '''
|
||||||
|
/(
|
||||||
|
\.eggs
|
||||||
|
| \.git
|
||||||
|
| \.hg
|
||||||
|
| \.mypy_cache
|
||||||
|
| \.tox
|
||||||
|
| \.venv
|
||||||
|
| _build
|
||||||
|
| buck-out
|
||||||
|
| build
|
||||||
|
| dist
|
||||||
|
|
||||||
|
# The following are specific to Black, you probably don't want those.
|
||||||
|
| blib2to3
|
||||||
|
| tests/data
|
||||||
|
| profiling
|
||||||
|
)/
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
39
requirements.txt
Normal file
39
requirements.txt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
alembic==1.7.5
|
||||||
|
Babel==2.9.1
|
||||||
|
bcrypt==3.2.0
|
||||||
|
black==21.11b1
|
||||||
|
blinker==1.4
|
||||||
|
cffi==1.15.0
|
||||||
|
click==8.0.3
|
||||||
|
-e git+https://git.vvvvvvaria.org/varia/distribusi.git@63becedaf51d069744b4088253d883d08e883aa1#egg=distribusi
|
||||||
|
Flask==2.0.2
|
||||||
|
Flask-BabelEx==0.9.4
|
||||||
|
Flask-Bcrypt==0.7.1
|
||||||
|
Flask-Login==0.5.0
|
||||||
|
Flask-Mail==0.9.1
|
||||||
|
Flask-Migrate==3.1.0
|
||||||
|
Flask-Principal==0.4.0
|
||||||
|
Flask-Security==3.0.0
|
||||||
|
Flask-SQLAlchemy==2.5.1
|
||||||
|
Flask-WTF==1.0.0
|
||||||
|
greenlet==1.1.2
|
||||||
|
itsdangerous==2.0.1
|
||||||
|
Jinja2==3.0.3
|
||||||
|
Mako==1.1.6
|
||||||
|
MarkupSafe==2.0.1
|
||||||
|
mypy-extensions==0.4.3
|
||||||
|
passlib==1.7.4
|
||||||
|
pathspec==0.9.0
|
||||||
|
Pillow==8.3.2
|
||||||
|
platformdirs==2.4.0
|
||||||
|
pycparser==2.21
|
||||||
|
python-magic==0.4.24
|
||||||
|
pytz==2021.3
|
||||||
|
regex==2021.11.10
|
||||||
|
six==1.16.0
|
||||||
|
speaklater==1.3
|
||||||
|
SQLAlchemy==1.4.27
|
||||||
|
tomli==1.2.2
|
||||||
|
typing-extensions==4.0.1
|
||||||
|
Werkzeug==2.0.2
|
||||||
|
WTForms==3.0.0
|
0
verse/__init.py__
Normal file
0
verse/__init.py__
Normal file
38
verse/app.py
Normal file
38
verse/app.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
|
from flask_login import (
|
||||||
|
LoginManager,
|
||||||
|
)
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
migrate = Migrate()
|
||||||
|
bcrypt = Bcrypt()
|
||||||
|
login_manager = LoginManager()
|
||||||
|
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
APP = Flask(__name__, static_folder="static")
|
||||||
|
|
||||||
|
APP.secret_key = 'secret-key'
|
||||||
|
APP.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///data/login.db"
|
||||||
|
APP.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
|
||||||
|
|
||||||
|
login_manager.session_protection = "strong"
|
||||||
|
login_manager.login_view = "login"
|
||||||
|
login_manager.login_message_category = "info"
|
||||||
|
|
||||||
|
csrf = CSRFProtect()
|
||||||
|
|
||||||
|
APP.config['SECRET_KEY'] = 'ty4425hk54a21eee5719b9s9df7sdfklx'
|
||||||
|
APP.config['UPLOAD_FOLDER'] = 'tmpupload'
|
||||||
|
|
||||||
|
csrf.init_app(APP)
|
||||||
|
login_manager.init_app(APP)
|
||||||
|
db.init_app(APP)
|
||||||
|
migrate.init_app(APP, db)
|
||||||
|
bcrypt.init_app(APP)
|
||||||
|
|
||||||
|
return APP
|
17
verse/deploydb.py
Normal file
17
verse/deploydb.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
def deploy():
|
||||||
|
"""Run deployment of database."""
|
||||||
|
from app import create_app, db
|
||||||
|
from flask_migrate import upgrade, migrate, init, stamp
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
app.app_context().push()
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# migrate database to latest revision
|
||||||
|
init()
|
||||||
|
stamp()
|
||||||
|
migrate()
|
||||||
|
upgrade()
|
||||||
|
|
||||||
|
|
||||||
|
deploy()
|
23
verse/loginform.py
Normal file
23
verse/loginform.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""Login form to validate user."""
|
||||||
|
from wtforms import (
|
||||||
|
StringField,
|
||||||
|
SubmitField,
|
||||||
|
PasswordField,
|
||||||
|
)
|
||||||
|
|
||||||
|
from wtforms import validators
|
||||||
|
from wtforms.validators import Length, Email
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(FlaskForm):
|
||||||
|
"""Login distribusiverse form class."""
|
||||||
|
|
||||||
|
email = StringField(
|
||||||
|
"Email address:",
|
||||||
|
validators=[validators.InputRequired(), Email(), Length(6, 64)],
|
||||||
|
)
|
||||||
|
password = PasswordField(
|
||||||
|
"Password:", validators=[validators.InputRequired(), Length(12, 72)]
|
||||||
|
)
|
||||||
|
submit = SubmitField("Sign In")
|
1
verse/migrations/README
Normal file
1
verse/migrations/README
Normal file
@ -0,0 +1 @@
|
|||||||
|
Single-database configuration for Flask.
|
50
verse/migrations/alembic.ini
Normal file
50
verse/migrations/alembic.ini
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic,flask_migrate
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[logger_flask_migrate]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = flask_migrate
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
91
verse/migrations/env.py
Normal file
91
verse/migrations/env.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
logger = logging.getLogger('alembic.env')
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
config.set_main_option(
|
||||||
|
'sqlalchemy.url',
|
||||||
|
str(current_app.extensions['migrate'].db.get_engine().url).replace(
|
||||||
|
'%', '%%'))
|
||||||
|
target_metadata = current_app.extensions['migrate'].db.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline():
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url, target_metadata=target_metadata, literal_binds=True
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online():
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# this callback is used to prevent an auto-migration from being generated
|
||||||
|
# when there are no changes to the schema
|
||||||
|
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||||
|
def process_revision_directives(context, revision, directives):
|
||||||
|
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||||
|
script = directives[0]
|
||||||
|
if script.upgrade_ops.is_empty():
|
||||||
|
directives[:] = []
|
||||||
|
logger.info('No changes in schema detected.')
|
||||||
|
|
||||||
|
connectable = current_app.extensions['migrate'].db.get_engine()
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
process_revision_directives=process_revision_directives,
|
||||||
|
**current_app.extensions['migrate'].configure_args
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
24
verse/migrations/script.py.mako
Normal file
24
verse/migrations/script.py.mako
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = ${repr(up_revision)}
|
||||||
|
down_revision = ${repr(down_revision)}
|
||||||
|
branch_labels = ${repr(branch_labels)}
|
||||||
|
depends_on = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
${downgrades if downgrades else "pass"}
|
32
verse/registerform.py
Normal file
32
verse/registerform.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""Register form to make a new user."""
|
||||||
|
from wtforms import (
|
||||||
|
StringField,
|
||||||
|
SubmitField,
|
||||||
|
PasswordField,
|
||||||
|
)
|
||||||
|
|
||||||
|
from wtforms import validators
|
||||||
|
from wtforms.validators import Length, Email, EqualTo
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterForm(FlaskForm):
|
||||||
|
"""Register for distribusi-verse form class"""
|
||||||
|
|
||||||
|
email = StringField(
|
||||||
|
"Email address:",
|
||||||
|
validators=[validators.InputRequired(), Email(), Length(6, 64)],
|
||||||
|
)
|
||||||
|
password = PasswordField(
|
||||||
|
"New password:",
|
||||||
|
validators=[validators.InputRequired(), Length(12, 72)],
|
||||||
|
)
|
||||||
|
confirmpassword = PasswordField(
|
||||||
|
"Confirm your password:",
|
||||||
|
validators=[
|
||||||
|
validators.InputRequired(),
|
||||||
|
Length(12, 72),
|
||||||
|
EqualTo("password", message="Passwords must match !"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
submit = SubmitField("Register to Distribusi-verse")
|
139
verse/start.py
Normal file
139
verse/start.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
"""This is the main flask distribusi page"""
|
||||||
|
from flask import (
|
||||||
|
render_template,
|
||||||
|
redirect,
|
||||||
|
request,
|
||||||
|
flash,
|
||||||
|
url_for,
|
||||||
|
session,
|
||||||
|
abort,
|
||||||
|
is_safe_url,
|
||||||
|
)
|
||||||
|
from sqlalchemy.exc import (
|
||||||
|
IntegrityError,
|
||||||
|
DataError,
|
||||||
|
DatabaseError,
|
||||||
|
InterfaceError,
|
||||||
|
InvalidRequestError,
|
||||||
|
)
|
||||||
|
from flask_login import (
|
||||||
|
login_user,
|
||||||
|
logout_user,
|
||||||
|
login_required,
|
||||||
|
)
|
||||||
|
|
||||||
|
from werkzeug.routing import BuildError
|
||||||
|
from flask_bcrypt import generate_password_hash, check_password_hash
|
||||||
|
from flask_wtf.csrf import CSRFError
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from app import create_app, db, login_manager
|
||||||
|
from usermodel import User
|
||||||
|
from loginform import LoginForm
|
||||||
|
from registerform import RegisterForm
|
||||||
|
|
||||||
|
APP = create_app()
|
||||||
|
|
||||||
|
|
||||||
|
@APP.before_request
|
||||||
|
def session_handler():
|
||||||
|
session.permanent = True
|
||||||
|
APP.permanent_session_lifetime = timedelta(minutes=1)
|
||||||
|
|
||||||
|
|
||||||
|
@APP.route("/")
|
||||||
|
def index():
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@APP.route("/login", methods=["GET", "POST"])
|
||||||
|
def login():
|
||||||
|
loginform = LoginForm()
|
||||||
|
if loginform.validate_on_submit():
|
||||||
|
try:
|
||||||
|
user = User.query.filter_by(email=loginform.email.data).first()
|
||||||
|
if check_password_hash(user.pwd, loginform.password.data):
|
||||||
|
login_user(user)
|
||||||
|
flash("Logged in successfully.", "success")
|
||||||
|
next = request.args.get("next")
|
||||||
|
if next is not None and not is_safe_url(next):
|
||||||
|
return abort(400)
|
||||||
|
return redirect(next or url_for("index"))
|
||||||
|
else:
|
||||||
|
flash("Invalid Username or password!", "danger")
|
||||||
|
except Exception as e:
|
||||||
|
flash(e, "danger")
|
||||||
|
return render_template("login.html", loginform=loginform)
|
||||||
|
|
||||||
|
|
||||||
|
@APP.route("/register", methods=["GET", "POST"])
|
||||||
|
def register():
|
||||||
|
registerform = RegisterForm()
|
||||||
|
if registerform.validate_on_submit():
|
||||||
|
try:
|
||||||
|
email = registerform.email.data
|
||||||
|
pwd = registerform.confirmpassword.data
|
||||||
|
|
||||||
|
newuser = User(
|
||||||
|
email=email,
|
||||||
|
pwd=generate_password_hash(pwd),
|
||||||
|
)
|
||||||
|
|
||||||
|
db.session.add(newuser)
|
||||||
|
db.session.commit()
|
||||||
|
flash("Account Succesfully created", "success")
|
||||||
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
|
except InvalidRequestError:
|
||||||
|
db.session.rollback()
|
||||||
|
flash("Something went wrong!", "danger")
|
||||||
|
except IntegrityError:
|
||||||
|
db.session.rollback()
|
||||||
|
flash("User already exists!.", "warning")
|
||||||
|
except DataError:
|
||||||
|
db.session.rollback()
|
||||||
|
flash("Invalid Entry", "warning")
|
||||||
|
except InterfaceError:
|
||||||
|
db.session.rollback()
|
||||||
|
flash("Error connecting to the database", "danger")
|
||||||
|
except DatabaseError:
|
||||||
|
db.session.rollback()
|
||||||
|
flash("Error connecting to the database", "danger")
|
||||||
|
except BuildError:
|
||||||
|
db.session.rollback()
|
||||||
|
flash("An error occured !", "danger")
|
||||||
|
return render_template("register.html", registerform=registerform)
|
||||||
|
|
||||||
|
|
||||||
|
@APP.route("/distribusi")
|
||||||
|
@login_required
|
||||||
|
def distribusi():
|
||||||
|
return "distribusi"
|
||||||
|
|
||||||
|
|
||||||
|
@APP.route("/admin")
|
||||||
|
@login_required
|
||||||
|
def admin():
|
||||||
|
return "admin"
|
||||||
|
|
||||||
|
|
||||||
|
@APP.route("/logout")
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
|
|
||||||
|
@APP.errorhandler(CSRFError)
|
||||||
|
def handle_csrf_error(e):
|
||||||
|
return render_template("csrf_error.html", reason=e.description), 400
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
return User.query.get(int(user_id))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
APP.debug = True
|
||||||
|
APP.run(port=5000)
|
54
verse/static/css/style.css
Normal file
54
verse/static/css/style.css
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
body
|
||||||
|
{
|
||||||
|
font-family: monospace;
|
||||||
|
background-color: black;
|
||||||
|
color:#E0B0FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#login{
|
||||||
|
width: 30%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
background-color:black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#login form {
|
||||||
|
width: 200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-left: 15%;
|
||||||
|
padding-right: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#login .required{
|
||||||
|
padding: 6px;
|
||||||
|
border: none;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#login input[type=text], input[type=password]{
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #E0B0FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
section#buttons{
|
||||||
|
position: fixed;
|
||||||
|
top: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
display:flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signin input {
|
||||||
|
border: none;
|
||||||
|
background: #E0B0FF;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signin input:hover {
|
||||||
|
background: #60337F;
|
||||||
|
}
|
6
verse/static/icons/about.txt
Normal file
6
verse/static/icons/about.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
This favicon was generated using the following font:
|
||||||
|
|
||||||
|
- Font Title: Klee One
|
||||||
|
- Font Author: Copyright 2020 The Klee Project Authors (https://github.com/fontworks-fonts/Klee)
|
||||||
|
- Font Source: http://fonts.gstatic.com/s/kleeone/v5/LDIxapCLNRc6A8oT4q4AOeekWPrP.ttf
|
||||||
|
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
BIN
verse/static/icons/android-chrome-192x192.png
Normal file
BIN
verse/static/icons/android-chrome-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
BIN
verse/static/icons/android-chrome-512x512.png
Normal file
BIN
verse/static/icons/android-chrome-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
verse/static/icons/apple-touch-icon.png
Normal file
BIN
verse/static/icons/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
verse/static/icons/favicon-16x16.png
Normal file
BIN
verse/static/icons/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 322 B |
BIN
verse/static/icons/favicon-32x32.png
Normal file
BIN
verse/static/icons/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 666 B |
BIN
verse/static/icons/favicon.ico
Normal file
BIN
verse/static/icons/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
1
verse/static/icons/site.webmanifest
Normal file
1
verse/static/icons/site.webmanifest
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
5
verse/static/js/script.js
Normal file
5
verse/static/js/script.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
console.log("everything is still smooth")
|
||||||
|
|
||||||
|
// function(e) {
|
||||||
|
// (e.keyCode === 13 || e.keyCode === 32) && $(this).trigger("click")
|
||||||
|
// }
|
24
verse/templates/base.html
Normal file
24
verse/templates/base.html
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Autonomous Practices X Distribusi-Verse</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css')}}">
|
||||||
|
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='icons/apple-touch-icon.png')}}">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='icons/favicon-32x32.png')}}">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='icons/favicon-16x16.png')}}">
|
||||||
|
<link rel="manifest" href="{{ url_for('static', filename='icons/site.webmanifest')}}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block main %}
|
||||||
|
{% endblock main %}
|
||||||
|
|
||||||
|
<section id="summary">
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
<script src="{{ url_for('static', filename='js/script.js')}}"></script>
|
||||||
|
</html>
|
32
verse/templates/index.html
Normal file
32
verse/templates/index.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<section id="buttons">
|
||||||
|
<div class="signin">
|
||||||
|
<a href="/login">
|
||||||
|
<input type="button" name="button" value="Sign in"></input>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="signin">
|
||||||
|
<a href="/register">
|
||||||
|
<input type="button" name="button" value="Register"></input>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<h2> Hi {{ current_user.email }}! </h2>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- a section with all the distribusis listed in the distribusiverse -->
|
||||||
|
<section id="distribusiverse">
|
||||||
|
<h2>List of distribusis</h2>
|
||||||
|
<ul>
|
||||||
|
<li>CCL</li>
|
||||||
|
<li>Crunk</li>
|
||||||
|
<li>CMOS4010</li>
|
||||||
|
<li>CMOS4046</li>
|
||||||
|
<li>Other Names</li>
|
||||||
|
<li>List of stuff</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
26
verse/templates/login.html
Normal file
26
verse/templates/login.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<section id="login">
|
||||||
|
<form class="form" action="{{ url_for('login') }}" method="post">
|
||||||
|
{{ loginform.csrf_token }}
|
||||||
|
<fieldset class="required">
|
||||||
|
{{ loginform.email.label }}
|
||||||
|
{{ loginform.email }}
|
||||||
|
{% for message in loginform.email.errors %}
|
||||||
|
<div class="error">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="required">
|
||||||
|
{{ loginform.password.label }}
|
||||||
|
{{ loginform.password }}
|
||||||
|
{% for message in loginform.password.errors %}
|
||||||
|
<div class="error">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="signin required">
|
||||||
|
{{ loginform.submit }}
|
||||||
|
<a href="/forgotpassword">Forgot Password?</a>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock main %}
|
32
verse/templates/register.html
Normal file
32
verse/templates/register.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block main %}
|
||||||
|
<section id="login">
|
||||||
|
<form class="form" action="{{ url_for('register') }}" method="post">
|
||||||
|
{{ registerform.csrf_token }}
|
||||||
|
<fieldset class="required">
|
||||||
|
{{ registerform.email.label }}
|
||||||
|
{{ registerform.email }}
|
||||||
|
{% for message in registerform.email.errors %}
|
||||||
|
<div class="error">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="required">
|
||||||
|
{{ registerform.password.label }}
|
||||||
|
{{ registerform.password }}
|
||||||
|
{% for message in registerform.password.errors %}
|
||||||
|
<div class="error">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="required">
|
||||||
|
{{ registerform.confirmpassword.label }}
|
||||||
|
{{ registerform.confirmpassword }}
|
||||||
|
{% for message in registerform.confirmpassword.errors %}
|
||||||
|
<div class="error">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="signin required">
|
||||||
|
{{ registerform.submit }}
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock main %}
|
16
verse/usermodel.py
Normal file
16
verse/usermodel.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from app import db
|
||||||
|
from flask_login import UserMixin
|
||||||
|
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
"""User model class for a user in distribusi-verse"""
|
||||||
|
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
distribusiname = db.Column(db.String(100), unique=True, nullable=True)
|
||||||
|
email = db.Column(db.String(150), unique=True, nullable=False)
|
||||||
|
pwd = db.Column(db.String(300), nullable=False, unique=False)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<User %r>" % self.username
|
Loading…
Reference in New Issue
Block a user