Merge pull request 'progress' (#11) from Toolsheds/distribusi-verse:main into main
Reviewed-on: crunk/distribusi-verse#11
@ -8,10 +8,15 @@ The contribution consisted of setting up distribusi. ruruhuis.nl (distribusi is
|
||||
|
||||
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.
|
||||
This project was made for Autonomous Practices at the WDKA in Rotterdam.
|
||||
|
||||
The second stage of this project might be the archiving part of the [Toolsheds fellowship.](https://nieuweinstituut.nl/en/articles/call-for-fellows-tool-sheds)
|
||||
|
||||
|
||||
## Work in progress
|
||||
|
||||
Amazingly helpful testers, currently I am writing some database upgrades and new functionalities, and they don't work, so have some patience with testing the in development material.
|
||||
Currently this repo is transforming the academic based search filters into adjustable
|
||||
small Autonomous space filters.
|
||||
|
||||
## Start your engines!
|
||||
|
||||
|
34
notes.md
@ -1,34 +0,0 @@
|
||||
# these are some notes
|
||||
|
||||
from distribusi.cli import build_argparser
|
||||
# from distribusi.distribusi import distribusify
|
||||
Works!
|
||||
|
||||
# Shit! We need entire CRUD functionality.
|
||||
Done!
|
||||
## Create:
|
||||
Done
|
||||
|
||||
### Uploading
|
||||
Done
|
||||
|
||||
### CSS editing.
|
||||
a user can edit CSS of a file in the folder called $distribusiname
|
||||
Todo: render the placeholder for html editor better.
|
||||
|
||||
### Theme selection
|
||||
a user can select a CSS file from a radio menu
|
||||
|
||||
### Distribusi
|
||||
A flag in de DB is set to true and distribusi is run on the folder of the users
|
||||
called $distribusiname
|
||||
|
||||
## Read:
|
||||
Based on flags set in the user DB the distribusi folders are set to visible.
|
||||
|
||||
|
||||
## Update:
|
||||
Done
|
||||
|
||||
## Delete:
|
||||
Done
|
@ -1,25 +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
|
||||
|
||||
[tool.black]
|
||||
line-length = 79
|
||||
target-version = ['py311']
|
||||
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
|
||||
| tests/data
|
||||
| profiling
|
||||
)/
|
||||
'''
|
||||
'''
|
||||
|
||||
|
||||
|
@ -1,48 +1,57 @@
|
||||
alembic==1.7.5
|
||||
APScheduler==3.10.4
|
||||
Babel==2.9.1
|
||||
bcrypt==3.2.0
|
||||
black==21.11b1
|
||||
black==24.4.0
|
||||
bleach==4.1.0
|
||||
bleach-allowlist==1.0.3
|
||||
blinker==1.4
|
||||
blinker==1.7.0
|
||||
cffi==1.15.0
|
||||
click==8.0.3
|
||||
click==8.1.7
|
||||
distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi@e291e7497e40211c2ebd54ca32a1f4bdaed71230
|
||||
dnspython==2.1.0
|
||||
email-validator==1.1.3
|
||||
Flask==2.0.2
|
||||
Flask==3.0.3
|
||||
Flask-APScheduler==1.13.1
|
||||
Flask-BabelEx==0.9.4
|
||||
Flask-Bcrypt==0.7.1
|
||||
Flask-Login==0.5.0
|
||||
Flask-Bcrypt==1.0.1
|
||||
Flask-Login==0.6.3
|
||||
Flask-Mail==0.9.1
|
||||
Flask-Migrate==3.1.0
|
||||
Flask-Principal==0.4.0
|
||||
Flask-Security==3.0.0
|
||||
Flask-Security-Too==4.1.3
|
||||
Flask-SQLAlchemy==2.5.1
|
||||
Flask-WTF==1.0.0
|
||||
greenlet==1.1.2
|
||||
Flask-SQLAlchemy==3.1.1
|
||||
Flask-WTF==1.2.1
|
||||
greenlet==3.0.3
|
||||
idna==3.3
|
||||
itsdangerous==2.0.1
|
||||
Jinja2==3.0.3
|
||||
isort==5.13.2
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.3
|
||||
Mako==1.1.6
|
||||
MarkupSafe==2.0.1
|
||||
MarkupSafe==2.1.5
|
||||
msgpack==1.0.8
|
||||
mypy-extensions==0.4.3
|
||||
packaging==21.3
|
||||
neovim==0.3.1
|
||||
packaging==24.0
|
||||
passlib==1.7.4
|
||||
pathspec==0.9.0
|
||||
Pillow==8.3.2
|
||||
platformdirs==2.4.0
|
||||
pycparser==2.21
|
||||
pynvim==0.5.0
|
||||
pyparsing==3.0.7
|
||||
python-dateutil==2.9.0.post0
|
||||
python-magic==0.4.24
|
||||
pytz==2021.3
|
||||
regex==2021.11.10
|
||||
six==1.16.0
|
||||
speaklater==1.3
|
||||
SQLAlchemy==1.4.27
|
||||
SQLAlchemy==2.0.29
|
||||
tomli==1.2.2
|
||||
typing_extensions==4.0.1
|
||||
typing_extensions==4.11.0
|
||||
tzlocal==5.2
|
||||
webencodings==0.5.1
|
||||
Werkzeug==2.0.2
|
||||
Werkzeug==3.0.2
|
||||
Whoosh==2.7.4
|
||||
WTForms==3.0.0
|
||||
distribusi @ git+https://git.vvvvvvaria.org/crunk/distribusi
|
||||
|
4
setup.py
@ -1,3 +1,3 @@
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
setup(name='library', version='1.0', packages=find_packages())
|
||||
setup(name="library", version="1.0", packages=find_packages())
|
||||
|
@ -1,21 +1,20 @@
|
||||
import os
|
||||
import shutil
|
||||
from flask import render_template
|
||||
|
||||
from flask import render_template
|
||||
from sqlalchemy.exc import (
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
|
||||
from app import db
|
||||
|
||||
from usermodel import User
|
||||
from distribusimodel import Distribusis
|
||||
from distribusisinfo import DistribusisInfo
|
||||
|
||||
from forms.adminuserform import AdminUserForm
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from forms.admindistribusiform import AdminDistribusiForm
|
||||
from forms.adminuserform import AdminUserForm
|
||||
from models.distribusi_model import Distribusis
|
||||
from models.user_model import User
|
||||
|
||||
|
||||
def AdminPage():
|
||||
@ -27,10 +26,6 @@ def AdminPage():
|
||||
if adminuserform.validate_on_submit():
|
||||
if adminuserform.delete.data:
|
||||
DeleteUsers(adminuserform)
|
||||
if adminuserform.tutors.data:
|
||||
ToggleUsersAsTutors(adminuserform, True)
|
||||
if adminuserform.nottutors.data:
|
||||
ToggleUsersAsTutors(adminuserform, False)
|
||||
|
||||
template = render_template(
|
||||
"admin.html",
|
||||
@ -51,23 +46,6 @@ def DeleteUsers(adminuserform):
|
||||
userform.errors.append(f"User {useremail} deleted!")
|
||||
|
||||
|
||||
def ToggleUsersAsTutors(adminuserform, is_tutor):
|
||||
for userform in adminuserform:
|
||||
if "user" in userform.id:
|
||||
if userform.data:
|
||||
useremail = userform.label.text
|
||||
user = User.query.filter_by(email=useremail).first()
|
||||
ToggleUserTutorinDb(user, is_tutor)
|
||||
userform.errors.append(f"Is User {useremail} tutor {is_tutor}")
|
||||
|
||||
|
||||
def ToggleUserTutorinDb(user, is_tutor):
|
||||
try:
|
||||
user.tutor = is_tutor
|
||||
db.session.commit()
|
||||
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
|
||||
db.session.rollback()
|
||||
|
||||
|
||||
def DeleteUserFromDb(user):
|
||||
try:
|
||||
|
@ -1,13 +1,15 @@
|
||||
import sys
|
||||
from app import create_app, db
|
||||
|
||||
from sqlalchemy.exc import (
|
||||
InvalidRequestError,
|
||||
InterfaceError,
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
from usermodel import User # noqa: F401
|
||||
from distribusimodel import Distribusis # noqa: F401
|
||||
|
||||
from app import create_app, db
|
||||
from models.distribusi_model import Distribusis # noqa: F401
|
||||
from models.user_model import User # noqa: F401
|
||||
|
||||
|
||||
def admintool():
|
||||
|
53
verse/app.py
@ -1,13 +1,14 @@
|
||||
import os
|
||||
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,
|
||||
)
|
||||
import tomllib
|
||||
|
||||
from flask import Flask
|
||||
from flask_bcrypt import Bcrypt
|
||||
from flask_login import LoginManager
|
||||
from flask_migrate import Migrate
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
|
||||
APP = Flask(__name__, static_folder="static")
|
||||
db = SQLAlchemy()
|
||||
migrate = Migrate()
|
||||
bcrypt = Bcrypt()
|
||||
@ -15,18 +16,16 @@ login_manager = LoginManager()
|
||||
|
||||
|
||||
def create_app():
|
||||
APP = Flask(__name__, static_folder="static")
|
||||
|
||||
APP.secret_key = "secret-key"
|
||||
APP.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///data/distribusiverse.db"
|
||||
APP.secret_key = os.urandom(24)
|
||||
APP.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///distribusiverse.db"
|
||||
APP.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
|
||||
APP.config["MAX_CONTENT_LENGTH"] = 150 * 1024 * 1024
|
||||
APP.config["MAX_CONTENT_LENGTH"] = 1024 * 1024 * 1024
|
||||
|
||||
APP.config["MAIL_SERVER"] = "mail.autonomic.zone"
|
||||
APP.config["MAIL_PORT"] = 587
|
||||
APP.config["MAIL_USE_SSL"] = False
|
||||
APP.config["MAIL_USE_TLS"] = True
|
||||
APP.config['MAIL_USERNAME'] = "noreply@vvvvvvaria.org"
|
||||
APP.config["MAIL_USERNAME"] = "noreply@vvvvvvaria.org"
|
||||
|
||||
login_manager.session_protection = "strong"
|
||||
login_manager.login_view = "index"
|
||||
@ -38,10 +37,36 @@ def create_app():
|
||||
APP.config["UPLOAD_FOLDER"] = "tmpupload"
|
||||
APP.config["PUBLIC_THEMES"] = "themes/publicthemes"
|
||||
|
||||
# user settings_file
|
||||
settings()
|
||||
|
||||
csrf.init_app(APP)
|
||||
login_manager.init_app(APP)
|
||||
db.init_app(APP)
|
||||
migrate.init_app(APP, db, render_as_batch=True)
|
||||
bcrypt.init_app(APP)
|
||||
|
||||
@APP.context_processor
|
||||
def inject_title():
|
||||
return dict(title=APP.config["title"])
|
||||
|
||||
return APP
|
||||
|
||||
|
||||
def settings():
|
||||
settings = settings_from_file()
|
||||
APP.config.update(settings)
|
||||
return APP
|
||||
|
||||
|
||||
def get_app():
|
||||
return APP
|
||||
|
||||
|
||||
def settings_from_file():
|
||||
settings = {}
|
||||
if os.path.isfile("settings_development.toml"):
|
||||
with open("settings_development.toml", "rb") as settings_file:
|
||||
return tomllib.load(settings_file)
|
||||
with open("settings.toml", "rb") as settings_file:
|
||||
return tomllib.load(settings_file)
|
||||
|
@ -1,11 +1,12 @@
|
||||
def deploy():
|
||||
"""Run deployment of database."""
|
||||
from flask_migrate import init, migrate, stamp, upgrade
|
||||
|
||||
from app import create_app, db
|
||||
from flask_migrate import upgrade, migrate, init, stamp
|
||||
from models.distribusi_model import Distribusis # noqa: F401
|
||||
|
||||
# This model is required for flask_migrate to make the table
|
||||
from usermodel import User # noqa: F401
|
||||
from distribusimodel import Distribusis # noqa: F401
|
||||
from models.user_model import User # noqa: F401
|
||||
|
||||
app = create_app()
|
||||
app.app_context().push()
|
||||
|
48
verse/distribusikan/distribusikan.py
Normal file
@ -0,0 +1,48 @@
|
||||
from flask import Blueprint
|
||||
from flask_login import login_required
|
||||
|
||||
from distribusikan.distribusiselector import DistribusiSelector
|
||||
|
||||
# Distribusi Information
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from distribusikan.distribusiworkflow import DistribusiWorkflow
|
||||
from distribusikan.editor import Editor
|
||||
from distribusikan.themeselector import ThemeSelector
|
||||
from distribusikan.uploadpage import UploadPage
|
||||
|
||||
distribusikan = Blueprint(
|
||||
"distribusikan",
|
||||
__name__,
|
||||
template_folder="templates/distribusikan",
|
||||
static_folder="static",
|
||||
)
|
||||
|
||||
|
||||
@distribusikan.route("/distribusi", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def distribusi():
|
||||
return DistribusiWorkflow()
|
||||
|
||||
|
||||
@distribusikan.route("/upload", methods=["POST"])
|
||||
@login_required
|
||||
def upload():
|
||||
return UploadPage()
|
||||
|
||||
|
||||
@distribusikan.route("/theme", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def theme():
|
||||
return ThemeSelector()
|
||||
|
||||
|
||||
@distribusikan.route("/editor", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def editor():
|
||||
return Editor()
|
||||
|
||||
|
||||
@distribusikan.route("/selector", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def selector():
|
||||
return DistribusiSelector()
|
@ -1,29 +1,28 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from flask import flash, render_template
|
||||
from flask_login import current_user
|
||||
from sqlalchemy.exc import (
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
|
||||
from usermodel import User
|
||||
from distribusimodel import Distribusis
|
||||
from distribusisinfo import DistribusisInfo
|
||||
|
||||
from forms.selectorform import SelectorForm
|
||||
from forms.uploadform import UploadForm
|
||||
from app import db
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from forms.distribusiform import DistribusiForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.publicthemeform import PublicThemeForm
|
||||
from forms.selectorform import SelectorForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.uploadform import UploadForm
|
||||
from models.distribusi_model import Distribusis
|
||||
from models.user_model import User
|
||||
|
||||
# UserPengguna
|
||||
from statuspengguna.helper import UserHelper
|
||||
|
||||
from app import db
|
||||
|
||||
|
||||
def DistribusiSelector():
|
||||
uploadform = UploadForm()
|
||||
@ -54,9 +53,8 @@ def AutoFillInUploadForm(uploadform, current_distribusi):
|
||||
).first()
|
||||
uploadform.sitename.data = distribusi.distribusiname
|
||||
uploadform.sitename.render_kw = {"readonly": True}
|
||||
uploadform.term.data = distribusi.term
|
||||
uploadform.course.data = distribusi.course
|
||||
uploadform.academicyear.data = distribusi.year
|
||||
uploadform.category.data = distribusi.category
|
||||
uploadform.year.data = distribusi.year
|
||||
uploadform.tags.data = distribusi.tags
|
||||
return uploadform
|
||||
|
@ -1,7 +1,7 @@
|
||||
from flask_login import current_user
|
||||
|
||||
from usermodel import User
|
||||
from distribusimodel import Distribusis
|
||||
from models.distribusi_model import Distribusis
|
||||
from models.user_model import User
|
||||
|
||||
|
||||
class DistribusisInfo:
|
@ -1,40 +1,34 @@
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
from flask_login import current_user
|
||||
from flask import (
|
||||
render_template,
|
||||
redirect,
|
||||
url_for,
|
||||
flash,
|
||||
)
|
||||
from sqlalchemy.exc import (
|
||||
InvalidRequestError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
DatabaseError,
|
||||
)
|
||||
from app import db
|
||||
|
||||
from usermodel import User
|
||||
from distribusimodel import Distribusis
|
||||
|
||||
# UserPengguna
|
||||
from statuspengguna.helper import UserHelper
|
||||
from distribusiselector import SelectorVisible
|
||||
|
||||
# Forms!
|
||||
from forms.uploadform import UploadForm
|
||||
from forms.distribusiform import DistribusiForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.publicthemeform import PublicThemeForm
|
||||
from forms.selectorform import SelectorForm
|
||||
|
||||
from distribusisinfo import DistribusisInfo
|
||||
|
||||
# Tada!
|
||||
from distribusi.cli import build_argparser
|
||||
from distribusi.distribusi import distribusify
|
||||
from flask import flash, redirect, render_template, url_for
|
||||
from flask_login import current_user
|
||||
from sqlalchemy.exc import (
|
||||
DatabaseError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
|
||||
from app import db
|
||||
from distribusikan.distribusiselector import SelectorVisible
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from forms.distribusiform import DistribusiForm
|
||||
from forms.publicthemeform import PublicThemeForm
|
||||
from forms.selectorform import SelectorForm
|
||||
from forms.themeform import ThemeForm
|
||||
|
||||
# Forms!
|
||||
from forms.uploadform import UploadForm
|
||||
from models.distribusi_model import Distribusis
|
||||
from models.user_model import User
|
||||
|
||||
# UserPengguna
|
||||
from statuspengguna.helper import UserHelper
|
||||
|
||||
|
||||
def DistribusiWorkflow():
|
||||
@ -78,7 +72,7 @@ def CleanUpDistribusiFiles(userfolder):
|
||||
def RemoveMacFolders(path):
|
||||
for filename in os.listdir(path):
|
||||
fullpath = os.path.join(path, filename)
|
||||
if filename.startswith('.'):
|
||||
if filename.startswith("."):
|
||||
if os.path.isdir(fullpath):
|
||||
shutil.rmtree(fullpath)
|
||||
else:
|
||||
@ -101,7 +95,7 @@ def GetCssFile(distribusi):
|
||||
|
||||
def RunDistribusi(userfolder, cssfile):
|
||||
parser = build_argparser()
|
||||
args = parser.parse_args(["--menu-with-index", "-s", cssfile])
|
||||
args = parser.parse_args(["-t", "--menu-with-index", "-s", cssfile])
|
||||
distribusify(args, userfolder)
|
||||
|
||||
|
@ -1,28 +1,27 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import bleach
|
||||
from bleach_allowlist import all_styles
|
||||
import shutil
|
||||
from flask import render_template
|
||||
|
||||
from werkzeug.utils import secure_filename
|
||||
from sqlalchemy.exc import (
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from app import db
|
||||
from distribusimodel import Distribusis
|
||||
|
||||
from statuspengguna.helper import UserHelper
|
||||
from distribusisinfo import DistribusisInfo
|
||||
|
||||
from forms.uploadform import UploadForm
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from forms.distribusiform import DistribusiForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.publicthemeform import PublicThemeForm
|
||||
from forms.editorform import EditorForm
|
||||
from forms.publicthemeform import PublicThemeForm
|
||||
from forms.selectorform import SelectorForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.uploadform import UploadForm
|
||||
from models.distribusi_model import Distribusis
|
||||
from statuspengguna.helper import UserHelper
|
||||
|
||||
|
||||
def Editor():
|
||||
@ -68,7 +67,7 @@ def SaveUploadCssFile(editorform, newcssfolder):
|
||||
cssfile.save(os.path.join(newcssfolder, cssfilename))
|
||||
openfile = open(os.path.join(newcssfolder, cssfilename), "r")
|
||||
cleancss = bleach.clean(openfile.read(), all_styles)
|
||||
cleancss = cleancss.replace('>', '>')
|
||||
cleancss = cleancss.replace(">", ">")
|
||||
openfile.close()
|
||||
cleanfile = open(os.path.join(newcssfolder, cssfilename), "w")
|
||||
cleanfile.write(cleancss)
|
||||
@ -81,7 +80,7 @@ def WriteCssToFile(editorform, newcssfolder):
|
||||
|
||||
cssfilename = f"{secure_filename(editorform.cssname.data)}.css"
|
||||
cleancss = bleach.clean(editorform.css.data, all_styles)
|
||||
cleancss = cleancss.replace('>', '>')
|
||||
cleancss = cleancss.replace(">", ">")
|
||||
with open(os.path.join(newcssfolder, cssfilename), "w") as cssfile:
|
||||
cssfile.write(cleancss)
|
||||
cssfile.close
|
||||
@ -91,8 +90,7 @@ def CopyPublicToUserFolder(editorform, publicfolder, newcssfolder):
|
||||
if not os.path.exists(newcssfolder):
|
||||
os.mkdir(newcssfolder)
|
||||
copycssfile = os.path.join(
|
||||
publicfolder,
|
||||
f"{secure_filename(editorform.cssname.data)}.css"
|
||||
publicfolder, f"{secure_filename(editorform.cssname.data)}.css"
|
||||
)
|
||||
print(f"copying file: {copycssfile}")
|
||||
print(f"to folder: {newcssfolder}")
|
@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="buttons">
|
||||
<div class="overview">
|
@ -4,7 +4,7 @@
|
||||
<p><a href="/editor">Go to CSS editor</a></p>
|
||||
{% else %}
|
||||
<p>
|
||||
You need to upload your files first before you can a css theme
|
||||
You need to upload your files first before you can select a css theme
|
||||
for your files.
|
||||
</p>
|
||||
<p><a href="#upload">Go to Step 1</a></p>
|
@ -3,7 +3,7 @@
|
||||
<p class="tooltip">Run distribusi on your files. This will generate your website and make
|
||||
your content public. <span class="tooltiptext">Distribusi will unpack your zip file and turn it into a website!
|
||||
</span></p>
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusi') }}">
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.distribusi') }}">
|
||||
{{ distribusiform.csrf_token }}
|
||||
{% if files_uploaded or distribusi_live %}
|
||||
<fieldset class="button required">
|
@ -1,7 +1,7 @@
|
||||
<div id="distribusi" class="workflow">
|
||||
<h2>Welcome back to your Distribusi</h2>
|
||||
<p>You have already uploaded a distribusi website:</p>
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('selector') }}">
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.selector') }}">
|
||||
{{ selectorform.csrf_token }}
|
||||
<fieldset class="required">
|
||||
{{ selectorform.distribusis.label }}
|
@ -4,7 +4,7 @@
|
||||
step 3.</p>
|
||||
<p>Don't forget to press Save</p>
|
||||
<hr>
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('theme') }}">
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.theme') }}">
|
||||
{{ themeform.csrf_token }}
|
||||
<fieldset class="required">
|
||||
{{ themeform.theme.label }}
|
||||
@ -16,14 +16,14 @@
|
||||
</fieldset>
|
||||
{% else %}
|
||||
<p>
|
||||
You need to upload your files first before you can a css theme
|
||||
You need to upload your files first before you can select a css theme
|
||||
for your files.
|
||||
</p>
|
||||
<a href="#upload">Go to Step 1</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
<hr>
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('theme') }}">
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.theme') }}">
|
||||
{{ publicthemeform.csrf_token }}
|
||||
<fieldset id="publicthemes" class="required">
|
||||
{{ publicthemeform.publicthemes.label }}
|
||||
@ -35,7 +35,7 @@
|
||||
</fieldset>
|
||||
{% else %}
|
||||
<p>
|
||||
You need to upload your files first before you can a css theme
|
||||
You need to upload your files first before you can select a css theme
|
||||
for your files.
|
||||
</p>
|
||||
<a href="#upload">Go to Step 1</a>
|
@ -1,7 +1,7 @@
|
||||
<div id="upload" class="workflow">
|
||||
<h2>Step 1: Upload</h2>
|
||||
<p>Upload your files here:</p>
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('upload') }}">
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.upload') }}">
|
||||
{{ uploadform.csrf_token }}
|
||||
<fieldset class="required">
|
||||
{{ uploadform.sitename.label }}
|
||||
@ -11,28 +11,19 @@
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<fieldset class="required">
|
||||
{{ uploadform.academicyear.label }}
|
||||
{{ uploadform.year.label }}
|
||||
<div class="selector-style">
|
||||
{{ uploadform.academicyear }}
|
||||
{% for message in uploadform.academicyear.errors %}
|
||||
{{ uploadform.year }}
|
||||
{% for message in uploadform.year.errors %}
|
||||
<div class="error">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="required">
|
||||
{{ uploadform.term.label }}
|
||||
{{ uploadform.category.label }}
|
||||
<div class="selector-style">
|
||||
{{ uploadform.term }}
|
||||
{% for message in uploadform.term.errors %}
|
||||
<div class="error">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="required">
|
||||
{{ uploadform.course.label }}
|
||||
<div class="selector-style">
|
||||
{{ uploadform.course }}
|
||||
{% for message in uploadform.course.errors %}
|
||||
{{ uploadform.category }}
|
||||
{% for message in uploadform.category.errors %}
|
||||
<div class="error">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('editor') }}" class="editform">
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('distribusikan.editor') }}" class="editform">
|
||||
<div class="editareas">
|
||||
<div class="editarea editor">
|
||||
<fieldset class="required">
|
@ -1,15 +1,15 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from flask import render_template
|
||||
|
||||
from statuspengguna.helper import UserHelper
|
||||
from distribusisinfo import DistribusisInfo
|
||||
|
||||
from forms.uploadform import UploadForm
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from forms.distribusiform import DistribusiForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.publicthemeform import PublicThemeForm
|
||||
from forms.selectorform import SelectorForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.uploadform import UploadForm
|
||||
from statuspengguna.helper import UserHelper
|
||||
|
||||
|
||||
def ThemeSelector():
|
||||
@ -30,9 +30,7 @@ def ThemeSelector():
|
||||
)
|
||||
MoveCssToUserFolder(current_distribusi, copycssfile)
|
||||
return RenderDistribusiTemplate(
|
||||
themeform,
|
||||
publicthemeform,
|
||||
current_distribusi
|
||||
themeform, publicthemeform, current_distribusi
|
||||
)
|
||||
|
||||
|
@ -1,22 +1,22 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from flask import flash
|
||||
from flask_login import current_user
|
||||
from sqlalchemy.exc import (
|
||||
IntegrityError,
|
||||
InvalidRequestError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
IntegrityError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
|
||||
from app import db
|
||||
|
||||
from usermodel import User
|
||||
from distribusimodel import Distribusis
|
||||
|
||||
from statuspengguna.helper import UserHelper
|
||||
from distribusiselector import SelectCurrentDistribusi
|
||||
from distribusikan.distribusiselector import SelectCurrentDistribusi
|
||||
from forms.uploadform import UploadForm
|
||||
from models.distribusi_model import Distribusis
|
||||
from models.user_model import User
|
||||
from statuspengguna.helper import UserHelper
|
||||
|
||||
|
||||
def UploadNewDistribusi(uploadfolder):
|
||||
@ -27,9 +27,8 @@ def UploadNewDistribusi(uploadfolder):
|
||||
newdistribusi = Distribusis(
|
||||
distribusiname=uploadform.sitename.data,
|
||||
userid=user.id,
|
||||
term=uploadform.term.data,
|
||||
course=uploadform.course.data,
|
||||
year=uploadform.academicyear.data,
|
||||
category=uploadform.category.data,
|
||||
year=uploadform.year.data,
|
||||
tags=uploadform.tags.data,
|
||||
)
|
||||
user.currentdistribusi = uploadform.sitename.data
|
||||
@ -71,9 +70,8 @@ def UploadUpdatedFiles(uploadfolder):
|
||||
distribusi = Distribusis.query.filter_by(
|
||||
distribusiname=current_distribusi
|
||||
).first()
|
||||
distribusi.term = uploadform.term.data
|
||||
distribusi.course = uploadform.course.data
|
||||
distribusi.year = uploadform.academicyear.data
|
||||
distribusi.category = uploadform.category.data
|
||||
distribusi.year = uploadform.year.data
|
||||
distribusi.tags = uploadform.tags.data
|
||||
distribusi.visible = False
|
||||
db.session.commit()
|
@ -1,20 +1,21 @@
|
||||
from flask import render_template
|
||||
|
||||
from app import APP
|
||||
from distribusikan.distribusiselector import SelectorVisible
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from distribusikan.upload import UploadNewDistribusi, UploadUpdatedFiles
|
||||
from forms.distribusiform import DistribusiForm
|
||||
from forms.themeform import ThemeForm
|
||||
from forms.publicthemeform import PublicThemeForm
|
||||
from forms.selectorform import SelectorForm
|
||||
from forms.themeform import ThemeForm
|
||||
|
||||
# UserPengguna
|
||||
from statuspengguna.helper import UserHelper
|
||||
|
||||
from upload import UploadNewDistribusi, UploadUpdatedFiles
|
||||
from distribusisinfo import DistribusisInfo
|
||||
from distribusiselector import SelectorVisible
|
||||
|
||||
|
||||
def UploadPage(uploadfolder):
|
||||
def UploadPage():
|
||||
"render upload page section of distribusi workflow"
|
||||
uploadfolder = APP.config["UPLOAD_FOLDER"]
|
||||
distribusiform = DistribusiForm()
|
||||
themeform = ThemeForm()
|
||||
publicthemeform = PublicThemeForm()
|
@ -1,9 +1,7 @@
|
||||
"""Form object declaration."""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (
|
||||
BooleanField,
|
||||
SubmitField,
|
||||
)
|
||||
from wtforms import BooleanField, SubmitField
|
||||
|
||||
|
||||
class AdminDistribusiForm(FlaskForm):
|
||||
@ -15,7 +13,7 @@ class AdminDistribusiForm(FlaskForm):
|
||||
class DistribusiListForm(AdminDistribusiForm):
|
||||
pass
|
||||
|
||||
for (i, distribusi) in enumerate(distribusis):
|
||||
for i, distribusi in enumerate(distribusis):
|
||||
setattr(
|
||||
DistribusiListForm,
|
||||
f"distribusi_{i}",
|
||||
|
@ -1,9 +1,7 @@
|
||||
"""Form object declaration."""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (
|
||||
SubmitField,
|
||||
BooleanField,
|
||||
)
|
||||
from wtforms import BooleanField, SubmitField
|
||||
|
||||
|
||||
class AdminUserForm(FlaskForm):
|
||||
@ -13,7 +11,7 @@ class AdminUserForm(FlaskForm):
|
||||
class UserListForm(AdminUserForm):
|
||||
pass
|
||||
|
||||
for (i, user) in enumerate(users):
|
||||
for i, user in enumerate(users):
|
||||
setattr(
|
||||
UserListForm,
|
||||
f"user_{i}",
|
||||
@ -22,8 +20,4 @@ class AdminUserForm(FlaskForm):
|
||||
|
||||
return UserListForm()
|
||||
|
||||
tutors = SubmitField("Are tutors")
|
||||
|
||||
nottutors = SubmitField("Are not tutors")
|
||||
|
||||
delete = SubmitField("Delete")
|
||||
|
@ -1,15 +1,15 @@
|
||||
"""Form to save your CSS editor work."""
|
||||
from wtforms import (
|
||||
StringField,
|
||||
TextAreaField,
|
||||
BooleanField,
|
||||
SubmitField,
|
||||
)
|
||||
|
||||
from wtforms import validators
|
||||
from wtforms.validators import Length
|
||||
from flask_wtf.file import FileField, FileAllowed, FileSize
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileAllowed, FileField, FileSize
|
||||
from wtforms import (
|
||||
BooleanField,
|
||||
StringField,
|
||||
SubmitField,
|
||||
TextAreaField,
|
||||
validators,
|
||||
)
|
||||
from wtforms.validators import Length
|
||||
|
||||
|
||||
class EditorForm(FlaskForm):
|
||||
|
@ -1,12 +1,8 @@
|
||||
"""Forgotten password form to help user."""
|
||||
from wtforms import (
|
||||
StringField,
|
||||
SubmitField,
|
||||
)
|
||||
|
||||
from wtforms import validators
|
||||
from wtforms.validators import Length, Email
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, validators
|
||||
from wtforms.validators import Email, Length
|
||||
|
||||
|
||||
class ForgotPasswordForm(FlaskForm):
|
||||
|
@ -1,13 +1,8 @@
|
||||
"""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
|
||||
from wtforms import PasswordField, StringField, SubmitField, validators
|
||||
from wtforms.validators import Email, Length
|
||||
|
||||
|
||||
class LoginForm(FlaskForm):
|
||||
|
@ -1,9 +1,7 @@
|
||||
"""Form object declaration."""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (
|
||||
RadioField,
|
||||
SubmitField,
|
||||
)
|
||||
from wtforms import RadioField, SubmitField
|
||||
|
||||
|
||||
class PublicThemeForm(FlaskForm):
|
||||
|
@ -1,22 +1,13 @@
|
||||
"""Register form to make a new user."""
|
||||
from wtforms import (
|
||||
StringField,
|
||||
SubmitField,
|
||||
PasswordField,
|
||||
)
|
||||
|
||||
from wtforms import validators
|
||||
from wtforms.validators import Length, Email, EqualTo, ValidationError
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import PasswordField, StringField, SubmitField, validators
|
||||
from wtforms.validators import Email, EqualTo, Length, ValidationError
|
||||
|
||||
|
||||
class RegisterForm(FlaskForm):
|
||||
"""Register for distribusi-verse form class"""
|
||||
|
||||
def hremail(form, field):
|
||||
if not field.data.endswith("@hr.nl"):
|
||||
raise ValidationError("Only HRO accounts are allowed.")
|
||||
|
||||
username = StringField(
|
||||
"Username:",
|
||||
validators=[validators.InputRequired(), Length(3, 150)],
|
||||
@ -27,9 +18,8 @@ class RegisterForm(FlaskForm):
|
||||
validators=[
|
||||
validators.InputRequired(),
|
||||
Email(),
|
||||
Length(6, 64),
|
||||
hremail,
|
||||
]
|
||||
Length(6, 128),
|
||||
],
|
||||
)
|
||||
|
||||
password = PasswordField(
|
||||
|
@ -1,12 +1,8 @@
|
||||
"""Reset Password Form form to reset a users PasswordField."""
|
||||
from wtforms import (
|
||||
SubmitField,
|
||||
PasswordField,
|
||||
)
|
||||
|
||||
from wtforms import validators
|
||||
from wtforms.validators import Length, EqualTo
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import PasswordField, SubmitField, validators
|
||||
from wtforms.validators import EqualTo, Length
|
||||
|
||||
|
||||
class ResetPasswordForm(FlaskForm):
|
||||
|
@ -1,5 +1,5 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import SubmitField, SelectField
|
||||
from wtforms import SelectField, SubmitField
|
||||
|
||||
|
||||
class SelectorForm(FlaskForm):
|
||||
|
@ -1,9 +1,7 @@
|
||||
"""Form object declaration."""
|
||||
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import (
|
||||
RadioField,
|
||||
SubmitField,
|
||||
)
|
||||
from wtforms import RadioField, SubmitField
|
||||
|
||||
|
||||
class ThemeForm(FlaskForm):
|
||||
|
@ -1,18 +1,20 @@
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileAllowed, FileRequired, FileSize
|
||||
from wtforms import validators
|
||||
from wtforms.validators import (
|
||||
Length,
|
||||
NumberRange,
|
||||
DataRequired,
|
||||
ValidationError,
|
||||
)
|
||||
from flask_wtf.file import FileAllowed, FileField, FileRequired, FileSize
|
||||
from wtforms import (
|
||||
SubmitField,
|
||||
StringField,
|
||||
IntegerField,
|
||||
SelectField,
|
||||
StringField,
|
||||
SubmitField,
|
||||
validators,
|
||||
)
|
||||
from wtforms.validators import (
|
||||
DataRequired,
|
||||
Length,
|
||||
NumberRange,
|
||||
ValidationError,
|
||||
)
|
||||
|
||||
from app import settings
|
||||
|
||||
|
||||
class UploadForm(FlaskForm):
|
||||
@ -22,64 +24,61 @@ class UploadForm(FlaskForm):
|
||||
if field.data.lower() == "new":
|
||||
raise ValidationError("Name has to be unique and not just new.")
|
||||
|
||||
def category_choices():
|
||||
APP = settings()
|
||||
config_categories = APP.config["categories"]
|
||||
categories = []
|
||||
for config_category in config_categories:
|
||||
categories.append((config_category, config_category))
|
||||
return categories
|
||||
|
||||
def year_choices():
|
||||
APP = settings()
|
||||
start_time = APP.config["start_time"]
|
||||
end_time = APP.config["end_time"]
|
||||
year_range = range(start_time.year, end_time.year, 1)
|
||||
year_choices = []
|
||||
for year in year_range:
|
||||
year_choices.append((str(year), str(year)))
|
||||
return year_choices
|
||||
|
||||
sitename = StringField(
|
||||
"Name of your website:",
|
||||
validators=[validators.InputRequired(), Length(2, 100), distribusiname],
|
||||
"Name of your archive section:",
|
||||
validators=[
|
||||
validators.InputRequired(),
|
||||
Length(2, 100),
|
||||
distribusiname,
|
||||
],
|
||||
)
|
||||
academicyear = SelectField(
|
||||
"Academic year:",
|
||||
year = SelectField(
|
||||
"Year:",
|
||||
validate_choice=True,
|
||||
coerce=str,
|
||||
choices=[
|
||||
(u'2021-2022', u'2021-2022'),
|
||||
(u'2022-2023', u'2022-2023'),
|
||||
(u'2023-2024', u'2023-2024'),
|
||||
(u'2024-2025', u'2024-2025'),
|
||||
(u'2020-2021', u'past: 2020-2021'),
|
||||
],
|
||||
choices=year_choices,
|
||||
option_widget=None,
|
||||
validators=[DataRequired()]
|
||||
validators=[DataRequired()],
|
||||
)
|
||||
term = SelectField(
|
||||
"Term:",
|
||||
category = SelectField(
|
||||
"Category:",
|
||||
validate_choice=True,
|
||||
coerce=str,
|
||||
choices=[
|
||||
(u'1.2', u'1.2'),
|
||||
(u'2.3', u'2.3'),
|
||||
(u'3.1', u'3.1'),
|
||||
(u'4.1', u'4.1'),
|
||||
(u'4.2', u'4.2'),
|
||||
],
|
||||
choices=category_choices,
|
||||
option_widget=None,
|
||||
validators=[DataRequired()]
|
||||
validators=[DataRequired()],
|
||||
)
|
||||
|
||||
tags = StringField(
|
||||
"Add tags, seperated by commas. No need for the '#' sign:",
|
||||
validators=[validators.InputRequired(), Length(2, 500)],
|
||||
)
|
||||
|
||||
course = SelectField(
|
||||
u'Course:',
|
||||
validate_choice=True,
|
||||
coerce=str,
|
||||
choices=[
|
||||
('hacking', u'Autonomous - Hacking'),
|
||||
('digitalcraft', u'Autonomous - Digital Craft'),
|
||||
('criticalstudies', u'Autonomous - Critical Studies'),
|
||||
('publicprivate', u'Autonomous - Public&Private'),
|
||||
],
|
||||
option_widget=None,
|
||||
validators=[DataRequired()]
|
||||
)
|
||||
|
||||
zipfile = FileField(
|
||||
"Upload your zip file with content here:",
|
||||
validators=[
|
||||
FileAllowed(["zip"], "Zip archives only!"),
|
||||
FileRequired(),
|
||||
FileSize(
|
||||
max_size=104857600,
|
||||
max_size=1073741824,
|
||||
message="Zipfile size must be smaller than 100MB",
|
||||
),
|
||||
],
|
||||
|
@ -3,9 +3,8 @@ from __future__ import with_statement
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from alembic import context
|
||||
from flask import current_app
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
@ -14,17 +13,19 @@ 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')
|
||||
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
|
||||
"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:
|
||||
@ -65,20 +66,20 @@ def run_migrations_online():
|
||||
# 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):
|
||||
if getattr(config.cmd_opts, "autogenerate", False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
logger.info("No changes in schema detected.")
|
||||
|
||||
connectable = current_app.extensions['migrate'].db.get_engine()
|
||||
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
|
||||
**current_app.extensions["migrate"].configure_args,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
|
17
verse/models/distribusi_file_model.py
Normal file
@ -0,0 +1,17 @@
|
||||
from app import db
|
||||
|
||||
|
||||
class DistribusiFiles(db.Model):
|
||||
"""Distribusi file model class for a single file in a distribusi"""
|
||||
|
||||
__tablename__ = "distribusi_files"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
type = db.Column(db.String(300), nullable=False, unique=True)
|
||||
distribusi = db.Column(db.Integer, db.ForeignKey("distribusis.id"))
|
||||
path = db.Column(db.String(4096), nullable=True, unique=False)
|
||||
description = db.Column(db.String(9), nullable=True, unique=False)
|
||||
tags = db.Column(db.String(500), nullable=True, unique=False)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Distribusi_File %r>" % self.distribusiname
|
@ -9,14 +9,10 @@ class Distribusis(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
distribusiname = db.Column(db.String(300), nullable=False, unique=True)
|
||||
userid = db.Column(db.Integer, db.ForeignKey("users.id"))
|
||||
term = db.Column(db.String(5), nullable=False, unique=False)
|
||||
course = db.Column(db.String(500), nullable=True, unique=False)
|
||||
|
||||
# Academic year eg:2020-2021, so no need for a Datetime object
|
||||
category = db.Column(db.String(500), nullable=True, unique=False)
|
||||
year = db.Column(db.String(9), nullable=True, unique=False)
|
||||
tags = db.Column(db.String(500), nullable=True, unique=False)
|
||||
publictheme = db.Column(db.String(300), unique=True, nullable=True)
|
||||
|
||||
visible = db.Column(db.Boolean, default=False)
|
||||
|
||||
def __repr__(self):
|
@ -1,6 +1,7 @@
|
||||
from app import db
|
||||
from flask_login import UserMixin
|
||||
|
||||
from app import db
|
||||
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
"""User model class for a user in distribusi-verse"""
|
||||
@ -14,8 +15,6 @@ class User(UserMixin, db.Model):
|
||||
currentdistribusi = db.Column(db.String(300), nullable=True, unique=False)
|
||||
resethash = db.Column(db.String(300), nullable=True, unique=True)
|
||||
resettime = db.Column(db.DateTime)
|
||||
#active = db.Column(db.Boolean, default=False)
|
||||
tutor = db.Column(db.Boolean, default=False)
|
||||
admin = db.Column(db.Boolean, default=False)
|
||||
|
||||
def __repr__(self):
|
4
verse/settings.toml
Normal file
@ -0,0 +1,4 @@
|
||||
title = "Varia Archive X Distribusi-Verse"
|
||||
categories = ["event","gathering","work session","workgroup","performance","music event"]
|
||||
start_time = 2017-11-03
|
||||
end_time = 2025-12-31
|
122
verse/start.py
@ -1,50 +1,41 @@
|
||||
"""This is the main flask distribusi page"""
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from flask import (
|
||||
render_template,
|
||||
redirect,
|
||||
url_for,
|
||||
session,
|
||||
send_from_directory,
|
||||
Blueprint,
|
||||
redirect,
|
||||
render_template,
|
||||
send_from_directory,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import (
|
||||
logout_user,
|
||||
login_required,
|
||||
current_user,
|
||||
)
|
||||
from flask_login import current_user, login_required, logout_user
|
||||
from flask_mail import Mail
|
||||
from flask_wtf.csrf import CSRFError
|
||||
from app import create_app, login_manager
|
||||
|
||||
from usermodel import User
|
||||
from distribusimodel import Distribusis
|
||||
|
||||
# Use upload form to populate filters
|
||||
from forms.uploadform import UploadForm
|
||||
|
||||
# Interface! these are seperate files in main folder
|
||||
from adminpage import AdminPage
|
||||
from editor import Editor
|
||||
from themeselector import ThemeSelector
|
||||
from distribusiworkflow import DistribusiWorkflow
|
||||
from distribusiselector import DistribusiSelector
|
||||
from uploadpage import UploadPage
|
||||
from app import create_app, login_manager
|
||||
from distribusikan.distribusikan import distribusikan
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
|
||||
# UserPengguna
|
||||
# Use upload form to populate filters
|
||||
from forms.uploadform import UploadForm
|
||||
from models.distribusi_model import Distribusis
|
||||
from models.user_model import User
|
||||
from statuspengguna.forgotpassword import forgot_password
|
||||
from statuspengguna.helper import UserHelper
|
||||
from statuspengguna.loginuser import LoginUser
|
||||
from statuspengguna.registeruser import RegisterUser
|
||||
from statuspengguna.forgotpassword import ForgotPassword
|
||||
from statuspengguna.resetpassword import ResetPassword
|
||||
|
||||
# Distribusi Information
|
||||
from distribusisinfo import DistribusisInfo
|
||||
from statuspengguna.loginuser import login_section
|
||||
from statuspengguna.registeruser import register_user
|
||||
|
||||
APP = create_app()
|
||||
stash_page = Blueprint("stash_page", __name__, static_folder="stash")
|
||||
APP.register_blueprint(login_section, url_prefix="/login")
|
||||
APP.register_blueprint(register_user, url_prefix="/register")
|
||||
APP.register_blueprint(forgot_password, url_prefix="/login/forgotpassword")
|
||||
APP.register_blueprint(distribusikan)
|
||||
APP.register_blueprint(stash_page)
|
||||
mail = Mail(APP)
|
||||
|
||||
|
||||
@APP.before_request
|
||||
@ -56,7 +47,6 @@ def session_handler():
|
||||
@APP.route("/")
|
||||
def index():
|
||||
UserHelper.reset_user_state()
|
||||
# http://localhost:5000/themes/publicthemes/RomeroTape/blueskies.css
|
||||
uploadform = UploadForm()
|
||||
distribusis = DistribusisInfo.visibledistribusis()
|
||||
distribusisindex = {}
|
||||
@ -65,23 +55,19 @@ def index():
|
||||
singledistribusi = {
|
||||
"username": user.username,
|
||||
"publictheme": distribusi.publictheme,
|
||||
"term": distribusi.term,
|
||||
"course": distribusi.course,
|
||||
"category": distribusi.category,
|
||||
"year": distribusi.year,
|
||||
"tags": distribusi.tags.split(","),
|
||||
}
|
||||
distribusisindex[distribusi.distribusiname] = singledistribusi
|
||||
years = uploadform.academicyear.choices
|
||||
terms = uploadform.term.choices
|
||||
courses = uploadform.course.choices
|
||||
|
||||
years = uploadform.year.choices
|
||||
categories = uploadform.category.choices
|
||||
adminuser = isadminuser()
|
||||
template = render_template(
|
||||
"index.html",
|
||||
"base/index.html",
|
||||
distribusisindex=distribusisindex,
|
||||
years=years,
|
||||
terms=terms,
|
||||
courses=courses,
|
||||
categories=categories,
|
||||
adminuser=adminuser,
|
||||
)
|
||||
return template
|
||||
@ -89,26 +75,7 @@ def index():
|
||||
|
||||
@APP.route("/help")
|
||||
def help():
|
||||
return render_template("help.html")
|
||||
|
||||
|
||||
@APP.route("/distribusi", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def distribusi():
|
||||
return DistribusiWorkflow()
|
||||
|
||||
|
||||
@APP.route("/upload", methods=["POST"])
|
||||
@login_required
|
||||
def upload():
|
||||
uploadfolder = APP.config["UPLOAD_FOLDER"]
|
||||
return UploadPage(uploadfolder)
|
||||
|
||||
|
||||
@APP.route("/theme", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def theme():
|
||||
return ThemeSelector()
|
||||
return render_template("base/help.html")
|
||||
|
||||
|
||||
@APP.route("/publicthemes/<path>")
|
||||
@ -116,22 +83,9 @@ def publicthemes(path):
|
||||
distribusi = Distribusis.query.filter_by(distribusiname=path).first()
|
||||
publicthemefolder = f"publicthemes/{distribusi.distribusiname}/"
|
||||
cssfile = f"{publicthemefolder}/{distribusi.publictheme}.css"
|
||||
print(cssfile)
|
||||
return send_from_directory("themes", cssfile, as_attachment=True)
|
||||
|
||||
|
||||
@APP.route("/editor", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def editor():
|
||||
return Editor()
|
||||
|
||||
|
||||
@APP.route("/selector", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def selector():
|
||||
return DistribusiSelector()
|
||||
|
||||
|
||||
@APP.route("/stash")
|
||||
def shortstashurl():
|
||||
return redirect(url_for("index"))
|
||||
@ -152,26 +106,6 @@ def logout():
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@APP.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
return LoginUser()
|
||||
|
||||
|
||||
@APP.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
return RegisterUser()
|
||||
|
||||
|
||||
@APP.route("/forgotpassword", methods=["GET", "POST"])
|
||||
def forgotpassword():
|
||||
return ForgotPassword(mail)
|
||||
|
||||
|
||||
@APP.route("/resetpassword/<path>", methods=["GET", "POST"])
|
||||
def resetpassword(path):
|
||||
return ResetPassword(path)
|
||||
|
||||
|
||||
@APP.errorhandler(CSRFError)
|
||||
def handle_csrf_error(e):
|
||||
return render_template("csrf_error.html", reason=e.description), 400
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Dropdown Button */
|
||||
/* for sorting on Academicyear, Term, Course
|
||||
/* for sorting on year and category
|
||||
*/
|
||||
button {
|
||||
background-color: #E0B0FF;
|
||||
|
@ -2,8 +2,8 @@ body
|
||||
{
|
||||
font-family: monospace, monospace;
|
||||
font-size: 15px;
|
||||
background-color: #272a33;
|
||||
color:#E0B0FF;
|
||||
background-color: #fdfdfd;
|
||||
color:#29d148;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.1;
|
||||
}
|
||||
@ -12,7 +12,7 @@ div#login{
|
||||
width: 30%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background-color:#272a33;
|
||||
background-color:#fdfdfd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@ -24,10 +24,10 @@ div#login form {
|
||||
}
|
||||
|
||||
input[type=text], input[type=password], input[type=file] {
|
||||
color: white;
|
||||
color: #C397DF;
|
||||
width: 18em;
|
||||
max-width: 18em;
|
||||
background-color: #2D3039;
|
||||
background-color: #fdfdfd;
|
||||
border: 1px solid #E0B0FF;
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ div#upload form {
|
||||
padding-left: auto;
|
||||
padding-right: auto;
|
||||
width: 31em;
|
||||
background-color:#30333f;
|
||||
background-color:#fdfdfd;
|
||||
text-decoration: none;
|
||||
scroll-behavior: smooth;
|
||||
border-style: outset;
|
||||
@ -149,12 +149,6 @@ input[type="submit"]:disabled:focus {
|
||||
background-color: #62b264;
|
||||
}
|
||||
|
||||
#tutors {
|
||||
color: black;
|
||||
background-color: #62b264;
|
||||
}
|
||||
|
||||
|
||||
/* unvisited link */
|
||||
a:link {
|
||||
color: #fff600;
|
||||
|
@ -24,9 +24,8 @@ function filterSelection(c, name, id) {
|
||||
}
|
||||
|
||||
function resetDropDownButtons(){
|
||||
document.getElementById("Academicyear").innerText = "Academic year";
|
||||
document.getElementById("Term").innerText = "Term";
|
||||
document.getElementById("Course").innerText = "Course";
|
||||
document.getElementById("Year").innerText = "Year";
|
||||
document.getElementById("Category").innerText = "Category";
|
||||
allactivebuttons = document.getElementsByClassName("activebtn");
|
||||
for(var i = 0;allactivebuttons.length; i++) {
|
||||
removeClass(allactivebuttons[i], "activebtn");
|
||||
|
0
verse/statuspengguna/__init__.py
Normal file
@ -1,17 +1,31 @@
|
||||
from uuid import uuid1
|
||||
from datetime import datetime
|
||||
from uuid import uuid1
|
||||
|
||||
from flask import Blueprint, render_template
|
||||
from flask_mail import Mail, Message
|
||||
from sqlalchemy.exc import (
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
from flask import render_template
|
||||
from flask_mail import Message
|
||||
|
||||
from usermodel import User
|
||||
from app import db, get_app
|
||||
from forms.forgotpasswordform import ForgotPasswordForm
|
||||
from app import db
|
||||
from models.user_model import User
|
||||
|
||||
mail = Mail(get_app())
|
||||
forgot_password = Blueprint(
|
||||
"forgotpassword",
|
||||
__name__,
|
||||
template_folder="templates/statuspengguna",
|
||||
static_folder="static",
|
||||
)
|
||||
|
||||
|
||||
@forgot_password.route("/", methods=["GET", "POST"])
|
||||
def forgotpassword():
|
||||
return ForgotPassword(mail)
|
||||
|
||||
|
||||
def ForgotPassword(mail):
|
||||
|
@ -1,17 +1,18 @@
|
||||
import os
|
||||
from flask_login import current_user
|
||||
|
||||
from flask import flash
|
||||
from flask_login import current_user
|
||||
from sqlalchemy.exc import (
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
|
||||
from usermodel import User
|
||||
from distribusimodel import Distribusis
|
||||
from distribusisinfo import DistribusisInfo
|
||||
from app import db
|
||||
from distribusikan.distribusisinfo import DistribusisInfo
|
||||
from models.distribusi_model import Distribusis
|
||||
from models.user_model import User
|
||||
|
||||
|
||||
class UserHelper:
|
||||
@ -76,10 +77,7 @@ class UserHelper:
|
||||
def distribusi_limit_reached():
|
||||
user = User.query.filter_by(email=current_user.email).first()
|
||||
distribusiamount = len(DistribusisInfo.getuserdistribusis(user.email))
|
||||
if user.tutor and distribusiamount > 14:
|
||||
print("tutor already has 15 distribusis")
|
||||
return True
|
||||
if not user.tutor and distribusiamount > 4:
|
||||
print("user already has 5 distribusis")
|
||||
if distribusiamount > 19:
|
||||
print("user already has 20 distribusis")
|
||||
return True
|
||||
return False
|
||||
|
@ -1,15 +1,31 @@
|
||||
from flask import (
|
||||
render_template,
|
||||
redirect,
|
||||
request,
|
||||
flash,
|
||||
url_for,
|
||||
Blueprint,
|
||||
abort,
|
||||
flash,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
send_from_directory,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from usermodel import User
|
||||
from forms.loginform import LoginForm
|
||||
from flask_login import login_user
|
||||
from flask_bcrypt import check_password_hash
|
||||
from flask_login import login_user
|
||||
|
||||
from forms.loginform import LoginForm
|
||||
from models.user_model import User
|
||||
|
||||
login_section = Blueprint(
|
||||
"login",
|
||||
__name__,
|
||||
template_folder="templates/statuspengguna",
|
||||
static_folder="static",
|
||||
)
|
||||
|
||||
|
||||
@login_section.route("/", methods=["GET", "POST"])
|
||||
def login():
|
||||
return LoginUser()
|
||||
|
||||
|
||||
def LoginUser():
|
||||
@ -21,11 +37,14 @@ def LoginUser():
|
||||
loginform.password.errors.append("Invalid email or password!")
|
||||
return render_template("login.html", loginform=loginform)
|
||||
if check_password_hash(user.password, loginform.password.data):
|
||||
print(type(user))
|
||||
login_user(user)
|
||||
flash("Logged in successfully.", "success")
|
||||
next = request.args.get("next")
|
||||
if next is not None and not is_safe_url(next): # noqa: F821
|
||||
print(next)
|
||||
return abort(400)
|
||||
print("index")
|
||||
return redirect(next or url_for("index"))
|
||||
else:
|
||||
flash("Invalid email or password!", "danger")
|
||||
|
@ -1,22 +1,30 @@
|
||||
from flask import (
|
||||
render_template,
|
||||
redirect,
|
||||
flash,
|
||||
url_for,
|
||||
)
|
||||
from flask import Blueprint, flash, redirect, render_template, url_for
|
||||
from flask_bcrypt import generate_password_hash
|
||||
from flask_login import login_user
|
||||
from sqlalchemy.exc import (
|
||||
IntegrityError,
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
IntegrityError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
from werkzeug.routing import BuildError
|
||||
from usermodel import User
|
||||
from forms.registerform import RegisterForm
|
||||
from flask_login import login_user
|
||||
from flask_bcrypt import generate_password_hash
|
||||
|
||||
from app import db
|
||||
from forms.registerform import RegisterForm
|
||||
from models.user_model import User
|
||||
|
||||
register_user = Blueprint(
|
||||
"register",
|
||||
__name__,
|
||||
template_folder="templates/statuspengguna",
|
||||
static_folder="static",
|
||||
)
|
||||
|
||||
|
||||
@register_user.route("/", methods=["GET", "POST"])
|
||||
def register():
|
||||
return RegisterUser()
|
||||
|
||||
|
||||
def RegisterUser():
|
||||
|
@ -1,23 +1,33 @@
|
||||
from datetime import datetime
|
||||
from flask import (
|
||||
render_template,
|
||||
redirect,
|
||||
flash,
|
||||
url_for,
|
||||
)
|
||||
|
||||
from flask import flash, redirect, render_template, url_for
|
||||
from flask_bcrypt import generate_password_hash
|
||||
from flask_login import login_user
|
||||
from sqlalchemy.exc import (
|
||||
IntegrityError,
|
||||
DataError,
|
||||
DatabaseError,
|
||||
DataError,
|
||||
IntegrityError,
|
||||
InterfaceError,
|
||||
InvalidRequestError,
|
||||
)
|
||||
from werkzeug.routing import BuildError
|
||||
from usermodel import User
|
||||
from forms.resetpasswordform import ResetPasswordForm
|
||||
from flask_login import login_user
|
||||
from flask_bcrypt import generate_password_hash
|
||||
|
||||
from app import db
|
||||
from forms.resetpasswordform import ResetPasswordForm
|
||||
from models.user_model import User
|
||||
from statuspengguna import statuspengguna
|
||||
|
||||
reset_password = Blueprint(
|
||||
"reset_password",
|
||||
__name__,
|
||||
template_folder="templates/statuspengguna",
|
||||
static_folder="static",
|
||||
)
|
||||
|
||||
|
||||
@reset_password.route("/resetpassword/<path>", methods=["GET", "POST"])
|
||||
def resetpassword(path):
|
||||
return ResetPassword(path)
|
||||
|
||||
|
||||
def ResetPassword(path):
|
||||
|
69
verse/statuspengguna/static/css/dropdown.css
Normal file
@ -0,0 +1,69 @@
|
||||
/* Dropdown Button */
|
||||
/* for sorting on year and category
|
||||
*/
|
||||
button {
|
||||
background-color: #E0B0FF;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
}
|
||||
.filter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.activebtn {
|
||||
background-color: #62b264;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: block;
|
||||
}
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Dropdown Content (Hidden by Default) */
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #E0B0FF;
|
||||
min-width: 120px;
|
||||
border: 2px solid;
|
||||
z-index: 1;
|
||||
border-style: outset;
|
||||
}
|
||||
|
||||
/* Links inside the dropdown */
|
||||
.dropdown-content button {
|
||||
color: black;
|
||||
padding: 6px;
|
||||
border: none;
|
||||
min-width: inherit;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
.dropbtn {
|
||||
margin-top: 1em;
|
||||
}
|
||||
/* Change color of dropdown links on hover */
|
||||
.dropdown-content button:hover {background-color: #62b264;}
|
||||
|
||||
|
||||
/* Show the dropdown menu on hover */
|
||||
.dropdown:hover .dropdown-content {display: block;}
|
||||
|
||||
/* Change the background color of the dropdown button when the dropdown content is shown */
|
||||
.dropdown:hover .dropbtn {background-color: #62b264;}
|
||||
|
||||
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
|
||||
.dropdown-content button {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.container > button {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
.dropdown > button {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
50
verse/statuspengguna/static/css/editor.css
Normal file
@ -0,0 +1,50 @@
|
||||
.editareas {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.editarea {
|
||||
width: 30%;
|
||||
border: 3px solid #E0B0FF;
|
||||
border-style: outset;
|
||||
margin-right: 1em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.editor {
|
||||
min-width: 35%;
|
||||
}
|
||||
.editform {
|
||||
width: 100%%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#editorsubmitform {
|
||||
padding-top: 1em;
|
||||
}
|
||||
.required label {
|
||||
display: block;
|
||||
padding-bottom: 2px;
|
||||
width: 100%
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 250px;
|
||||
background: #E0B0FF;
|
||||
outline: none;
|
||||
font-family: Courier, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
iframe {
|
||||
bottom: 0;
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
width: 100%;
|
||||
height: 30em;
|
||||
}
|
||||
#html {
|
||||
background-color: #60337F;
|
||||
color: lightgrey;
|
||||
}
|
53
verse/statuspengguna/static/css/selector.css
Normal file
@ -0,0 +1,53 @@
|
||||
.selector-style {
|
||||
padding: 0;
|
||||
width: 20em;
|
||||
max-width: 20em;
|
||||
position: relative;
|
||||
border: none;
|
||||
background: #E0B0FF;
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.selector-style select {
|
||||
padding: 0.2em 0.2em;
|
||||
width: 20em;
|
||||
max-width: 20em;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background-color: #E0B0FF;
|
||||
background-image: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.selector-style:after {
|
||||
top: 50%;
|
||||
left: 95%;
|
||||
border: solid;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
border-top-color: #000000;
|
||||
margin-top: -2px;
|
||||
z-index: 100;
|
||||
}
|
||||
select.selector option{
|
||||
color: white;
|
||||
background-color: #60337F;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.selector-style select:focus {
|
||||
outline: none;
|
||||
}
|
||||
.selector-style select option:hover {
|
||||
background: #60337F;
|
||||
}
|
281
verse/statuspengguna/static/css/style.css
Normal file
@ -0,0 +1,281 @@
|
||||
body
|
||||
{
|
||||
font-family: monospace, monospace;
|
||||
font-size: 15px;
|
||||
background-color: #fdfdfd;
|
||||
color:#29d148;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
div#login{
|
||||
width: 30%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background-color:#fdfdfd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#login form {
|
||||
width: 24em;
|
||||
margin: 0 auto;
|
||||
padding-left: 15%;
|
||||
padding-right: 15%;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password], input[type=file] {
|
||||
color: #C397DF;
|
||||
width: 18em;
|
||||
max-width: 18em;
|
||||
background-color: #fdfdfd;
|
||||
border: 1px solid #E0B0FF;
|
||||
}
|
||||
|
||||
div#upload form {
|
||||
padding-right: 15%;
|
||||
}
|
||||
|
||||
.workflow{
|
||||
margin-top: 1em;
|
||||
padding: 0.5em;
|
||||
padding-left: auto;
|
||||
padding-right: auto;
|
||||
width: 31em;
|
||||
background-color:#fdfdfd;
|
||||
text-decoration: none;
|
||||
scroll-behavior: smooth;
|
||||
border-style: outset;
|
||||
}
|
||||
.workflow > p {
|
||||
padding-left: 1em;
|
||||
}
|
||||
.workflow > h2 {
|
||||
padding-left: 0.4em;;
|
||||
}
|
||||
|
||||
.workflow input{
|
||||
max-width: 20em;
|
||||
}
|
||||
|
||||
#mainworkflow
|
||||
{
|
||||
width: 30em;
|
||||
margin:0 auto;
|
||||
}
|
||||
|
||||
#distribusiverse {
|
||||
margin-bottom: 11em;
|
||||
}
|
||||
#distribusi-index {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
div#buttons{
|
||||
position: fixed;
|
||||
top: 0.5em;
|
||||
right: 0.5em;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div#buttons .distribusi input{
|
||||
border: none;
|
||||
background: #fff600;
|
||||
text-decoration: none;
|
||||
margin: 0.2em;
|
||||
}
|
||||
div#buttons .distribusi input:hover{
|
||||
background: #ffbf00;
|
||||
|
||||
}
|
||||
fieldset.required {
|
||||
border: none;
|
||||
}
|
||||
|
||||
fieldset.required > ul {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
fieldset.required > ul > li{
|
||||
list-style-type: none;
|
||||
}
|
||||
fieldset.tagfield > input {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
#publicthemes > ul {
|
||||
max-height: 20em;
|
||||
overflow: auto;
|
||||
}
|
||||
#publicthemes > ul > li{
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
background: #E0B0FF;
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
input:hover {
|
||||
background: #60337F;
|
||||
}
|
||||
|
||||
input[type="submit"]:disabled:hover,
|
||||
input[type="submit"]:disabled,
|
||||
input[type="submit"]:disabled:focus {
|
||||
background-color: #2D3039;
|
||||
color: #d28cff;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-size: 110%;
|
||||
color: #F92020;
|
||||
}
|
||||
|
||||
#delete {
|
||||
color: black;
|
||||
background-color: #F92020;
|
||||
}
|
||||
|
||||
#update {
|
||||
color: black;
|
||||
background-color: #62b264;
|
||||
}
|
||||
|
||||
|
||||
/* unvisited link */
|
||||
a:link {
|
||||
color: #fff600;
|
||||
}
|
||||
|
||||
/* visited link */
|
||||
a:visited {
|
||||
color: #d28cff;
|
||||
}
|
||||
|
||||
/* mouse over link */
|
||||
a:hover {
|
||||
color: #60337F;
|
||||
}
|
||||
|
||||
/* selected link */
|
||||
a:active {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* STOLEN GOODS */
|
||||
#fancyboi::before {
|
||||
content: "$ ";
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
@keyframes flash {
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
@keyframes reveal {
|
||||
from { width: 2em; } /* Width of ::before */
|
||||
to { width: 55%; }
|
||||
}
|
||||
#fancyboi {
|
||||
width: 55%;
|
||||
padding: 0.5em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
animation: reveal 4s linear;
|
||||
text-overflow: "█";
|
||||
background-color: #2D3039;
|
||||
}
|
||||
#fancyboi::after {
|
||||
content: "█";
|
||||
animation: flash 0.5s step-end infinite;
|
||||
}
|
||||
}
|
||||
|
||||
div.maincontent{
|
||||
width: 55%;
|
||||
border: 3px #E0B0FF;
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
border-style: outset;
|
||||
}
|
||||
|
||||
.tags{
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
text-align: center;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.searched {
|
||||
background: #fff600 !important;
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border-bottom: 1px dotted black;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 120px;
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -60px;
|
||||
|
||||
/* Fade in tooltip - takes 1 second to go from 0% to 100% opac: */
|
||||
opacity: 0;
|
||||
transition: opacity 2s;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.code-example {
|
||||
width: 100%;
|
||||
color: black;
|
||||
padding: 1em;
|
||||
box-sizing: border-box;
|
||||
background: #E0B0FF;
|
||||
outline: none;
|
||||
font-family: Courier, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
/*
|
||||
Project colors so far.
|
||||
light
|
||||
#E0B0FF
|
||||
medium
|
||||
#d28cff
|
||||
dark
|
||||
#60337F
|
||||
|
||||
background dark
|
||||
#2D3039
|
||||
|
||||
yellow important
|
||||
#fff600
|
||||
|
||||
red: danger
|
||||
ff5a5a
|
||||
backgrounds
|
||||
*/
|
6
verse/statuspengguna/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/statuspengguna/static/icons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
verse/statuspengguna/static/icons/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
verse/statuspengguna/static/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
verse/statuspengguna/static/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 322 B |
BIN
verse/statuspengguna/static/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
verse/statuspengguna/static/icons/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
1
verse/statuspengguna/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"}
|
92
verse/statuspengguna/static/js/dropdown.js
Normal file
@ -0,0 +1,92 @@
|
||||
filterSelection("all", "None");
|
||||
function filterSelection(c, name, id) {
|
||||
resetDropDownButtons();
|
||||
var i;
|
||||
var button = document.getElementById(id);
|
||||
if(button){
|
||||
button.innerText = name;
|
||||
addClass(button, "activebtn");
|
||||
}
|
||||
var alldistribusis = document.getElementsByClassName("filter");
|
||||
if (c == "all") {
|
||||
for (i = 0; i < alldistribusis.length; i++) {
|
||||
addClass(alldistribusis[i], "show");
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i = 0; i < alldistribusis.length; i++) {
|
||||
removeClass(alldistribusis[i], "show");
|
||||
if (alldistribusis[i].className.indexOf(c) > -1) {
|
||||
addClass(alldistribusis[i], "show");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetDropDownButtons(){
|
||||
document.getElementById("Year").innerText = "Year";
|
||||
document.getElementById("Category").innerText = "Category";
|
||||
allactivebuttons = document.getElementsByClassName("activebtn");
|
||||
for(var i = 0;allactivebuttons.length; i++) {
|
||||
removeClass(allactivebuttons[i], "activebtn");
|
||||
}
|
||||
}
|
||||
function addClass(element, name) {
|
||||
var i, arr1, arr2;
|
||||
arr1 = element.className.split(" ");
|
||||
arr2 = name.split(" ");
|
||||
for (i = 0; i < arr2.length; i++) {
|
||||
if (arr1.indexOf(arr2[i]) == -1) {element.className += " " + arr2[i];}
|
||||
}
|
||||
}
|
||||
|
||||
function removeClass(element, name) {
|
||||
var i, arr1, arr2;
|
||||
arr1 = element.className.split(" ");
|
||||
arr2 = name.split(" ");
|
||||
for (i = 0; i < arr2.length; i++) {
|
||||
while (arr1.indexOf(arr2[i]) > -1) {
|
||||
arr1.splice(arr1.indexOf(arr2[i]), 1);
|
||||
}
|
||||
}
|
||||
element.className = arr1.join(" ");
|
||||
}
|
||||
|
||||
|
||||
let searchInput = document.getElementById('tagsearch');
|
||||
let timeout = null;
|
||||
// Listen for keystroke events
|
||||
searchInput.addEventListener('keyup', function (e) {
|
||||
// Clear the timeout if it has already been set.
|
||||
clearTimeout(timeout);
|
||||
// Make a new timeout set to go off in 1000ms (1 second)
|
||||
timeout = setTimeout(function () {
|
||||
console.log('Input Value:', searchInput.value);
|
||||
if (searchInput.value.length > 2) {
|
||||
searchTags(searchInput.value);
|
||||
} else {
|
||||
clearSearchTags();
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
function searchTags(searchInput) {
|
||||
let tag_ele = document.getElementsByClassName('tags');
|
||||
for (var i = 0; i < tag_ele.length; ++i) {
|
||||
let searchText = searchInput.toLowerCase().trim();
|
||||
let tagtext = tag_ele[i].innerText.toLowerCase();
|
||||
if(searchText.includes(tagtext) || tagtext.includes(searchText)) {
|
||||
addClass(tag_ele[i], "searched");
|
||||
}
|
||||
else {
|
||||
removeClass(tag_ele[i], "searched");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearSearchTags() {
|
||||
let tag_ele = document.getElementsByClassName('tags');
|
||||
for (var i = 0; i < tag_ele.length; ++i) {
|
||||
removeClass(tag_ele[i], "searched");
|
||||
}
|
||||
}
|
19
verse/statuspengguna/static/js/editorupdate.js
Normal file
@ -0,0 +1,19 @@
|
||||
function update() {
|
||||
|
||||
var html = document.getElementById("html");
|
||||
var css = document.getElementById("css");
|
||||
var code = document.getElementById("code").contentWindow.document;
|
||||
|
||||
document.body.onkeyup = function(){
|
||||
code.open();
|
||||
code.writeln(html.value+"<style>"+css.value+"</style>");
|
||||
code.close();
|
||||
};
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
code.open();
|
||||
code.writeln(html.value+"<style>"+css.value+"</style>");
|
||||
code.close();
|
||||
});
|
||||
};
|
||||
|
||||
update();
|
25
verse/statuspengguna/static/js/script.js
Normal file
@ -0,0 +1,25 @@
|
||||
console.log("everything is still smooth")
|
||||
|
||||
function scrollToTheme() {
|
||||
var uploadsuccessful = document.getElementById("uploadsuccessful");
|
||||
if(uploadsuccessful){
|
||||
const theme = document.getElementById('theme')
|
||||
theme.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToLaunch() {
|
||||
var cssSelected = document.getElementById("cssSelected");
|
||||
if(cssSelected){
|
||||
const launch = document.getElementById('launch')
|
||||
launch.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", scrollToTheme);
|
||||
document.addEventListener("DOMContentLoaded", scrollToLaunch);
|
||||
|
||||
// function(e) {
|
||||
// (e.keyCode === 13 || e.keyCode === 32) && $(this).trigger("click")
|
||||
// }
|
75
verse/statuspengguna/static/svg/arrow_1.svg
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="240"
|
||||
height="320"
|
||||
viewBox="0 0 63.5 84.666667"
|
||||
version="1.1"
|
||||
id="svgArrowOne"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
sodipodi:docname="arrow_1.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="236"
|
||||
inkscape:cy="70"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-height="1041"
|
||||
inkscape:window-x="1366"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<circle
|
||||
id="path865"
|
||||
style="fill:#060000;stroke:#fcfffe;stroke-width:0.264583"
|
||||
cx="167.50412"
|
||||
cy="23.866825"
|
||||
r="0.211455" />
|
||||
<circle
|
||||
id="path867"
|
||||
style="fill:#e0b0ff;stroke:#fcfffe;stroke-width:0.264583;fill-opacity:1"
|
||||
cx="167.50412"
|
||||
cy="23.866825"
|
||||
r="0.211455" />
|
||||
<circle
|
||||
id="path2716"
|
||||
style="fill:#d28cff;stroke:#fcfffe;stroke-width:0.264583"
|
||||
cx="31.623327"
|
||||
cy="-54.739456"
|
||||
r="0.1057275" />
|
||||
<circle
|
||||
id="path2718"
|
||||
style="fill:#d28cff;stroke:#fcfffe;stroke-width:0.264583"
|
||||
cx="31.623327"
|
||||
cy="-54.739456"
|
||||
r="0.1057275" />
|
||||
<path
|
||||
style="fill:#d28cff;fill-opacity:1;stroke:none;stroke-width:0.799999"
|
||||
id="path2732"
|
||||
d="m 19.948434,3.1817159 c 1.290131,0.6550689 2.512225,1.4411814 3.755485,2.1807733 1.793529,1.0335667 3.565214,2.1021852 5.308482,3.2186226 0.483624,0.3097273 0.976024,0.6070071 1.443413,0.9407313 0.457224,0.3264641 0.885243,0.6920049 1.327862,1.0380079 2.123546,1.795334 3.693565,4.041838 4.640125,6.658655 0.242448,0.67026 0.417224,1.3631 0.625834,2.04465 0.468096,1.928539 0.850726,3.889399 1.025174,5.869019 0.146409,1.661472 0.09044,3.337613 0.06891,5.002416 -0.136327,2.53704 -0.886677,4.958631 -1.507659,7.401952 -0.562415,2.08257 -2.195008,3.525988 -3.642546,5.02172 -1.668862,1.796187 -3.430227,3.511629 -4.96204,5.428684 -1.232474,1.501685 -2.187443,3.200289 -3.147382,4.881737 -1.04435,1.719871 -1.86704,3.57665 -2.548164,5.467477 -0.710967,2.173634 -0.907385,4.460639 -1.024063,6.730664 0.02091,1.346742 0.05687,2.707192 0.493364,3.995719 0.26353,0.777933 0.396191,0.938664 0.795115,1.653278 0.654367,1.054661 1.365786,2.066507 2.102377,3.064017 0,0 3.841737,-0.248322 3.841737,-0.248322 v 0 c -0.75511,-0.983935 -1.542788,-1.939549 -2.17802,-3.011543 -0.382704,-0.652497 -0.537692,-0.849823 -0.799641,-1.562883 -0.455204,-1.239128 -0.493109,-2.567154 -0.52219,-3.871724 0.107265,-2.234121 0.278343,-4.485997 1.012129,-6.617327 0.708365,-1.865136 1.556754,-3.687384 2.575162,-5.404406 0.953056,-1.662009 1.928715,-3.32604 3.127534,-4.825259 1.51579,-1.902804 3.233504,-3.631578 4.905512,-5.395754 1.510255,-1.536197 3.212571,-3.04624 3.747334,-5.218498 0.540033,-2.47709 1.199993,-4.93743 1.330515,-7.483557 0.02558,-1.263529 0.06192,-2.073423 0.0096,-3.332359 C 41.644752,24.222411 41.147902,21.675409 40.546318,19.16389 40.076453,17.470971 39.976922,16.848842 39.271149,15.277213 38.380408,13.293695 37.001362,11.650365 35.365848,10.236089 34.920391,9.8793346 34.491789,9.5004423 34.02947,9.1658256 33.563896,8.8288536 33.065526,8.5396762 32.585453,8.223701 30.879732,7.1010218 29.144655,6.0327407 27.391958,4.9847267 26.212505,4.238663 25.04853,3.4635151 23.848505,2.7523961 c 0,0 -3.900058,0.4293198 -3.900058,0.4293198 z" />
|
||||
<path
|
||||
style="fill:#d28cff;fill-opacity:1;stroke:none;stroke-width:0.799999"
|
||||
id="path2736"
|
||||
d="m 17.143337,69.050471 c 1.633713,0.894598 3.185906,1.900385 4.678149,3.014075 1.115423,0.934426 2.160169,1.94547 3.113059,3.044203 1.826876,2.397014 -1.146831,1.665475 4.779041,1.263248 0.383141,-1.686153 0.758291,-3.374861 1.165924,-5.055598 0.206131,-0.876517 0.390895,-1.75066 0.653066,-2.611993 0,0 -3.799028,-0.103103 -3.799028,-0.103103 v 0 c -0.220279,0.87104 -0.384717,1.750875 -0.582906,2.627255 -0.396558,1.670132 -0.773763,3.348908 -1.349638,4.968864 1.273906,0.05246 2.548034,0.09977 3.821714,0.157393 0.02885,0.0013 -0.07003,0.03579 -0.08584,0.01161 -0.06107,-0.09337 -0.07566,-0.209923 -0.112684,-0.315174 -0.133537,-0.379648 -0.146335,-0.493044 -0.361696,-0.837912 -0.08452,-0.135356 -0.188793,-0.257328 -0.283188,-0.385993 -1.017286,-1.099923 -2.077482,-2.15043 -3.232635,-3.10924 -1.452916,-1.095015 -2.937697,-2.134709 -4.528692,-3.02179 0,0 -3.87465,0.354142 -3.87465,0.354142 z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
12
verse/statuspengguna/static/svg/arrow_2.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="240" height="320" version="1.1" viewBox="0 0 63.5 84.667" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="167.5" cy="23.867" r=".21146" fill="#060000" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<circle cx="167.5" cy="23.867" r=".21146" fill="#e0b0ff" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<g fill="#d28cff">
|
||||
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<path d="m29.598 1.3629c0.29231 0.93057 0.37445 1.9094 0.44145 2.8774 0.01955 0.99076 0.29893 1.9483 0.43855 2.9253 0.04901 1.1628 0.31687 2.2714 0.7734 3.3386 0.60807 1.244 1.6241 2.1846 2.6231 3.1104 0.76132 0.7147 1.5224 1.4053 2.414 1.9528 0.85013 0.49091 1.8227 0.71794 2.7627 0.97569 1.4036 0.22312 2.1015 0.68412 3.1203 1.648 1.1101 1.0733 1.7779 2.4463 2.4311 3.8184 0.5827 1.142 0.76303 2.3828 0.73638 3.6503-0.08765 1.5129-0.67764 2.6923-1.5432 3.8976-0.84272 1.225-2.1589 1.8621-3.4214 2.5577-1.3039 0.74177-2.5458 1.586-3.8965 2.2408-1.5184 0.78493-3.077 1.4779-4.6378 2.1709-1.2968 0.70155-2.6371 1.3714-3.7845 2.3055-1.5755 1.3431-2.9301 2.8922-4.2407 4.4866-1.0773 1.3646-2.2517 2.7443-2.9262 4.3683-0.12219 0.2942-0.20988 0.60157-0.31482 0.90236-0.3176 1.1367-0.50828 2.2967-0.36852 3.4764 0.04693 0.39614 0.19687 1.057 0.27962 1.4461 0.30758 1.2289 0.84547 2.3725 1.4798 3.4625 0.70009 1.3121 1.6509 2.4545 2.6734 3.5243 1.0661 1.0736 2.1636 2.1225 3.154 3.2675 0.78168 0.87789 1.0179 1.9433 1.1372 3.0756 0.03544 0.92388 0.11577 1.8587 0.07073 2.7841-0.01773 0.36408-0.1093 0.87867-0.16701 1.2409-0.09034 1.015-0.40258 1.9744-0.65202 2.9542-0.22907 0.93923-0.24592 1.9133-0.26994 2.875 0.01627 0.16165 0.03253 0.3233 0.04879 0.48495 0 0 3.8677-0.04599 3.8677-0.04599l-0.06033-0.42856c0.0271-0.93446 0.05799-1.8763 0.28398-2.7884 0.25461-0.98959 0.52171-1.9723 0.61567-2.9949 0.18691-1.3678 0.16751-2.7404 0.09837-4.1178-0.10464-1.2084-0.33697-2.37-1.1681-3.3092-0.98696-1.161-2.0984-2.2119-3.1752-3.2885-1.0093-1.0344-1.9609-2.1448-2.6312-3.4346-0.61359-1.0586-1.166-2.1573-1.4984-3.3403-0.02657-0.1263-0.26629-1.2542-0.28656-1.3963-0.16054-1.1256 0.0079-2.2354 0.32978-3.3186 0.10325-0.28669 0.1907-0.57958 0.30976-0.86007 0.67556-1.5915 1.8191-2.9377 2.8755-4.2834 1.3254-1.6128 2.6979-3.0863 4.332-4.3948 1.1702-0.86077 2.4531-1.5682 3.751-2.2163 1.5673-0.6912 3.1104-1.4304 4.6479-2.1857 1.3437-0.6747 2.5823-1.5317 3.8988-2.2576 1.3084-0.71914 2.6043-1.447 3.4487-2.7293 0.89598-1.305 1.4437-2.5132 1.5458-4.1199 0.04656-1.326-0.09489-2.646-0.72638-3.8363-0.70909-1.3836-1.3422-2.8175-2.4524-3.9439-0.95909-0.99062-1.6743-1.6678-3.0935-1.8533-0.92413-0.22693-1.8609-0.47835-2.7296-0.86415-0.37733-0.23195-0.7701-0.43303-1.1225-0.70362-0.56026-0.43018-0.30161-0.27292-0.85155-0.76539-0.15495-0.13876-0.31951-0.26638-0.47927-0.39957-0.94654-0.91944-2.0219-1.7437-2.6195-2.9576-0.46026-1.0278-0.6805-2.1099-0.72907-3.2345-0.13163-0.97099-0.41801-1.9171-0.4545-2.8972-0.062288-0.99479-0.16352-1.9871-0.37122-2.9636l-3.9178 0.11147z"/>
|
||||
<path d="m22.025 71.064c1.1076 0.81571 1.8963 1.9751 2.6453 3.1096 0.63322 0.93788 1.2476 1.8855 1.6118 2.9617 0.28693 0.82149 0.62284 1.6252 1.0669 2.3743 1.0301 1.4304 1.5291 0.58576 4.4538 0.16545 0.14811-0.02127 0.42422-1.2095 0.46069-1.36 0.25411-0.96357 0.61812-1.6049 1.3587-2.2583 0.75053-0.69358 1.7635-0.9666 2.6863-1.3526 1.0408-0.39579 2.1353-0.68763 3.1287-1.1962 0.12805-0.06035 0.2561-0.12069 0.38414-0.18103 0 0-3.8517-0.43584-3.8517-0.43584-0.12618 0.06059-0.25236 0.12119-0.37854 0.18179-1.0095 0.4429-2.0552 0.79223-3.0867 1.1786-0.97696 0.38612-1.9942 0.73078-2.7537 1.4922-0.6784 0.69992-1.1041 1.5244-1.3418 2.4736-0.08563 0.30808-0.14275 0.60119-0.30832 0.87972-0.05051 0.08497-0.29376 0.21564-0.1952 0.22324 1.2279 0.09468 2.4644-0.01566 3.6945 0.04656 0.07116 0.0036-0.09855 0.1467-0.16824 0.13185-0.0954-0.02032-0.13522-0.1406-0.20282-0.2109-0.48406-0.68952-0.78002-1.4975-1.0935-2.2751-0.36094-1.1214-0.97173-2.1151-1.6698-3.0606-0.6571-0.99538-1.3971-2.1349-2.2397-2.9817-0.09081-0.09127-0.19994-0.16228-0.29991-0.24342l-3.9009 0.33725z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
12
verse/statuspengguna/static/svg/arrow_3.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="240" height="320" version="1.1" viewBox="0 0 63.5 84.667" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="167.5" cy="23.867" r=".21146" fill="#060000" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<circle cx="167.5" cy="23.867" r=".21146" fill="#e0b0ff" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<g fill="#d28cff">
|
||||
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<circle cx="31.623" cy="-54.739" r=".10573" stroke="#fcfffe" stroke-width=".26458"/>
|
||||
<path d="m56.965 3.4219c0.12058 1.1521-0.2318 2.054-0.81004 3.0337-1.0875 1.5907-2.5336 2.8659-4.0843 3.9867-2.1505 1.3863-4.4585 2.4853-6.8625 3.3491-2.7815 0.78715-5.6335 1.3087-8.4879 1.7525-3.134 0.44066-6.2863 0.71397-9.4313 1.0632-3.4224 0.40837-6.8733 1.0287-10.07 2.3675-0.54185 0.22694-1.0603 0.5062-1.5904 0.75929-0.89954 0.54647-1.6469 0.92677-2.4068 1.6606-1.3388 1.2929-2.0863 2.9786-2.778 4.6654-0.87449 2.3542-1.198 4.6214-1.1672 7.1174 0.18717 1.5284 0.31332 3.0753 0.6288 4.5852 0.091869 0.43967 0.3934 1.5785 0.51155 2.0327 0.39771 1.1363 0.63254 2.4445 1.5286 3.3253 0.14764 0.14511 0.3231 0.25888 0.48465 0.38832 0.20368 0.10006 0.39798 0.22205 0.61104 0.30019 0.42615 0.15629 1.2969 0.32297 1.7205 0.41193 1.1822 0.24832 2.3631 0.46842 3.5556 0.66151 1.9566 0.35044 3.9445 0.66205 5.8228 1.331 1.3844 0.50374 2.5213 1.4098 3.6121 2.3711 1.0386 0.99728 2.1001 1.9882 2.7426 3.3026 0.16634 0.3403 0.28751 0.70088 0.43126 1.0513 0.86215 2.4558 1.0225 5.0522 0.9327 7.631-0.07031 1.5349-0.29391 3.1185-1.156 4.4316-0.12638 0.19249-0.28466 0.36202-0.42699 0.54304-0.81011 0.9275-1.7382 1.735-2.5931 2.6178-0.73658 0.82062-1.6053 0.85394-2.5586 1.1927-0.94832 0.22808-1.8262 0.65178-2.7421 0.97327-1.1741 0.61913-2.4826 0.76669-3.7501 1.0714-1.2105 0.21338-2.3976 0.48893-3.5183 1.0015-0.67364 0.34917-1.2076 0.81882-1.6407 1.4361l3.8658 0.2497c0.43909-0.54046 0.98065-0.94428 1.6142-1.2389 1.1138-0.46371 2.2851-0.70648 3.4664-0.93518 1.2893-0.27722 2.5716-0.54674 3.7736-1.1162 0.91684-0.34231 1.829-0.69308 2.7728-0.9567 1.0196-0.30093 1.8099-0.54985 2.5486-1.3801 0.87014-0.87766 1.7909-1.7075 2.5674-2.6739 0.13769-0.20005 0.28985-0.39088 0.41308-0.60015 0.82211-1.3961 1.0263-3.0131 1.0537-4.6056 0.05629-2.6169-0.0783-5.2743-0.90909-7.7781-0.13345-0.36853-0.2428-0.74671-0.40035-1.1056-0.60136-1.3698-1.6464-2.4153-2.7131-3.4287-1.0905-1.0008-2.2045-1.983-3.6142-2.5099-0.24452-0.10125-0.48125-0.22386-0.73356-0.30376-0.26822-0.08493-0.54885-0.12408-0.82261-0.18901-1.4524-0.34445-2.9022-0.68462-4.3809-0.90277-1.1723-0.18185-2.3227-0.40058-3.4815-0.65277-0.54954-0.11959-1.1101-0.19272-1.6518-0.34365-0.20174-0.05621-0.39234-0.14664-0.58852-0.21996-0.15779-0.10023-0.32954-0.18128-0.47337-0.30068-0.94359-0.78332-1.2089-2.059-1.6142-3.1487-0.16754-0.59766-0.40066-1.3998-0.53448-2.0013-0.33082-1.487-0.45328-3.013-0.62991-4.523-0.03942-2.3761 0.26799-4.7319 1.1144-6.9675 0.36685-0.8728 0.62759-1.5813 1.1166-2.3961 0.98125-1.6348 2.2842-2.7836 3.9639-3.6713 0.51139-0.24392 1.0106-0.5151 1.5342-0.73175 3.1926-1.3212 6.6474-1.892 10.059-2.2825 3.192-0.33755 6.3934-0.58867 9.5696-1.062 2.856-0.4763 5.7214-0.9862 8.4967-1.8227 2.4241-0.91126 4.7643-2.0293 6.9388-3.445 1.5814-1.1701 3.0403-2.506 4.1596-4.1381 0.60624-1.0679 0.95747-1.9925 0.94037-3.2331h-3.9289z"/>
|
||||
<path d="m12.023 67.543c0.49829 0.89935 0.97249 1.783 1.2618 2.7744 0.05193 0.50463 0.27477 0.97017 0.38441 1.4597 0.09562 0.42695 0.06722 0.87236 0.05564 1.3052-0.02684 0.65007-0.04547 1.3125-0.23635 1.9412-0.02066 0.06805-0.31569 0.75033-0.34246 0.81269-1.0192 2.7097-0.33194 1.7204 3.7574 1.4613 0.62653-0.65486 1.2245-1.3292 1.8921-1.9454 0.77212-0.76506 1.7378-1.0867 2.7527-1.378 1.0883-0.39202 2.2359-0.50662 3.3814-0.5819 0.64057-0.03615 1.2822-0.04273 1.9235-0.05158 0 0-3.7766-0.59155-3.7766-0.59155-0.64632 0.01315-1.2932 0.02268-1.9381 0.07203-1.1598 0.09954-2.3084 0.26952-3.4108 0.66115-1.0413 0.32357-1.9937 0.77464-2.7811 1.553-0.66771 0.6262-1.2491 1.3374-1.9404 1.9381 1.2373 0.03262 2.4793-0.01378 3.712 0.09787 0.08161 0.0074-0.02469 0.17515-0.08494 0.23068-0.03731 0.03439-0.0096-0.10235-1.85e-4 -0.15222 0.07511-0.39908 0.24315-0.78076 0.38022-1.1601 0.37962-0.92942 0.58939-1.8859 0.57777-2.896 0.01506-0.47523 0.04582-0.96424-0.0577-1.4334-0.10794-0.48914-0.37419-0.93937-0.40173-1.4497-0.15402-0.55207-0.32508-1.0754-0.57723-1.592-0.14541-0.29797-0.33208-0.5746-0.4773-0.87265-0.05226-0.10725-0.08657-0.22235-0.12986-0.33352l-3.9242 0.13069z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="mainworkflow">
|
||||
<div class="workflow">
|
||||
@ -6,7 +6,8 @@
|
||||
<p>
|
||||
Enter the email address that was used to register with Distribusiverse.
|
||||
</p>
|
||||
<form class="form" action="{{ url_for('forgotpassword') }}" method="post">
|
||||
<form class="form" action="{{ url_for('forgotpassword.forgotpassword') }}"
|
||||
method="post">
|
||||
{{ forgotpasswordform.csrf_token }}
|
||||
<fieldset class="required">
|
||||
{{ forgotpasswordform.email.label }}
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="login">
|
||||
<form class="form" action="{{ url_for('login') }}" method="post">
|
||||
<form class="form" action="{{ url_for('login.login') }}" method="post">
|
||||
{{ loginform.csrf_token }}
|
||||
<fieldset class="required">
|
||||
{{ loginform.email.label }}
|
||||
@ -19,7 +19,7 @@
|
||||
</fieldset>
|
||||
<fieldset class="button required">
|
||||
{{ loginform.submit }}
|
||||
<a href="/forgotpassword">Forgot Password?</a>
|
||||
<a href="forgotpassword">Forgot Password?</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="login">
|
||||
<form class="form" action="{{ url_for('register') }}" method="post">
|
||||
<form class="form" action="{{ url_for('register.register') }}" method="post">
|
||||
{{ registerform.csrf_token }}
|
||||
<fieldset class="required">
|
||||
{{ registerform.username.label }}
|
@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="login">
|
||||
{% if linkvalid%}
|
@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="buttons">
|
||||
<div class="overview">
|
||||
@ -14,9 +14,6 @@
|
||||
</div>
|
||||
<div class="maincontent">
|
||||
<h2>Admin Page</h2>
|
||||
<p>Here you can bulk delete distribusis and users or make users into tutors
|
||||
<strong> These actions cannot be undone! </strong>
|
||||
</p>
|
||||
<div id="distribusiverse" class="maincontent">
|
||||
<h2>List of distribusis</h2>
|
||||
<form method="POST" enctype="multipart/form-data" action="{{ url_for('admin') }}">
|
||||
@ -53,12 +50,6 @@
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<fieldset class="button required">
|
||||
{{ adminuserform.tutors }}
|
||||
</fieldset>
|
||||
<fieldset class="button required">
|
||||
{{ adminuserform.nottutors }}
|
||||
</fieldset>
|
||||
<fieldset class="button required">
|
||||
{{ adminuserform.delete }}
|
||||
</fieldset>
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Autonomous Practices X Distribusi-Verse</title>
|
||||
<title>{{title}}</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css')}}">
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/selector.css')}}">
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
20
verse/templates/base/filtermenu.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% block menu %}
|
||||
<button onclick="filterSelection('all')" id="removefilter">Remove filter</button>
|
||||
<div class="dropdown">
|
||||
<button id="Year" class="dropbtn">Year</button>
|
||||
<div class="dropdown-content">
|
||||
{% for year in years %}
|
||||
<button type="button" name="button" onclick="filterSelection('{{ year[0] }}', '{{ year[1] }}', 'Year')" >{{ year[1] }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button id="Category" class="dropbtn">Category</button>
|
||||
<div class="dropdown-content">
|
||||
{% for category in categories %}
|
||||
<button type="button" name="button" onclick="filterSelection('{{ category[0] }}', '{{ category[1] }}', 'Category')" >{{ category[1] }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<input id="tagsearch" type="text" placeholder="Search..">
|
||||
{% endblock menu %}
|
@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="buttons">
|
||||
<div class="overview">
|
@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "base/base.html" %}
|
||||
{% block main %}
|
||||
<div id="buttons">
|
||||
|
||||
@ -46,19 +46,14 @@
|
||||
<div class="maincontent">
|
||||
<p>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.
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
This particular work in progress project <strong>Distribusi-verse</strong> is an attempt to make distribusi into a webinterface that can be operated remotely without any knowlegde of CLI. Attempting to combine the ideas of distribusi with the ideas of a <a href="https://tildeverse.org/">Tildeverse</a> and <a href="https://tilde.club/">Tilde club</a>, but also be neither of these ideas. See a full list of tildeverse members <a href="https://tildeverse.org/members/">here</a>.</p>
|
||||
<hr>
|
||||
<p>This project is made for <a href="https://www.wdka.nl/practices/autonomous-practices/">Autonomous Practices </a>at the <a href="https://www.wdka.nl/">WDKA</a> in Rotterdam.</p>
|
||||
</div>
|
||||
<!-- a div with all the distribusis listed in the distribusiverse -->
|
||||
<div id="distribusiverse" class="maincontent">
|
||||
<h2>List of distribusis</h2>
|
||||
{% include 'filtermenu.html' %}
|
||||
{% include 'base/filtermenu.html' %}
|
||||
<ul id="distribusi-index">
|
||||
{% for name, distribusi in distribusisindex.items() %}
|
||||
<li class='distribusi filter {{ distribusi["term"] }} {{ distribusi["year"] }} {{ distribusi["course"] }}'>
|
||||
<li class='distribusi filter {{ distribusi["category"] }} {{ distribusi["year"] }} '>
|
||||
<a href='stash/{{name}}/index.html'>{{distribusi["username"]}}:{{name}}</a>
|
||||
{% for tag in distribusi["tags"] %}
|
||||
<span class="tags">{{tag}}</span>
|
@ -1,28 +0,0 @@
|
||||
{% block menu %}
|
||||
<button onclick="filterSelection('all')" id="removefilter">Remove filter</button>
|
||||
<div class="dropdown">
|
||||
<button id="Academicyear" class="dropbtn">Academic year</button>
|
||||
<div class="dropdown-content">
|
||||
{% for year in years %}
|
||||
<button type="button" name="button" onclick="filterSelection('{{ year[0] }}', '{{ year[1] }}', 'Academicyear')" >{{ year[1] }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button id="Term" class="dropbtn">Term</button>
|
||||
<div class="dropdown-content">
|
||||
{% for term in terms %}
|
||||
<button type="button" name="button" onclick="filterSelection('{{ term[0] }}', '{{ term[1] }}', 'Term')" >{{ term[1] }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button id="Course" class="dropbtn">Course</button>
|
||||
<div class="dropdown-content">
|
||||
{% for course in courses %}
|
||||
<button type="button" name="button" onclick="filterSelection('{{ course[0] }}' , '{{ course[1] }}', 'Course')" >{{ course[1] }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<input id="tagsearch" type="text" placeholder="Search..">
|
||||
{% endblock menu %}
|