Compare commits

..

53 Commits

Author SHA1 Message Date
13effa5372 Update 'README.md' 2024-04-01 23:33:59 +02:00
49e141ebbd Update 'README.md' 2024-03-31 15:49:05 +02:00
b6c001b8eb template for pdf and image upload 2024-03-31 12:25:53 +02:00
e521349ad5 relative directory still needs solution 2024-03-31 12:25:22 +02:00
fd3ee119fb required forms 2024-03-31 11:51:44 +02:00
b499fea9cf refactoring some file names and locations 2024-03-31 11:33:03 +02:00
572cb0fe16 removing redundant code and autoformatting 2024-03-31 00:30:40 +01:00
5e48463403 small fixed 2024-03-30 20:23:24 +01:00
f393f7389f moved form code to application folder 2024-03-30 18:22:53 +01:00
b435b159e3 moved form code to application folder 2024-03-30 18:09:04 +01:00
7fc2534744 I are programmer 2024-03-30 16:34:11 +01:00
fe0aa6e826 more unstructured css wrangling 2024-03-30 15:52:45 +01:00
7a0d84cec3 further login styling 2024-03-30 14:09:35 +01:00
8f27a86514 icons for login 2024-03-30 13:40:07 +01:00
08530a8c85 new pip freeze 2024-03-30 12:06:43 +01:00
25bd8b36e7 readme tickbox 2024-03-30 12:03:08 +01:00
6010b76309 user login part 2 2024-03-30 12:00:47 +01:00
c18f7735f5 clear diff 2024-03-30 11:31:29 +01:00
a5bbaacf8f Update 'README.md' 2024-03-02 13:13:29 +01:00
d6b3286bc5 continue 2023-12-03 15:32:54 +01:00
7db20b290e sort and format 2023-12-03 14:19:53 +01:00
b986b11850 update requirements 2023-12-03 14:17:49 +01:00
544d3fa8a0 remove distribusi user fields from copied model 2023-12-03 14:16:58 +01:00
12bb82a976 sunday developer 2023-12-03 13:30:45 +01:00
c77661e985 user login scaffolding 2023-12-03 11:33:07 +01:00
4a2e6f76ca black autoformatting 2023-12-01 15:58:33 +01:00
452bf73b84 upload pdf added to template and form 2023-12-01 15:23:37 +01:00
e779a764b4 added option to download pdf 2023-11-29 23:40:12 +01:00
36090a402c deleted custion and borrow fields 2023-11-29 23:14:58 +01:00
944ecb59ea Delete 'library/data/files/fileshere' 2023-11-26 18:46:12 +01:00
a67888117f end of day README update 2023-11-26 18:44:16 +01:00
221aa229d4 black formatting 2023-11-26 18:38:33 +01:00
8fe24a8820 placeholder files 2023-11-26 18:23:42 +01:00
0c21688c00 adding flexible title from settings file 2023-11-26 18:15:22 +01:00
fc750a1e14 large breaking update 2023-11-26 17:52:21 +01:00
76fc4719c2 Update 'README.md' 2023-11-24 12:38:06 +01:00
f35260bb4a Update 'README.md' 2023-11-24 11:48:09 +01:00
a99abc0b09 moving towards settings.toml for config 2023-11-15 20:19:07 +01:00
62473c0f48 remove varia specific images 2023-07-15 15:35:58 +02:00
cc3b5f55b8 remove varia specific font 2023-07-15 15:28:53 +02:00
200b689f55 remove varia specific code 2023-07-15 14:36:55 +02:00
1a72a74184 first readme adjustment 2023-07-15 14:20:22 +02:00
9d4e67b089 updated pip requirements 2023-07-14 21:17:28 +02:00
52b513bc2a search as part of flask 2023-07-14 21:16:30 +02:00
ff7189af66 index books onn applications startup and once every 10 minutes 2023-07-14 12:54:43 +02:00
d306b61b2d search the library 2023-07-12 21:09:22 +02:00
ff30e05391 updated requirements 2023-07-12 18:51:05 +02:00
c35f80582c pompidom 2023-06-05 20:24:04 +02:00
baecbc71b1 on hover cursor pointer 2023-06-05 20:19:56 +02:00
ad11579ada search bar placeholder 2023-06-05 20:18:28 +02:00
eb016d23bb small edit pyproject file 2023-06-05 20:18:00 +02:00
e294b56a85 lightly less crazy design 2023-06-05 20:02:15 +02:00
bec27fdaff requirements.txt for debain bullseye python 3.9.2 2023-06-05 19:34:03 +02:00
70 changed files with 1900 additions and 1101 deletions

9
.gitignore vendored
View File

@ -6,3 +6,12 @@
build/ build/
dist/ dist/
pip-wheel-metadata/ pip-wheel-metadata/
*.db
instance/*
migrations/*
library/data/*.toc
library/data/*.csv
library/data/*.seg
library/data/MAIN_WRITELOCK
library/files/*.pdf
settings_development.toml

View File

@ -1,6 +1,7 @@
# varia-library-website # csv-library-website
> Work In Progress > Work in progress: this is a simple flask application that turns a csv file into a website.
> originally used to show the physical books like in the [Varia Library Website](https://library.varia.zone/).. but it can be used for any collection of books, digital or physical as described in the csv file
## Hacking ## Hacking
@ -17,3 +18,19 @@ $ cd library && python page.py
``` ```
Or run `make`. Or run `make`.
## major changes
* currently this software is broken.
## readme driven development
* add regular login instead of a secret key ✅
* have a settings file for the application ✅
* remove varia library specific code ✅
* downloadable pdfs ✅
* mail for forgotten passwords tested/
* flask-oidc for keycloak login
* refactor csvparser into library and publication classes
* uploadable pdfs
* upon boot check for images of the book otherwise extract front page of pdfs
* implement [openlibrary](https://openlibrary.org/) ISBN to book in libarary software
* [openreads](https://github.com/mateusz-bak/openreads) ability to import this csv file
* maybe also GoodReads, BookWyrm

92
library/app.py Normal file
View File

@ -0,0 +1,92 @@
import csv
import os
import tomllib
import flask_apscheduler
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
from whoosh.fields import *
from whoosh.index import create_in
from whoosh.qparser import QueryParser
from application.csvparser import CsvParser
db = SQLAlchemy()
migrate = Migrate()
bcrypt = Bcrypt()
login_manager = LoginManager()
SCRIPT_DIR = os.path.dirname(__file__)
DATA_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "data"))
def create_app():
settings = settings_from_file()
APP = Flask(__name__, static_folder="static")
APP.config["SECRET_KEY"] = os.urandom(24)
APP.config["UPLOAD_FOLDER"] = "tmpupload"
APP.config["IMAGE_FOLDER"] = "static/images"
APP.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///libraryusers.db"
APP.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
APP.config["LIBRARY_FILENAME"] = settings["libaryfilename"]
APP.config["TITLE"] = settings["title"]
csrf = CSRFProtect(APP)
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)
scheduler = flask_apscheduler.APScheduler()
scheduler.api_enabled = False
scheduler.init_app(APP)
scheduler.start()
index_books(APP.config["LIBRARY_FILENAME"], APP.config["IMAGE_FOLDER"])
@scheduler.task("interval", id="update", minutes=10)
def update():
index_books(APP.config["LIBRARY_FILENAME"], APP.config["IMAGE_FOLDER"])
@APP.context_processor
def inject_title():
return dict(title=APP.config["TITLE"])
return APP
def get_app():
APP = Flask(__name__, static_folder="static")
return APP
def index_books(filename: str, image_folder: str):
csvparser = CsvParser(filename, image_folder)
filename = os.path.join(DATA_DIR, csvparser.csv_file)
schema = Schema(
title=TEXT(stored=True), path=ID(stored=True), content=TEXT
)
ix = create_in(DATA_DIR, schema)
writer = ix.writer()
with open(filename, "r", encoding="utf_8_sig") as libcsv:
csv_as_dict = csv.DictReader(libcsv)
for row in csv_as_dict:
rowcontent = csvparser.concatenate_csv_row(row)
writer.add_document(title=row["Id"], path="/a", content=rowcontent)
writer.commit()
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)

View File

@ -0,0 +1,218 @@
import csv
import os
import shutil
from tempfile import NamedTemporaryFile
SCRIPT_DIR = os.path.dirname(__file__)
DATA_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "../data"))
FIELDNAMES = [
"Id",
"Publication",
"Author",
"Year",
"Fields",
"Type",
"Publishers",
"License",
"LicenseShort",
"Highlights",
"Comments",
"Files",
]
class CsvParser:
def __init__(self, csv_file, image_dir):
self.csv_file = csv_file
self.image_dir = image_dir
def _hasimage(self, id):
"""does this Id from the csv have an image uploaded"""
image_jpg = os.path.join(self.image_dir, "image-{0}.jpg".format(id))
if os.path.exists(image_jpg):
return True
else:
return False
def _getpublicationfromcsvrow(self, row):
"""get entire publication info from a csv row"""
year = row["Year"]
if not year:
year = "Unknown"
license = row["License"]
if not license:
license = "No license mentioned"
pubinfo = {
"Title": row["Publication"],
"Author": row["Author"],
"Year": year,
"Fields": row["Fields"],
"Type": row["Type"],
"Publishers": row["Publishers"],
"License": license,
"Highlights": row["Highlights"],
"Comments": row["Comments"],
"Files": row["Files"],
"Image": self._hasimage(row["Id"]),
}
return pubinfo
def parsecsv(self):
"""Test function to inspect csv file as dict"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
return csv_as_dict
def getpublications(self):
"""get an overview of all publications for the main page"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
publications = {}
for row in csv_as_dict:
year = row["Year"]
if not year:
year = "Unknown"
pubinfo = {
"Title": row["Publication"],
"Author": row["Author"],
"Type": row["Type"].lower().title(),
"Year": year,
"License": row["License"].lower().title(),
"Image": self._hasimage(row["Id"]),
}
publications[row["Id"]] = pubinfo
return publications
def gettypes(self):
"""for the dynamic menu get the unique types of publicatons"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
listoftypes = []
for row in csv_as_dict:
lowertype = row["Type"].lower().title()
if lowertype not in listoftypes:
listoftypes.append(lowertype)
return listoftypes
def getyears(self):
"""for the dynamic menu get the unique years for publicatons"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
listofyears = []
for row in csv_as_dict:
uniqueyear = row["Year"]
if not uniqueyear:
uniqueyear = "Unknown"
if uniqueyear not in listofyears:
listofyears.append(uniqueyear)
listofyears.sort()
return listofyears
def getlicenses(self):
"""for the dynamic menu get the unique liscenses for publicatons"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
listoflicenses = []
for row in csv_as_dict:
license = row["License"].lower().title()
if not license:
license = "No License Mentioned"
if license not in listoflicenses:
listoflicenses.append(license)
return listoflicenses
def getfieldsofinterest(self):
"""for the R&R page get the fields of interest from the publicatons"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
fieldsofinterest = {}
for row in csv_as_dict:
fields = row["Fields"].split(",")
fieldsofinterest[row["Id"]] = fields
return fieldsofinterest
def getfullpublication(self, pubid):
"""For the single book view, most complete overview"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
pubinfo = {}
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
for row in csv_as_dict:
if pubid == row["Id"]:
pubinfo = self._getpublicationfromcsvrow(row)
# print(pubinfo)
return pubinfo
def generatenewpublicationid(self):
"""When uploading a book generate a new unique ID"""
libcsv = open(os.path.join(DATA_DIR, self.csv_file), "r")
allidsincsv = []
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
for row in csv_as_dict:
allidsincsv.append(int(row["Id"]))
return str(max(allidsincsv) + 1)
def writepublication(self, publicationform):
"""When uploading a publication writes entry to the csv"""
id = generatenewpublicationid()
with open(
os.path.join(DATA_DIR, "varlib.csv"), "a", newline=""
) as csvfile:
csv_as_writer = csv.DictWriter(csvfile, FIELDNAMES=FIELDNAMES)
csv_as_writer.writerow(
{
"Id": id,
"Publication": publicationform.uploadpublication.data,
"Author": publicationform.author.data,
"Year": publicationform.year.data,
"Fields": publicationform.fields.data,
"Type": publicationform.type.data,
"Publishers": publicationform.publishers.data,
"License": publicationform.license.data,
"LicenseShort": publicationform.licenseshort.data,
"Highlights": publicationform.highlights.data,
"Comments": publicationform.comments.data,
}
)
print("succesfully written book to csv")
return id
def editborrowedby(self, pubid, borrower):
"""Edits the borrowed by field for a publication entry in csv"""
tempfile = NamedTemporaryFile("w+t", newline="", delete=False)
filename = os.path.join(DATA_DIR, "varlib.csv")
with open(filename, "r", newline="") as libcsv, tempfile:
csv_as_dict = csv.DictReader(libcsv)
csv_as_writer = csv.DictWriter(tempfile, FIELDNAMES=FIELDNAMES)
# use the reader to read where, then writer to write the new row.
csv_as_writer.writeheader()
for row in csv_as_dict:
if pubid == row["Id"]:
print("publication changes borrower")
print(row["Publication"])
row["Currently borrowed by"] = borrower
csv_as_writer.writerow(row)
shutil.move(tempfile.name, filename)
def concatenate_csv_row(self, row):
rowcontent = []
rowcontent.append(row["Publication"])
rowcontent.append(row["Author"])
rowcontent.append(row["Fields"])
rowcontent.append(row["Type"])
rowcontent.append(row["Publishers"])
rowcontent.append(row["Highlights"])
rowcontent.append(row["Comments"])
return " ".join(rowcontent)

View File

@ -1,10 +1,5 @@
"""Form object declaration."""
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import ( from wtforms import StringField, SubmitField, validators
StringField,
SubmitField,
)
from wtforms import validators
from wtforms.validators import Length from wtforms.validators import Length
@ -20,13 +15,4 @@ class BorrowForm(FlaskForm):
), ),
], ],
) )
secret = StringField(
"Librarians secret:",
[
validators.InputRequired(),
Length(
min=2, message="Fill in the secret to unlock to library."
),
],
)
submit = SubmitField("Borrow") submit = SubmitField("Borrow")

View File

@ -0,0 +1,13 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, validators
from wtforms.validators import Email, Length
class ForgotPasswordForm(FlaskForm):
"""Forgotten password csv-library form"""
email = StringField(
"Email address:",
validators=[validators.InputRequired(), Email(), Length(6, 64)],
)
submit = SubmitField("Send email")

View File

@ -0,0 +1,14 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField
from wtforms import (
SubmitField,
validators,
)
class ImageUploadForm(FlaskForm):
"""Image upload form."""
image = FileField(
"Image of the book:",
validators=[FileAllowed(["jpg", "png", "gif", "webp"], "Images only!")],
)
submit = SubmitField("Submit")

View File

@ -0,0 +1,17 @@
"""Login form to validate user."""
from flask_wtf import FlaskForm
from wtforms import PasswordField, StringField, SubmitField, validators
from wtforms.validators import Email, Length
class LoginForm(FlaskForm):
"""Login csv-library form"""
email = StringField(
"Email address:",
validators=[validators.InputRequired(), Email(), Length(6, 64)],
)
password = PasswordField(
"Password:", validators=[validators.InputRequired()]
)
submit = SubmitField("Sign In")

View File

@ -0,0 +1,14 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField
from wtforms import (
SubmitField,
validators,
)
class PdfUploadForm(FlaskForm):
"""Pdf upload form."""
pdf = FileField(
"Pdf of the book:",
validators=[FileAllowed(["pdf"], "Only pdf uploads supported")],
)
submit = SubmitField("Submit")

View File

@ -1,17 +1,13 @@
"""Form object declaration."""
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed from flask_wtf.file import FileAllowed, FileField
from wtforms import validators
from wtforms import ( from wtforms import (
StringField,
IntegerField, IntegerField,
RadioField, RadioField,
StringField,
SubmitField, SubmitField,
validators,
) )
from wtforms.validators import ( from wtforms.validators import Length, NumberRange
Length,
NumberRange,
)
class PublicationForm(FlaskForm): class PublicationForm(FlaskForm):
@ -39,9 +35,10 @@ class PublicationForm(FlaskForm):
], ],
) )
year = IntegerField( year = IntegerField(
"Year:", [validators.InputRequired(), NumberRange(min=0, max=2050)] "Year:",
[validators.InputRequired(), NumberRange(min=0, max=2050)],
default=2023,
) )
custodian = StringField("Custodian:")
fields = StringField("Fields:") fields = StringField("Fields:")
type = StringField( type = StringField(
"Type of publication:", "Type of publication:",
@ -58,34 +55,14 @@ class PublicationForm(FlaskForm):
) )
publishers = StringField("Publishers:") publishers = StringField("Publishers:")
license = StringField("License:") license = StringField("License:")
licenseshort = RadioField(
"Select the closest license type:",
choices=[
("Anti-copyright", "Anti-copyright"),
("No License Mentioned", "No License Mentioned"),
("Free Art License", "Free Art License"),
("Copyright", "Copyright"),
("Copyleft", "Copyleft"),
("Creative Commons", "Creative Commons"),
("Public Domain", "Public Domain"),
(
"GNU Free Documentation License",
"GNU Free Documentation License",
),
],
)
highlights = StringField("Highlights from the publication:") highlights = StringField("Highlights from the publication:")
comments = StringField("Comments on the publication:") comments = StringField("Comments on the publication:")
borrowed = StringField("Currently borrowed by:")
image = FileField( image = FileField(
"Image of the book:", "Image of the book:",
validators=[FileAllowed(["jpg", "png", "gif"], "Images only!")], validators=[FileAllowed(["jpg", "png", "gif"], "Images only!")],
) )
secret = StringField( pdf = FileField(
"Librarians secret:", "Pdf of the book:",
[ validators=[FileAllowed(["pdf"], "Only pdf uploads supported")],
validators.InputRequired(),
Length(min=2, message="Fill in the secret to unlock to library."),
],
) )
submit = SubmitField("Submit") submit = SubmitField("Submit")

View File

@ -0,0 +1,37 @@
"""Register form to make a new user."""
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 csv-library form"""
username = StringField(
"Username:",
validators=[validators.InputRequired(), Length(3, 150)],
)
email = StringField(
"Email address:",
validators=[
validators.InputRequired(),
Email(),
Length(6, 64),
],
)
password = PasswordField(
"New password:",
validators=[validators.InputRequired(), Length(12, 72)],
)
confirmpassword = PasswordField(
"Confirm your password:",
validators=[
validators.InputRequired(),
Length(12, 72),
EqualTo("password", message="Passwords must match !"),
],
)
submit = SubmitField("Register to the library")

View File

@ -0,0 +1,22 @@
"""Reset Password Form form to reset a users PasswordField."""
from flask_wtf import FlaskForm
from wtforms import PasswordField, SubmitField, validators
from wtforms.validators import EqualTo, Length
class ResetPasswordForm(FlaskForm):
"""ResetPassword for csv-library form"""
password = PasswordField(
"New password:",
validators=[validators.InputRequired(), Length(12, 72)],
)
confirmpassword = PasswordField(
"Confirm your password:",
validators=[
validators.InputRequired(),
Length(12, 72),
EqualTo("password", message="Passwords must match !"),
],
)
submit = SubmitField("Reset your password")

View File

@ -0,0 +1,19 @@
from flask_login import UserMixin
from app import db
class User(UserMixin, db.Model):
"""User model class for a user in the library"""
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(150), unique=True, nullable=False)
email = db.Column(db.String(150), unique=True, nullable=False)
password = db.Column(db.String(300), nullable=False, unique=False)
resethash = db.Column(db.String(300), nullable=True, unique=True)
resettime = db.Column(db.DateTime)
def __repr__(self):
return "<User %r>" % self.email

View File

@ -0,0 +1,22 @@
import os
from whoosh.fields import *
from whoosh.index import open_dir
from whoosh.qparser import QueryParser
from application.csvparser import CsvParser
SCRIPT_DIR = os.path.dirname(__file__)
DATA_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "../data"))
def search(searchinput):
"""search and get search result titles and return them as book ids"""
ix = open_dir(DATA_DIR)
with ix.searcher() as searcher:
query = QueryParser("content", ix.schema).parse(searchinput)
search_results = searcher.search(query)
searched_book_ids = []
for book in search_results:
searched_book_ids.append(book["title"])
return searched_book_ids

View File

@ -0,0 +1,59 @@
from datetime import datetime
from uuid import uuid1
from flask import render_template
from flask_mail import Message
from sqlalchemy.exc import (
DatabaseError,
DataError,
InterfaceError,
InvalidRequestError,
)
from app import db
from application.forms.forgotpasswordform import ForgotPasswordForm
from application.models.usermodel import User
def ForgotPassword():
forgotpasswordform = ForgotPasswordForm()
if forgotpasswordform.validate_on_submit():
user = User.query.filter_by(
email=forgotpasswordform.email.data
).first()
if user is not None:
resethash = AddResetPasswordHash(user, forgotpasswordform)
ResetPassWordMessage(user, resethash, mail)
forgotpasswordform.email.errors.append(
f"""If {forgotpasswordform.email.data} exists, an email is send with
a password reset link. (If your inbox doesn't
contain any new mail, please check your spam folder.)"""
)
return render_template(
"user/forgotpassword.html", forgotpasswordform=forgotpasswordform
)
def AddResetPasswordHash(user, forgotpasswordform):
resethash = uuid1().hex
try:
user.resettime = datetime.now()
user.resethash = resethash
db.session.commit()
except (InvalidRequestError, DataError, InterfaceError, DatabaseError):
forgotpasswordform.email.errors.append("Something went wrong!")
db.session.rollback()
return resethash
def ResetPassWordMessage(user, resethash, mail):
msg = Message(
"Forgotten Password ",
sender=("mailer", "test@this.com"),
recipients=[user.email],
)
msg.html = f"""{user.username} has requested a password reset for
libary website.<br><hr>
<a href='http://localhost:5000/resetpassword/{resethash}'>Click here to
reset your password.</a>"""
mail.send(msg)

View File

@ -0,0 +1,30 @@
from flask import abort, flash, redirect, render_template, request, url_for
from flask_bcrypt import check_password_hash
from flask_login import login_user
from application.forms.loginform import LoginForm
from application.models.usermodel import User
def LoginUser():
loginform = LoginForm()
if loginform.validate_on_submit():
try:
user = User.query.filter_by(email=loginform.email.data).first()
if user is None:
loginform.password.errors.append("Invalid email or password!")
return render_template("login.html", loginform=loginform)
if check_password_hash(user.password, loginform.password.data):
login_user(user)
flash("Logged in successfully.", "success")
next = request.args.get("next")
if next is not None and not is_safe_url(next): # noqa: F821
return abort(400)
return redirect(next or url_for("index"))
else:
flash("Invalid email or password!", "danger")
loginform.password.errors.append("Invalid email or password!")
return render_template("user/login.html", loginform=loginform)
except Exception as e:
flash(e, "danger")
return render_template("user/login.html", loginform=loginform)

View File

@ -0,0 +1,66 @@
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 (
DatabaseError,
DataError,
IntegrityError,
InterfaceError,
InvalidRequestError,
)
from werkzeug.routing import BuildError
from app import db
from application.forms.registerform import RegisterForm
from application.models.usermodel import User
def RegisterUser():
registerform = RegisterForm()
if registerform.validate_on_submit():
try:
username = registerform.username.data
email = registerform.email.data
password = registerform.confirmpassword.data
newuser = User(
username=username,
email=email,
password=generate_password_hash(password),
)
db.session.add(newuser)
db.session.commit()
flash("Account Succesfully created", "success")
login_user(newuser)
return redirect(url_for("index"))
except InvalidRequestError:
db.session.rollback()
registerform.email.errors.append("Something went wrong!")
flash("Something went wrong!", "danger")
except IntegrityError:
db.session.rollback()
registerform.email.errors.append("User already exists!")
flash("User already exists!", "warning")
except DataError:
db.session.rollback()
registerform.email.errors.append("Invalid Entry")
flash("Invalid Entry", "warning")
except InterfaceError:
db.session.rollback()
registerform.email.errors.append(
"Error connecting to the database"
)
flash("Error connecting to the database", "danger")
except DatabaseError:
db.session.rollback()
registerform.email.errors.append(
"Error connecting to the database"
)
flash("Error connecting to the database", "danger")
except BuildError:
db.session.rollback()
registerform.email.errors.append("Unknown error occured!")
flash("An error occured !", "danger")
return render_template("user/register.html", registerform=registerform)

View File

@ -0,0 +1,78 @@
from datetime import datetime
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 (
DatabaseError,
DataError,
IntegrityError,
InterfaceError,
InvalidRequestError,
)
from werkzeug.routing import BuildError
from app import db
from application.forms.resetpasswordform import ResetPasswordForm
from application.models.usermodel import User
def ResetPassword(path):
linkvalid = False
user = User.query.filter_by(resethash=path).first()
if user is None:
return redirect(url_for("index"))
timepassed = datetime.now() - user.resettime
if timepassed.days < 1:
linkvalid = True
resetpasswordform = ResetPasswordForm()
if resetpasswordform.validate_on_submit():
return ResetUserPasswordInDB(user, resetpasswordform)
return render_template(
"resetpassword.html",
resetpasswordform=resetpasswordform,
path=path,
linkvalid=linkvalid,
)
def ResetUserPasswordInDB(user, resetpasswordform):
try:
newpassword = resetpasswordform.confirmpassword.data
user.password = generate_password_hash(newpassword)
user.resethash = None
user.resettime = None
db.session.commit()
flash("Password Succesfully updated", "success")
login_user(user)
return redirect(url_for("index"))
except InvalidRequestError:
db.session.rollback()
resetpasswordform.email.errors.append("Something went wrong!")
flash("Something went wrong!", "danger")
except IntegrityError:
db.session.rollback()
resetpasswordform.email.errors.append("User already exists!")
flash("User already exists!", "warning")
except DataError:
db.session.rollback()
resetpasswordform.email.errors.append("Invalid Entry")
flash("Invalid Entry", "warning")
except InterfaceError:
db.session.rollback()
resetpasswordform.email.errors.append(
"Error connecting to the database"
)
flash("Error connecting to the database", "danger")
except DatabaseError:
db.session.rollback()
resetpasswordform.email.errors.append(
"Error connecting to the database"
)
flash("Error connecting to the database", "danger")
except BuildError:
db.session.rollback()
resetpasswordform.email.errors.append("Unknown error occured!")
flash("An error occured !", "danger")

View File

@ -1,224 +0,0 @@
"""This parses the varlib.csv but only in a way
that is actually useful for the site"""
from tempfile import NamedTemporaryFile
import shutil
import csv
import os
script_dir = os.path.dirname(__file__)
data_dir = os.path.abspath(os.path.join(script_dir, "../data"))
image_dir = os.path.abspath(os.path.join(script_dir, "../static/images"))
fieldnames = [
"Id",
"Publication",
"Author",
"Year",
"Custodian",
"Fields",
"Type",
"Publishers",
"License",
"LicenseShort",
"Highlights",
"Comments",
"Currently borrowed by",
]
def parsecsv():
"""Test function to inspect csv file as dict"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
return csv_as_dict
def getpublications():
"""get an overview of all publications for the main page"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
publications = {}
for row in csv_as_dict:
year = row["Year"]
if not year:
year = "Unknown"
pubinfo = {
"Title": row["Publication"],
"Author": row["Author"],
"Type": row["Type"].lower().title(),
"Year": year,
"License": row["LicenseShort"].lower().title(),
"Image": hasimage(row["Id"]),
}
publications[row["Id"]] = pubinfo
return publications
def hasimage(id):
"""does this Id from the csv have an image uploaded"""
image_jpg = os.path.join(image_dir, "image-{0}.jpg".format(id))
if os.path.exists(image_jpg):
return True
else:
return False
def gettypes():
"""for the dynamic menu get the unique types of publicatons"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
listoftypes = []
for row in csv_as_dict:
lowertype = row["Type"].lower().title()
if lowertype not in listoftypes:
listoftypes.append(lowertype)
return listoftypes
def getyears():
"""for the dynamic menu get the unique years for publicatons"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
listofyears = []
for row in csv_as_dict:
uniqueyear = row["Year"]
if not uniqueyear:
uniqueyear = "Unknown"
if uniqueyear not in listofyears:
listofyears.append(uniqueyear)
listofyears.sort()
return listofyears
def getlicenses():
"""for the dynamic menu get the unique liscenses for publicatons"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
listoflicenses = []
for row in csv_as_dict:
license = row["LicenseShort"].lower().title()
if not license:
license = "No License Mentioned"
if license not in listoflicenses:
listoflicenses.append(license)
return listoflicenses
def getfieldsofinterest():
"""for the R&R page get the fields of interest from the publicatons"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
fieldsofinterest = {}
for row in csv_as_dict:
fields = row["Fields"].split(",")
fieldsofinterest[row["Id"]] = fields
return fieldsofinterest
def getpublicationfromcsvrow(row):
"""get entire publication info from a csv row"""
year = row["Year"]
if not year:
year = "Unknown"
license = row["License"]
if not license:
license = "No license mentioned"
borrowed = row["Currently borrowed by"]
if not borrowed:
borrowed = "No one"
pubinfo = {
"Title": row["Publication"],
"Author": row["Author"],
"Year": year,
"Custodian": row["Custodian"],
"Fields": row["Fields"],
"Type": row["Type"],
"Publishers": row["Publishers"],
"License": license,
"Highlights": row["Highlights"],
"Comments": row["Comments"],
"Borrowed": borrowed,
"Image": hasimage(row["Id"]),
}
return pubinfo
def getfullpublication(pubid):
"""For the single book view, most complete overview"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
pubinfo = {}
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
for row in csv_as_dict:
if pubid == row["Id"]:
pubinfo = getpublicationfromcsvrow(row)
# print(pubinfo)
return pubinfo
def generatenewpublicationid():
"""When uploading a book generate a new unique ID"""
libcsv = open(os.path.join(data_dir, "varlib.csv"), "r")
allidsincsv = []
with libcsv:
csv_as_dict = csv.DictReader(libcsv)
for row in csv_as_dict:
allidsincsv.append(int(row["Id"]))
return str(max(allidsincsv) + 1)
def writepublication(uploadform):
"""When uploading a publication writes entry to the csv"""
id = generatenewpublicationid()
with open(
os.path.join(data_dir, "varlib.csv"), "a", newline=""
) as csvfile:
csv_as_writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
csv_as_writer.writerow(
{
"Id": id,
"Publication": uploadform.uploadpublication.data,
"Author": uploadform.author.data,
"Year": uploadform.year.data,
"Custodian": uploadform.custodian.data,
"Fields": uploadform.fields.data,
"Type": uploadform.type.data,
"Publishers": uploadform.publishers.data,
"License": uploadform.license.data,
"LicenseShort": uploadform.licenseshort.data,
"Highlights": uploadform.highlights.data,
"Comments": uploadform.comments.data,
"Currently borrowed by": uploadform.borrowed.data,
}
)
print("succesfully written book to csv")
return id
def editborrowedby(pubid, borrower):
"""Edits the borrowed by field for a publication entry in csv"""
tempfile = NamedTemporaryFile("w+t", newline="", delete=False)
filename = os.path.join(data_dir, "varlib.csv")
with open(filename, "r", newline="") as libcsv, tempfile:
csv_as_dict = csv.DictReader(libcsv)
csv_as_writer = csv.DictWriter(tempfile, fieldnames=fieldnames)
# use the reader to read where, then writer to write the new row.
csv_as_writer.writeheader()
for row in csv_as_dict:
if pubid == row["Id"]:
print("publication changes borrower")
print(row["Publication"])
row["Currently borrowed by"] = borrower
csv_as_writer.writerow(row)
shutil.move(tempfile.name, filename)

View File

@ -1,66 +0,0 @@
Id,Publication,Author,Year,Custodian,Fields,Type,Publishers,License,LicenseShort,Highlights,Comments,Currently borrowed by
1,The Economics of Anarchism,Anarcho,2012,Varia,"Economics, Anarchism",Zine,theanarchistlibrary.org,Anti-copyright,Anti-copyright,"The labourer retains, even after he has recieved his wages, a natural right in the thing he has produced",,No one
2,Identity Politics - An Anthology,The Anarchist Library,,Varia,Identity politics,Zine,Paper Jam Collective,No license mentioned,No license mentioned,,Varia,No one
3,The mythology of work,CrimeThinc.com,,Varia,"Work, Anticapitalism",Zine,CrimeThinc.com,No license mentioned,No license mentioned,,"A selection from 'Work', a 376-page analysis of contemporary capitalism",
4,Forget Shorter Showers - Why Personal Change Does Not Equal Political Change,Derrick Jensen,2009,Varia,Environmental justice,Zine,,No license mentioned,No license mentioned,Green consumerism isn't enough.,,
5,Choreo-Graphic-Hypothesis,"<meta-author=""Joana Chicau"";>",2018,Varia,"Live Coding, Choreography",Paperback,Self published: Joana Chicau,Free Art License 1.3,Free Art License,"Theatrical actions are not necessary to the performance, Avoid if at all possible",,
6,Point of no Return,,2013,Varia,Anarchism in the West,Zine,,No license mentioned,No license mentioned,,Stories about becoming an anarchist in the West in the 80s,
7,Waar slapen de kroegtijgers van Oud Charlois,Jacco Weener,,Varia,"Oud Charlois, Drawing",Paperback,Self-published,No license mentioned,No license mentioned,,,
8,Dubbeldruk Play with me,"Editorial team: Karin de Jong, Giulia de Giovanelli, Floor van Luijk",2019,Varia,"Copyright, Publishing, Printing",Parazine,Printroom,Free art license,Free art license,,"Confusing licenses mentioned in the about text on the back of the publication, FAL, Copy-left and copy right all mentioned",
9,To Boldly Go: a starters guide to hand made and diy films,Film Werkplaats Worm Rotterdam,2008,Varia,"Experimental film, Analog film, DIY, Hand-made",Paperback,Knust (Nijmegen),'No license mentioned,No license mentioned,,,
10,Anarchism: Basic concepts and ideas,Peter Storm,2018,Varia,"Anarchism, Dutch Theory",Zine,Ravotr Editions in collaboration with Paper Jam,Feel free to use and copy this text as you see fit. Mentioning source and author would be greatly appreciated.,No license mentioned,,Revised text of a transcribed lecture Storm gave in Rotterdam.,
11,Queering Anarchism,"Deric Shannon, Abbey Willis",,Varia,"Anarchism, Queer Theory",Zine,Paper Jam Collective,No license mentioned,No license mentioned,,,
12,Abolish restaurants,Prole.info,2010,Varia,"Labour, Food industry",Paperback,Pm Press,Copyright 2010,Copyright,Drawing on a range of anti-capitalist ideas as well as a heaping plate of personal experience,,CCL
13,"Elk Woord Een Vonk: Verboden Teksten, verwerpelijke vervolging: de zaak Joke Kaviaar",Steungroep 13 September,2014,Varia (or Luke?),"Joke Kaviaar, Immigration, Activism, Forbidden Texts, Incarceration",Softcover,Self-published,Copyleft,Copyleft,,https://13-september.nl,
14,A NO BORDERS manifesto,Ill Will Editions,2015,Varia,Migrant justice,Zine,Self-published,No license mentioned,No license mentioned,,,
16,Futur Musique,De fanfare voor vooruit strevende volksmuziek,2018,Varia,"Musical Instruments, DIY",Zine,Self-published,No license mentioned,No license mentioned,Copied instructions on building one's own musical instruments,,
17,Franchir le cap,Quentin Juhel,2019,Varia,Convivial computation,Zine,Self-publsihed,Creative commons CC-BY-SA,Creative commons,,,
18,Consensus: Decision Making,Seeds For Change UK,2010,Varia,"Decision making, Consensus",Zine,Self-published,No license mentioned,No license mentioned,Short guide ,,
19,"Waar ook op gestemd wordt, wij laten ons niet regeren",Anarchistische Groep Nijmegen,2014,Varia,Democracy,Zine,Self-published,No license mentioned,No license mentioned,,,
20,The NGO sector: the trojan horse of capitalism,Crn Blok,,Varia,"Anti-capitalism, NGO",Zine,Self-published,Copyleft,Copyleft,,,
21,Messing Around With Packet Radio,"Roel Roscam Abbing, Dennis De Bel",,Varia,"Packet radio, DIY, radio, wireless",Zine,Self-published,Public domain,Public domain,,,
22,Organising Socially: affinity groups & decision making,Seeds for change,,Varia,"Consensus, Decision making, Affinity groups",Zine,Paper Jam Collective,No license mentioned,No license mentioned,,,
23,The Moral of the Xerox,"Florian Cramer, Clara Lobregat Balaguer",2017,Varia,"Piracy, Cultural Appropriation",Zine,Self-published,No license mentioned,No license mentioned,"Printed in diocese of Cologne, Germany on the joyous occasion of the Pluriversale VII: Stealing from the west for the critical parishioners of Akademie der Kunste der Welt",,
24,Non-Western Anarchisms,Jason Adams,,Varia,Non-Western Anarchisms,Zine,Zaba Books,No license mentioned,No license mentioned,"The purpose of this paper is to help anarchist/anti-authoritarian movements active today to reconceptualise the history and theory of first-wave anarchism on the global level, and to reconsider its relevance to the continuing anarchist project.",,
33,The immaterial labor union #7: immersive advertisement,Lídia Pereira and Δεριζαματζορ Προμπλεμ ιναυστραλια,,Varia,"labour, Advertisement, immersion, social media",Zine,Self-published,Zine is published under Gnu free documentation license 1.3 unless otherwise specified ,GNU Free Documentation License,,,
34,The immateriality labor union #10: immateriality,Lídia Pereira and Δεριζαματζορ Προμπλεμ ιναυστραλια,2017,Varia,"Labour, Immateriality",Zine,Self-published,GNU Free Documentation License,GNU Free Documentation License,,Varia,No one
35,The immaterial labor union. Special Issue #1: Homebrew Server Club,Homebrew Server Club,2017,Varia,"Self-Hosting, Servers, DIY",Zine,Self-published,CC-BY-SA,Creative commons,,,
36,Pervasive labour union. Special issue #2: The Entreprecariat,Silvio Lorusso,2017,Varia,"Entreprecariat, Labour, Precarity",Zine,Self-published,No license mentioned,No license mentioned,,Between April and May 2017 the Zine's name changed from Immaterial Labor Union to Pervasive Labour Union,
37,'Pervasive labour union #13: Fed Up,Lídia Pereira,2019,Varia,"Labour, DIY, federation",Zine,Self-published,"GNU Free Documentation License 1.3, CC-0, Copyright (C) 2019, Julia Janssen, Peer Production License",GNU Free Documentation License,,,
38,Each Page a Function,Raphaël Bastide,2019,Varia,"Automation, Drawing, Web to Print",Paperback,LeMegot editions,No license mentioned,No license mentioned,,,
39,In Beweging: Magazine van de Anarchistische Groep Nijmegen,Anarchistische Groep Nijmegen ,2018,Varia,"Anarchism, 1st of May, Nijmegen",Zine,Self-published,No license mentioned,No license mentioned,,Anarchistische Bieb de Zwarte Uil. library in Nijmegen open on saturday from 12:00 till 17:30,
40,Deep pockets #2 Shadowbook: Writing through the digital 2014-2018,Miriam Rasch,2018,Varia,"Language, digital, communication",Paperback,Institute of Network Cultures,CC BY-NC-SA 4.0,Creative Commons,,,
41,The Techno Galactic Software Observatory Reader,Constant,2018,Varia,"Software Studies, Software Curious People, Observation",Paperback,Self- published,No license mentioned,No license mentioned,,Reader for Techno Galactic Software Observatory work session,
42,Upsetting Settings,Piet Zwart Institute,2019,Varia,"Catalogue, Experimental publishing",Softcover,XPUB,Copyleft,Copyleft,,Dissertations from the Piet Zwart Institute 2017-2019,
43,I Have Witnessed First Time Experiences ,Connie Butler,2016,Varia,Fine Art,Softcover,Piet Zwart institute MFA,Copyright 2016,Copyright,,,
44,The Age of Aquariums: old horoscopes for the new year,Brenda Bosma,2013,Varia,Horoscope,Paperback,Subbacultcha!,No license mentioned,No license mentioned,,,
45,The Internet Measurement Handbook,,2018,Varia,"Internet, blackout, cyber-security",Paperback,Netblocks,CC,Creative Commons,,,
46,Issue #4 2019,Design Department Sandberg,2019,Varia ,Design theory,Paperback,Drukkerij RaddraaierSSP,No license mentioned,No license mentioned,,Dissertations from the Design Museum,
47,O Bike and Friends,Dennis De Bel,,Varia,"Bike sharing, DIY",Zine,Self-published,No license mentioned,No license mentioned,,,
48,Algoliterary Encounters,Algolit,2017,Varia,"Natural text processing, machine learning, algorithms",Catalogue,Algolit,Free Art License,Free Art License,,,
49,Der Fall von Afrin,Unknown,,Varia,Turkish politics,Zine,Unknown,No license mentioned,No license mentioned,,,
50,Andy de Fiets: Letter to Robin Kinross ,"Paul Haworth, Sam de Groot",2010,Varia,Typography,Paperback,TRUE TRUE TRUE Amsterdam,"Copyright 2009,2010",Copyright,,,
51,Ora Elastică,ODD,2018,Varia,"Art, Indonesia, Romania, non-western art theory",Softcover,frACTalia,No license mentioned,No license mentioned,,,
52,4 x Through The Labyrinth,"O. Nicolai & J. Wenzel, translated by Sadie Plant",2012,Joana Chicau,Labyrinths,Softcover,Rollo Press,Copyright,Copyright,,,
53,Towards an Immersive Intelligence,Joseph Nechvatal,2009,Joana Chicau?,"Virtual reality, Computer Art",Paperback,Edgewise,Copyright,Copyright,,,
54,Xavan et Jaluka - The Death of the Authors 1946,Peter Westenberg,2018,Varia,Public domain,Zine,Constant Verlag,Copyleft,Copyleft,,,
55,Spreekt U Sint-Gillis? Parlez-vous Saint-Gillois?,,,Varia,"Bruxelles, Language, Sint-Gillis",Softcover,Constant Verlag,Copyleft,Copyleft,,,
56,Mondothèque: a radiated book,"André Castro , Sînziana Păltineanu , Dennis Pohl , Dick Reckard , Natacha Roussel , Femke Snelting , Alexia de Visscher",2016,Varia,"Archive, Otlet, Library science, Mondaneum, Google, Mons",Softcover,Constant,Copyleft,Copyleft,,,
57,"The Death of the Authors: 'James Joyce, Rabindranath Tagore & Their Return to Life in Four Seasons",A Constant Remix,2013,Varia,"James Joyce, Ulysses",Softcover,Constant,Public domain,Public domain,,'generated from Public domain sources,
58,Verbindingen/Jonctions 10 : Tracks in electr(on)ic fields,Constant,2009,Varia,"Art, activism, technological culture",Softcover,Constant,Copyleft,Copyleft,,,
59,Conversations,Constant,2014,Varia,"Software studies, Libre graphics",Softcover,Constant Verlag,Copyleft,Copyleft,,,
60,Networks of one's own #1: Etherbox,"Michael Murtaugh, An Mertens, Roel Roscam Abbing, Femke Snelting",2018,Varia,"Networks, Digital Infrastructures, DIY, DIWO, Executable publication, Experimental Publishing",Paperback,Constant Verlag,Copyleft,Copyleft,,,
61,Mots de la cage aux ours - woorden uit de berenkuil,Constant,2012,Varia,"words, language, Bruxelles",Softcover,Constant,Copyleft,Copyleft,,,
62,Snake rituals and switching circuits,Florian Cramer,2009,Danny,"mass communication, personal communication, new media",paperback,Piet Zwart Institute,Creative Commons Attribution-Share Alike 3.0,Creative Commons,The function of a medium is ultimately decided by its users and not by its creators,,
63,Magium issue 1: On Eating in isolation,Alice Strete,2020,Varia,"food, sharing, personal stories, consumption",zine,Self Published,Free Art License,Free Art License,,,No one
64,Networks of One's Own 2: three takes on taking care,"Varia, Constant and Colm ONeill",2019,Varia,"Software, internet, taking care, homebrew",paperback,Varia,Copyleft,Copyleft,Networks Of Ones Own is a periodic para-nodal publication that is itself collectively within a network.,,No one
65,My Hard-Drive Died Along With My Heart ,Thomas Walsklaar,2016,Varia,"Hard-drives, Data, Loss, Trust, Technology, collection, materiality, obsolescence, preservation, progress, writing ",paperback,Self Published, No License Mentioned,No License Mentioned,,"We always seem to be looking for a new technical solution for knowledge and information storage. We hope there is one magical, final solution, one that will solve every issu But easy solutions create their own problems. The perceived view of the stable nature of digital information differs from reality. There are many points of failure, like old physical formats, lost or non functional machines, companies that go bankrupt, file formats with no support in the future, or changing user licenses. It seems that the more technical the technology gets, the more problems it creates.",No one
67,"Low power to the people: Pirates, protest. and politics in FM radio activism",Christina Dunbar-Hester,2014,Danny,"Radio, Activism, FM, Wireless, Legal, Policies, Piracy, Politics",hardcover,MIT press,Copyright,Copyright,The internet didn't drop down from the sky--it was created by the military and we need to take it back,,No one
68,Pretty fly for a wi-fi,Roel Roscam Abbing,2014,Varia,"Wireless, Wifi, internet, DIY, antennas ",paperback,Self Published, GNU Free Documentation License 1.3,GNU Free Documentation License,,appreciate the DIY etched beauty of Object #4,Danny
69,TX Pirate radio dispatches from eighties London ,Stephen Hebditch,2017,Danny,"Radio, Activism, FM, AM, Wireless, Piracy, Politics, Zine culture",paperback,Tx Publications,Copyright,Copyright,,,Danny
70,The impossibility of interface,Matthew Fuller,2006,Varia,"Interface, Software, Software culture, Media design, ",paperback,HRO,No License Mentioned,No License Mentioned,,"Such contextualisation, in turn, suggests ways of developing digital media with an awareness of its social ramifications.",
71,Let's use free speech to praise pirate radio ,Andrew Bushard,2014,Danny,"Radio, Poetry, Piracy",paperback, Free Press Media Press Inc,No License Mentioned,No License Mentioned,At the risk of sounding melodramatic I want to consider pirate radio as innately noble,"25 Poems, 26 Pages Pirate radio represents magical rebellion. Pirate radio signifies creative protest. Pirate radio can change the world.",
72,Cursusboek voor het N-examen,VERON vereniging experimenteel onderzoek nederland,2012,Varia,"Radio, Broadcasting, Exams, equipment, Radio amateurs",Paperback,Stichting servicebureau VERON,Copyright,Copyright,,,No one
73,Freax the brief history of the computer demoscene,Tamás Polgár,2008,Danny,"Demoscene, Coding, Software art, Amiga, Home Computers, Music",paperback,CSW Verlag digitalkultur,Copyright,Copyright,,Very thorough first hand account of the demoscene until the end of the amiga era. The book hints towards freax volume 2 but this book was never made.,Luke
74,Tasks of the Contingent Librarian,Simon Browne,2020,Varia,Contingent Librarianship,Box of cards,de Appel,Copyleft,Copyleft,,,
75,Handmade Electronic Music,Nicolas Collins,2009,Danny,"Music, DIY, Hacking, Hardware, How-To",Paperback,Routledge,Copyright,Copyright,,,No one
1 Id Publication Author Year Custodian Fields Type Publishers License LicenseShort Highlights Comments Currently borrowed by
2 1 The Economics of Anarchism Anarcho 2012 Varia Economics, Anarchism Zine theanarchistlibrary.org Anti-copyright Anti-copyright The labourer retains, even after he has recieved his wages, a natural right in the thing he has produced No one
3 2 Identity Politics - An Anthology The Anarchist Library Varia Identity politics Zine Paper Jam Collective No license mentioned No license mentioned Varia No one
4 3 The mythology of work CrimeThinc.com Varia Work, Anticapitalism Zine CrimeThinc.com No license mentioned No license mentioned A selection from 'Work', a 376-page analysis of contemporary capitalism
5 4 Forget Shorter Showers - Why Personal Change Does Not Equal Political Change Derrick Jensen 2009 Varia Environmental justice Zine No license mentioned No license mentioned Green consumerism isn't enough.
6 5 Choreo-Graphic-Hypothesis <meta-author="Joana Chicau";> 2018 Varia Live Coding, Choreography Paperback Self published: Joana Chicau Free Art License 1.3 Free Art License Theatrical actions are not necessary to the performance, Avoid if at all possible
7 6 Point of no Return 2013 Varia Anarchism in the West Zine No license mentioned No license mentioned Stories about becoming an anarchist in the West in the 80s
8 7 Waar slapen de kroegtijgers van Oud Charlois Jacco Weener Varia Oud Charlois, Drawing Paperback Self-published No license mentioned No license mentioned
9 8 Dubbeldruk Play with me Editorial team: Karin de Jong, Giulia de Giovanelli, Floor van Luijk 2019 Varia Copyright, Publishing, Printing Parazine Printroom Free art license Free art license Confusing licenses mentioned in the about text on the back of the publication, FAL, Copy-left and copy right all mentioned
10 9 To Boldly Go: a starters guide to hand made and diy films Film Werkplaats Worm Rotterdam 2008 Varia Experimental film, Analog film, DIY, Hand-made Paperback Knust (Nijmegen) 'No license mentioned No license mentioned
11 10 Anarchism: Basic concepts and ideas Peter Storm 2018 Varia Anarchism, Dutch Theory Zine Ravotr Editions in collaboration with Paper Jam Feel free to use and copy this text as you see fit. Mentioning source and author would be greatly appreciated. No license mentioned Revised text of a transcribed lecture Storm gave in Rotterdam.
12 11 Queering Anarchism Deric Shannon, Abbey Willis Varia Anarchism, Queer Theory Zine Paper Jam Collective No license mentioned No license mentioned
13 12 Abolish restaurants Prole.info 2010 Varia Labour, Food industry Paperback Pm Press Copyright 2010 Copyright Drawing on a range of anti-capitalist ideas as well as a heaping plate of personal experience CCL
14 13 Elk Woord Een Vonk: Verboden Teksten, verwerpelijke vervolging: de zaak Joke Kaviaar Steungroep 13 September 2014 Varia (or Luke?) Joke Kaviaar, Immigration, Activism, Forbidden Texts, Incarceration Softcover Self-published Copyleft Copyleft https://13-september.nl
15 14 A NO BORDERS manifesto Ill Will Editions 2015 Varia Migrant justice Zine Self-published No license mentioned No license mentioned
16 16 Futur Musique De fanfare voor vooruit strevende volksmuziek 2018 Varia Musical Instruments, DIY Zine Self-published No license mentioned No license mentioned Copied instructions on building one's own musical instruments
17 17 Franchir le cap Quentin Juhel 2019 Varia Convivial computation Zine Self-publsihed Creative commons CC-BY-SA Creative commons
18 18 Consensus: Decision Making Seeds For Change UK 2010 Varia Decision making, Consensus Zine Self-published No license mentioned No license mentioned Short guide
19 19 Waar ook op gestemd wordt, wij laten ons niet regeren Anarchistische Groep Nijmegen 2014 Varia Democracy Zine Self-published No license mentioned No license mentioned
20 20 The NGO sector: the trojan horse of capitalism Crn Blok Varia Anti-capitalism, NGO Zine Self-published Copyleft Copyleft
21 21 Messing Around With Packet Radio Roel Roscam Abbing, Dennis De Bel Varia Packet radio, DIY, radio, wireless Zine Self-published Public domain Public domain
22 22 Organising Socially: affinity groups & decision making Seeds for change Varia Consensus, Decision making, Affinity groups Zine Paper Jam Collective No license mentioned No license mentioned
23 23 The Moral of the Xerox Florian Cramer, Clara Lobregat Balaguer 2017 Varia Piracy, Cultural Appropriation Zine Self-published No license mentioned No license mentioned Printed in diocese of Cologne, Germany on the joyous occasion of the Pluriversale VII: Stealing from the west for the critical parishioners of Akademie der Kunste der Welt
24 24 Non-Western Anarchisms Jason Adams Varia Non-Western Anarchisms Zine Zaba Books No license mentioned No license mentioned The purpose of this paper is to help anarchist/anti-authoritarian movements active today to reconceptualise the history and theory of first-wave anarchism on the global level, and to reconsider its relevance to the continuing anarchist project.
25 33 The immaterial labor union #7: immersive advertisement Lídia Pereira and Δεριζαματζορ Προμπλεμ ιναυστραλια Varia labour, Advertisement, immersion, social media Zine Self-published Zine is published under Gnu free documentation license 1.3 unless otherwise specified GNU Free Documentation License
26 34 The immateriality labor union #10: immateriality Lídia Pereira and Δεριζαματζορ Προμπλεμ ιναυστραλια 2017 Varia Labour, Immateriality Zine Self-published GNU Free Documentation License GNU Free Documentation License Varia No one
27 35 The immaterial labor union. Special Issue #1: Homebrew Server Club Homebrew Server Club 2017 Varia Self-Hosting, Servers, DIY Zine Self-published CC-BY-SA Creative commons
28 36 Pervasive labour union. Special issue #2: The Entreprecariat Silvio Lorusso 2017 Varia Entreprecariat, Labour, Precarity Zine Self-published No license mentioned No license mentioned Between April and May 2017 the Zine's name changed from Immaterial Labor Union to Pervasive Labour Union
29 37 'Pervasive labour union #13: Fed Up Lídia Pereira 2019 Varia Labour, DIY, federation Zine Self-published GNU Free Documentation License 1.3, CC-0, Copyright (C) 2019, Julia Janssen, Peer Production License GNU Free Documentation License
30 38 Each Page a Function Raphaël Bastide 2019 Varia Automation, Drawing, Web to Print Paperback LeMegot editions No license mentioned No license mentioned
31 39 In Beweging: Magazine van de Anarchistische Groep Nijmegen Anarchistische Groep Nijmegen 2018 Varia Anarchism, 1st of May, Nijmegen Zine Self-published No license mentioned No license mentioned Anarchistische Bieb de Zwarte Uil. library in Nijmegen open on saturday from 12:00 till 17:30
32 40 Deep pockets #2 Shadowbook: Writing through the digital 2014-2018 Miriam Rasch 2018 Varia Language, digital, communication Paperback Institute of Network Cultures CC BY-NC-SA 4.0 Creative Commons
33 41 The Techno Galactic Software Observatory Reader Constant 2018 Varia Software Studies, Software Curious People, Observation Paperback Self- published No license mentioned No license mentioned Reader for Techno Galactic Software Observatory work session
34 42 Upsetting Settings Piet Zwart Institute 2019 Varia Catalogue, Experimental publishing Softcover XPUB Copyleft Copyleft Dissertations from the Piet Zwart Institute 2017-2019
35 43 I Have Witnessed First Time Experiences Connie Butler 2016 Varia Fine Art Softcover Piet Zwart institute MFA Copyright 2016 Copyright
36 44 The Age of Aquariums: old horoscopes for the new year Brenda Bosma 2013 Varia Horoscope Paperback Subbacultcha! No license mentioned No license mentioned
37 45 The Internet Measurement Handbook 2018 Varia Internet, blackout, cyber-security Paperback Netblocks CC Creative Commons
38 46 Issue #4 2019 Design Department Sandberg 2019 Varia Design theory Paperback Drukkerij RaddraaierSSP No license mentioned No license mentioned Dissertations from the Design Museum
39 47 O Bike and Friends Dennis De Bel Varia Bike sharing, DIY Zine Self-published No license mentioned No license mentioned
40 48 Algoliterary Encounters Algolit 2017 Varia Natural text processing, machine learning, algorithms Catalogue Algolit Free Art License Free Art License
41 49 Der Fall von Afrin Unknown Varia Turkish politics Zine Unknown No license mentioned No license mentioned
42 50 Andy de Fiets: Letter to Robin Kinross Paul Haworth, Sam de Groot 2010 Varia Typography Paperback TRUE TRUE TRUE Amsterdam Copyright 2009,2010 Copyright
43 51 Ora Elastică ODD 2018 Varia Art, Indonesia, Romania, non-western art theory Softcover frACTalia No license mentioned No license mentioned
44 52 4 x Through The Labyrinth O. Nicolai & J. Wenzel, translated by Sadie Plant 2012 Joana Chicau Labyrinths Softcover Rollo Press Copyright Copyright
45 53 Towards an Immersive Intelligence Joseph Nechvatal 2009 Joana Chicau? Virtual reality, Computer Art Paperback Edgewise Copyright Copyright
46 54 Xavan et Jaluka - The Death of the Authors 1946 Peter Westenberg 2018 Varia Public domain Zine Constant Verlag Copyleft Copyleft
47 55 Spreekt U Sint-Gillis? Parlez-vous Saint-Gillois? Varia Bruxelles, Language, Sint-Gillis Softcover Constant Verlag Copyleft Copyleft
48 56 Mondothèque: a radiated book André Castro , Sînziana Păltineanu , Dennis Pohl , Dick Reckard , Natacha Roussel , Femke Snelting , Alexia de Visscher 2016 Varia Archive, Otlet, Library science, Mondaneum, Google, Mons Softcover Constant Copyleft Copyleft
49 57 The Death of the Authors: 'James Joyce, Rabindranath Tagore & Their Return to Life in Four Seasons A Constant Remix 2013 Varia James Joyce, Ulysses Softcover Constant Public domain Public domain 'generated from Public domain sources
50 58 Verbindingen/Jonctions 10 : Tracks in electr(on)ic fields Constant 2009 Varia Art, activism, technological culture Softcover Constant Copyleft Copyleft
51 59 Conversations Constant 2014 Varia Software studies, Libre graphics Softcover Constant Verlag Copyleft Copyleft
52 60 Networks of one's own #1: Etherbox Michael Murtaugh, An Mertens, Roel Roscam Abbing, Femke Snelting 2018 Varia Networks, Digital Infrastructures, DIY, DIWO, Executable publication, Experimental Publishing Paperback Constant Verlag Copyleft Copyleft
53 61 Mots de la cage aux ours - woorden uit de berenkuil Constant 2012 Varia words, language, Bruxelles Softcover Constant Copyleft Copyleft
54 62 Snake rituals and switching circuits Florian Cramer 2009 Danny mass communication, personal communication, new media paperback Piet Zwart Institute Creative Commons Attribution-Share Alike 3.0 Creative Commons The function of a medium is ultimately decided by its users and not by its creators
55 63 Magium issue 1: On Eating in isolation Alice Strete 2020 Varia food, sharing, personal stories, consumption zine Self Published Free Art License Free Art License No one
56 64 Networks of One's Own 2: three takes on taking care Varia, Constant and Colm O’Neill 2019 Varia Software, internet, taking care, homebrew paperback Varia Copyleft Copyleft Networks Of One’s Own is a periodic para-nodal publication that is itself collectively within a network. No one
57 65 My Hard-Drive Died Along With My Heart Thomas Walsklaar 2016 Varia Hard-drives, Data, Loss, Trust, Technology, collection, materiality, obsolescence, preservation, progress, writing paperback Self Published No License Mentioned No License Mentioned We always seem to be looking for a new technical solution for knowledge and information storage. We hope there is one magical, final solution, one that will solve every issu But easy solutions create their own problems. The perceived view of the stable nature of digital information differs from reality. There are many points of failure, like old physical formats, lost or non functional machines, companies that go bankrupt, file formats with no support in the future, or changing user licenses. It seems that the more technical the technology gets, the more problems it creates. No one
58 67 Low power to the people: Pirates, protest. and politics in FM radio activism Christina Dunbar-Hester 2014 Danny Radio, Activism, FM, Wireless, Legal, Policies, Piracy, Politics hardcover MIT press Copyright Copyright The internet didn't drop down from the sky--it was created by the military and we need to take it back No one
59 68 Pretty fly for a wi-fi Roel Roscam Abbing 2014 Varia Wireless, Wifi, internet, DIY, antennas paperback Self Published GNU Free Documentation License 1.3 GNU Free Documentation License appreciate the DIY etched beauty of Object #4 Danny
60 69 TX Pirate radio dispatches from eighties London Stephen Hebditch 2017 Danny Radio, Activism, FM, AM, Wireless, Piracy, Politics, Zine culture paperback Tx Publications Copyright Copyright Danny
61 70 The impossibility of interface Matthew Fuller 2006 Varia Interface, Software, Software culture, Media design, paperback HRO No License Mentioned No License Mentioned Such contextualisation, in turn, suggests ways of developing digital media with an awareness of its social ramifications.
62 71 Let's use free speech to praise pirate radio Andrew Bushard 2014 Danny Radio, Poetry, Piracy paperback ‎ Free Press Media Press Inc No License Mentioned No License Mentioned At the risk of sounding melodramatic I want to consider pirate radio as innately noble 25 Poems, 26 Pages Pirate radio represents magical rebellion. Pirate radio signifies creative protest. Pirate radio can change the world.
63 72 Cursusboek voor het N-examen VERON vereniging experimenteel onderzoek nederland 2012 Varia Radio, Broadcasting, Exams, equipment, Radio amateurs Paperback Stichting servicebureau VERON Copyright Copyright No one
64 73 Freax the brief history of the computer demoscene Tamás Polgár 2008 Danny Demoscene, Coding, Software art, Amiga, Home Computers, Music paperback CSW Verlag digitalkultur Copyright Copyright Very thorough first hand account of the demoscene until the end of the amiga era. The book hints towards freax volume 2 but this book was never made. Luke
65 74 Tasks of the Contingent Librarian Simon Browne 2020 Varia Contingent Librarianship Box of cards de Appel Copyleft Copyleft
66 75 Handmade Electronic Music Nicolas Collins 2009 Danny Music, DIY, Hacking, Hardware, How-To Paperback Routledge Copyright Copyright No one

23
library/deploydb.py Normal file
View File

@ -0,0 +1,23 @@
from flask_migrate import init, migrate, stamp, upgrade
from app import create_app, db
def deploy():
"""Run deployment of database."""
# This model is required for flask_migrate to make the table
from application.models.usermodel import User # noqa: F401
app = create_app()
app.app_context().push()
db.create_all()
# migrate database to latest revision
init()
stamp()
migrate()
upgrade()
deploy()

0
library/files/files_here Normal file
View File

View File

@ -0,0 +1 @@
Single-database configuration for Flask.

View File

@ -0,0 +1,50 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

113
library/migrations/env.py Normal file
View File

@ -0,0 +1,113 @@
import logging
from logging.config import fileConfig
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.
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")
def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions["migrate"].db.get_engine()
except (TypeError, AttributeError):
# this works with Flask-SQLAlchemy>=3
return current_app.extensions["migrate"].db.engine
def get_engine_url():
try:
return (
get_engine()
.url.render_as_string(hide_password=False)
.replace("%", "%%")
)
except AttributeError:
return str(get_engine().url).replace("%", "%%")
# 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", get_engine_url())
target_db = current_app.extensions["migrate"].db
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_metadata():
if hasattr(target_db, "metadatas"):
return target_db.metadatas[None]
return target_db.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=get_metadata(), literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, "autogenerate", False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info("No changes in schema detected.")
conf_args = current_app.extensions["migrate"].configure_args
if conf_args.get("process_revision_directives") is None:
conf_args["process_revision_directives"] = process_revision_directives
connectable = get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=get_metadata(), **conf_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,27 @@
"""empty message
Revision ID: 3105f85a9d8e
Revises:
Create Date: 2024-03-30 17:50:00.878071
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "3105f85a9d8e"
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@ -1,48 +1,65 @@
"""This is the main flask library page""" """This is the main flask library page"""
import os
import flask
from requests import get
from icalendar import Calendar
import datetime import datetime
import json
import os
from datetime import timedelta
import bcrypt import bcrypt
from flask import ( from flask import (
render_template, Blueprint,
redirect, redirect,
render_template,
request, request,
session,
url_for,
) )
from flask_wtf.csrf import CSRFProtect from flask_login import current_user, login_required, logout_user
from werkzeug.utils import secure_filename from flask_wtf.csrf import CSRFError, CSRFProtect
from icalendar import Calendar
from PIL import Image from PIL import Image
from rnrfeed.rnrfeeder import getevents, getlatestevent from requests import get
from uploadform import PublicationForm from werkzeug.utils import secure_filename
from borrowform import BorrowForm
from csvparser.csvparser import ( from app import create_app, login_manager
getlicenses, from application.search import search
getpublications, from application.csvparser import CsvParser
gettypes, from application.forms.borrowform import BorrowForm
getyears, from application.forms.image_uploadform import ImageUploadForm
getfullpublication, from application.forms.pdf_uploadform import PdfUploadForm
writepublication, from application.forms.publicationform import PublicationForm
editborrowedby, from application.models.usermodel import User
from application.user.forgotpassword import ForgotPassword
from application.user.loginuser import LoginUser
from application.user.registeruser import RegisterUser
from application.user.resetpassword import ResetPassword
APP = create_app()
csrf = CSRFProtect()
csrf.init_app(APP)
files = Blueprint("files", __name__, static_folder="files")
APP.register_blueprint(files)
csvparser = CsvParser(
APP.config["LIBRARY_FILENAME"], APP.config["IMAGE_FOLDER"]
) )
csrf = CSRFProtect() @APP.before_request
APP = flask.Flask(__name__, static_folder="static") def session_handler():
APP.config["SECRET_KEY"] = "ty4425hk54a21eee5719b9s9df7sdfklx" session.permanent = True
APP.config["UPLOAD_FOLDER"] = "tmpupload" APP.permanent_session_lifetime = timedelta(minutes=30)
csrf.init_app(APP)
@APP.route("/") @APP.route("/")
def index(): def index():
"""Main route, shows all the books and you can filter them, a bit""" """Main route, shows all the books and you can filter them
pubtypes = gettypes() based on year, type"""
pubyears = getyears() pubtypes = csvparser.gettypes()
publicenses = getlicenses() pubyears = csvparser.getyears()
publicatons = getpublications() publicenses = csvparser.getlicenses()
publicatons = csvparser.getpublications()
template = render_template( template = render_template(
"index.html", "index.html",
publications=publicatons, publications=publicatons,
@ -54,87 +71,57 @@ def index():
@APP.route("/upload", methods=["GET", "POST"]) @APP.route("/upload", methods=["GET", "POST"])
@login_required
def upload(): def upload():
"""Upload route, a page to upload a book to the csv""" """Upload route, a page to upload a book to the csv"""
uploadform = PublicationForm() publicationform = PublicationForm()
if request.method == "POST": if request.method == "POST":
if uploadform.validate_on_submit() and checksecret( if publicationform.validate_on_submit():
uploadform.secret.data id = csvparser.writepublication(publicationform)
): saveimage(publicationform.image.data, id)
id = writepublication(uploadform)
saveimage(uploadform.image.data, id)
return redirect(str(id), code=303) return redirect(str(id), code=303)
else: else:
return render_template("upload.html", uploadform=uploadform) return render_template("upload.html", publicationform=publicationform)
print("test") return render_template("upload.html", publicationform=publicationform)
return render_template("upload.html", uploadform=uploadform)
@APP.route("/<publicationID>", methods=["GET", "POST"]) @APP.route("/<publicationID>", methods=["GET", "POST"])
def show_book(publicationID): def show_book(publicationID):
"""route for a single publication, shows full info and allows borrowing""" """route for a single publication, shows full info and allows borrowing"""
fullpublication = getfullpublication(publicationID) fullpublication = csvparser.getfullpublication(publicationID)
borrowform = BorrowForm() borrowform = BorrowForm()
image_uploadform = ImageUploadForm()
pdf_uploadform = PdfUploadForm()
if request.method == "POST": if request.method == "POST":
if borrowform.validate_on_submit() and checksecret( if borrowform.validate_on_submit():
borrowform.secret.data
):
editborrowedby(publicationID, borrowform.borrowed.data) editborrowedby(publicationID, borrowform.borrowed.data)
fullpublication["Borrowed"] = borrowform.borrowed.data fullpublication["Borrowed"] = borrowform.borrowed.data
return render_template( if image_uploadform.validate_on_submit():
"publication.html", saveimage(image_uploadform.image.data, fullpublication.id)
fullpublication=fullpublication,
publicationID=publicationID,
borrowform=borrowform,
)
# return a full publication with or without form errors # return a full publication with or without form errors
return render_template( return render_template(
"publication.html", "publication.html",
fullpublication=fullpublication, fullpublication=fullpublication,
publicationID=publicationID, publicationID=publicationID,
borrowform=borrowform, borrowform=borrowform,
image_uploadform=image_uploadform,
pdf_uploadform=pdf_uploadform,
) )
@APP.route("/pastevents") @APP.route("/search/<search_query>", methods=["GET"])
def pastevents(): def searchbooks(search_query):
"""show past R&R events and book recommendations""" print(f"Searched for {search_query}")
events = getevents() search_results = search(search_query)
return render_template("pastevents.html", events=events) return json.dumps(search_results)
@APP.route("/upcoming")
def latestevent():
"""show upcoming or latest R&R events and book recommendations"""
event = getlatestevent()
return render_template("upcomingevent.html", event=event)
@APP.context_processor
def upcoming_or_latest():
"""determines wether the newest R&R event is upcoming or not"""
upcoming = True
ics = get("https://varia.zone/events.ics").text
gcal = Calendar.from_ical(ics)
eventtimes = [
c.get("dtstart").dt
for c in gcal.walk()
if c.name == "VEVENT" and "Read & Repair" in c.get("summary")
]
now = datetime.datetime.now()
eventtimes.sort()
eventtimes.reverse()
if now > eventtimes[0]:
upcoming = False
return dict(upcoming=upcoming)
def saveimage(image, id): def saveimage(image, id):
"""helper function that can save images""" """helper function that can save images"""
image.save(os.path.join(APP.config["UPLOAD_FOLDER"], image.filename)) image.save(os.path.join(APP.config["UPLOAD_FOLDER"], image.filename))
orig_image = Image.open( orig_image = Image.open(
os.path.join(APP.config["UPLOAD_FOLDER"], image.filename) os.path.join(APP.confmailig["UPLOAD_FOLDER"], image.filename)
) )
new_width = 640 new_width = 640
new_height = int(new_width * orig_image.height / orig_image.width) new_height = int(new_width * orig_image.height / orig_image.width)
@ -144,14 +131,46 @@ def saveimage(image, id):
os.remove(os.path.join(APP.config["UPLOAD_FOLDER"], image.filename)) os.remove(os.path.join(APP.config["UPLOAD_FOLDER"], image.filename))
def checksecret(secret): @APP.route("/logout")
"""small simple check to a secret, library group members can upload""" @login_required
with open("secret") as f: def logout():
secrethash = f.readline().rstrip() logout_user()
if bcrypt.checkpw(secret.encode("utf-8"), secrethash.encode("utf-8")): return redirect(url_for("index"))
return True
else:
return False @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()
@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
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@APP.errorhandler(CSRFError)
def handle_csrf_error(e):
return render_template("csrf_error.html", reason=e.description), 400
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,51 +0,0 @@
from feedparser import parse
from csvparser.csvparser import getfieldsofinterest, getfullpublication
feed = parse("http://varia.zone/en/feeds/all-en.rss.xml")
def getentries():
entries = {}
for entry in feed.entries:
if "readrepair" in entry.category:
entries[entry.title] = []
entrylist = entries[entry.title]
entrylist.append(entry.description)
entrylist.append(rabbithole(entry))
return entries
def getlatestevent():
for entry in feed.entries:
if "readrepair" in entry.category:
entry[entry.title] = []
entrylist = entry[entry.title]
entrylist.append(entry.description)
return entry
def gettitles():
titles = []
for entry in feed.entries:
if "readrepair" in entry.category:
titles.append(entry.title)
return titles
def rabbithole(entry):
pubtitles = {}
fieldsofinterest = getfieldsofinterest()
categories = [t.get('term').lower() for t in entry.tags]
for id, fields in fieldsofinterest.items():
if [f for f in fields if(f.strip().lower() in categories)]:
# print("book found")
publicationinfo = getfullpublication(id)
fulltitle = "{0} - {1}".format(
publicationinfo["Author"], publicationinfo["Title"])
pubtitles[id] = fulltitle
return pubtitles
def getevents():
events = getentries()
return events

View File

@ -1 +0,0 @@
$2b$12$kZC/e1smAiBCntQxLUpsZ.H0Y5VkWG/YLt18wIdGmONtijkXYaVsO

3
library/settings.toml Normal file
View File

@ -0,0 +1,3 @@
title = "Your library title"
libaryfilename = "newlib.csv"
readmethod = "download"

View File

@ -1,23 +0,0 @@
#pastevents{
position: fixed;
top: -1em;
right: 3em;
height: 14em;
}
#upcomingevents{
position: fixed;
top: -2.5em;
right: 6em;
height: 14em;
}
#pastevents:hover{
z-index: 1;
}
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
#upcomingevents,#pastevents {
position:absolute;
width: 15%;
height: auto;
}
}

View File

@ -1,26 +1,21 @@
/* Dropdown Button */ /* Dropdown Button */
/* for sorting on Year, Type, License /* for sorting on Year, Type, License
*/ */
.menu {
display:flex;
gap: 0.3em;
position: relative;
margin-left: 1em;
}
.filter { .filter {
display: none; display: none;
} }
.activebtn { .activebtn {
background-color: #62b264; background-color: #62b264;
} }
#leftmostbtn{
margin-left: 1em;
}
.show { .show {
display: block; display: block;
} }
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */ /* Dropdown Content (Hidden by Default) */
.dropdown-content { .dropdown-content {
display: none; display: none;
@ -41,7 +36,7 @@
text-decoration: none; text-decoration: none;
display: block; display: block;
} }
.dropbtn { .dropbtn .dropdown {
margin-top: 1em; margin-top: 1em;
} }
/* Change color of dropdown links on hover */ /* Change color of dropdown links on hover */
@ -54,6 +49,30 @@
/* Change the background color of the dropdown button when the dropdown content is shown */ /* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {background-color: #3e8e41;} .dropdown:hover .dropbtn {background-color: #3e8e41;}
#booksearch{
background: #f1f1f1;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
border: 3px solid black;
font-family: inherit;
font-size: 100%;
box-shadow: 0.3em 0.35em rgba(0,0,0,0.3);
}
button {
border: 3px solid black;
padding: 0.8em;
color: black;
min-width: auto;
background-color: #f1f1f1;
font-family: inherit;
font-size: 100%;
box-shadow: 0.3em 0.35em rgba(0,0,0,0.3);
cursor: pointer;
}
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) { @media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
.dropdown-content button { .dropdown-content button {
font-size: 0.7em; font-size: 0.7em;

View File

@ -0,0 +1,9 @@
.feather {
width: 24px;
height: 24px;
stroke: currentColor;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}

View File

@ -1,56 +1,34 @@
@font-face {
font-family: "libreBaskerville";
src: url(../fonts/LibreBaskerville-Regular.otf);
}
html, body { html, body {
margin: 0; margin: 0;
font-family: Garamond, serif; font-family: "libreBaskerville";
background-repeat: no-repeat; font-style: normal;
background-attachment: fixed;
} }
body:after { #library {
font-size: .8em;
position: fixed;
width: 100%;
text-align: center;
}
#cloud {
overflow: hidden;
width: 1px; height: 1px;
transform: translate(-100%, -100%);
border-radius: 50%;
z-index: -1;
position: fixed;
}
@font-face {
font-family: alphaClouds;
src: url(../fonts/AlphaClouds.ttf);
}
#varia {
line-height: 1.03em;
position: relative; position: relative;
color: #FFFFFF; color: #FFFFFF;
text-shadow: 2px 2px #8B5B7F; text-shadow: 2px 2px #004225;
font-size: 52px; font-size: 1.5em;
text-align: center;
font-family: alphaClouds;
mix-blend-mode: difference; mix-blend-mode: difference;
margin: 1em 0; left: 1em;
} }
@supports (-webkit-text-stroke: 1px lightpink) { @supports (-webkit-text-stroke: 1px darkgreen) {
#varia { #library {
-webkit-text-stroke: 1px lightpink; -webkit-text-stroke: 1px darkgreen;
} }
} }
.container {
margin 0 auto;
}
#bookshelf { #bookshelf {
max-width: 90%; max-width: 90%;
margin-top: 3em; margin-top: 3em;
margin-bottom: 3em;
margin-left: 1em;
display: block; display: block;
columns: 30rem; columns: 30rem;
gap: 1rem; gap: 1rem;
@ -71,57 +49,23 @@ body:after {
} }
#publication { #publication {
margin-bottom: 3em;
margin-top: 3em; margin-top: 3em;
} margin-left: 1em;
#latestevent {
text-align: center;
color: #DD4F77;
}
#upcomingevent {
text-align: center;
color: #404d81;
}
.event {
margin: 0 1em 1em;
max-width: calc(90% - 3em);
min-width: calc(90% - 3em);
margin-top: 3em;
padding: 6px;
display: inline-block;
float: left;
border: 3px solid black;
background-color: #f1f1f1;
border-spacing: 0;
border-collapse: collapse;
z-index: 10;
}
button {
z-index: 10;
border: 3px solid black;
padding: 6px;
color: black;
min-width: auto;
background-color: #f1f1f1;
} }
table { table {
margin: 0 1em 1em; border: 1px solid black;
z-index: 10;
border: 3px solid black;
background-color: #f1f1f1; background-color: #f1f1f1;
border-spacing: 0; border-spacing: 0;
} }
tbody:hover {background-color: #ddd;}
tr { tr {
margin: 0; margin: 0;
} }
td { td {
margin:0;
padding: 0.5em; padding: 0.5em;
min-width: auto; min-width: auto;
max-width: 60%; max-width: 60%;
@ -140,18 +84,60 @@ td {
.error{ .error{
color: #ff1111; color: #ff1111;
} }
a:link { text-decoration: none; } a:link { text-decoration: none; }
a:visited { text-decoration: none; } a:visited { text-decoration: none; }
a:hover { text-decoration: none; } a:hover { text-decoration: none; }
a:active { text-decoration: none; } a:active { text-decoration: none; }
div#login {
width: 25%;
margin-left: auto;
margin-right: auto;
text-decoration: none;
}
input[type=text], input[type=password], input[type=file] {
width: 18em;
max-width: 18em;
border: 1px solid #E0B0FF;
}
input {
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 0.8em;
cursor: pointer;
}
div#auth_buttons{
position: absolute;
top: 0.7em;
right: 0.7em;
display:flex;
gap: 1em;
flex-direction: row;
}
div#auth_buttons a{
font-size: 0.8em;
padding: 0.2em;
}
.auth {
color: white;
cursor: pointer;
border: 1px solid #404d81;
background-color: #fefefe;
}
.auth:hover{
background-color: #efefef;
}
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) { @media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
#varia { #library {
text-align: left; text-align: left;
max-width: 70%; max-width: 70%;
} }
.event {
max-width: 90%;
margin: 3em 1em 1em 1em;
}
} }

View File

@ -1,4 +1,4 @@
#uploadform { #publicationform {
max-width: 60%; max-width: 60%;
margin-top: 3em; margin-top: 3em;
margin-left: 1em; margin-left: 1em;
@ -13,12 +13,12 @@
position: relative; position: relative;
} }
.uploadform-field { .publicationform-field {
margin: 0; margin: 0;
padding: 1em 0em 1em 0em; padding: 1em 0em 1em 0em;
} }
input[type=text], select { .publicationform-field input[type=text], select {
width: 100%; width: 100%;
padding: 1em 3em; padding: 1em 3em;
padding-left: 0.5em; padding-left: 0.5em;
@ -44,9 +44,7 @@ input[type=submit] {
background-color: #DD4F77; background-color: #DD4F77;
text-align: right; text-align: right;
color: white; color: white;
padding: 1em 3em;
border: none; border: none;
cursor: pointer;
} }
input[type=submit]:hover { input[type=submit]:hover {
@ -59,7 +57,7 @@ fieldset{
padding-left: 0em; padding-left: 0em;
} }
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) { @media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
#uploadform { #publicationform {
max-width: 100%; max-width: 100%;
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg"><defs>
<symbol id="log-in">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
<polyline points="10 17 15 12 10 7"></polyline>
<line x1="15" y1="12" x2="3" y2="12"></line>
</symbol>
<symbol id="log-out">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
<polyline points="16 17 21 12 16 7"></polyline>
<line x1="21" y1="12" x2="9" y2="12"></line>
</symbol>
<symbol id="user-plus">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="8.5" cy="7" r="4"></circle>
<line x1="20" y1="8" x2="20" y2="14"></line>
<line x1="23" y1="11" x2="17" y2="11"></line>
</symbol>
</defs></svg>

After

Width:  |  Height:  |  Size: 673 B

View File

@ -1,3 +0,0 @@
*
*/
!.gitignore

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

View File

@ -1,4 +1,3 @@
// Filter section ===================== old school code divider ================
filterSelection("all", "None"); filterSelection("all", "None");
function filterSelection(c, id) { function filterSelection(c, id) {
@ -29,11 +28,14 @@ function resetDropDownButtons(){
document.getElementById("License").innerText = "License"; document.getElementById("License").innerText = "License";
document.getElementById("PubType").innerText = "Type"; document.getElementById("PubType").innerText = "Type";
document.getElementById("Year").innerText = "Year"; document.getElementById("Year").innerText = "Year";
document.getElementById('booksearch').value= "";
document.getElementById('booksearch').placeholder = "🔍 Search..";
allactivebuttons = document.getElementsByClassName("activebtn"); allactivebuttons = document.getElementsByClassName("activebtn");
for(var i = 0;allactivebuttons.length; i++) { for(var i = 0;allactivebuttons.length; i++) {
removeClass(allactivebuttons[i], "activebtn"); removeClass(allactivebuttons[i], "activebtn");
} }
} }
function addClass(element, name) { function addClass(element, name) {
var i, arr1, arr2; var i, arr1, arr2;
arr1 = element.className.split(" "); arr1 = element.className.split(" ");

View File

@ -1,32 +0,0 @@
// Cloud section ===================== old school code divider =================
function rn(from, to) {
return ~~(Math.random() * (to - from + 1)) + from;
}
function rs() {
return arguments[rn(1, arguments.length) - 1];
}
function boxShadows(max) {
let ret = [];
for (let i = 0; i < max; ++i) {
ret.push(`
${ rn(1, 110) }vw ${ rn(1, 110) }vh ${ rn(20, 30) }vmin ${ rn(10, 60) }vmin
${ rs('#F52D75', '#CCBD4F', '#32497F', '#EB4377') }
`)
}
return ret.join(',');
}
const cloud = document.querySelector('#cloud');
function update() {
if (window.screen.availWidth > 400 && window.screen.availHeight > 400 ) {
cloud.style.boxShadow = boxShadows(30);
}
else {
document.body.style.backgroundImage = "linear-gradient(to bottom right, white, #F52D75)";
}
}
window.addEventListener('load', update);

View File

@ -0,0 +1,42 @@
let searchInput = document.getElementById('booksearch');
var allpublications = document.getElementsByClassName("filter");
const ENTER_KEY_CODE = 13;
searchInput.addEventListener('keyup', function(e) {
if (e.keyCode === ENTER_KEY_CODE) {
if (searchInput.value.length > 2) {
searchBooks(searchInput.value);
} else {
clearSearchBooks();
}
}
})
function searchBooks(searchQuery) {
let searchUrl = `search/${searchQuery}`
fetch(searchUrl)
.then(response => response.json())
.then(searchdata => {
console.log(`book ids: ${searchdata} found for ${searchQuery}`);
if (searchdata === undefined || searchdata.length == 0) return;
for (i = 0; i < allpublications.length; i++) {
removeClass(allpublications[i], "show");
}
searchdata.forEach(bookid => {
showBookId(bookid)
});
})
}
function showBookId(bookid) {
let book = document.getElementById(bookid)
addClass(book, "show");
}
function clearSearchBooks() {
for (i = 0; i < allpublications.length; i++) {
addClass(allpublications[i], "show");
}
}

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
width="197.81665"
height="539.99939"
viewBox="0 0 197.06678 537.95139"
xml:space="preserve"
sodipodi:docname="bookmark-upcoming.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
id="metadata13"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs11"><rect
x="207.04167"
y="97.061699"
width="111.52129"
height="86.161156"
id="rect17" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1916"
inkscape:window-height="1041"
id="namedview9"
showgrid="false"
inkscape:zoom="1.0725863"
inkscape:cx="-8.3621061"
inkscape:cy="267.6066"
inkscape:window-x="1366"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1"
inkscape:document-rotation="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<g
id="g4"
transform="translate(-170.442,-5e-4)">
<path
d="M 346.164,0 H 191.939 c -11.857,0 -21.497,9.716 -21.497,21.497 v 505.894 c 0,11.857 6.12,14.076 13.617,4.896 0,0 56.304,-68.697 70.609,-87.822 14.306,-19.125 15.683,-21.268 32.972,0 17.365,21.268 66.938,87.363 66.938,87.363 7.191,9.41 12.929,7.496 12.929,-4.283 V 21.497 C 367.66,9.716 357.945,0 346.164,0 Z"
fill="#404d81"
id="path2" />
</g>
<text
xml:space="preserve"
id="text15"
style="font-size:40px;line-height:1.25;font-family:unscii;-inkscape-font-specification:unscii;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect17);"
transform="translate(-182.94372,-1.3642201)" /><text
xml:space="preserve"
style="font-size:42.6667px;line-height:1.25;font-family:unscii;-inkscape-font-specification:unscii;letter-spacing:0px;word-spacing:0px"
x="98.764069"
y="245.41867"
id="text23"
inkscape:transform-center-x="-22.440464"
inkscape:transform-center-y="6.5100632"><tspan
sodipodi:role="line"
id="tspan21"
x="98.764069"
y="245.41867"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:Carlito;-inkscape-font-specification:Carlito;text-align:center;text-anchor:middle;fill:#ffffff">Latest</tspan><tspan
sodipodi:role="line"
x="98.764069"
y="300.75204"
id="tspan25"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:Carlito;-inkscape-font-specification:Carlito;text-align:center;text-anchor:middle;fill:#ffffff">Read &amp; </tspan><tspan
sodipodi:role="line"
x="98.764069"
y="356.08542"
id="tspan27"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:Carlito;-inkscape-font-specification:Carlito;text-align:center;text-anchor:middle;fill:#ffffff">Repair</tspan></text></svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
width="197.81665"
height="539.99939"
viewBox="0 0 197.06678 537.95139"
xml:space="preserve"
sodipodi:docname="bookmark-past.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs9"><rect
x="177.97797"
y="223.17792"
width="182.00249"
height="193.39874"
id="rect21" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="956"
inkscape:window-height="1041"
id="namedview7"
showgrid="false"
inkscape:zoom="1.0725863"
inkscape:cx="97.88594"
inkscape:cy="268.97551"
inkscape:window-x="2326"
inkscape:window-y="18"
inkscape:window-maximized="0"
inkscape:current-layer="Capa_1"
inkscape:document-rotation="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<g
id="g4"
transform="translate(-170.442,-5e-4)">
<path
d="M 346.164,0 H 191.939 c -11.857,0 -21.497,9.716 -21.497,21.497 v 505.894 c 0,11.857 6.12,14.076 13.617,4.896 0,0 56.304,-68.697 70.609,-87.822 14.306,-19.125 15.683,-21.268 32.972,0 17.365,21.268 66.938,87.363 66.938,87.363 7.191,9.41 12.929,7.496 12.929,-4.283 V 21.497 C 367.66,9.716 357.945,0 346.164,0 Z"
fill="#dd4f77"
id="path2" />
</g>
<text
xml:space="preserve"
id="text19"
style="font-size:42.6667px;line-height:1.25;font-family:unscii;-inkscape-font-specification:unscii;text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect21);"
x="57.5"
y="0"
transform="translate(-170.442,-5e-4)"><tspan
x="232.94822"
y="262.5111"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Carlito;-inkscape-font-specification:Carlito;fill:#ffffff">Past </tspan></tspan><tspan
x="212.52112"
y="317.84447"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Carlito;-inkscape-font-specification:Carlito;fill:#ffffff">Events</tspan></tspan></text></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
width="197.81665"
height="539.99939"
viewBox="0 0 197.06678 537.95139"
xml:space="preserve"
sodipodi:docname="bookmark-upcoming.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
id="metadata13"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs11"><rect
x="207.04167"
y="97.061699"
width="111.52129"
height="86.161156"
id="rect17" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1916"
inkscape:window-height="1041"
id="namedview9"
showgrid="false"
inkscape:zoom="1.0725863"
inkscape:cx="-8.3621061"
inkscape:cy="267.6066"
inkscape:window-x="1366"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1"
inkscape:document-rotation="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<g
id="g4"
transform="translate(-170.442,-5e-4)">
<path
d="M 346.164,0 H 191.939 c -11.857,0 -21.497,9.716 -21.497,21.497 v 505.894 c 0,11.857 6.12,14.076 13.617,4.896 0,0 56.304,-68.697 70.609,-87.822 14.306,-19.125 15.683,-21.268 32.972,0 17.365,21.268 66.938,87.363 66.938,87.363 7.191,9.41 12.929,7.496 12.929,-4.283 V 21.497 C 367.66,9.716 357.945,0 346.164,0 Z"
fill="#404d81"
id="path2" />
</g>
<text
xml:space="preserve"
id="text15"
style="font-size:40px;line-height:1.25;font-family:unscii;-inkscape-font-specification:unscii;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect17);"
transform="translate(-182.94372,-1.3642201)" /><text
xml:space="preserve"
style="font-size:42.6667px;line-height:1.25;font-family:unscii;-inkscape-font-specification:unscii;letter-spacing:0px;word-spacing:0px"
x="98.764069"
y="245.41867"
id="text23"
inkscape:transform-center-x="-22.440464"
inkscape:transform-center-y="6.5100632"><tspan
sodipodi:role="line"
id="tspan21"
x="98.764069"
y="245.41867"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:Carlito;-inkscape-font-specification:Carlito;text-align:center;text-anchor:middle;fill:#ffffff">Upcoming</tspan><tspan
sodipodi:role="line"
x="98.764069"
y="300.75204"
id="tspan25"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:Carlito;-inkscape-font-specification:Carlito;text-align:center;text-anchor:middle;fill:#ffffff">Read &amp; </tspan><tspan
sodipodi:role="line"
x="98.764069"
y="356.08542"
id="tspan27"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:42.6667px;font-family:Carlito;-inkscape-font-specification:Carlito;text-align:center;text-anchor:middle;fill:#ffffff">Repair</tspan></text></svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -3,11 +3,11 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>varia library zone</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/style.css')}}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/dropdown.css')}}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/dropdown.css')}}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bookmark.css')}}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/upload.css')}}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/upload.css')}}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/feather.css')}}">
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='icons/apple-touch-icon.png')}}"> <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='icons/apple-touch-icon.png')}}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='icons/favicon-32x32.png')}}"> <link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='icons/favicon-32x32.png')}}">
@ -15,23 +15,8 @@
<link rel="manifest" href="{{ url_for('static', filename='icons/site.webmanifest')}}"> <link rel="manifest" href="{{ url_for('static', filename='icons/site.webmanifest')}}">
</head> </head>
<body> <body>
<div id="cloud"></div> <a href="/" id="library"><h1 >{{title}}</h1></a>
<a href="/"><h1 id="varia">VARIA LIBRARY COLLECTION</h1></a>
<a href="pastevents"><img src="{{ url_for('static', filename='svg/bookmark-past.svg')}}" id="pastevents" /></a>
{% if upcoming %}
<a href="upcoming"><img src="{{ url_for('static', filename='svg/bookmark-upcoming.svg')}}" id="upcomingevents" /></a>
{% else %}
<a href="upcoming"><img src="{{ url_for('static', filename='svg/bookmark-latest.svg')}}" id="upcomingevents" /></a>
{% endif %}
{% block main %} {% block main %}
{% endblock main %} {% endblock main %}
<svg width="0">
<filter id="filter">
<feTurbulence type="fractalNoise"
baseFrequency=".001" numOctaves="5" />
<feDisplacementMap in="SourceGraphic" scale="240" />
</filter>
</svg>
</body> </body>
<script src="{{ url_for('static', filename='js/script.js')}}"></script>
</html> </html>

View File

@ -0,0 +1,29 @@
{% block bookshelf %}
<div id="bookshelf">
{% for id, pubinfo in publications.items() %}
<div id="{{ id }}" class='book filter {{ pubinfo["Type"] }} {{ pubinfo["Year"] }} {{ pubinfo["License"] }}'>
<table>
<tbody>
{%if pubinfo["Image"]%}
<tr>
<td colspan="2" class="tdimage">
<img src="{{ url_for('static', filename='images/image-{0}.jpg'.format(id))}}" alt="">
</td>
</tr>
{% endif %}
<tr>
<td class="author">Author/Editor:</td>
<td>{{ pubinfo["Author"] }}</td>
</tr>
<tr>
<td class="title">Title:</td>
<td><a href='{{ id }}'>{{ pubinfo["Title"] }}</a></td>
</tr>
</tbody>
</table>
</div>
{% endfor%}
</div>
<script src="{{ url_for('static', filename='js/dropdown.js')}}"></script>
<script src="{{ url_for('static', filename='js/search.js')}}"></script>
{% endblock bookshelf %}

View File

@ -1,34 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block main %} {% block main %}
<div id="nav" class="container">
{% include 'menu.html' %} {% include 'menu.html' %}
</div> {% include 'user_authorization.html' %}
<div id="bookshelf"> {% include 'bookshelf.html' %}
{% for id, pubinfo in publications.items() %}
<div class='book filter {{ pubinfo["Type"] }} {{ pubinfo["Year"] }} {{ pubinfo["License"] }}'>
<a href='{{ id }}'>
<table>
<tbody>
{%if pubinfo["Image"]%}
<tr>
<td colspan="2" class="tdimage">
<img src="{{ url_for('static', filename='images/image-{0}.jpg'.format(id))}}" alt="">
</td>
</tr>
{% endif %}
<tr>
<td>Author/Editor</td>
<td>{{ pubinfo["Author"] }}</td>
</tr>
<tr>
<td>Title</td>
<td>{{ pubinfo["Title"] }}</td>
</tr>
</tbody>
</table>
</a>
</div>
{% endfor%}
</div>
<script src="{{ url_for('static', filename='js/dropdown.js')}}"></script>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,14 @@
{% block menu %} {% block menu %}
<button id="leftmostbtn" onclick="filterSelection('all')">All books</button> <nav id="nav" class="menu">
<button><a href="/upload">Upload</a></button> <div class="dropdown">
<div class="dropdown"> <button onclick="filterSelection('all')" class="dropbtn">Reset search</button>
</div>
{% if current_user.is_authenticated %}
<div class="dropdown">
<button class="dropbtn"><a href="/upload">Upload</a></button>
</div>
{% endif %}
<div class="dropdown">
<button id="PubType" class="dropbtn">Type</button> <button id="PubType" class="dropbtn">Type</button>
<div class="dropdown-content"> <div class="dropdown-content">
{% for pubtype in pubtypes %} {% for pubtype in pubtypes %}
@ -25,4 +32,8 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="dropdown">
<input class="dropbtn" id="booksearch" type="text" placeholder="🔍 Search..">
</div>
</nav>
{% endblock menu %} {% endblock menu %}

View File

@ -1,22 +0,0 @@
{% extends "base.html" %}
{% block main %}
<div id="nav" class="container">
<button id="leftmostbtn"><a href="/">All books</a></button>
<button><a href="/upload">Upload</a></button>
<div class="dropdown" style="visibility: hidden">
<button id="Year" class="dropbtn">Year</button>
</div>
</div>
{% for eventtitle, text in events.items() %}
<div class="event">
<h2>{{ eventtitle }}</h2>
{{ text[0]|safe }}
<p>For those interested to learn more on the topics of this read and repair event the physical library at varia offers the following books:</p>
<ul>
{% for link, booktitle in text[1].items() %}
<li><a href="/{{ link }}">{{ booktitle }}</a></li>
{% endfor%}
</ul>
</div>
{% endfor%}
{% endblock %}

View File

@ -1,8 +1,15 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block main %} {% block main %}
<div id="nav" class="container"> {% include 'user_authorization.html' %}
<button id="leftmostbtn"><a href="/">All books</a></button> <div id="nav" class="menu">
<div class="dropdown">
<button><a href="/">All books</a></button>
</div>
{% if current_user.is_authenticated %}
<div class="dropdown">
<button><a href="/upload">Upload</a></button> <button><a href="/upload">Upload</a></button>
</div>
{% endif %}
<div class="dropdown" style="visibility: hidden"> <div class="dropdown" style="visibility: hidden">
<button id="Year" class="dropbtn">Year</button> <button id="Year" class="dropbtn">Year</button>
</div> </div>
@ -29,10 +36,6 @@
<td>Year</td> <td>Year</td>
<td>{{ fullpublication["Year"] }}</td> <td>{{ fullpublication["Year"] }}</td>
</tr> </tr>
<tr>
<td>Custodian</td>
<td>{{ fullpublication["Custodian"] }}</td>
</tr>
<tr> <tr>
<td>Fields</td> <td>Fields</td>
<td>{{ fullpublication["Fields"] }}</td> <td>{{ fullpublication["Fields"] }}</td>
@ -58,31 +61,43 @@
<td><p>{{ fullpublication["Comments"] }}</p></td> <td><p>{{ fullpublication["Comments"] }}</p></td>
</tr> </tr>
<tr> <tr>
<td>Currently borrowed by:</td> <td>File</td>
<td><p>{{ fullpublication["Borrowed"] }}</p></td> <td><a href='files/{{fullpublication["Files"]}}'><p>{{ fullpublication["Files"] }}</p></a></td>
</tr> </tr>
{% if current_user.is_authenticated %}
<tr> <tr>
<td colspan="2"> <td>Upload new</td>
<form class="borrow" method="POST" action="/{{ publicationID }}"> <td>
{{ borrowform.csrf_token }} <form method="POST" enctype="multipart/form-data" action="/{{ publicationID }}">
<fieldset class="borrowform-field"> {{ pdf_uploadform.csrf_token }}
{{ borrowform.borrowed.label }} <fieldset class="fileupload-field">
{{ borrowform.borrowed }} {{ pdf_uploadform.pdf.label }}
{% for message in borrowform.borrowed.errors %} {{ pdf_uploadform.pdf }}
{% for message in pdf_uploadform.pdf.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<fieldset class="borrowform-field"> {{ pdf_uploadform.submit }}
{{ borrowform.secret.label }} </form>
{{ borrowform.secret }}
{% for message in borrowform.secret.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
{{ borrowform.submit }}
<form>
</td> </td>
</tr> </tr>
<tr>
<td>Upload new</td>
<td>
<form method="POST" enctype="multipart/form-data" action="/{{ publicationID }}">
{{ image_uploadform.csrf_token }}
<fieldset class="fileupload-field">
{{ image_uploadform.image.label }}
{{ image_uploadform.image }}
{% for message in image_uploadform.image.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
{{ image_uploadform.submit }}
</form>
</td>
</tr>
{% endif %}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -1,20 +0,0 @@
{% extends "base.html" %}
{% block main %}
<div id="nav" class="container">
<button id="leftmostbtn"><a href="/">All books</a></button>
<button><a href="/upload">Upload</a></button>
<div class="dropdown" style="visibility: hidden">
<button id="Year" class="dropbtn">Year</button>
</div>
</div>
<div class="event">
{% if upcoming %}
<h2 id="upcomingevent">Upcoming event!</h2>
{% else %}
<p id="latestevent">Unfortunately this Read&Repair event has already happened, keep an eye on <a href="http://varia.zone/">varia.zone</a>
or this site for upcoming Read&Repair and other Varia events!</p>
{% endif %}
<h2>{{ event.title }}</h2>
{{ event.description|safe }}
</div>
{% endblock %}

View File

@ -1,115 +1,102 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block main %} {% block main %}
<div id="nav" class="container"> <div id="nav" class="menu">
<button id="leftmostbtn"><a href="/">All books</a></button> <div class="dropdown">
<button><a href="/">Back to books</a></button>
</div>
<div class="dropdown">
<button><a href="/upload">Upload</a></button> <button><a href="/upload">Upload</a></button>
</div>
<div class="dropdown" style="visibility: hidden"> <div class="dropdown" style="visibility: hidden">
<button id="Year" class="dropbtn">Year</button> <button id="Year" class="dropbtn">Year</button>
</div> </div>
</div> </div>
<div id="uploadform"> <div id="publicationform">
{% for message in uploadform.uploadpublication.errors %} {% for message in publicationform.uploadpublication.errors %}
<div>{{ message }}</div> <div>{{ message }}</div>
{% endfor %} {% endfor %}
{% for message in uploadform.author.errors %} {% for message in publicationform.author.errors %}
<div>{{ message }}</div> <div>{{ message }}</div>
{% endfor %} {% endfor %}
<h2 id="uploadformtitle">Upload a new book</h2> <h2 id="publicationformtitle">Upload a new book</h2>
<form method="POST" enctype="multipart/form-data" action="{{ url_for('upload') }}"> <form method="POST" enctype="multipart/form-data" action="{{ url_for('upload') }}">
{{ uploadform.csrf_token }} {{ publicationform.csrf_token }}
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.uploadpublication.label }} {{ publicationform.uploadpublication.label }}
{{ uploadform.uploadpublication }} {{ publicationform.uploadpublication }}
{% for message in uploadform.uploadpublication.errors %} {% for message in publicationform.uploadpublication.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.author.label }} {{ publicationform.author.label }}
{{ uploadform.author }} {{ publicationform.author }}
{% for message in uploadform.author.errors %} {% for message in publicationform.author.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.year.label }} {{ publicationform.year.label }}
{{ uploadform.year }} {{ publicationform.year }}
{% for message in uploadform.year.errors %} {% for message in publicationform.year.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.custodian.label }} {{ publicationform.fields.label }}
{{ uploadform.custodian }} {{ publicationform.fields }}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.fields.label }} {{ publicationform.type.label }}
{{ uploadform.fields }} {{ publicationform.type }}
</fieldset> {% for message in publicationform.type.errors %}
<fieldset class="uploadform-field">
{{ uploadform.type.label }}
{{ uploadform.type }}
{% for message in uploadform.type.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.publishers.label }} {{ publicationform.publishers.label }}
{{ uploadform.publishers }} {{ publicationform.publishers }}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.license.label }} {{ publicationform.license.label }}
{{ uploadform.license }} {{ publicationform.license }}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.licenseshort.label }} {{ publicationform.highlights.label }}
{{ uploadform.licenseshort }} {{ publicationform.highlights }}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="publicationform-field">
{{ uploadform.highlights.label }} {{ publicationform.comments.label }}
{{ uploadform.highlights }} {{ publicationform.comments }}
</fieldset>
<fieldset class="uploadform-field">
{{ uploadform.comments.label }}
{{ uploadform.comments }}
</fieldset>
<fieldset class="uploadform-field">
{{ uploadform.borrowed.label }}
{{ uploadform.borrowed }}
</fieldset> </fieldset>
<fieldset class="fileupload-field"> <fieldset class="fileupload-field">
{{ uploadform.image.label }} {{ publicationform.image.label }}
{{ uploadform.image }} {{ publicationform.image }}
{% for message in uploadform.image.errors %} {% for message in publicationform.image.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</fieldset> </fieldset>
<fieldset class="uploadform-field"> <fieldset class="fileupload-field">
{{ uploadform.secret.label }} {{ publicationform.pdf.label }}
{{ uploadform.secret }} {{ publicationform.pdf }}
{% for message in uploadform.secret.errors %} {% for message in publicationform.pdf.errors %}
<div class="error">{{ message }}</div> <div class="error">{{ message }}</div>
{% endfor %} {% endfor %}
</fieldset> </fieldset>
{{ publicationform.submit }}
{{ uploadform.submit }}
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block main %}
<div id="mainworkflow">
<div class="workflow">
<h2>Forgot your password?</h2>
<p>
Enter the email address that was used to register with the library.
</p>
<form class="form" action="{{ url_for('forgotpassword') }}" method="post">
{{ forgotpasswordform.csrf_token }}
<fieldset class="required">
{{ forgotpasswordform.email.label }}
{{ forgotpasswordform.email }}
{% for message in forgotpasswordform.email.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="button required error">
{{ forgotpasswordform.submit }}
<div class="overview">
<a href="/">
<input type="button" name="button" value="Back to main page"></input>
</a>
</div>
</fieldset>
</form>
</div>
</div
{% endblock main %}

View File

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block main %}
<div id="login">
<form class="form" action="{{ url_for('login') }}" method="post">
{{ loginform.csrf_token }}
<fieldset class="required">
{{ loginform.email.label }}
{{ loginform.email }}
{% for message in loginform.email.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="required">
{{ loginform.password.label }}
{{ loginform.password }}
{% for message in loginform.password.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="button required">
{{ loginform.submit }}
<a href="/forgotpassword">Forgot Password?</a>
</fieldset>
</form>
</div>
{% endblock main %}

View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block main %}
<div id="login">
<form class="form" action="{{ url_for('register') }}" method="post">
{{ registerform.csrf_token }}
<fieldset class="required">
{{ registerform.username.label }}
{{ registerform.username }}
{% for message in registerform.username.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="required">
{{ registerform.email.label }}
{{ registerform.email }}
{% for message in registerform.email.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="required">
{{ registerform.password.label }}
{{ registerform.password }}
{% for message in registerform.password.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="required">
{{ registerform.confirmpassword.label }}
{{ registerform.confirmpassword }}
{% for message in registerform.confirmpassword.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="button required">
{{ registerform.submit }}
</fieldset>
</form>
</div>
{% endblock main %}

View File

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block main %}
<div id="login">
{% if linkvalid%}
<form class="form" action="{{ url_for('resetpassword', path=path) }}" method="post">
{{ resetpasswordform.csrf_token }}
<fieldset class="required">
{{ resetpasswordform.password.label }}
{{ resetpasswordform.password }}
{% for message in resetpasswordform.password.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="required">
{{ resetpasswordform.confirmpassword.label }}
{{ resetpasswordform.confirmpassword }}
{% for message in resetpasswordform.confirmpassword.errors %}
<div class="error">{{ message }}</div>
{% endfor %}
</fieldset>
<fieldset class="button required">
{{ resetpasswordform.submit }}
</fieldset>
</form>
{% else %}
<h3>Password reset link no longer valid.</h3>
{% endif %}
</div>
{% endblock main %}

View File

@ -0,0 +1,31 @@
{% block auth %}
<div id="auth_buttons">
{% if not current_user.is_authenticated %}
<div class="auth">
<a href="/login">
<svg class="feather">
<use href="{{ url_for('static', filename='icons/users-feather-sprite.svg')+ '#log-in'}}" />
</svg>
<span>Sign in</span>
</a>
</div>
<div class="auth">
<a href="/register">
<svg class="feather">
<use href="{{ url_for('static', filename='icons/users-feather-sprite.svg') + '#user-plus'}}" />
</svg>
<span>Register</span>
</a>
</div>
{% else %}
<div class="auth">
<a href="/logout">
<svg class="feather">
<use href="{{ url_for('static', filename='icons/users-feather-sprite.svg') + '#log-out'}}" />
</svg>
<span>Sign out</span>
</a>
</div>
{% endif %}
</div>
{% endblock auth %}

View File

@ -1,6 +1,6 @@
[tool.black] [tool.black]
line-length = 79 line-length = 79
target-version = ['py37', 'py38', 'py39'] target-version = ['py310','py311']
include = '\.pyi?$' include = '\.pyi?$'
exclude = ''' exclude = '''
/( /(
@ -21,4 +21,3 @@ exclude = '''
| profiling | profiling
)/ )/
''' '''

View File

@ -1,7 +1,318 @@
icalendar anyio==4.2.0
feedparser appdirs==1.4.4
flask apsw==3.43.0.0
flask_wtf argcomplete==3.1.4
requests asttokens==2.4.1
Pillow async-timeout==4.0.3
bcrypt attrs==23.2.0
beautifulsoup4==4.12.3
blinker==1.7.0
Brotli==1.1.0
certifi==2023.11.17
chardet==5.2.0
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
coloredlogs==15.0.1
configobj==5.0.8
contourpy==1.0.7
crit==3.17.1
cryptography==42.0.5
css-parser==1.0.10
cssselect==1.2.0
cupshelpers==1.0
cycler==0.12.1
dbus-python==1.3.2
decorator==5.1.1
defcon==0.10.3
Deprecated==1.2.14
deprecation==2.0.7
distlib==0.3.8
distro==1.9.0
dnspython==2.6.1
executing==2.0.1
feedparser==6.0.10
filelock==3.13.1
fontPens==0.2.4
fonttools==4.46.0
freetype-py==2.4.0
fs==2.4.16
greenlet==3.0.1
gyp-next==0.16.2
h11==0.14.0
h2==4.1.0
hpack==4.0.0
html2text==2024.2.26
html5-parser==0.4.12
html5lib==1.1
httpcore==1.0.4
httplib2==0.20.4
httpx==0.27.0
humanfriendly==10.0
hyperframe==6.0.0
hypothesis==6.99.9
idna==3.6
ifaddr==0.2.0
img2pdf==0.5.1
importlib-metadata==4.12.0
ipython==8.20.0
jedi==0.19.1
jeepney==0.8.0
kiwisolver==0.0.0
lazr.restfulclient==0.14.6
lazr.uri==1.0.6
libevdev==0.11
lxml==5.1.0
lz4==4.0.2+dfsg
Markdown==3.6
markdown-it-py==3.0.0
matplotlib==3.6.3
matplotlib-inline==0.1.6
mdurl==0.1.2
mechanize==0.4.9
more-itertools==10.2.0
mpmath==1.3.0
msgpack==1.0.3
mutagen==1.46.0
netifaces==0.11.0
nicotine-plus==3.2.9
notify2==0.3
numpy==1.26.4
oauthlib==3.2.2
ocrmypdf==15.2.0+dfsg1
olefile==0.46
openshot-qt==3.1.1
packaging==23.2
parso==0.8.3
pbr==6.0.0
pdfminer.six==20221105
pexpect==4.9.0
pikepdf==8.7.1
pillow==10.2.0
pipx==1.4.3
platformdirs==4.2.0
pluggy==1.4.0
prompt-toolkit==3.0.43
protobuf==4.21.12
psutil==5.9.8
ptyprocess==0.7.0
pure-eval==0.0.0
py==1.11.0
py7zr==0.11.3+dfsg
pycairo==1.25.1
pychm==0.8.6
pycryptodomex==3.20.0
pycups==2.0.1
pycurl==7.45.3
Pygments==2.17.2
PyGObject==3.48.1
PyJWT==2.7.0
pynvim==0.5.0
PyOpenGL==3.1.7
pyparsing==3.1.1
PyQt5==5.15.10
PyQt5-sip==12.13.0
PyQt6==6.6.1
PyQt6-sip==13.6.0
PyQt6-WebEngine==6.6.0
pysmbc==1.0.25.1
python-apt==2.7.7
python-dateutil==2.9.0
python-debian==0.1.49
python-debianbts==4.0.2
pytz==2024.1
pyudev==0.24.0
pyxdg==0.28
PyYAML==6.0.1
pyzmq==24.0.1
regex==2022.10.31
reportbug==13.0.1
reportlab==4.1.0
repoze.lru==0.7
requests==2.31.0
rich==13.7.1
rlPyCairo==0.3.0
Routes==2.5.1
SciPy==1.11.4
scour==0.38.2
sgmllib3k==1.0.0
six==1.16.0
sniffio==1.3.0
sortedcontainers==2.4.0
soupsieve==2.5
stack-data==0.6.3
stevedore==5.1.0
sympy==1.12
terminator==2.1.3
texttable==1.6.7
traitlets==5.5.0
types-aiofiles==23.2
types-aws-xray-sdk==2.12
types-beautifulsoup4==4.12
types-bleach==6.1
types-boltons==23.0
types-boto==2.49
types-braintree==4.24
types-cachetools==5.3
types-caldav==1.3
types-cffi==1.16
types-chevron==0.14
types-click-default-group==1.2
types-click-spinner==0.1
types-colorama==0.4
types-commonmark==0.9
types-console-menu==0.8
types-croniter==2.0
types-dateparser==1.1
types-decorator==5.1
types-Deprecated==1.2
types-dockerfile-parse==2.0
types-docopt==0.6
types-docutils==0.20
types-editdistance==0.6
types-entrypoints==0.4
types-ExifRead==3.0
types-first==2.0
types-flake8-2020==1.8
types-flake8-bugbear==23.9.16
types-flake8-builtins==2.2
types-flake8-docstrings==1.7
types-flake8-plugin-utils==1.3
types-flake8-rst-docstrings==0.3
types-flake8-simplify==0.21
types-flake8-typing-imports==1.15
types-Flask-Cors==4.0
types-Flask-Migrate==4.0
types-Flask-SocketIO==5.3
types-fpdf2==2.7.4
types-gdb==12.1
types-google-cloud-ndb==2.2
types-greenlet==3.0
types-hdbcli==2.18
types-html5lib==1.1
types-httplib2==0.22
types-humanfriendly==10.0
types-ibm-db==3.2
types-influxdb-client==1.38
types-inifile==0.4
types-JACK-Client==0.5
types-jmespath==1.0
types-jsonschema==4.19
types-keyboard==0.13
types-ldap3==2.9
types-libsass==0.22
types-Markdown==3.5
types-mock==5.1
types-mypy-extensions==1.0
types-mysqlclient==2.2
types-netaddr==0.9
types-oauthlib==3.2
types-openpyxl==3.1
types-opentracing==2.4
types-paho-mqtt==1.6
types-paramiko==3.3
types-parsimonious==0.10
types-passlib==1.7
types-passpy==1.0
types-peewee==3.17
types-pep8-naming==0.13
types-pexpect==4.8
types-pika-ts==1.3
types-Pillow==10.1
types-playsound==1.3
types-pluggy==1.2.0
types-polib==1.2
types-portpicker==1.6
types-protobuf==4.24
types-psutil==5.9
types-psycopg2==2.9
types-pyasn1==0.5
types-pyaudio==0.2
types-PyAutoGUI==0.9
types-pycocotools==2.0
types-pycurl==7.45.2
types-pyfarmhash==0.3
types-pyflakes==3.1
types-Pygments==2.16
types-pyinstaller==6.1
types-pyjks==20.0
types-PyMySQL==1.1
types-pynput==1.7
types-pyOpenSSL==23.3
types-pyRFC3339==1.1
types-PyScreeze==0.1.29
types-pyserial==3.5
types-pysftp==0.2
types-pytest-lazy-fixture==0.6
types-python-crontab==3.0
types-python-datemath==1.5
types-python-dateutil==2.8
types-python-gflags==3.1
types-python-jose==3.3
types-python-nmap==0.7
types-python-slugify==8.0
types-python-xlib==0.33
types-pytz==2023.3.post1
types-pywin32==306
types-pyxdg==0.28
types-PyYAML==6.0
types-qrcode==7.4
types-redis==4.6.0
types-regex==2023.10.3
types-requests==2.31
types-requests-oauthlib==1.3
types-retry==0.9
types-s2clientprotocol==5
types-seaborn==0.13
types-Send2Trash==1.8
types-setuptools==68.2
types-simplejson==3.19
types-singledispatch==4.1
types-six==1.16
types-slumber==0.7
types-stdlib-list==0.8
types-stripe==3.5
types-tabulate==0.9
types-tensorflow==2.12
types-toml==0.10
types-toposort==1.10
types-tqdm==4.66
types-translationstring==1.4
types-tree-sitter==0.20.1
types-tree-sitter-languages==1.8
types-ttkthemes==3.2
types-tzlocal==5.1
types-ujson==5.8
types-untangle==1.2
types-usersettings==1.1
types-uWSGI==2.0
types-vobject==0.9
types-waitress==2.1
types-WebOb==1.8
types-whatthepatch==1.0
types-workalendar==17.0
types-WTForms==3.1
types-xmltodict==0.13
types-zstd==1.5
types-zxcvbn==4.4
typing_extensions==4.10.0
unicodedata2==15.1.0
urllib3==1.26.18
userpath==1.9.1
virtualenv==20.25.1+ds
virtualenv-clone==0.5.7
virtualenvwrapper==4.8.4
wadllib==1.3.6
wcwidth==0.2.13
webencodings==0.5.1
WebOb==1.8.7
websocket-client==1.7.0
websockets==10.4
wrapt==1.15.0
wxPython==4.2.1
xdg==5
xxhash==0.0.0
yt-dlp==2024.3.10
zeroconf==0.131.0
zim==0.75.2
zipp==1.0.0

View File

@ -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.1", packages=find_packages())