Merge pull request 'We did a lot of stuff :D' (#6) from drew-dev into stranck-dev
Reviewed-on: #6
This commit is contained in:
commit
f8ed348d51
|
@ -0,0 +1,72 @@
|
|||
from email.mime.text import MIMEText
|
||||
from sanic import response, redirect, Blueprint, exceptions
|
||||
from config import *
|
||||
from utils import *
|
||||
from ext import *
|
||||
import sqlite3
|
||||
import smtplib
|
||||
import random
|
||||
import string
|
||||
import httpx
|
||||
import json
|
||||
|
||||
bp = Blueprint("admin", url_prefix="/manage/admin")
|
||||
|
||||
def credentialsCheck (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.isAdmin() : raise exceptions.Forbidden("Birichino :)")
|
||||
|
||||
@bp.get('/cache/clear')
|
||||
async def clearCache(request, order:Order):
|
||||
credentialsCheck(request, order)
|
||||
await request.app.ctx.om.fill_cache()
|
||||
return redirect(f'/manage/admin')
|
||||
|
||||
@bp.get('/room/unconfirm/<code>')
|
||||
async def unconfirmRoom(request, code, order:Order):
|
||||
credentialsCheck(request, order)
|
||||
dOrder = await getOrderByCode_safe(request, code)
|
||||
|
||||
if(not dOrder.room_confirmed):
|
||||
raise exceptions.BadRequest("Room is not confirmed!")
|
||||
|
||||
ppl = getPeopleInRoomByRoomId(request, code)
|
||||
for p in ppl:
|
||||
await p.edit_answer('room_confirmed', "False")
|
||||
await p.send_answers()
|
||||
|
||||
return redirect(f'/manage/nosecount')
|
||||
|
||||
@bp.get('/room/delete/<code>')
|
||||
async def deleteRoom(request, code, order:Order):
|
||||
credentialsCheck(request, order)
|
||||
dOrder = await getOrderByCode_safe(request, code)
|
||||
|
||||
ppl = getPeopleInRoomByRoomId(request, code)
|
||||
for p in ppl:
|
||||
await p.edit_answer('room_id', None)
|
||||
await p.edit_answer('room_confirmed', "False")
|
||||
await p.edit_answer('room_name', None)
|
||||
await p.edit_answer('pending_room', None)
|
||||
await p.edit_answer('pending_roommates', None)
|
||||
await p.edit_answer('room_members', None)
|
||||
await p.edit_answer('room_owner', None)
|
||||
await p.edit_answer('room_secret', None)
|
||||
await p.send_answers()
|
||||
|
||||
await dOrder.send_answers()
|
||||
return redirect(f'/manage/nosecount')
|
||||
|
||||
@bp.post('/room/rename/<code>')
|
||||
async def renameRoom(request, code, order:Order):
|
||||
credentialsCheck(request, order)
|
||||
dOrder = await getOrderByCode_safe(request, code)
|
||||
|
||||
name = request.form.get('name')
|
||||
if len(name) > 64 or len(name) < 4:
|
||||
raise exceptions.BadRequest("Your room name is invalid. Please try another one.")
|
||||
|
||||
await dOrder.edit_answer("room_name", name)
|
||||
await dOrder.send_answers()
|
||||
return redirect(f'/manage/nosecount')
|
22
app.py
22
app.py
|
@ -31,8 +31,9 @@ from stats import bp as stats_bp
|
|||
from api import bp as api_bp
|
||||
from carpooling import bp as carpooling_bp
|
||||
from checkin import bp as checkin_bp
|
||||
from admin import bp as admin_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, admin_bp])
|
||||
|
||||
@app.exception(exceptions.SanicException)
|
||||
async def clear_session(request, exception):
|
||||
|
@ -40,8 +41,8 @@ async def clear_session(request, exception):
|
|||
r = html(tpl.render(exception=exception))
|
||||
|
||||
if exception.status_code == 403:
|
||||
del r.cookies["foxo_code"]
|
||||
del r.cookies["foxo_secret"]
|
||||
r.delete_cookie("foxo_code")
|
||||
r.delete_cookie("foxo_secret")
|
||||
return r
|
||||
|
||||
@app.before_server_start
|
||||
|
@ -63,8 +64,12 @@ async def main_start(*_):
|
|||
app.ctx.tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=True)
|
||||
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(ITEMS_ID_MAP=ITEMS_ID_MAP)
|
||||
app.ctx.tpl.globals.update(ITEM_VARIATIONS_MAP=ITEM_VARIATIONS_MAP)
|
||||
app.ctx.tpl.globals.update(ROOM_TYPE_NAMES=ROOM_TYPE_NAMES)
|
||||
app.ctx.tpl.globals.update(PROPIC_MIN_SIZE=PROPIC_MIN_SIZE)
|
||||
app.ctx.tpl.globals.update(PROPIC_MAX_SIZE=PROPIC_MAX_SIZE)
|
||||
app.ctx.tpl.globals.update(PROPIC_MAX_FILE_SIZE=sizeof_fmt(PROPIC_MAX_FILE_SIZE))
|
||||
app.ctx.tpl.globals.update(int=int)
|
||||
app.ctx.tpl.globals.update(len=len)
|
||||
|
||||
|
@ -157,6 +162,15 @@ async def download_ticket(request, order: Order):
|
|||
|
||||
return raw(res.content, content_type='application/pdf')
|
||||
|
||||
@app.route("/manage/admin")
|
||||
async def admin(request, order: Order):
|
||||
await request.app.ctx.om.updateCache()
|
||||
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.isAdmin(): raise exceptions.Forbidden("Birichino :)")
|
||||
tpl = app.ctx.tpl.get_template('admin.html')
|
||||
return html(tpl.render(order=order))
|
||||
|
||||
@app.route("/manage/logout")
|
||||
async def logour(request):
|
||||
raise exceptions.Forbidden("You have been logged out.")
|
||||
|
|
|
@ -8,47 +8,98 @@ base_url = "http://urlllllllllllllllllllll/api/v1/"
|
|||
base_url_event = f"{base_url}organizers/{ORGANIZER}/events/{EVENT_NAME}/"
|
||||
|
||||
PROPIC_DEADLINE = 9999999999
|
||||
PROPIC_MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
|
||||
PROPIC_MAX_SIZE = (2048, 2048) # (Width, Height)
|
||||
PROPIC_MIN_SIZE = (125, 125) # (Width, Height)
|
||||
|
||||
FILL_CACHE = True
|
||||
CACHE_EXPIRE_TIME = 60 * 60 * 4
|
||||
|
||||
DEV_MODE = True
|
||||
|
||||
ITEM_IDS = {
|
||||
'ticket': [126, 127, 155],
|
||||
'membership_card': [128,],
|
||||
'sponsorship': [55, 56], # first one = normal, second = super
|
||||
'early_arrival': [133],
|
||||
'late_departure': [134],
|
||||
'room': 135,
|
||||
# Metadata property for item-id mapping
|
||||
METADATA_NAME = "item_name"
|
||||
# Metadata property for internal category mapping (not related to pretix's category)
|
||||
METADATA_CATEGORY = "category_name"
|
||||
|
||||
# Maps Products metadata name <--> ID
|
||||
ITEMS_ID_MAP = {
|
||||
'early_bird_ticket': 126,
|
||||
'regular_ticket': 127,
|
||||
'staff_ticket': 155,
|
||||
'daily_ticket': 162,
|
||||
'sponsorship_item': 129,
|
||||
'early_arrival_admission': 133,
|
||||
'late_departure_admission': 134,
|
||||
'membership_card_item': 128,
|
||||
'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
|
||||
'room_type': 135,
|
||||
'room_guest': 136,
|
||||
'daily_1': 163,
|
||||
'daily_2': 164,
|
||||
'daily_3': 165,
|
||||
'daily_4': 166,
|
||||
'daily_5': None
|
||||
}
|
||||
|
||||
# Maps Products' variants metadata name <--> ID
|
||||
ITEM_VARIATIONS_MAP = {
|
||||
'sponsorship_item': {
|
||||
'sponsorship_item_normal': 55,
|
||||
'sponsorship_item_super': 56
|
||||
},
|
||||
'bed_in_room': {
|
||||
'bed_in_room_main_1': 83,
|
||||
'bed_in_room_main_2': 67,
|
||||
'bed_in_room_main_3': 68,
|
||||
'bed_in_room_main_4': 69,
|
||||
'bed_in_room_main_5': 70,
|
||||
'bed_in_room_overflow1_2': 75,
|
||||
},
|
||||
'room_type': {
|
||||
'single': 57,
|
||||
'double': 58,
|
||||
'triple': 59,
|
||||
'quadruple': 60,
|
||||
'quintuple': 61
|
||||
},
|
||||
'room_guest': {
|
||||
'single': 57,
|
||||
'double': 58,
|
||||
'triple': 59,
|
||||
'quadruple': 60,
|
||||
'quintuple': 61
|
||||
}
|
||||
}
|
||||
|
||||
ADMINS_PRETIX_ROLE_NAMES = ["Reserved Area admin", "main staff"]
|
||||
|
||||
# Links Products' variants' ids with the internal category name
|
||||
CATEGORIES_LIST_MAP = {
|
||||
'tickets': [],
|
||||
'memberships': [],
|
||||
'sponsorships': [],
|
||||
'tshirts': [],
|
||||
'extra_days': [],
|
||||
'rooms': [],
|
||||
'dailys': []
|
||||
}
|
||||
# Create a bunch of "room" items which will get added to the order once somebody gets a room.
|
||||
# Map variationId -> numberOfPeopleInRoom
|
||||
ROOM_MAP = {
|
||||
# Map item_name -> room capacity
|
||||
ROOM_CAPACITY_MAP = {
|
||||
# SACRO CUORE
|
||||
83: 1,
|
||||
67: 2,
|
||||
68: 3,
|
||||
69: 4,
|
||||
70: 5,
|
||||
'bed_in_room_main_1': 1,
|
||||
'bed_in_room_main_2': 2,
|
||||
'bed_in_room_main_3': 3,
|
||||
'bed_in_room_main_4': 4,
|
||||
'bed_in_room_main_5': 5,
|
||||
|
||||
# OVERFLOW 1
|
||||
75: 2
|
||||
'bed_in_room_overflow1_2': 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"
|
||||
}
|
||||
# Autofilled
|
||||
ROOM_TYPE_NAMES = { }
|
||||
|
||||
# This is used for feedback sending inside of the app. Feedbacks will be sent to the specified chat using the bot api id.
|
||||
TG_BOT_API = '123456789:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# To connect with pretix's data
|
||||
# WIP to not commit
|
||||
# DrewTW
|
||||
import string
|
||||
import httpx
|
||||
import json
|
||||
from ext import *
|
||||
from config import *
|
||||
from sanic import response
|
||||
from sanic import Blueprint
|
||||
import logging
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
def checkConfig():
|
||||
if (not DEV_MODE) and DUMMY_DATA:
|
||||
log.warn('It is strongly unadvised to use dummy data in production')
|
||||
|
||||
def getOrders(page):
|
||||
return None
|
||||
|
||||
def getOrder(code):
|
||||
return None
|
|
@ -8,7 +8,7 @@ bp = Blueprint("export", url_prefix="/manage/export")
|
|||
@bp.route("/export.csv")
|
||||
async def export_csv(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.code not in ADMINS: raise exceptions.Forbidden("Birichino :)")
|
||||
if not order.isAdmin(): raise exceptions.Forbidden("Birichino :)")
|
||||
|
||||
page = 0
|
||||
orders = {}
|
||||
|
|
40
ext.py
40
ext.py
|
@ -45,7 +45,7 @@ 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 CATEGORIES_LIST_MAP['tickets']:
|
||||
self.position_id = p['id']
|
||||
self.position_positionid = p['positionid']
|
||||
self.position_positiontypeid = p['item']
|
||||
|
@ -55,32 +55,33 @@ class Order:
|
|||
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']:
|
||||
if p['item'] in CATEGORIES_LIST_MAP['dailys']:
|
||||
self.daily = True
|
||||
self.dailyDays.append(ITEM_IDS['daily_addons'].index(p['item']))
|
||||
self.dailyDays.append(CATEGORIES_LIST_MAP['dailys'].index(p['item']))
|
||||
|
||||
if p['item'] in ITEM_IDS['membership_card']:
|
||||
if p['item'] in CATEGORIES_LIST_MAP['memberships']:
|
||||
self.has_card = True
|
||||
|
||||
if p['item'] in ITEM_IDS['sponsorship']:
|
||||
self.sponsorship = 'normal' if p['variation'] == ITEMS_IDS['sponsorship'][0] else 'super'
|
||||
if p['item'] == ITEMS_ID_MAP['sponsorship_item']:
|
||||
sponsorshipType = keyFromValue(ITEM_VARIATIONS_MAP['sponsorship_item'], p['variation'])
|
||||
self.sponsorship = sponsorshipType[0].replace ('sponsorship_item_', '') if len(sponsorshipType) > 0 else None
|
||||
|
||||
if p['attendee_name']:
|
||||
self.first_name = p['attendee_name_parts']['given_name']
|
||||
self.last_name = p['attendee_name_parts']['family_name']
|
||||
|
||||
if p['item'] == ITEM_IDS['early_arrival']:
|
||||
if p['item'] == ITEMS_ID_MAP['early_arrival_admission']:
|
||||
self.has_early = True
|
||||
|
||||
if p['item'] == ITEM_IDS['late_departure']:
|
||||
if p['item'] == ITEMS_ID_MAP['late_departure_admission']:
|
||||
self.has_late = True
|
||||
|
||||
if p['item'] == ITEM_IDS['bed_in_room']:
|
||||
if p['item'] == ITEMS_ID_MAP['bed_in_room']:
|
||||
roomTypeLst = keyFromValue(ITEM_VARIATIONS_MAP['bed_in_room'], p['variation'])
|
||||
roomTypeId = roomTypeLst[0] if len(roomTypeLst) > 0 else None
|
||||
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.room_person_no = ROOM_CAPACITY_MAP[roomTypeId] if roomTypeId in ROOM_CAPACITY_MAP else None
|
||||
|
||||
self.total = float(data['total'])
|
||||
self.fees = 0
|
||||
|
@ -96,6 +97,8 @@ class Order:
|
|||
self.payment_provider = data['payment_provider']
|
||||
self.comment = data['comment']
|
||||
self.phone = data['phone']
|
||||
self.loadAns()
|
||||
def loadAns(self):
|
||||
self.shirt_size = self.ans('shirt_size')
|
||||
self.is_artist = True if self.ans('is_artist') != 'No' else False
|
||||
self.is_fursuiter = True if self.ans('is_fursuiter') != 'No' else False
|
||||
|
@ -122,8 +125,8 @@ class Order:
|
|||
self.nfc_id = self.ans('nfc_id')
|
||||
self.can_scan_nfc = True if self.ans('can_scan_nfc') != 'No' else False
|
||||
self.actual_room = self.ans('actual_room')
|
||||
self.staff_role = self.ans('staff_role')
|
||||
self.telegram_username = self.ans('telegram_username').strip('@') if self.ans('telegram_username') else None
|
||||
|
||||
def __getitem__(self, var):
|
||||
return self.data[var]
|
||||
|
||||
|
@ -136,6 +139,12 @@ class Order:
|
|||
return a['answer']
|
||||
return None
|
||||
|
||||
def isBadgeValid (self):
|
||||
return self.ans('propic') and (not self.is_fursuiter or self.ans('propic_fursuiter'))
|
||||
|
||||
def isAdmin (self):
|
||||
return self.code in ADMINS or self.staff_role in ADMINS_PRETIX_ROLE_NAMES
|
||||
|
||||
async def edit_answer_fileUpload(self, name, fileName, mimeType, data : bytes):
|
||||
if(mimeType != None and data != None):
|
||||
async with httpx.AsyncClient() as client:
|
||||
|
@ -147,6 +156,7 @@ class Order:
|
|||
await self.edit_answer(name, res['id'])
|
||||
else:
|
||||
await self.edit_answer(name, None)
|
||||
self.loadAns()
|
||||
|
||||
async def edit_answer(self, name, new_answer):
|
||||
found = False
|
||||
|
@ -168,7 +178,6 @@ class Order:
|
|||
async with httpx.AsyncClient() as client:
|
||||
res = await client.get(join(base_url_event, 'questions/'), headers=headers)
|
||||
res = res.json()
|
||||
|
||||
for r in res['results']:
|
||||
if r['identifier'] != name: continue
|
||||
|
||||
|
@ -178,6 +187,7 @@ class Order:
|
|||
'answer': new_answer,
|
||||
'options': r['options']
|
||||
})
|
||||
self.loadAns()
|
||||
|
||||
async def send_answers(self):
|
||||
async with httpx.AsyncClient() as client:
|
||||
|
@ -204,6 +214,7 @@ class Order:
|
|||
|
||||
self.pending_update = False
|
||||
self.time = -1
|
||||
self.loadAns()
|
||||
|
||||
@dataclass
|
||||
class Quotas:
|
||||
|
@ -254,6 +265,7 @@ class OrderManager:
|
|||
self.order_list.remove(code)
|
||||
|
||||
async def fill_cache(self):
|
||||
await loadItems()
|
||||
await loadQuestions()
|
||||
self.empty()
|
||||
p = 0
|
||||
|
|
|
@ -10,7 +10,7 @@ bp = Blueprint("karaoke", url_prefix="/manage/karaoke")
|
|||
@bp.get("/admin")
|
||||
async def show_songs(request, order: Order):
|
||||
|
||||
if order.code not in ADMINS:
|
||||
if not order.isAdmin():
|
||||
raise exceptions.Forbidden("Birichino")
|
||||
|
||||
orders = [x for x in request.app.ctx.om.cache.values() if x.karaoke_songs]
|
||||
|
@ -28,7 +28,7 @@ async def show_songs(request, order: Order):
|
|||
@bp.post("/approve")
|
||||
async def approve_songs(request, order: Order):
|
||||
|
||||
if order.code not in ADMINS:
|
||||
if not order.isAdmin():
|
||||
raise exceptions.Forbidden("Birichino")
|
||||
|
||||
for song in request.form:
|
||||
|
@ -44,7 +44,7 @@ async def sing_song(request, order: Order, songname):
|
|||
|
||||
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
|
||||
|
||||
if order.code not in ADMINS:
|
||||
if not order.isAdmin():
|
||||
raise exceptions.Forbidden("Birichino")
|
||||
|
||||
songname = unquote(songname)
|
||||
|
|
43
propic.py
43
propic.py
|
@ -6,14 +6,19 @@ from PIL import Image
|
|||
from io import BytesIO
|
||||
from hashlib import sha224
|
||||
from time import time
|
||||
import os
|
||||
|
||||
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")
|
||||
if (DEV_MODE):
|
||||
print("Resetting {fn} picture for {orderCod}".format(fn="Badge" if not isFursuiter else "fursuit", orderCod = order.code))
|
||||
with open("res/propic/default.png", "rb") as f:
|
||||
data = f.read()
|
||||
f.close()
|
||||
os.remove(f"res/propic/{order.ans(f'propic{s}')}") # converted file
|
||||
os.remove(f"res/propic/{order.ans(f'propic{s}').split(".jpg")[0]}_original.jpg") # original file
|
||||
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()
|
||||
|
@ -41,17 +46,30 @@ async def upload_propic(request, order: Order):
|
|||
|
||||
if not body[0].body: continue
|
||||
|
||||
h = sha224(body[0].body).hexdigest()[:32]
|
||||
# Check max file size
|
||||
if len(body[0].body) > PROPIC_MAX_FILE_SIZE:
|
||||
raise exceptions.BadRequest("File size too large for " + ("Profile picture" if fn == 'propic' else 'Fursuit picture'))
|
||||
|
||||
h = sha224(body[0].body).hexdigest()[:32]
|
||||
errorDetails = ''
|
||||
try:
|
||||
img = Image.open(BytesIO(body[0].body))
|
||||
if(img.size[0] > 2048 or img.size[1] > 2048):
|
||||
raise exceptions.BadRequest("Maximum allowed dimensions: 2048x2048")
|
||||
|
||||
with open(f"res/propic/{fn}_{order.code}_original", "wb") as f:
|
||||
f.write(body[0].body)
|
||||
|
||||
width, height = img.size
|
||||
# Checking for min / max size
|
||||
if width < PROPIC_MIN_SIZE[0] or height < PROPIC_MIN_SIZE[1]:
|
||||
errorDetails = "Image too small [{width}x{width}] for {pfpn}".format(width=width, pfpn=("Profile picture" if fn == 'propic' else 'Fursuit picture'))
|
||||
raise exceptions.BadRequest(errorDetails)
|
||||
|
||||
if width > PROPIC_MAX_SIZE[0] or height > PROPIC_MAX_SIZE[1]:
|
||||
errorDetails = "Image too big [{width}x{width}] for {pfpn}".format(width=width, pfpn=("Profile picture" if fn == 'propic' else 'Fursuit picture'))
|
||||
raise exceptions.BadRequest(errorDetails)
|
||||
|
||||
|
||||
with open(f"res/propic/{fn}_{order.code}_original.jpg", "wb") as f:
|
||||
f.write(body[0].body)
|
||||
f.flush()
|
||||
f.close()
|
||||
|
||||
aspect_ratio = width/height
|
||||
if aspect_ratio > 1:
|
||||
crop_amount = (width - height) / 2
|
||||
|
@ -61,19 +79,22 @@ async def upload_propic(request, order: Order):
|
|||
img = img.crop((0, crop_amount, width, height - crop_amount))
|
||||
|
||||
img = img.convert('RGB')
|
||||
width, height = img.size
|
||||
|
||||
img.thumbnail((512,512))
|
||||
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)
|
||||
f.flush()
|
||||
f.close()
|
||||
|
||||
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.")
|
||||
if DEV_MODE: print(traceback.format_exc())
|
||||
raise exceptions.BadRequest(errorDetails if errorDetails else "The image you uploaded is not valid.")
|
||||
else:
|
||||
await order.edit_answer(fn, f"{fn}_{order.code}_{h}.jpg")
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>
|
After Width: | Height: | Size: 300 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-440q17 0 28.5-11.5T480-480q0-17-11.5-28.5T440-520q-17 0-28.5 11.5T400-480q0 17 11.5 28.5T440-440ZM280-120v-80l240-40v-445q0-15-9-27t-23-14l-208-34v-80l220 36q44 8 72 41t28 77v512l-320 54Zm-160 0v-80h80v-560q0-34 23.5-57t56.5-23h400q34 0 57 23t23 57v560h80v80H120Zm160-80h400v-560H280v560Z"/></svg>
|
After Width: | Height: | Size: 399 B |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z"/></svg>
|
After Width: | Height: | Size: 310 B |
|
@ -0,0 +1,28 @@
|
|||
function confirmAction (intent, sender) {
|
||||
if (['rename', 'unconfirm', 'delete'].includes (intent) == false) return
|
||||
let href = sender.getAttribute('action')
|
||||
let intentTitle = document.querySelector("#intentText")
|
||||
let intentEdit = document.querySelector("#intentRename")
|
||||
let intentEditPanel = document.querySelector("#intentEditPanel")
|
||||
let intentFormAction = document.querySelector("#intentFormAction")
|
||||
let intentSend = document.querySelector("#intentSend")
|
||||
// Resetting ui
|
||||
intentEdit.setAttribute('required', false)
|
||||
intentFormAction.setAttribute('method', 'GET')
|
||||
intentEditPanel.style.display = 'none';
|
||||
|
||||
intentTitle.innerText = intent + ' room'
|
||||
intentFormAction.setAttribute('action', href)
|
||||
switch (intent){
|
||||
case 'rename':
|
||||
intentEditPanel.style.display = 'block';
|
||||
intentEdit.setAttribute('required', true)
|
||||
intentFormAction.setAttribute('method', 'POST')
|
||||
break
|
||||
case 'unconfirm':
|
||||
break
|
||||
case 'delete':
|
||||
break
|
||||
}
|
||||
document.getElementById('modalRoomconfirm').setAttribute('open', 'true');
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
div.room-actions {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.room-actions > a {
|
||||
background-color: var(--card-background-color);
|
||||
font-size: 12pt;
|
||||
padding: 0.7rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
div.room-actions > a:hover {
|
||||
background-color: var(--primary-focus);
|
||||
}
|
||||
|
||||
div.room-actions > a.act-del:hover {
|
||||
background-color: var(--del-color);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
/* Other blocks' styles */
|
||||
@import url('propic.css');
|
||||
@import url('admin.css');
|
||||
@import url('room.css');
|
||||
|
||||
summary:has(span.status) {
|
||||
background-color: #ffaf0377;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
span.nsc-room-counter {
|
||||
font-size: medium;
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
#intentFormAction #intentText {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
div.room-actions a>span {
|
||||
display: none;
|
||||
}
|
||||
}
|
34
room.py
34
room.py
|
@ -49,7 +49,7 @@ async def delete_room(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.room_id != order.code:
|
||||
if order.room_owner:
|
||||
raise exceptions.BadRequest("You are not allowed to delete room of others.")
|
||||
|
||||
if order.ans('room_confirmed'):
|
||||
|
@ -177,6 +177,9 @@ async def approve_roomreq(request, code, 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.room_owner:
|
||||
raise exceptions.BadRequest("You are not the owner of the room!")
|
||||
|
||||
if not code in order.pending_roommates:
|
||||
raise exceptions.BadRequest("You cannot accept people that didn't request to join your room")
|
||||
|
||||
|
@ -231,6 +234,9 @@ async def reject_roomreq(request, code, 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.room_owner:
|
||||
raise exceptions.BadRequest("You are not the owner of the room!")
|
||||
|
||||
if not code in order.pending_roommates:
|
||||
raise exceptions.BadRequest("You cannot reject people that didn't request to join your room")
|
||||
|
||||
|
@ -253,6 +259,32 @@ async def reject_roomreq(request, code, order: Order):
|
|||
|
||||
return redirect('/manage/welcome')
|
||||
|
||||
@bp.post("/rename")
|
||||
async def rename_room(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.room_owner:
|
||||
raise exceptions.BadRequest("You are not the owner of the room!")
|
||||
|
||||
if not order.room_id:
|
||||
raise exceptions.BadRequest("Try joining a room before renaming it.")
|
||||
|
||||
if order.room_confirmed:
|
||||
raise exceptions.BadRequest("You can't rename a confirmed room!")
|
||||
|
||||
if order.room_id != order.code:
|
||||
raise exceptions.BadRequest("You are not allowed to rename rooms of others.")
|
||||
|
||||
name = request.form.get('name')
|
||||
if len(name) > 64 or len(name) < 4:
|
||||
raise exceptions.BadRequest("Your room name is invalid. Please try another one.")
|
||||
|
||||
await order.edit_answer("room_name", name)
|
||||
await order.send_answers()
|
||||
|
||||
return redirect('/manage/welcome')
|
||||
|
||||
@bp.route("/confirm")
|
||||
async def confirm_room(request, order: Order, quotas: Quotas):
|
||||
if not order:
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Admin panel{% 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>
|
||||
<!-- Quick controls -->
|
||||
<h2>Admin panel</h2>
|
||||
<a href="/manage/admin/cache/clear" role="button" title="Reload the orders' data and the re-sync items' indexes from pretix">Clear cache</a>
|
||||
<a href="/manage/nosecount" role="button" title="Reload the orders' data and the re-sync items' indexes from pretix">Manage rooms</a>
|
||||
<hr>
|
||||
</main>
|
||||
|
||||
{% endblock %}
|
|
@ -7,6 +7,7 @@
|
|||
<meta name="supported-color-schemes" content="light dark">
|
||||
<link rel="stylesheet" href="/res/pico.min.css">
|
||||
<link rel="stylesheet" href="/res/styles/base.css">
|
||||
<link rel="icon" type="image/x-icon" href="/res/icons/favicon.ico">
|
||||
<style>
|
||||
.people div {text-align:center;}
|
||||
.people h3, .people p, .people h5 {margin:0;}
|
||||
|
@ -99,6 +100,7 @@
|
|||
{% if order %}
|
||||
<a href="/manage/carpooling">Carpooling</a>
|
||||
<a style="float:right;" href="/manage/logout">Logout</a>
|
||||
{% if order.isAdmin() %}<a style="float:right;" href="/manage/admin">Admin panel</a>{% endif %}
|
||||
<a style="float:right;color: #b1b1b1;">Logged in as <i>{{order.ans('fursona_name')}}</i></a>
|
||||
{% endif %}
|
||||
<br clear="both" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<details id="badge">
|
||||
<summary role="button"><img src="/res/icons/badge.svg" class="icon"/>Badge Customization {% if not order.ans('propic') %}<span class="status">⚠️</span>{% endif %}</summary>
|
||||
<summary role="button"><img src="/res/icons/badge.svg" class="icon"/>Badge Customization {% if not order.isBadgeValid() %}<span class="status">⚠️</span>{% endif %}</summary>
|
||||
{# Badge is always shown #}
|
||||
<h2>Badge</h2>
|
||||
{% if order.propic_locked %}
|
||||
|
@ -11,23 +11,23 @@
|
|||
<form method="POST" enctype="multipart/form-data" action="/manage/propic/upload">
|
||||
<div class="grid" style="text-align:center;margin-bottom:1em;">
|
||||
<div>
|
||||
{% if not order.ans('propic') %}
|
||||
<input type="file" value="" accept="image/jpeg,image/png" name="propic" />
|
||||
{% endif %}
|
||||
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic') or 'default.png'), effects = true %}
|
||||
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic') or 'default.png'), effects = false %}
|
||||
{% include 'blocks/propic.html' %}
|
||||
{% endwith %}
|
||||
<p>Normal Badge</p>
|
||||
{% if not order.ans('propic') %}
|
||||
<input type="file" value="" accept="image/jpeg,image/png" name="propic" />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if order.is_fursuiter %}
|
||||
<div>
|
||||
{% if not order.ans('propic_fursuiter') %}
|
||||
<input type="file" value="" accept="image/jpeg,image/png" name="propic_fursuiter" />
|
||||
{% endif %}
|
||||
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic_fursuiter') or 'default.png'), effects = true %}
|
||||
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic_fursuiter') or 'default.png'), effects = false %}
|
||||
{% include 'blocks/propic.html' %}
|
||||
{% endwith %}
|
||||
<p>Fursuit Badge</p>
|
||||
{% if not order.ans('propic_fursuiter') %}
|
||||
<input type="file" value="" accept="image/jpeg,image/png" name="propic_fursuiter" />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -36,7 +36,8 @@
|
|||
<p class="notice">⚠️ The deadline to upload pictures for the badge has expired. For last-minute changes, please contact the support over at <a href="mailto:info@furizon.net">info@furizon.net</a>. If your badge has been printed already, changing it will incur in a 2€ fee. You can also get extra badges at the reception for the same price. If you upload a propic now, it might not be printed on time.</p>
|
||||
{% else %}
|
||||
<p><em>
|
||||
Min size: 64x64 - Max Size: 5MB, 2048x2048 - Formats: jpg, png<br />
|
||||
Min size: {{PROPIC_MIN_SIZE[0]}}x{{PROPIC_MIN_SIZE[1]}} - Max Size: {{PROPIC_MAX_FILE_SIZE}}, {{PROPIC_MAX_SIZE[0]}}x{{PROPIC_MAX_SIZE[1]}} - Formats: jpg, png<br />
|
||||
Photos whose aspect ratio is not a square will be cropped
|
||||
Badge photos must clearly show the fursona/fursuit head.<br />Memes and low quality images will be removed and may limit your ability to upload pics in the future.
|
||||
</em></p>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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 {% if not order.room_confirmed %}<span class="status">⚠️</span>{% endif %}</summary>
|
||||
<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, contact the <a href="https://furizon.net/contact/">Furizon's Staff</a>.</p>
|
||||
|
@ -77,13 +77,17 @@
|
|||
|
||||
<p class="grid">
|
||||
{% if order.room_owner %}
|
||||
{% if len(room_members) == 1 and not order.room_confirmed %}
|
||||
|
||||
{% if not order.room_confirmed %}
|
||||
|
||||
{% if len(room_members) == 1 %}
|
||||
<a href="/manage/room/delete" role="button">Delete room</a>
|
||||
{% 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 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>
|
||||
|
||||
<a role="button" href="javascript:document.getElementById('modal-roomrename').setAttribute('open', 'true');">Rename room {{[None,'single','double','triple','quadruple','quintuple'][order.room_person_no]}} room</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if order.room_id and not order.room_confirmed %}
|
||||
|
@ -104,8 +108,10 @@
|
|||
{% if person.status == 'pending' %}
|
||||
<td><strong style="color:red;">UNPAID</strong></td>
|
||||
{% endif %}
|
||||
{% if order.room_owner %}
|
||||
<td style="width:1%;white-space: nowrap;"><a role="button" href="/manage/room/approve/{{person.code}}">Approve</a></td>
|
||||
<td style="width:1%;white-space: nowrap;"><a role="button" href="/manage/room/reject/{{person.code}}">Reject</a></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -45,7 +45,20 @@
|
|||
</article>
|
||||
</dialog>
|
||||
|
||||
<dialog id="modal-roomrename">
|
||||
<article>
|
||||
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
|
||||
<h3>Rename this room</h3>
|
||||
<p>Enter your room's new name!</p>
|
||||
<p>This name will be public and shown in the room list, so nothing offensive! :)</p>
|
||||
|
||||
<form method="POST" action="/manage/room/rename">
|
||||
<label for="name"></label>
|
||||
<input type="text" name="name" required minlength="4" maxlength="64" />
|
||||
<input type="submit" value="Rename room" />
|
||||
</form>
|
||||
</article>
|
||||
</dialog>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
{% block title %}Furizon 2024 Nosecount{% endblock %}
|
||||
{% block main %}
|
||||
<main class="container">
|
||||
{% if order.isAdmin() %}
|
||||
<script src="/res/scripts/roomManager.js"></script>
|
||||
{% endif %}
|
||||
<header>
|
||||
<picture>
|
||||
<source srcset="/res/furizon.png" media="(prefers-color-scheme:dark)">
|
||||
|
@ -11,13 +14,22 @@
|
|||
<p>Welcome to the nosecount page! Here you can see all of the available rooms at the convention, as well as the occupants currently staying in each room. Use this page to find your friends and plan your meet-ups.</p>
|
||||
{% for o in orders.values() %}
|
||||
{% if o.code == o.room_id and o.room_confirmed %}
|
||||
<h4 style="margin-top:1em;">{{o.room_name}}</h4>
|
||||
<h4 style="margin-top:1em;">
|
||||
<span>{{o.room_name}}</span>
|
||||
{% if order.isAdmin() %}
|
||||
<div class="room-actions">
|
||||
<a onclick="confirmAction('rename', this)" action="/manage/admin/room/rename/{{o.code}}"><img src="/res/icons/pencil.svg" class="icon" /><span>Rename</span></a>
|
||||
<a onclick="confirmAction('unconfirm', this)" action="/manage/admin/room/unconfirm/{{o.code}}"><img src="/res/icons/door_open.svg" class="icon" /><span>Unconfirm</span></a>
|
||||
<a class="act-del" onclick="confirmAction('delete', this)" action="/manage/admin/room/delete/{{o.code}}"><img src="/res/icons/delete.svg" class="icon" /><span>Delete</span></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</h4>
|
||||
<div class="grid people" style="padding-bottom:1em;">
|
||||
{% for m in o.room_members %}
|
||||
{% if m in orders %}
|
||||
{% with person = orders[m] %}
|
||||
<div style="margin-bottom: 1em;">
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
|
||||
{% include 'blocks/propic.html' %}
|
||||
{% endwith %}
|
||||
<h5>{{person.ans('fursona_name')}}</h5>
|
||||
|
@ -34,14 +46,22 @@
|
|||
<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 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 %}
|
||||
<h3>
|
||||
<span>{{o.room_name}}</span>
|
||||
{% if o.room_person_no - len(o.room_members) > 0 %} <span class="nsc-room-counter"> - Remaining slots: {{o.room_person_no - len(o.room_members)}}</span> {% endif %}
|
||||
{% if order.isAdmin() %}
|
||||
<div class="room-actions">
|
||||
<a onclick="confirmAction('rename', this)" action="/manage/admin/room/rename/{{o.code}}"><img src="/res/icons/pencil.svg" class="icon" /><span>Rename</span></a>
|
||||
<a class="act-del" onclick="confirmAction('delete', this)" action="/manage/admin/room/delete/{{o.code}}"><img src="/res/icons/delete.svg" class="icon" /><span>Delete</span></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</h3>
|
||||
<div class="grid people" style="padding-bottom:1em;">
|
||||
{% for m in o.room_members %}
|
||||
{% if m in orders %}
|
||||
{% with person = orders[m] %}
|
||||
<div style="margin-bottom: 1em;">
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
|
||||
{% include 'blocks/propic.html' %}
|
||||
{% endwith %}
|
||||
<h5>{{person.ans('fursona_name')}}</h5>
|
||||
|
@ -61,7 +81,7 @@
|
|||
<div class="grid people" style="padding-bottom:1em;">
|
||||
{% endif %}
|
||||
<div style="margin-bottom: 1em;">
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
|
||||
{% include 'blocks/propic.html' %}
|
||||
{% endwith %}
|
||||
|
||||
|
@ -78,7 +98,7 @@
|
|||
<div class="grid people" style="padding-bottom:1em;">
|
||||
{% endif %}
|
||||
<div style="margin-bottom: 1em;">
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
|
||||
{% include 'blocks/propic.html' %}
|
||||
{% endwith %}
|
||||
<h5>{{person.ans('fursona_name')}}</h5>
|
||||
|
@ -86,5 +106,22 @@
|
|||
{% if loop.last %}</div>{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<form id="intentFormAction" method="GET" action="">
|
||||
<dialog id="modalRoomconfirm">
|
||||
<article>
|
||||
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
|
||||
<h3 id="intentText">Confirm room edit</h3>
|
||||
<p id="intentDescription"></p>
|
||||
<div id="intentEditPanel">
|
||||
<label for="name">Enter a new room name</label>
|
||||
<input id="intentRename" name="name" type="text" value="" minlength="4" maxlength="64"/>
|
||||
</div>
|
||||
<footer>
|
||||
<input id="intentSend" type="submit" value="Confirm" />
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
</form>
|
||||
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
105
utils.py
105
utils.py
|
@ -1,7 +1,11 @@
|
|||
from os.path import join
|
||||
from sanic import exceptions
|
||||
from config import *
|
||||
import httpx
|
||||
|
||||
METADATA_TAG = "meta_data"
|
||||
VARIATIONS_TAG = "variations"
|
||||
|
||||
QUESTION_TYPES = { #https://docs.pretix.eu/en/latest/api/resources/questions.html
|
||||
"number": "N",
|
||||
"one_line_string": "S",
|
||||
|
@ -33,3 +37,104 @@ async def loadQuestions():
|
|||
data = res.json()
|
||||
for q in data['results']:
|
||||
TYPE_OF_QUESTIONS[q['id']] = q['type']
|
||||
|
||||
async def loadItems():
|
||||
global ITEMS_ID_MAP
|
||||
global ITEM_VARIATIONS_MAP
|
||||
global CATEGORIES_LIST_MAP
|
||||
global ROOM_TYPE_NAMES
|
||||
async with httpx.AsyncClient() as client:
|
||||
p = 0
|
||||
while 1:
|
||||
p += 1
|
||||
res = await client.get(join(base_url_event, f"items/?page={p}"), headers=headers)
|
||||
|
||||
if res.status_code == 404: break
|
||||
|
||||
data = res.json()
|
||||
for q in data['results']:
|
||||
# Map item id
|
||||
itemName = checkAndGetName ('item', q)
|
||||
if itemName and itemName in ITEMS_ID_MAP:
|
||||
ITEMS_ID_MAP[itemName] = q['id']
|
||||
# If item has variations, map them, too
|
||||
if itemName in ITEM_VARIATIONS_MAP and VARIATIONS_TAG in q:
|
||||
isBedInRoom = itemName == 'bed_in_room'
|
||||
for v in q[VARIATIONS_TAG]:
|
||||
variationName = checkAndGetName('variation', v)
|
||||
if variationName and variationName in ITEM_VARIATIONS_MAP[itemName]:
|
||||
ITEM_VARIATIONS_MAP[itemName][variationName] = v['id']
|
||||
if isBedInRoom and variationName in ITEM_VARIATIONS_MAP['bed_in_room']:
|
||||
roomName = v['name'] if 'name' in v and isinstance(v['name'], str) else None
|
||||
if not roomName and 'value' in v:
|
||||
roomName = v['value'][list(v['value'].keys())[0]]
|
||||
ROOM_TYPE_NAMES[v['id']] = roomName
|
||||
# Adds itself to the category list
|
||||
categoryName = checkAndGetCategory ('item', q)
|
||||
if not categoryName: continue
|
||||
CATEGORIES_LIST_MAP[categoryName].append(q['id'])
|
||||
if (DEV_MODE):
|
||||
print (f'Mapped Items:')
|
||||
print (ITEMS_ID_MAP)
|
||||
print (f'Mapped Variations:')
|
||||
print (ITEM_VARIATIONS_MAP)
|
||||
print (f'Mapped categories:')
|
||||
print (CATEGORIES_LIST_MAP)
|
||||
print (f'Mapped Rooms:')
|
||||
print (ROOM_TYPE_NAMES)
|
||||
|
||||
# Tries to get an item name from metadata. Prints a warning if an item has no metadata
|
||||
def checkAndGetName(type, q):
|
||||
itemName = extractMetadataName(q)
|
||||
if not itemName and DEV_MODE:
|
||||
print (type + ' ' + q['id'] + ' has not been mapped.')
|
||||
return itemName
|
||||
|
||||
def checkAndGetCategory (type, q):
|
||||
categoryName = extractCategory (q)
|
||||
if not categoryName and DEV_MODE:
|
||||
print (type + ' ' + q['id'] + ' has no category set.')
|
||||
return categoryName
|
||||
|
||||
# Checks if the item has specified metadata name
|
||||
def internalNameCheck (toExtract, name):
|
||||
return toExtract and name and METADATA_TAG in toExtract and toExtract[METADATA_TAG][METADATA_NAME] == str(name)
|
||||
|
||||
# Returns the item_name metadata from the item or None if not defined
|
||||
def extractMetadataName (toExtract):
|
||||
return extractData(toExtract, [METADATA_TAG, METADATA_NAME])
|
||||
|
||||
# Returns the category_name metadata from the item or None if not defined
|
||||
def extractCategory (toExtract):
|
||||
return extractData(toExtract, [METADATA_TAG, METADATA_CATEGORY])
|
||||
|
||||
def extractData (dataFrom, tags):
|
||||
data = dataFrom
|
||||
for t in tags:
|
||||
if t not in data: return None
|
||||
data = data[t]
|
||||
return data
|
||||
|
||||
def keyFromValue(dict, value):
|
||||
return [k for k,v in dict.items() if v == value]
|
||||
|
||||
def sizeof_fmt(num, suffix="B"):
|
||||
for unit in ("", "K", "M", "G", "T", "P", "E", "Z"):
|
||||
if abs(num) < 1000.0:
|
||||
return f"{num:3.1f}{unit}{suffix}"
|
||||
num /= 1000.0
|
||||
return f"{num:.1f}Yi{suffix}"
|
||||
|
||||
async def getOrderByCode_safe(request, code):
|
||||
res = await request.app.ctx.om.get_order(code=code)
|
||||
if res is None:
|
||||
raise exceptions.BadRequest(f"[getOrderByCode_safe] Code {code} not found!")
|
||||
return res
|
||||
|
||||
def getPeopleInRoomByRoomId(request, roomId):
|
||||
c = request.app.ctx.om.cache
|
||||
ret = []
|
||||
for person in c.values():
|
||||
if person.room_id == roomId:
|
||||
ret.append(person)
|
||||
return ret
|
Loading…
Reference in New Issue