Furizon overlord - working

This commit is contained in:
Stranck 2023-12-30 11:27:42 +01:00
parent c735144dca
commit 160d186aeb
33 changed files with 661 additions and 106 deletions

2
.gitignore vendored
View File

@ -161,3 +161,5 @@ res/propic/propic_*
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
config.py config.py
furizon_webinit_riverside2023.tar.gz
diomerdas

View File

@ -0,0 +1,26 @@
# Furizon Webint
Furizon Webint is a powerful control panel designed to complement Pretix, providing management of various aspects related to the attendance of participants at furry conventions. Originally developed for Furizon Beyond (2023), this application is currently undergoing a rehaul to become more versatile and adaptable for use in any convention.
## How does it work?
The integration with Pretix is achieved by leveraging a simple nginx rule. When individuals place orders through Pretix, they usually receive a "magic" link that allows them to manage their order. Using a nginx rule, we redirect these requests to this backend. This process is seamless because the essential information needed for managing Pretix orders can still be accessed via a shorter URL without compromising any functionality.
## Why not a pretix plugin?
Developing plugins for Pretix was far too tedious, and Pretix didn't have the flexibility needed for this panel.
## What can it do?
- User badges management (allow attendees to upload pictures within the deadlines)
- Manage hotel rooms (attendees can create, join, delete rooms)
- Show a nosecount public page
- Data export
- Car pooling (let attendees post announcements and organize trips)
- Karaoke Queue management (apply to sing for the karaoke contest and manage the queue)
- Manage the events and present them via API for usage with he app
- Export an API to be used for the mobile app (no plans to open source that, sorry ☹️)
- Check-in management
## How to run it
1. Create a Python virtual environment (venv).
2. Install the required dependencies from the `requirements.txt` file.
3. Edit the `config.py` file with your specific data. You can use `config.example.py` as a template to guide you.
4. Set up an nginx rule to redirect requests for `/manage/` and `/[a-z0-9]+/[a-z0-9]+/order/[A-Z0-9]+/[a-z0-9]+/open/[a-z0-9]+/` to the Furizon Webint backend.
5. Run `app.py`. By default, the application will listen on `0.0.0.0:8188`.

17
app.py
View File

@ -10,6 +10,7 @@ from os.path import join
from ext import * from ext import *
from config import * from config import *
from aztec_code_generator import AztecCode from aztec_code_generator import AztecCode
from propic import resetDefaultPropic
from io import BytesIO from io import BytesIO
from asyncio import Queue from asyncio import Queue
import sqlite3 import sqlite3
@ -32,7 +33,7 @@ from carpooling import bp as carpooling_bp
from checkin import bp as checkin_bp from checkin import bp as checkin_bp
app.blueprint([room_bp, karaoke_bp, propic_bp, export_bp, stats_bp, api_bp, carpooling_bp, checkin_bp]) app.blueprint([room_bp, karaoke_bp, propic_bp, export_bp, stats_bp, api_bp, carpooling_bp, checkin_bp])
@app.exception(exceptions.SanicException) @app.exception(exceptions.SanicException)
async def clear_session(request, exception): async def clear_session(request, exception):
tpl = app.ctx.tpl.get_template('error.html') tpl = app.ctx.tpl.get_template('error.html')
@ -50,7 +51,7 @@ async def main_start(*_):
app.ctx.om = OrderManager() app.ctx.om = OrderManager()
if FILL_CACHE: if FILL_CACHE:
log.info("Filling cache!") log.info("Filling cache!")
await app.ctx.om.fill_cache() await app.ctx.om.updateCache()
log.info("Cache fill done!") log.info("Cache fill done!")
app.ctx.nfc_counts = sqlite3.connect('data/nfc_counts.db') app.ctx.nfc_counts = sqlite3.connect('data/nfc_counts.db')
@ -61,6 +62,7 @@ async def main_start(*_):
app.ctx.tpl.globals.update(time=time) app.ctx.tpl.globals.update(time=time)
app.ctx.tpl.globals.update(PROPIC_DEADLINE=PROPIC_DEADLINE) app.ctx.tpl.globals.update(PROPIC_DEADLINE=PROPIC_DEADLINE)
app.ctx.tpl.globals.update(ITEM_IDS=ITEM_IDS) app.ctx.tpl.globals.update(ITEM_IDS=ITEM_IDS)
app.ctx.tpl.globals.update(ROOM_TYPE_NAMES=ROOM_TYPE_NAMES)
app.ctx.tpl.globals.update(int=int) app.ctx.tpl.globals.update(int=int)
app.ctx.tpl.globals.update(len=len) app.ctx.tpl.globals.update(len=len)
@ -80,7 +82,7 @@ async def redirect_explore(request, code, secret, order: Order, secret2=None):
if not order: if not order:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
res = await client.get(join(base_url, f"orders/{code}/"), headers=headers) res = await client.get(join(base_url_event, f"orders/{code}/"), headers=headers)
print(res.json()) print(res.json())
if res.status_code != 200: if res.status_code != 200:
raise exceptions.NotFound("This order code does not exist. Check that your order wasn't deleted, or the link is correct.") raise exceptions.NotFound("This order code does not exist. Check that your order wasn't deleted, or the link is correct.")
@ -102,6 +104,11 @@ async def welcome(request, order: Order, quota: Quotas):
if not order: if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!") raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if order.ans("propic_file") is None:
await resetDefaultPropic(request, order, False)
if order.ans("propic_fursuiter_file") is None:
await resetDefaultPropic(request, order, True)
pending_roommates = [] pending_roommates = []
if order.pending_roommates: if order.pending_roommates:
@ -139,7 +146,7 @@ async def download_ticket(request, order: Order):
raise exceptions.Forbidden("You are not allowed to download this ticket.") raise exceptions.Forbidden("You are not allowed to download this ticket.")
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
res = await client.get(join(base_url, f"orders/{order.code}/download/pdf/"), headers=headers) res = await client.get(join(base_url_event, f"orders/{order.code}/download/pdf/"), headers=headers)
if res.status_code == 409: if res.status_code == 409:
raise exceptions.SanicException("Your ticket is still being generated. Please try again later!", status_code=res.status_code) raise exceptions.SanicException("Your ticket is still being generated. Please try again later!", status_code=res.status_code)
@ -150,7 +157,7 @@ async def download_ticket(request, order: Order):
@app.route("/manage/logout") @app.route("/manage/logout")
async def logour(request): async def logour(request):
raise exceptions.Forbidden("You have been logged out.", status_code=403) raise exceptions.Forbidden("You have been logged out.")
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0", port=8188, dev=DEV_MODE) app.run(host="0.0.0.0", port=8188, dev=DEV_MODE)

View File

@ -2,7 +2,7 @@ from sanic.response import html, redirect, text
from sanic import Blueprint, exceptions, response from sanic import Blueprint, exceptions, response
from random import choice from random import choice
from ext import * from ext import *
from config import headers, base_url from config import headers, base_url_event
from PIL import Image from PIL import Image
from os.path import isfile from os.path import isfile
from os import unlink from os import unlink
@ -64,7 +64,7 @@ async def do_checkin(request):
if not order.checked_in: if not order.checked_in:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
res = await client.post(base_url.replace('events/beyond/', 'checkinrpc/redeem/'), json={'secret': order.barcode, 'source_type': 'barcode', 'type': 'entry', 'lists': [3,]}, headers=headers) res = await client.post(base_url_event.replace(f'events/{EVENT_NAME}/', 'checkinrpc/redeem/'), json={'secret': order.barcode, 'source_type': 'barcode', 'type': 'entry', 'lists': [3,]}, headers=headers)
tpl = request.app.ctx.tpl.get_template('checkin_3.html') tpl = request.app.ctx.tpl.get_template('checkin_3.html')
return html(tpl.render(order=order, room_owner=room_owner, roommates=roommates)) return html(tpl.render(order=order, room_owner=room_owner, roommates=roommates))

View File

@ -1,32 +1,53 @@
API_TOKEN = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
ORGANIZER = 'furizon' ORGANIZER = 'furizon'
EVENT_NAME = 'river-side-2023' EVENT_NAME = 'overlord'
API_TOKEN = 'xxxxxxxxxxxxxxxxxxxxxx' HOSTNAME = 'reg.furizon.net'
HOSTNAME = 'your-pretix-hostname'
headers = {'Host': HOSTNAME, 'Authorization': f'Token {API_TOKEN}'} headers = {'Host': HOSTNAME, 'Authorization': f'Token {API_TOKEN}'}
base_url = f"https://{HOSTNAME}/api/v1/organizers/{ORGANIZER}/events/{EVENT_NAME}/" base_url = "http://urlllllllllllllllllllll/api/v1/"
base_url_event = f"{base_url}organizers/{ORGANIZER}/events/{EVENT_NAME}/"
PROPIC_DEADLINE = 1683575684 PROPIC_DEADLINE = 9999999999
FILL_CACHE = True FILL_CACHE = True
CACHE_EXPIRE_TIME = 60 * 60 * 4
DEV_MODE = True DEV_MODE = True
ITEM_IDS = { ITEM_IDS = {
'ticket': [90,], 'ticket': [126, 127, 155],
'membership_card': [91,], 'membership_card': [128,],
'sponsorship': [], # first one = normal, second = super 'sponsorship': [55, 56], # first one = normal, second = super
'early_arrival': [], 'early_arrival': [133],
'late_departure': [], 'late_departure': [134],
'room': 98 'room': 135,
'bed_in_room': 153,
'daily': 162,
'daily_addons': [163, 164, 165, 166] #This should be in date order. If there are holes in the daily-span, insert an unexisting id
} }
# Create a bunch of "room" items which will get added to the order once somebody gets a room. # Create a bunch of "room" items which will get added to the order once somebody gets a room.
# Map variationId -> numberOfPeopleInRoom
ROOM_MAP = { ROOM_MAP = {
1: 16, # SACRO CUORE
2: 17, 83: 1,
3: 18, 67: 2,
4: 19, 68: 3,
5: 20 69: 4,
70: 5,
# OVERFLOW 1
75: 2
}
ROOM_TYPE_NAMES = {
83: "Park Hotel Sacro Cuore (main hotel) - Single",
67: "Park Hotel Sacro Cuore (main hotel) - Double",
68: "Park Hotel Sacro Cuore (main hotel) - Triple",
69: "Park Hotel Sacro Cuore (main hotel) - Quadruple",
70: "Park Hotel Sacro Cuore (main hotel) - Quintuple",
# OVERFLOW 1
75: "Hotel San Valier (overflow hotel) - Double"
} }
# This is used for feedback sending inside of the app. Feedbacks will be sent to the specified chat using the bot api id. # This is used for feedback sending inside of the app. Feedbacks will be sent to the specified chat using the bot api id.
@ -36,6 +57,6 @@ TG_CHAT_ID = -1234567
# These order codes have additional functions. # These order codes have additional functions.
ADMINS = ['XXXXX', 'YYYYY'] ADMINS = ['XXXXX', 'YYYYY']
SMTP_HOST = 'your-smtp-host.com' SMTP_HOST = 'host'
SMTP_USER = 'username' SMTP_USER = 'user'
SMTP_PASSWORD = 'password' SMTP_PASSWORD = 'pw'

Binary file not shown.

Binary file not shown.

61
ext.py
View File

@ -2,6 +2,7 @@ from dataclasses import dataclass
from sanic import Request, exceptions from sanic import Request, exceptions
import httpx import httpx
import re import re
from utils import *
from config import * from config import *
from os.path import join from os.path import join
import json import json
@ -32,6 +33,11 @@ class Order:
self.country = 'xx' self.country = 'xx'
self.address = None self.address = None
self.checked_in = False self.checked_in = False
self.room_type = None
self.daily = False
self.dailyDays = []
self.room_person_no = 0
self.answers = []
idata = data['invoice_address'] idata = data['invoice_address']
if idata: if idata:
@ -39,13 +45,22 @@ class Order:
self.country = idata['country'] self.country = idata['country']
for p in self.data['positions']: for p in self.data['positions']:
if p['item'] in (ITEM_IDS['ticket'] + ITEM_IDS['daily']): if p['item'] in (ITEM_IDS['ticket'] + [ITEM_IDS['daily']]):
self.position_id = p['id'] self.position_id = p['id']
self.position_positionid = p['positionid'] self.position_positionid = p['positionid']
self.position_positiontypeid = p['item'] self.position_positiontypeid = p['item']
self.answers = p['answers'] self.answers = p['answers']
for i, ans in enumerate(self.answers):
if(TYPE_OF_QUESTIONS[self.answers[i]['question']] == QUESTION_TYPES['file_upload']):
self.answers[i]['answer'] = "file:keep"
self.barcode = p['secret'] self.barcode = p['secret']
self.checked_in = bool(p['checkins']) self.checked_in = bool(p['checkins'])
if p['item'] == ITEM_IDS['daily']:
self.daily = True
if p['item'] in ITEM_IDS['daily_addons']:
self.daily = True
self.dailyDays.append(ITEM_IDS['daily_addons'].index(p['item']))
if p['item'] in ITEM_IDS['membership_card']: if p['item'] in ITEM_IDS['membership_card']:
self.has_card = True self.has_card = True
@ -62,6 +77,10 @@ class Order:
if p['item'] == ITEM_IDS['late_departure']: if p['item'] == ITEM_IDS['late_departure']:
self.has_late = True self.has_late = True
if p['item'] == ITEM_IDS['bed_in_room']:
self.bed_in_room = p['variation']
self.room_person_no = ROOM_MAP[self.bed_in_room] if self.bed_in_room in ROOM_MAP else None
self.total = float(data['total']) self.total = float(data['total'])
self.fees = 0 self.fees = 0
@ -117,6 +136,18 @@ class Order:
return a['answer'] return a['answer']
return None return None
async def edit_answer_fileUpload(self, name, fileName, mimeType, data : bytes):
if(mimeType != None and data != None):
async with httpx.AsyncClient() as client:
localHeaders = dict(headers)
localHeaders['Content-Type'] = mimeType
localHeaders['Content-Disposition'] = f'attachment; filename="{fileName}"'
res = await client.post(join(base_url, 'upload'), headers=localHeaders, content=data)
res = res.json()
await self.edit_answer(name, res['id'])
else:
await self.edit_answer(name, None)
async def edit_answer(self, name, new_answer): async def edit_answer(self, name, new_answer):
found = False found = False
self.pending_update = True self.pending_update = True
@ -135,7 +166,7 @@ class Order:
if (not found) and (new_answer is not None): if (not found) and (new_answer is not None):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
res = await client.get(join(base_url, 'questions/'), headers=headers) res = await client.get(join(base_url_event, 'questions/'), headers=headers)
res = res.json() res = res.json()
for r in res['results']: for r in res['results']:
@ -158,7 +189,7 @@ class Order:
del self.answers[i]['options'] del self.answers[i]['options']
del self.answers[i]['option_identifiers'] del self.answers[i]['option_identifiers']
res = await client.patch(join(base_url, f'orderpositions/{self.position_id}/'), headers=headers, json={'answers': self.answers}) res = await client.patch(join(base_url_event, f'orderpositions/{self.position_id}/'), headers=headers, json={'answers': self.answers})
if res.status_code != 200: if res.status_code != 200:
for ans, err in zip(self.answers, res.json()['answers']): for ans, err in zip(self.answers, res.json()['answers']):
@ -167,6 +198,10 @@ class Order:
raise exceptions.ServerError('There has been an error while updating this answers.') raise exceptions.ServerError('There has been an error while updating this answers.')
for i, ans in enumerate(self.answers):
if(TYPE_OF_QUESTIONS[self.answers[i]['question']] == QUESTION_TYPES['file_upload']):
self.answers[i]['answer'] = "file:keep"
self.pending_update = False self.pending_update = False
self.time = -1 self.time = -1
@ -183,7 +218,7 @@ class Quotas:
async def get_quotas(request: Request=None): async def get_quotas(request: Request=None):
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
res = await client.get(join(base_url, 'quotas/?order=id&with_availability=true'), headers=headers) res = await client.get(join(base_url_event, 'quotas/?order=id&with_availability=true'), headers=headers)
res = res.json() res = res.json()
return Quotas(res) return Quotas(res)
@ -194,9 +229,20 @@ async def get_order(request: Request=None):
class OrderManager: class OrderManager:
def __init__(self): def __init__(self):
self.lastCacheUpdate = 0
self.empty()
def empty(self):
self.cache = {} self.cache = {}
self.order_list = [] self.order_list = []
async def updateCache(self):
t = time()
if(t - self.lastCacheUpdate > CACHE_EXPIRE_TIME):
print("Re-filling cache!")
await self.fill_cache()
self.lastCacheUpdate = t
def add_cache(self, order): def add_cache(self, order):
self.cache[order.code] = order self.cache[order.code] = order
if not order.code in self.order_list: if not order.code in self.order_list:
@ -208,12 +254,14 @@ class OrderManager:
self.order_list.remove(code) self.order_list.remove(code)
async def fill_cache(self): async def fill_cache(self):
await loadQuestions()
self.empty()
p = 0 p = 0
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
while 1: while 1:
p += 1 p += 1
res = await client.get(join(base_url, f"orders/?page={p}"), headers=headers) res = await client.get(join(base_url_event, f"orders/?page={p}"), headers=headers)
if res.status_code == 404: break if res.status_code == 404: break
@ -233,6 +281,7 @@ class OrderManager:
if order.nfc_id == nfc_id: if order.nfc_id == nfc_id:
return order return order
await self.updateCache()
# If a cached order is needed, just get it if available # If a cached order is needed, just get it if available
if code and cached and code in self.cache and time()-self.cache[code].time < 3600: if code and cached and code in self.cache and time()-self.cache[code].time < 3600:
return self.cache[code] return self.cache[code]
@ -246,7 +295,7 @@ class OrderManager:
print('Fetching', code, 'with secret', secret) print('Fetching', code, 'with secret', secret)
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
res = await client.get(join(base_url, f"orders/{code}/"), headers=headers) res = await client.get(join(base_url_event, f"orders/{code}/"), headers=headers)
if res.status_code != 200: if res.status_code != 200:
if request: if request:
raise exceptions.Forbidden("Your session has expired due to order deletion or change! Please check your E-Mail for more info.") raise exceptions.Forbidden("Your session has expired due to order deletion or change! Please check your E-Mail for more info.")

View File

@ -9,6 +9,15 @@ from time import time
bp = Blueprint("propic", url_prefix="/manage/propic") bp = Blueprint("propic", url_prefix="/manage/propic")
async def resetDefaultPropic(request, order: Order, isFursuiter, sendAnswer=True):
s = "_fursuiter" if isFursuiter else ""
print("Resetting default propic")
with open("res/propic/default.png", "rb") as f:
data = f.read()
await order.edit_answer_fileUpload(f'propic{s}_file', f'propic{s}_file_{order.code}_default.png', 'image/png', data)
if(sendAnswer):
await order.send_answers()
@bp.post("/upload") @bp.post("/upload")
async def upload_propic(request, order: Order): async def upload_propic(request, order: Order):
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!") if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
@ -21,8 +30,10 @@ async def upload_propic(request, order: Order):
if request.form.get('submit') == 'Delete main image': if request.form.get('submit') == 'Delete main image':
await order.edit_answer('propic', None) await order.edit_answer('propic', None)
await resetDefaultPropic(request, order, False, sendAnswer=False)
elif request.form.get('submit') == 'Delete fursuit image': elif request.form.get('submit') == 'Delete fursuit image':
await order.edit_answer('propic_fursuiter', None) await order.edit_answer('propic_fursuiter', None)
await resetDefaultPropic(request, order, True, sendAnswer=False)
else: else:
for fn, body in request.files.items(): for fn, body in request.files.items():
if fn not in ['propic', 'propic_fursuiter']: if fn not in ['propic', 'propic_fursuiter']:
@ -49,8 +60,17 @@ async def upload_propic(request, order: Order):
img = img.convert('RGB') img = img.convert('RGB')
img.thumbnail((512,512)) img.thumbnail((512,512))
img.save(f"res/propic/{fn}_{order.code}_{h}.jpg") imgBytes = BytesIO()
except: img.save(imgBytes, format='jpeg')
imgBytes = imgBytes.getvalue()
with open(f"res/propic/{fn}_{order.code}_{h}.jpg", "wb") as f:
f.write(imgBytes)
await order.edit_answer_fileUpload(f'{fn}_file', f'{fn}_file_{order.code}_{h}.jpg', 'image/jpeg', imgBytes)
except Exception:
import traceback
print(traceback.format_exc())
raise exceptions.BadRequest("The image you uploaded is not valid.") raise exceptions.BadRequest("The image you uploaded is not valid.")
else: else:
await order.edit_answer(fn, f"{fn}_{order.code}_{h}.jpg") await order.edit_answer(fn, f"{fn}_{order.code}_{h}.jpg")

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 MiB

BIN
reg.furizon.net/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

BIN
reg.furizon.net/bgVert.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

21
reg.furizon.net/fz23.html Normal file
View File

@ -0,0 +1,21 @@
<html>
<head>
<title>Thanks!</title>
<style>
body {font-family: sans-serif;color:#eee;background:#222;}
main {margin: 4em auto;max-width:50em;line-height:2em;}
h1, h2 {color:#e90;}
</style>
</head>
<body>
<main>
<h1 id="a">Thanks for participating in Furizon ENABLE_JAVASCRIPT!</h1>
<p>We just came back home and we need some time to regain energy and get back to normal! The registration system is currently offline, and we are working on making it better!</p>
<h2>When will it be back online?</h2>
<p>The registration system was hosted in a server in the convention network itself. After the end of the convention, the network was disassembled. We will soon setup the server again!</p>
<h2>Can i contact you?</h2>
<p>Yes. Just write to info@furizon.net</p>
</main>
</body>
<script>document.getElementById("a").innerText = "Thanks for participating in Furizon " + (new Date().getFullYear()) + "!"</script>
</html>

162
reg.furizon.net/index.html Normal file
View File

@ -0,0 +1,162 @@
<html>
<head>
<title>Countdown to Furizon</title>
<link rel="preconnect" href="https://fonts.bunny.net">
<meta name="viewport" content="width=500rem" />
<link href="https://fonts.bunny.net/css?family=pt-serif-caption:400" rel="stylesheet" />
<meta property="og:title" content="Furizon Overlord · Booking">
<meta property="og:description" content="Acquista i biglietti per Furizon Overlord, la convention furry Italiana.">
<style>
*{margin:0;border:0;padding:0;}
body {
font-family: 'PT Serif Caption';
background: url('bg.jpg') center center no-repeat;
backdrop-filter: blur(5px);
background-color:#111;
height:100%;
}
@media screen and (min-width: calc(100vh * 1.406)) { /*1.406 is width/height of the image*/
.containedBg {
width: 100%;
height: 100%;
background: url('bg.jpg') center center no-repeat;
background-size: contain;
}
}
@media screen and (max-width: calc(100vh * 1.406)) and (min-width: calc(100vh * 0.706)) {
.containedBg {
width: 100%;
height: 100%;
background: url('bg.jpg') right center no-repeat;
background-size: cover;
}
}
@media screen and (max-width: calc(100vh * 0.706)) {
.containedBg {
width: 100%;
height: 100%;
background: url('bgVert.jpg') center center no-repeat;
background-size: contain;
}
}
img {
display:block;
height: 6rem;
margin: 0 auto;
}
main {
padding: 1em 0;
font-size: 1.2rem;
color: #fff;
width:100%;
box-sizing:border-box;
background:rgba(0,0,0,0.5);
text-align:center;
position:absolute;
top: 50%;
transform: translateY(-50%);
}
em {
color: #FCB53F;
font-size: 0.5em;
text-decoration:none;
}
main a {
font-size:1.6em;
margin-top: 0.5em;
color: #FCB53F;
background:#000;
border-radius: 5px;
text-decoration:none;
padding: 0.3em 0.6em;
display:inline-block;
transition: all 0.2s;
}
main a:hover {
background:#FCB53F;
color: #000;
}
span {
font-size: 2em;
}
#timer {display:block;max-width:30em;margin: 0 auto;}
#clock {display:block;}
</style>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://y.foxo.me/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</head>
<body>
<div class="containedBg"></div>
<main>
<div id="timer">
<img src="https://furizon.net/wp-content/uploads/2022/08/Logo_base_no_sfondoo_Furizon_1-1.png" />
<h2>Overlord Registration</h2>
<span id="clock">Enjoy furizon~</span>
<a id="button" href="https://reg.furizon.net/furizon/overlord/" style="display:none">Book now!</a>
</div>
</main>
<script>
// Set the date we're counting down to
var countDownDate = 1705143600 * 1000;
var now = new Date().getTime();
if(now <= countDownDate) {
// Update the count down every 1 second
var x = setInterval(function() {
// Get today's date and time
var now = new Date().getTime();
// Find the distance between now and the count down date
var distance = countDownDate - now;
// Time calculations for days, hours, minutes and seconds
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Display the result in the element with id="timer"
document.getElementById("clock").innerHTML = days + "<em>d</em> " + hours + "<em>h</em> "
+ minutes + "<em>m</em> " + seconds + "<em>s</em>";
// If the count down is finished, write some text
if (distance < 0) {
clearInterval(x);
document.getElementById("button").style.display = 'block';
document.getElementById("clock").style.display = 'none';
}
}, 1000);
} else {
document.getElementById("button").style.display = 'block';
document.getElementById("clock").style.display = 'none';
}
</script>
</body>
</html>

View File

@ -0,0 +1,132 @@
<html>
<head>
<title>Countdown to Furizon</title>
<link rel="preconnect" href="https://fonts.bunny.net">
<meta name="viewport" content="width=500rem" />
<link href="https://fonts.bunny.net/css?family=pt-serif-caption:400" rel="stylesheet" />
<meta property="og:title" content="Furizon Beyond · Booking">
<meta property="og:description" content="Acquista i biglietti per Furizon Beyond, la convention furry Italiana.">
<style>
*{margin:0;border:0;padding:0;}
body {
font-family: 'PT Serif Caption';
background-image: url('https://furizon.net/wp-content/uploads/2022/12/Header-banner.jpg?id=1635');
background-repeat: no-repeat;
background-position: right;
background-size: cover;
background-color:#111;
height:100%;
}
img {
display:block;
height: 6rem;
margin: 0 auto;
}
main {
padding: 1em 0;
font-size: 1.2rem;
color: #fff;
width:100%;
box-sizing:border-box;
background:rgba(0,0,0,0.5);
text-align:center;
position:absolute;
top: 50%;
transform: translateY(-50%);
}
em {
color: #FCB53F;
font-size: 0.5em;
text-decoration:none;
}
main a {
font-size:1.6em;
margin-top: 0.5em;
color: #FCB53F;
background:#000;
border-radius: 5px;
text-decoration:none;
padding: 0.3em 0.6em;
display:inline-block;
transition: all 0.2s;
}
main a:hover {
background:#FCB53F;
color: #000;
}
#timer {display:block;max-width:30em;margin: 0 auto;}
#clock {display:block;}
</style>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://y.foxo.me/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</head>
<body>
<main>
<div id="timer">
<img src="https://furizon.net/wp-content/uploads/2022/08/Logo_base_no_sfondoo_Furizon_1-1.png" />
<h2>Thank you to everybody who registered for Furizon 2023!</h2>
<p>Too late to register? You can still sign up to the waiting list! We will contact you as soon as a spot is available for you~</p>
<!--<span id="clock">Enjoy furizon~</span>-->
<a id="button" href="https://reg.furizon.net/furizon/beyond/waitinglist?item=38">Join waiting list</a>
<a id="button" href="https://reg.furizon.net/furizon/beyond/redeem">Redeem code</a>
</div>
</main>
<!--<script>
// Set the date we're counting down to
var countDownDate = 1685455200 * 1000;
var now = new Date().getTime();
if(now <= countDownDate) {
// Update the count down every 1 second
var x = setInterval(function() {
// Get today's date and time
var now = new Date().getTime();
// Find the distance between now and the count down date
var distance = countDownDate - now;
// Time calculations for days, hours, minutes and seconds
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Display the result in the element with id="timer"
document.getElementById("clock").innerHTML = days + "<em>d</em> " + hours + "<em>h</em> "
+ minutes + "<em>m</em> " + seconds + "<em>s</em>";
// If the count down is finished, write some text
if (distance < 0) {
clearInterval(x);
document.getElementById("clock").style.display = 'none';
}
}, 1000);
}
</script>-->
</body>
</html>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

View File

@ -94,13 +94,13 @@ function clock() {
currentTime = new Date(); currentTime = new Date();
let ts = currentTime.toString(); let ts = currentTime.toString();
ts = ts.replace("GMT+0200 (Central European Summer Time)", ""); ts = ts.replace("GMT+0200 (Central European Summer Time)", "");
ts = ts.replace("2023", "<br />"); ts = ts.replace("2024", "<br />");
document.getElementById("clock").innerHTML = ts; document.getElementById("clock").innerHTML = ts;
}, 1000); }, 1000);
} }
function fastForward() { function fastForward() {
currentTime = new Date("2023-05-29T18:00Z"); currentTime = new Date("2024-06-03T18:00Z");
setInterval(() => { setInterval(() => {
updateDivs(cachedData); updateDivs(cachedData);
currentTime.setMinutes(currentTime.getMinutes()+1); currentTime.setMinutes(currentTime.getMinutes()+1);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

63
room.py
View File

@ -18,6 +18,9 @@ async def room_create_post(request, order: Order):
if order.room_id: if order.room_id:
error = "You are already in another room. You need to delete (if it's yours) or leave it before creating another." error = "You are already in another room. You need to delete (if it's yours) or leave it before creating another."
if order.daily:
raise exceptions.BadRequest("You cannot create a room if you have a daily ticket!")
if not error: if not error:
await order.edit_answer('room_name', name) await order.edit_answer('room_name', name)
@ -35,6 +38,9 @@ async def room_create(request, order: Order):
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!") if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if order.daily:
raise exceptions.BadRequest("You cannot create a room if you have a daily ticket!")
tpl = request.app.ctx.tpl.get_template('create_room.html') tpl = request.app.ctx.tpl.get_template('create_room.html')
return html(tpl.render(order=order)) return html(tpl.render(order=order))
@ -70,6 +76,9 @@ async def join_room(request, order: Order):
if order.room_id: if order.room_id:
raise exceptions.BadRequest("You are in another room already. Why would you join another?") raise exceptions.BadRequest("You are in another room already. Why would you join another?")
if order.daily:
raise exceptions.BadRequest("You cannot join a room if you have a daily ticket!")
code = request.form.get('code').strip() code = request.form.get('code').strip()
room_secret = request.form.get('room_secret').strip() room_secret = request.form.get('room_secret').strip()
@ -87,6 +96,9 @@ async def join_room(request, order: Order):
if room_owner.room_confirmed: if room_owner.room_confirmed:
raise exceptions.BadRequest("The room you're trying to join has been confirmed already") raise exceptions.BadRequest("The room you're trying to join has been confirmed already")
if room_owner.bed_in_room != order.bed_in_room:
raise exceptions.BadRequest("This room's ticket is of a different type than yours!")
#if room_owner.pending_roommates and (order.code in room_owner.pending_roommates): #if room_owner.pending_roommates and (order.code in room_owner.pending_roommates):
#raise exceptions.BadRequest("What? You should never reach this check, but whatever...") #raise exceptions.BadRequest("What? You should never reach this check, but whatever...")
@ -252,9 +264,11 @@ async def confirm_room(request, order: Order, quotas: Quotas):
if order.room_id != order.code: if order.room_id != order.code:
raise exceptions.BadRequest("You are not allowed to confirm rooms of others.") raise exceptions.BadRequest("You are not allowed to confirm rooms of others.")
if quotas.get_left(len(order.room_members)) == 0: # This is not needed anymore you buy tickets already
raise exceptions.BadRequest("There are no more rooms of this size to reserve.") #if quotas.get_left(len(order.room_members)) == 0:
# raise exceptions.BadRequest("There are no more rooms of this size to reserve.")
bed_in_room = order.bed_in_room # Variation id of the ticket for that kind of room
room_members = [] room_members = []
for m in order.room_members: for m in order.room_members:
if m == order.code: if m == order.code:
@ -267,8 +281,18 @@ async def confirm_room(request, order: Order, quotas: Quotas):
if res.status != 'paid': if res.status != 'paid':
raise exceptions.BadRequest("Somebody hasn't paid.") raise exceptions.BadRequest("Somebody hasn't paid.")
if res.bed_in_room != bed_in_room:
raise exceptions.BadRequest("Somebody has a ticket for a different type of room!")
if res.daily:
raise exceptions.BadRequest("Somebody in your room has a daily ticket!")
room_members.append(res) room_members.append(res)
if len(room_members) != order.room_person_no and order.room_person_no != None:
raise exceptions.BadRequest("The number of people in your room mismatches your type of ticket!")
for rm in room_members: for rm in room_members:
await rm.edit_answer('room_id', order.code) await rm.edit_answer('room_id', order.code)
@ -276,28 +300,19 @@ async def confirm_room(request, order: Order, quotas: Quotas):
await rm.edit_answer('pending_roommates', None) await rm.edit_answer('pending_roommates', None)
await rm.edit_answer('pending_room', None) await rm.edit_answer('pending_room', None)
thing = { # This should now be useless because in the ticket there already is the ticket/room type
'order': order.code, # thing = {
'addon_to': order.position_positionid, # 'order': order.code,
'item': ITEM_IDS['room'], # 'addon_to': order.position_positionid,
'variation': ROOM_MAP[len(room_members)] # 'item': ITEM_IDS['room'],
} # 'variation': ROOM_MAP[len(room_members)]
# }
async with httpx.AsyncClient() as client: #
res = await client.post(join(base_url, "orderpositions/"), headers=headers, json=thing) # async with httpx.AsyncClient() as client:
# res = await client.post(join(base_url_event, "orderpositions/"), headers=headers, json=thing)
if res.status_code != 201: #
raise exceptions.BadRequest("Something has gone wrong! Please contact support immediately") # if res.status_code != 201:
# raise exceptions.BadRequest("Something has gone wrong! Please contact support immediately")
'''for rm in room_members:
if rm.code == order.code: continue
thing = {
'order': rm.code,
'addon_to': rm.position_positionid,
'item': ITEM_IDS['room'],
'variation': ROOM_MAP[len(room_members)]
}
res = await client.post(join(base_url, "orderpositions/"), headers=headers, json=thing) '''
for rm in room_members: for rm in room_members:
await rm.send_answers() await rm.send_answers()

View File

@ -6,7 +6,16 @@ bp = Blueprint("stats", url_prefix="/manage")
@bp.route("/nosecount") @bp.route("/nosecount")
async def nose_count(request, order: Order): async def nose_count(request, order: Order):
await request.app.ctx.om.updateCache()
orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: len(x[1].room_members), reverse=True) if value.status not in ['c', 'e']} orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: len(x[1].room_members), reverse=True) if value.status not in ['c', 'e']}
tpl = request.app.ctx.tpl.get_template('nosecount.html') tpl = request.app.ctx.tpl.get_template('nosecount.html')
return html(tpl.render(orders=orders, order=order)) return html(tpl.render(orders=orders, order=order))
@bp.route("/fursuitcount")
async def fursuit_count(request, order: Order):
await request.app.ctx.om.updateCache()
orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: len(x[1].room_members), reverse=True) if value.status not in ['c', 'e']}
tpl = request.app.ctx.tpl.get_template('fursuitcount.html')
return html(tpl.render(orders=orders, order=order))

View File

@ -86,9 +86,11 @@
<a href="/manage/welcome">Your Booking</a> <a href="/manage/welcome">Your Booking</a>
{% endif %} {% endif %}
<a href="/manage/nosecount">Nose Count</a> <a href="/manage/nosecount">Nose Count</a>
<a href="/manage/fursuitcount">Fursuit Count</a>
{% if order %} {% if order %}
<a href="/manage/carpooling">Carpooling</a> <a href="/manage/carpooling">Carpooling</a>
<a style="float:right;" href="/manage/logout">Logout</a> <a style="float:right;" href="/manage/logout">Logout</a>
<a style="float:right;color: #b1b1b1;">Logged in as <i>{{order.ans('fursona_name')}}</i></a>
{% endif %} {% endif %}
<br clear="both" /> <br clear="both" />
</nav> </nav>

View File

@ -5,7 +5,7 @@
{% if order.propic_locked %} {% if order.propic_locked %}
<p class="notice">⚠️ You have been limited from further editing your profile pic.</p> <p class="notice">⚠️ You have been limited from further editing your profile pic.</p>
{% endif %} {% endif %}
{% if (not order.ans('propic')) or (order.ans('is_fursuiter') != 'No' and not order.ans('propic_fursuiter')) %} {% if not order.ans('propic') or (order.is_fursuiter and not order.ans('propic_fursuiter')) %}
<p class="notice">⚠️ One or more badge pictures are missing! This will cause you badge to be empty, so make sure to upload something before the deadline!</p> <p class="notice">⚠️ One or more badge pictures are missing! This will cause you badge to be empty, so make sure to upload something before the deadline!</p>
{% endif %} {% endif %}
<form method="POST" enctype="multipart/form-data" action="/manage/propic/upload"> <form method="POST" enctype="multipart/form-data" action="/manage/propic/upload">
@ -20,7 +20,7 @@
{% endif %} {% endif %}
<p>Normal Badge</p> <p>Normal Badge</p>
</div> </div>
{% if order.ans('is_fursuiter') != 'No' %} {% if order.is_fursuiter %}
<div> <div>
{% if not order.ans('propic_fursuiter') %} {% if not order.ans('propic_fursuiter') %}
<input type="file" value="" accept="image/jpeg,image/png" name="propic_fursuiter" /> <input type="file" value="" accept="image/jpeg,image/png" name="propic_fursuiter" />
@ -50,7 +50,7 @@
{% if order.ans('propic_fursuiter') %} {% if order.ans('propic_fursuiter') %}
<input type="submit" name="submit" value="Delete fursuit image" {{'disabled' if time() > PROPIC_DEADLINE else ''}} /> <input type="submit" name="submit" value="Delete fursuit image" {{'disabled' if time() > PROPIC_DEADLINE else ''}} />
{% endif %} {% endif %}
{% if (not order.ans('propic')) or (order.ans('is_fursuiter') != 'No' and not order.ans('propic_fursuiter')) %} {% if not order.ans('propic') or (order.is_fursuiter and not order.ans('propic_fursuiter')) %}
<input type="submit" name="submit" value="Upload" /> <input type="submit" name="submit" value="Upload" />
{% endif %} {% endif %}
</div> </div>

View File

@ -18,7 +18,7 @@
</tr> </tr>
</table> </table>
{% if order.status == 'paid' and order.room_confirmed %} {% if order.status == 'paid' and order.room_confirmed %}
<p style="text-align:right;"><a href="/manage/download_ticket?name=BEYOND-{{order.code}}.pdf" role="button">Download ticket</a></p> <p style="text-align:right;"><a href="/manage/download_ticket?name=OVERLORD-{{order.code}}.pdf" role="button">Download ticket</a></p>
{% endif %} {% endif %}
{% if order.status != 'paid' %} {% if order.status != 'paid' %}
<a href="{{order.url}}"><button>Payment area</button></a> <a href="{{order.url}}"><button>Payment area</button></a>

View File

@ -1,12 +1,14 @@
<details id="room"> <details id="room">
<summary role="button"><img src="/res/icons/bedroom.svg" class="icon"/> Accomodation & Roommates <span class="status">{% if not order.room_confirmed %}⚠️{% endif %}</span></summary> <summary role="button"><img src="/res/icons/bedroom.svg" class="icon"/> Accomodation & Roommates <span class="status">{% if not order.room_confirmed %}⚠️{% endif %}</span></summary>
<h2>Your room {% if room_members %}- {{room_members[0].ans('room_name')}}{% endif %}</h2> <h2 style="margin-bottom:0;">Your room {% if room_members %}- {{room_members[0].ans('room_name')}}{% endif %}</h2>
<p><b>Room's type:</b> {{ROOM_TYPE_NAMES[order.bed_in_room]}}.</p>
<p class="notice" style="background:#0881c0"><b>Note! </b> Only people with the same room type can be roommates. If you need help, you can check the support guide <a href="https://pornhub.com">clicking here</a>.</p>
{# Show alert if room owner has wrong people inside #} {# Show alert if room owner has wrong people inside #}
{% if room_members and quota.get_left(len(room_members)) == 0 and (not order.room_confirmed) %} {# {% if room_members and quota.get_left(len(room_members)) == 0 and (not order.room_confirmed) %} #}
<p class="notice">⚠️ Your room contains {{len(room_members)}} people inside, but sadly there are no more {{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}} rooms. You need to add or remove people until you reach the size of an available room if you want to confirm it.</p> {# <p class="notice">⚠️ Your room contains {{len(room_members)}} people inside, but sadly there are no more {{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}} rooms. You need to add or remove people until you reach the size of an available room if you want to confirm it.</p> #}
{% endif %} {# {% endif %} #}
{# Show alert if room was not confirmed #} {# Show alert if room was not confirmed #}
{% if order.room_id and not order.room_confirmed %} {% if order.room_id and not order.room_confirmed %}
@ -15,7 +17,8 @@
{# Show notice if the room is confirmed #} {# Show notice if the room is confirmed #}
{% if order.room_confirmed %} {% if order.room_confirmed %}
<p class="notice" style="background:#060">✅ Your <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room has been confirmed</p> {# <p class="notice" style="background:#060">✅ Your <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room has been confirmed</p> #}
<p class="notice" style="background:#060">✅ Your <strong>{{[None,'single','double','triple','quadruple','quintuple'][order.room_person_no]}}</strong> room has been confirmed</p>
{% endif %} {% endif %}
{# Show roommates if room is set #} {# Show roommates if room is set #}
@ -30,7 +33,7 @@
</div> </div>
<h3>{{person.ans('fursona_name')}}</h3> <h3>{{person.ans('fursona_name')}}</h3>
{% if person.code == order.room_id %}<p><strong style="color:#c6f">ROOM OWNER</strong></p>{% endif %} {% if person.code == order.room_id %}<p><strong style="color:#c6f">ROOM OWNER</strong></p>{% endif %}
<p>{{person.ans('staff_title') if person.ans('staff_title') else ''}} {{'Fursuiter' if person.ans('is_fursuiter') != 'No'}}</p> <p>{{person.ans('staff_title') if person.ans('staff_title') else ''}} {{'Fursuiter' if person.is_fursuiter}}</p>
{% if person.status == 'pending' %} {% if person.status == 'pending' %}
<p><strong style="color:red;">UNPAID</strong></p> <p><strong style="color:red;">UNPAID</strong></p>
{% endif %} {% endif %}
@ -42,7 +45,8 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if order.room_id == order.code and not order.room_confirmed and len(room_members) < 5 %} {# {% if order.room_id == order.code and not order.room_confirmed and len(room_members) < 5%} #}
{% if order.room_id == order.code and not order.room_confirmed and len(room_members) < order.room_person_no %}
<div> <div>
<a href="javascript:document.getElementById('modal-roominvite').setAttribute('open', 'true');"> <a href="javascript:document.getElementById('modal-roominvite').setAttribute('open', 'true');">
<div class="propic-container"> <div class="propic-container">
@ -79,7 +83,8 @@
{% endif %} {% endif %}
{% if not order.room_confirmed %} {% if not order.room_confirmed %}
<a role="button" {% if not room.forbidden and quota.get_left(len(room_members)) > 0 %}href="javascript:document.getElementById('modal-roomconfirm').setAttribute('open', 'true');"{% endif %}>Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a> {# <a role="button" {% if not room.forbidden and quota.get_left(len(room_members)) > 0 %}href="javascript:document.getElementById('modal-roomconfirm').setAttribute('open', 'true');"{% endif %}>Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a> #}
<a role="button" {% if not room.forbidden and len(room_members) == order.room_person_no %}href="javascript:document.getElementById('modal-roomconfirm').setAttribute('open', 'true');"{% endif %}>Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][order.room_person_no]}}</strong> room</a>
{% endif %} {% endif %}
{% else %} {% else %}
{% if order.room_id and not order.room_confirmed %} {% if order.room_id and not order.room_confirmed %}
@ -113,13 +118,13 @@
{% endif %} {% endif %}
{# Room availability is always shown #} {# Room availability is always shown #}
<h4>Room availability</h4> {# <h4>Room availability</h4> #}
<table> {# <table> #}
{% for q in quota.data['results'] if 'Room' in q['name'] %} {# {% for q in quota.data['results'] if 'Room' in q['name'] %} #}
<tr {% if q['available_number'] == 0 %}style="text-decoration:line-through;"{% endif %}> {# <tr {% if q['available_number'] == 0 %}style="text-decoration:line-through;"{% endif %}> #}
<td>{{q['name']}}</td> {# <td>{{q['name']}}</td> #}
<td>{{q['available_number']}} left</td> {# <td>{{q['available_number']}} left</td> #}
</tr> {# </tr> #}
{% endfor %} {# {% endfor %} #}
</table> {# </table> #}
</details> </details>

View File

@ -29,16 +29,18 @@
<table> <table>
<tr> <tr>
<td>Room type</td> <td>Room type</td>
<td><strong>{{[None,'Single','Double','Triple','Quadruple','Quintuple'][len(room_members)]}} Room</strong></td> {# <td><strong>{{[None,'Single','Double','Triple','Quadruple','Quintuple'][len(room_members)]}} Room</strong></td> #}
</tr> <td><strong>{{[None,'Single','Double','Triple','Quadruple','Quintuple'][order.room_person_no]}} Room</strong></td>
<tr>
<td>Rooms left of this type</td>
<td><strong>{{quota.get_left(len(room_members))}}</strong></td>
</tr> </tr>
{# <tr> #}
{# <td>Rooms left of this type</td> #}
{# <td><strong>{{quota.get_left(len(room_members))}}</strong></td> #}
{# </tr> #}
</table> </table>
<footer> <footer>
<a href="javascript:document.getElementById('modal-roomconfirm').removeAttribute('open')" role="button">Close</a> <a href="javascript:document.getElementById('modal-roomconfirm').removeAttribute('open')" role="button">Close</a>
<a href="/manage/room/confirm" role="button">Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a> {# <a href="/manage/room/confirm" role="button">Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a> #}
<a href="/manage/room/confirm" role="button">Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][order.room_person_no]}}</strong> room</a>
</footer> </footer>
</article> </article>
</dialog> </dialog>

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Furizon 2023 Carpooling{% endblock %} {% block title %}Furizon 2024 Carpooling{% endblock %}
{% block main %} {% block main %}
<main class="container"> <main class="container">
<header> <header>
@ -42,13 +42,13 @@
Day of departure Day of departure
<select name="day_departure" id="day_departure"> <select name="day_departure" id="day_departure">
<option value="m29th" {{'selected' if order.carpooling_message.day_departure == 'm29th'}}>May 29th</option>
<option value="m30th" {{'selected' if order.carpooling_message.day_departure == 'm30th'}}>May 30th</option>
<option value="m31st" {{'selected' if order.carpooling_message.day_departure == 'm31st'}}>May 31st</option>
<option value="j1st" {{'selected' if order.carpooling_message.day_departure == 'j1st'}}>June 1st</option>
<option value="j2nd" {{'selected' if order.carpooling_message.day_departure == 'j2nd'}}>June 2nd</option>
<option value="j3rd" {{'selected' if order.carpooling_message.day_departure == 'j3rd'}}>June 3rd</option> <option value="j3rd" {{'selected' if order.carpooling_message.day_departure == 'j3rd'}}>June 3rd</option>
<option value="j4th" {{'selected' if order.carpooling_message.day_departure == 'j4th'}}>June 4th</option> <option value="j4th" {{'selected' if order.carpooling_message.day_departure == 'j4th'}}>June 4th</option>
<option value="j5th" {{'selected' if order.carpooling_message.day_departure == 'j5th'}}>June 5th</option>
<option value="j6th" {{'selected' if order.carpooling_message.day_departure == 'j6th'}}>June 6th</option>
<option value="j7th" {{'selected' if order.carpooling_message.day_departure == 'j7th'}}>June 7th</option>
<option value="j8th" {{'selected' if order.carpooling_message.day_departure == 'j8th'}}>June 8th</option>
<option value="j9th" {{'selected' if order.carpooling_message.day_departure == 'j9th'}}>June 9th</option>
</select> </select>
</label> </label>
<textarea id="message" name="message" style="height:10em;" placeholder="Write here your message" required>{{order.carpooling_message.message}}</textarea> <textarea id="message" name="message" style="height:10em;" placeholder="Write here your message" required>{{order.carpooling_message.message}}</textarea>

28
tpl/fursuitcount.html Normal file
View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block title %}Furizon 2024 Fursuitcount{% endblock %}
{% block main %}
<main class="container">
<header>
<picture>
<source srcset="/res/furizon.png" media="(prefers-color-scheme:dark)">
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
</picture>
</header>
<!--h2>Fursuit-count!</h2-->
<p>Welcome to the fursuit-count page! Here you can see all of the fursuits that you'll find at Furizon!</p>
{% for person in orders.values() if person.is_fursuiter%}
{% if loop.first %}
<div class="grid people" style="padding-bottom:1em;">
{% endif %}
<div style="margin-bottom: 1em;">
<div class="propic-container">
<img class="propic propic-{{person.sponsorship}}" src="/res/propic/{{person.ans('propic_fursuiter') or 'default.png'}}" />
<img class="propic-flag" src="/res/flags/{{person.country.lower()}}.svg" />
</div>
<h5>{{person.ans('fursona_name')}}</h5>
</div>
{% if loop.last %}</div>{% endif %}
{% endfor %}
</main>
{% endblock %}

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Furizon 2023 Karaoke Admin{% endblock %} {% block title %}Furizon 2024 Karaoke Admin{% endblock %}
{% block main %} {% block main %}
<main class="container"> <main class="container">
<h1>Karaoke Admin</h1> <h1>Karaoke Admin</h1>

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Furizon 2023 Nosecount{% endblock %} {% block title %}Furizon 2024 Nosecount{% endblock %}
{% block main %} {% block main %}
<main class="container"> <main class="container">
<header> <header>
@ -29,13 +29,14 @@
</div> </div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% for o in orders.values() if (o.code == o.room_id and not o.room_confirmed and len(o.room_members) > 1) %} {% for o in orders.values() if (o.code == o.room_id and not o.room_confirmed) %}
{% if loop.first %} {% if loop.first %}
<hr /> <hr />
<h1>Unconfirmed rooms</h1> <h1>Unconfirmed rooms</h1>
<p>These unconfirmed rooms are still being organized and may be subject to change. These rooms may also have openings for additional roommates. If you are interested in sharing a room, you can use this page to find potential roommates</p> <p>These unconfirmed rooms are still being organized and may be subject to change. These rooms may also have openings for additional roommates. If you are interested in sharing a room, you can use this page to find potential roommates</p>
{% endif %} {% endif %}
<h3>{{o.room_name}}</h3> <h3 style="display:inline-block">{{o.room_name}}</h3>
{% if o.room_person_no - len(o.room_members) > 0 %} <p style="display:inline-block"> - Remaining slots: {{o.room_person_no - len(o.room_members)}}</p> {% endif %}
<div class="grid people" style="padding-bottom:1em;"> <div class="grid people" style="padding-bottom:1em;">
{% for m in o.room_members %} {% for m in o.room_members %}
{% if m in orders %} {% if m in orders %}
@ -54,7 +55,7 @@
{% if loop.last %}</div>{% endif %} {% if loop.last %}</div>{% endif %}
{% endfor %} {% endfor %}
{% for person in orders.values() if (not person.room_id or len(person.room_members) == 1) and (not person.room_confirmed)%} {% for person in orders.values() if (not person.room_id or len(person.room_members) == 1) and (not person.room_confirmed) and not person.daily %}
{% if loop.first %} {% if loop.first %}
<hr /> <hr />
<h1>Roomless furs</h1> <h1>Roomless furs</h1>
@ -69,7 +70,24 @@
<h5>{{person.ans('fursona_name')}}</h5> <h5>{{person.ans('fursona_name')}}</h5>
</div> </div>
{% if loop.last %}</div>{% endif %} {% if loop.last %}</div>{% endif %}
{% endfor %} {% endfor %}
{% for person in orders.values() if person.daily %}
{% if loop.first %}
<hr />
<h1>Daily furs!</h1>
<p>These furs will not stay in our hotels, but may be there with us just a few days!</p>
<div class="grid people" style="padding-bottom:1em;">
{% endif %}
<div style="margin-bottom: 1em;">
<div class="propic-container">
<img class="propic propic-{{person.sponsorship}}" src="/res/propic/{{person.ans('propic') or 'default.png'}}" />
<img class="propic-flag" src="/res/flags/{{person.country.lower()}}.svg" />
</div>
<h5>{{person.ans('fursona_name')}}</h5>
</div>
{% if loop.last %}</div>{% endif %}
{% endfor %}
</main> </main>
{% endblock %} {% endblock %}

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Furizon 2023 Nosecount{% endblock %} {% block title %}Furizon 2024 Nosecount{% endblock %}
{% block main %} {% block main %}
<main class="container"> <main class="container">
<header> <header>
@ -8,7 +8,7 @@
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;"> <img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
</picture> </picture>
</header> </header>
<p class="notice" style="margin:2em 0;">⚠️ This privacy policy is a courtesy explanation of <a href="https://furizon.net/wp-content/uploads/2023/01/Regolamento-Furizon-Beyond-en-GB.pdf">the one you signed up when registering to the con</a></p> {# <p class="notice" style="margin:2em 0;">⚠️ This privacy policy is a courtesy explanation of <a href="https://furizon.net/wp-content/uploads/2023/01/Regolamento-Furizon-Beyond-en-GB.pdf">the one you signed up when registering to the con</a></p> #}
<h1>Privacy policy of this private area</h1> <h1>Privacy policy of this private area</h1>
<p>We collect only the data that is needed by law and to make sure that your convention experience is up to your expectations. Keep reading to know why and how we collect this data.</p> <p>We collect only the data that is needed by law and to make sure that your convention experience is up to your expectations. Keep reading to know why and how we collect this data.</p>

View File

@ -12,7 +12,7 @@
<p>From here, you can easily manage all aspects of your booking, including composing your hotel room, designing your badge, and updating your payment information. Simply use the buttons below to navigate between the different sections.</p> <p>From here, you can easily manage all aspects of your booking, including composing your hotel room, designing your badge, and updating your payment information. Simply use the buttons below to navigate between the different sections.</p>
<p>Buttons marked with ⚠️ require your attention</p> <p>Buttons marked with ⚠️ require your attention</p>
<p>If you have any questions or issues while using this page, please don't hesitate to contact us for assistance. We look forward to seeing you at Furizon Riverside!</p> <p>If you have any questions or issues while using this page, please don't hesitate to contact us for assistance. We look forward to seeing you at Furizon Overlord!</p>
<hr /> <hr />
<h2 id="info">Useful information</h2> <h2 id="info">Useful information</h2>
<table> <table>
@ -24,13 +24,14 @@
</tr> </tr>
<tr> <tr>
<th>When{{' (convention)' if order.has_early or order.has_late else ''}}?</th> <th>When{{' (convention)' if order.has_early or order.has_late else ''}}?</th>
<td>13 October → 15 October 2023</td></td> {# This should be early/late excluded! #}
<td>4 June → 8 June 2024</td></td>
</tr> </tr>
{% if order.has_early or order.has_late %} {% if order.has_early or order.has_late %}
<tr> <tr>
<th>When (check-in)?</th> <th>When (check-in)?</th>
<td> <td>
{{('12' if order.has_early else '13')|safe}} October → {{('16' if order.has_late else '15')|safe}} October 2023 {{('3' if order.has_early else '4')|safe}} October → {{('9' if order.has_late else '8')|safe}} June 2024
{% if order.has_early %} {% if order.has_early %}
<span class="tag">EARLY</span> <span class="tag">EARLY</span>
{% endif %} {% endif %}
@ -53,7 +54,7 @@
{% if order.status == 'paid' and order.room_confirmed %} {% if order.status == 'paid' and order.room_confirmed %}
<br /> <br />
<img src="/res/icons/pdf.svg" class="icon" /> <img src="/res/icons/pdf.svg" class="icon" />
<a href="/manage/download_ticket?name=RIVERSIDE-{{order.code}}.pdf" target="_blank">Download ticket PDF</a> <a href="/manage/download_ticket?name=OVERLORD-{{order.code}}.pdf" target="_blank">Download ticket PDF</a>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -61,7 +62,7 @@
<h2>Manage your booking</h2> <h2>Manage your booking</h2>
{% include 'blocks/payment.html' %} {% include 'blocks/payment.html' %}
{% if order.position_positiontypeid not in ITEM_IDS['daily'] %} {% if not order.daily %}
{% include 'blocks/room.html' %} {% include 'blocks/room.html' %}
{% endif %} {% endif %}
{% include 'blocks/badge.html' %} {% include 'blocks/badge.html' %}

35
utils.py Normal file
View File

@ -0,0 +1,35 @@
from os.path import join
from config import *
import httpx
QUESTION_TYPES = { #https://docs.pretix.eu/en/latest/api/resources/questions.html
"number": "N",
"one_line_string": "S",
"multi_line_string": "T",
"boolean": "B",
"choice_from_list": "C",
"multiple_choice_from_list": "M",
"file_upload": "F",
"date": "D",
"time": "H",
"date_time": "W",
"country_code": "CC",
"telephone_number": "TEL"
}
TYPE_OF_QUESTIONS = {} # maps questionId -> type
async def loadQuestions():
global TYPE_OF_QUESTIONS
TYPE_OF_QUESTIONS.clear()
async with httpx.AsyncClient() as client:
p = 0
while 1:
p += 1
res = await client.get(join(base_url_event, f"questions/?page={p}"), headers=headers)
if res.status_code == 404: break
data = res.json()
for q in data['results']:
TYPE_OF_QUESTIONS[q['id']] = q['type']