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.
#.idea/
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 config import *
from aztec_code_generator import AztecCode
from propic import resetDefaultPropic
from io import BytesIO
from asyncio import Queue
import sqlite3
@ -32,7 +33,7 @@ from carpooling import bp as carpooling_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.exception(exceptions.SanicException)
async def clear_session(request, exception):
tpl = app.ctx.tpl.get_template('error.html')
@ -50,7 +51,7 @@ async def main_start(*_):
app.ctx.om = OrderManager()
if FILL_CACHE:
log.info("Filling cache!")
await app.ctx.om.fill_cache()
await app.ctx.om.updateCache()
log.info("Cache fill done!")
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(PROPIC_DEADLINE=PROPIC_DEADLINE)
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(len=len)
@ -80,7 +82,7 @@ async def redirect_explore(request, code, secret, order: Order, secret2=None):
if not order:
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())
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.")
@ -102,6 +104,11 @@ async def welcome(request, order: Order, quota: Quotas):
if not order:
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 = []
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.")
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:
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")
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__":
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 random import choice
from ext import *
from config import headers, base_url
from config import headers, base_url_event
from PIL import Image
from os.path import isfile
from os import unlink
@ -64,7 +64,7 @@ async def do_checkin(request):
if not order.checked_in:
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')
return html(tpl.render(order=order, room_owner=room_owner, roommates=roommates))

View File

@ -1,32 +1,53 @@
API_TOKEN = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
ORGANIZER = 'furizon'
EVENT_NAME = 'river-side-2023'
API_TOKEN = 'xxxxxxxxxxxxxxxxxxxxxx'
HOSTNAME = 'your-pretix-hostname'
EVENT_NAME = 'overlord'
HOSTNAME = 'reg.furizon.net'
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
CACHE_EXPIRE_TIME = 60 * 60 * 4
DEV_MODE = True
ITEM_IDS = {
'ticket': [90,],
'membership_card': [91,],
'sponsorship': [], # first one = normal, second = super
'early_arrival': [],
'late_departure': [],
'room': 98
'ticket': [126, 127, 155],
'membership_card': [128,],
'sponsorship': [55, 56], # first one = normal, second = super
'early_arrival': [133],
'late_departure': [134],
'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.
# Map variationId -> numberOfPeopleInRoom
ROOM_MAP = {
1: 16,
2: 17,
3: 18,
4: 19,
5: 20
# SACRO CUORE
83: 1,
67: 2,
68: 3,
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.
@ -36,6 +57,6 @@ TG_CHAT_ID = -1234567
# These order codes have additional functions.
ADMINS = ['XXXXX', 'YYYYY']
SMTP_HOST = 'your-smtp-host.com'
SMTP_USER = 'username'
SMTP_PASSWORD = 'password'
SMTP_HOST = 'host'
SMTP_USER = 'user'
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
import httpx
import re
from utils import *
from config import *
from os.path import join
import json
@ -32,6 +33,11 @@ class Order:
self.country = 'xx'
self.address = None
self.checked_in = False
self.room_type = None
self.daily = False
self.dailyDays = []
self.room_person_no = 0
self.answers = []
idata = data['invoice_address']
if idata:
@ -39,13 +45,22 @@ class Order:
self.country = idata['country']
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_positionid = p['positionid']
self.position_positiontypeid = p['item']
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.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']:
self.has_card = True
@ -62,6 +77,10 @@ class Order:
if p['item'] == ITEM_IDS['late_departure']:
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.fees = 0
@ -117,6 +136,18 @@ class Order:
return a['answer']
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):
found = False
self.pending_update = True
@ -135,7 +166,7 @@ class Order:
if (not found) and (new_answer is not None):
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()
for r in res['results']:
@ -158,7 +189,7 @@ class Order:
del self.answers[i]['options']
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:
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.')
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.time = -1
@ -183,7 +218,7 @@ class Quotas:
async def get_quotas(request: Request=None):
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()
return Quotas(res)
@ -194,9 +229,20 @@ async def get_order(request: Request=None):
class OrderManager:
def __init__(self):
self.lastCacheUpdate = 0
self.empty()
def empty(self):
self.cache = {}
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):
self.cache[order.code] = order
if not order.code in self.order_list:
@ -208,12 +254,14 @@ class OrderManager:
self.order_list.remove(code)
async def fill_cache(self):
await loadQuestions()
self.empty()
p = 0
async with httpx.AsyncClient() as client:
while 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
@ -233,6 +281,7 @@ class OrderManager:
if order.nfc_id == nfc_id:
return order
await self.updateCache()
# 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:
return self.cache[code]
@ -246,7 +295,7 @@ class OrderManager:
print('Fetching', code, 'with secret', secret)
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 request:
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")
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")
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!")
@ -21,8 +30,10 @@ async def upload_propic(request, order: Order):
if request.form.get('submit') == 'Delete main image':
await order.edit_answer('propic', None)
await resetDefaultPropic(request, order, False, sendAnswer=False)
elif request.form.get('submit') == 'Delete fursuit image':
await order.edit_answer('propic_fursuiter', None)
await resetDefaultPropic(request, order, True, sendAnswer=False)
else:
for fn, body in request.files.items():
if fn not in ['propic', 'propic_fursuiter']:
@ -49,8 +60,17 @@ async def upload_propic(request, order: Order):
img = img.convert('RGB')
img.thumbnail((512,512))
img.save(f"res/propic/{fn}_{order.code}_{h}.jpg")
except:
imgBytes = BytesIO()
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.")
else:
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();
let ts = currentTime.toString();
ts = ts.replace("GMT+0200 (Central European Summer Time)", "");
ts = ts.replace("2023", "<br />");
ts = ts.replace("2024", "<br />");
document.getElementById("clock").innerHTML = ts;
}, 1000);
}
function fastForward() {
currentTime = new Date("2023-05-29T18:00Z");
currentTime = new Date("2024-06-03T18:00Z");
setInterval(() => {
updateDivs(cachedData);
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:
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:
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 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')
return html(tpl.render(order=order))
@ -70,6 +76,9 @@ async def join_room(request, order: Order):
if order.room_id:
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()
room_secret = request.form.get('room_secret').strip()
@ -87,6 +96,9 @@ async def join_room(request, order: Order):
if room_owner.room_confirmed:
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):
#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:
raise exceptions.BadRequest("You are not allowed to confirm rooms of others.")
if quotas.get_left(len(order.room_members)) == 0:
raise exceptions.BadRequest("There are no more rooms of this size to reserve.")
# This is not needed anymore you buy tickets already
#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 = []
for m in order.room_members:
if m == order.code:
@ -267,8 +281,18 @@ async def confirm_room(request, order: Order, quotas: Quotas):
if res.status != '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)
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:
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_room', None)
thing = {
'order': order.code,
'addon_to': order.position_positionid,
'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)
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) '''
# This should now be useless because in the ticket there already is the ticket/room type
# thing = {
# 'order': order.code,
# 'addon_to': order.position_positionid,
# 'item': ITEM_IDS['room'],
# 'variation': ROOM_MAP[len(room_members)]
# }
#
# 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")
for rm in room_members:
await rm.send_answers()

View File

@ -6,7 +6,16 @@ bp = Blueprint("stats", url_prefix="/manage")
@bp.route("/nosecount")
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']}
tpl = request.app.ctx.tpl.get_template('nosecount.html')
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>
{% endif %}
<a href="/manage/nosecount">Nose Count</a>
<a href="/manage/fursuitcount">Fursuit Count</a>
{% if order %}
<a href="/manage/carpooling">Carpooling</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 %}
<br clear="both" />
</nav>

View File

@ -5,7 +5,7 @@
{% if order.propic_locked %}
<p class="notice">⚠️ You have been limited from further editing your profile pic.</p>
{% 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>
{% endif %}
<form method="POST" enctype="multipart/form-data" action="/manage/propic/upload">
@ -20,7 +20,7 @@
{% endif %}
<p>Normal Badge</p>
</div>
{% if order.ans('is_fursuiter') != 'No' %}
{% if order.is_fursuiter %}
<div>
{% if not order.ans('propic_fursuiter') %}
<input type="file" value="" accept="image/jpeg,image/png" name="propic_fursuiter" />
@ -50,7 +50,7 @@
{% if order.ans('propic_fursuiter') %}
<input type="submit" name="submit" value="Delete fursuit image" {{'disabled' if time() > PROPIC_DEADLINE else ''}} />
{% 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" />
{% endif %}
</div>

View File

@ -18,7 +18,7 @@
</tr>
</table>
{% 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 %}
{% if order.status != 'paid' %}
<a href="{{order.url}}"><button>Payment area</button></a>

View File

@ -1,12 +1,14 @@
<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>
<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 #}
{% 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>
{% endif %}
{# {% 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> #}
{# {% endif %} #}
{# Show alert if room was not confirmed #}
{% if order.room_id and not order.room_confirmed %}
@ -15,7 +17,8 @@
{# Show notice if the room is 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 %}
{# Show roommates if room is set #}
@ -30,7 +33,7 @@
</div>
<h3>{{person.ans('fursona_name')}}</h3>
{% 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' %}
<p><strong style="color:red;">UNPAID</strong></p>
{% endif %}
@ -42,7 +45,8 @@
{% endif %}
{% 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>
<a href="javascript:document.getElementById('modal-roominvite').setAttribute('open', 'true');">
<div class="propic-container">
@ -79,7 +83,8 @@
{% endif %}
{% 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 %}
{% else %}
{% if order.room_id and not order.room_confirmed %}
@ -113,13 +118,13 @@
{% endif %}
{# Room availability is always shown #}
<h4>Room availability</h4>
<table>
{% for q in quota.data['results'] if 'Room' in q['name'] %}
<tr {% if q['available_number'] == 0 %}style="text-decoration:line-through;"{% endif %}>
<td>{{q['name']}}</td>
<td>{{q['available_number']}} left</td>
</tr>
{% endfor %}
</table>
{# <h4>Room availability</h4> #}
{# <table> #}
{# {% for q in quota.data['results'] if 'Room' in q['name'] %} #}
{# <tr {% if q['available_number'] == 0 %}style="text-decoration:line-through;"{% endif %}> #}
{# <td>{{q['name']}}</td> #}
{# <td>{{q['available_number']}} left</td> #}
{# </tr> #}
{# {% endfor %} #}
{# </table> #}
</details>

View File

@ -29,16 +29,18 @@
<table>
<tr>
<td>Room type</td>
<td><strong>{{[None,'Single','Double','Triple','Quadruple','Quintuple'][len(room_members)]}} Room</strong></td>
</tr>
<tr>
<td>Rooms left of this type</td>
<td><strong>{{quota.get_left(len(room_members))}}</strong></td>
{# <td><strong>{{[None,'Single','Double','Triple','Quadruple','Quintuple'][len(room_members)]}} Room</strong></td> #}
<td><strong>{{[None,'Single','Double','Triple','Quadruple','Quintuple'][order.room_person_no]}} Room</strong></td>
</tr>
{# <tr> #}
{# <td>Rooms left of this type</td> #}
{# <td><strong>{{quota.get_left(len(room_members))}}</strong></td> #}
{# </tr> #}
</table>
<footer>
<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>
</article>
</dialog>

View File

@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Furizon 2023 Carpooling{% endblock %}
{% block title %}Furizon 2024 Carpooling{% endblock %}
{% block main %}
<main class="container">
<header>
@ -42,13 +42,13 @@
Day of 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="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>
</label>
<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" %}
{% block title %}Furizon 2023 Karaoke Admin{% endblock %}
{% block title %}Furizon 2024 Karaoke Admin{% endblock %}
{% block main %}
<main class="container">
<h1>Karaoke Admin</h1>

View File

@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Furizon 2023 Nosecount{% endblock %}
{% block title %}Furizon 2024 Nosecount{% endblock %}
{% block main %}
<main class="container">
<header>
@ -29,13 +29,14 @@
</div>
{% endif %}
{% 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 %}
<hr />
<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>
{% 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;">
{% for m in o.room_members %}
{% if m in orders %}
@ -54,7 +55,7 @@
{% if loop.last %}</div>{% endif %}
{% 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 %}
<hr />
<h1>Roomless furs</h1>
@ -69,7 +70,24 @@
<h5>{{person.ans('fursona_name')}}</h5>
</div>
{% 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>
{% endblock %}

View File

@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Furizon 2023 Nosecount{% endblock %}
{% block title %}Furizon 2024 Nosecount{% endblock %}
{% block main %}
<main class="container">
<header>
@ -8,7 +8,7 @@
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
</picture>
</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>
<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>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 />
<h2 id="info">Useful information</h2>
<table>
@ -24,13 +24,14 @@
</tr>
<tr>
<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>
{% if order.has_early or order.has_late %}
<tr>
<th>When (check-in)?</th>
<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 %}
<span class="tag">EARLY</span>
{% endif %}
@ -53,7 +54,7 @@
{% if order.status == 'paid' and order.room_confirmed %}
<br />
<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 %}
</td>
</tr>
@ -61,7 +62,7 @@
<h2>Manage your booking</h2>
{% include 'blocks/payment.html' %}
{% if order.position_positiontypeid not in ITEM_IDS['daily'] %}
{% if not order.daily %}
{% include 'blocks/room.html' %}
{% endif %}
{% 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']