Merge branch 'stranck-dev' of https://git.foxo.me/Furizon/furizon_webint into stranck-dev
This commit is contained in:
commit
ec457e7181
|
@ -169,3 +169,4 @@ furizon.net/site/*
|
||||||
furizon.net.zip
|
furizon.net.zip
|
||||||
stuff/secrets.py
|
stuff/secrets.py
|
||||||
backups/*
|
backups/*
|
||||||
|
log.txt
|
||||||
|
|
283
admin.py
283
admin.py
|
@ -1,10 +1,16 @@
|
||||||
from sanic import response, redirect, Blueprint, exceptions
|
from sanic import response, redirect, Blueprint, exceptions
|
||||||
from email_util import send_missing_propic_message
|
from email_util import send_missing_propic_message
|
||||||
|
from random import choice
|
||||||
from room import unconfirm_room_by_order
|
from room import unconfirm_room_by_order
|
||||||
from config import *
|
from config import *
|
||||||
from utils import *
|
from utils import *
|
||||||
from ext import *
|
from ext import *
|
||||||
|
from io import StringIO
|
||||||
from sanic.log import logger
|
from sanic.log import logger
|
||||||
|
import csv
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
bp = Blueprint("admin", url_prefix="/manage/admin")
|
bp = Blueprint("admin", url_prefix="/manage/admin")
|
||||||
|
|
||||||
|
@ -42,6 +48,7 @@ async def login_as(request, code, order:Order):
|
||||||
|
|
||||||
@bp.get('/room/verify')
|
@bp.get('/room/verify')
|
||||||
async def verify_rooms(request, order:Order):
|
async def verify_rooms(request, order:Order):
|
||||||
|
await clear_cache(request, order)
|
||||||
already_checked, success = await request.app.ctx.om.update_cache()
|
already_checked, success = await request.app.ctx.om.update_cache()
|
||||||
if not already_checked and success:
|
if not already_checked and success:
|
||||||
orders = filter(lambda x: x.status not in ['c', 'e'] and x.room_id == x.code, request.app.ctx.om.cache.values())
|
orders = filter(lambda x: x.status not in ['c', 'e'] and x.room_id == x.code, request.app.ctx.om.cache.values())
|
||||||
|
@ -54,8 +61,20 @@ async def unconfirm_room(request, code, order:Order):
|
||||||
await unconfirm_room_by_order(order=dOrder, throw=True, request=request)
|
await unconfirm_room_by_order(order=dOrder, throw=True, request=request)
|
||||||
return redirect(f'/manage/nosecount')
|
return redirect(f'/manage/nosecount')
|
||||||
|
|
||||||
|
@bp.get('/room/autoconfirm')
|
||||||
|
async def autoconfirm_room(request, order:Order):
|
||||||
|
await clear_cache(request, order)
|
||||||
|
orders = request.app.ctx.om.cache.values()
|
||||||
|
for order in orders:
|
||||||
|
if(order.code == order.room_id and not order.room_confirmed and len(order.room_members) == order.room_person_no):
|
||||||
|
logger.info(f"Auto-Confirming room {order.room_id}")
|
||||||
|
await confirm_room_by_order(order, request)
|
||||||
|
await clear_cache(request, order)
|
||||||
|
return redirect(f'/manage/admin')
|
||||||
|
|
||||||
@bp.get('/room/delete/<code>')
|
@bp.get('/room/delete/<code>')
|
||||||
async def delete_room(request, code, order:Order):
|
async def delete_room(request, code, order:Order):
|
||||||
|
await clear_cache(request, order)
|
||||||
dOrder = await get_order_by_code(request, code, throwException=True)
|
dOrder = await get_order_by_code(request, code, throwException=True)
|
||||||
|
|
||||||
ppl = await get_people_in_room_by_code(request, code)
|
ppl = await get_people_in_room_by_code(request, code)
|
||||||
|
@ -75,6 +94,7 @@ async def delete_room(request, code, order:Order):
|
||||||
|
|
||||||
@bp.post('/room/rename/<code>')
|
@bp.post('/room/rename/<code>')
|
||||||
async def rename_room(request, code, order:Order):
|
async def rename_room(request, code, order:Order):
|
||||||
|
await clear_cache(request, order)
|
||||||
dOrder = await get_order_by_code(request, code, throwException=True)
|
dOrder = await get_order_by_code(request, code, throwException=True)
|
||||||
|
|
||||||
name = request.form.get('name')
|
name = request.form.get('name')
|
||||||
|
@ -85,6 +105,192 @@ async def rename_room(request, code, order:Order):
|
||||||
await dOrder.send_answers()
|
await dOrder.send_answers()
|
||||||
return redirect(f'/manage/nosecount')
|
return redirect(f'/manage/nosecount')
|
||||||
|
|
||||||
|
@bp.get('/room/wizard')
|
||||||
|
async def room_wizard(request, order:Order):
|
||||||
|
'''Tries to autofill unconfirmed rooms and other matches together'''
|
||||||
|
# Clear cache first
|
||||||
|
await clear_cache(request, order)
|
||||||
|
|
||||||
|
#Separate orders which have incomplete rooms and which have no rooms
|
||||||
|
all_orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: (x[1].room_person_no, len(x[1].room_members)), reverse=True) if (value.status not in ['canceled', 'expired'] and not value.daily and value.bed_in_room != ITEM_VARIATIONS_MAP["bed_in_room"]["bed_in_room_no_room"])}
|
||||||
|
orders = {key:value for key,value in sorted(all_orders.items(), key=lambda x: x[1].ans('fursona_name')) if not value.room_confirmed}
|
||||||
|
# Orders with incomplete rooms
|
||||||
|
incomplete_orders = {key:value for key,value in orders.items() if value.code == value.room_id and (value.room_person_no - len(value.room_members)) > 0}
|
||||||
|
# Roomless furs
|
||||||
|
roomless_orders = {key:value for key,value in orders.items() if(not value.room_id and not value.daily and value.bed_in_room != ITEM_VARIATIONS_MAP["bed_in_room"]["bed_in_room_no_room"])}
|
||||||
|
|
||||||
|
# Result map
|
||||||
|
result_map = {}
|
||||||
|
|
||||||
|
# Check overflows
|
||||||
|
room_quota_overflow = {}
|
||||||
|
for key, value in ITEM_VARIATIONS_MAP['bed_in_room'].items():
|
||||||
|
if key != "bed_in_room_no_room":
|
||||||
|
room_quota = get_quota(ITEMS_ID_MAP['bed_in_room'], value)
|
||||||
|
capacity = ROOM_CAPACITY_MAP[key] if key in ROOM_CAPACITY_MAP else 1
|
||||||
|
current_quota = len(list(filter(lambda y: y.bed_in_room == value and y.room_owner == True, all_orders.values())))
|
||||||
|
room_quota_overflow[value] = current_quota - int(room_quota.size / capacity) if room_quota else 0
|
||||||
|
if DEV_MODE and EXTRA_PRINTS:
|
||||||
|
print(f"There are {current_quota} of room type {key} out of a total of ({room_quota.size} / {capacity})")
|
||||||
|
|
||||||
|
# Init rooms to remove
|
||||||
|
result_map["void"] = []
|
||||||
|
|
||||||
|
# Remove rooms that are over quota
|
||||||
|
for room_type, overflow_qty in {key:value for key,value in room_quota_overflow.items() if value > 0}.items():
|
||||||
|
sorted_rooms = sorted(incomplete_orders.values(), key=lambda r: len(r.room_members))
|
||||||
|
sorted_rooms = [r for r in sorted_rooms if r.bed_in_room == room_type]
|
||||||
|
for room_to_remove in sorted_rooms[:overflow_qty]:
|
||||||
|
# Room codes to remove
|
||||||
|
result_map["void"].append(room_to_remove.code)
|
||||||
|
# Move room members to the roomless list
|
||||||
|
for member_code in room_to_remove.room_members:
|
||||||
|
roomless_orders[member_code] = all_orders[member_code]
|
||||||
|
del incomplete_orders[room_to_remove.code]
|
||||||
|
|
||||||
|
# Fill already existing rooms
|
||||||
|
for room_order in incomplete_orders.items():
|
||||||
|
room = room_order[1]
|
||||||
|
to_add = []
|
||||||
|
count = room.room_person_no
|
||||||
|
alreadyPresent = len(room.room_members)
|
||||||
|
missing_slots = count - alreadyPresent
|
||||||
|
for _ in range(missing_slots):
|
||||||
|
compatible_roomates = {key:value for key,value in roomless_orders.items() if value.bed_in_room == room.bed_in_room}
|
||||||
|
if len(compatible_roomates.items()) == 0: break
|
||||||
|
# Try picking a roomate that's from the same country and room type
|
||||||
|
country = room.country.lower()
|
||||||
|
roomless_by_country = {key:value for key,value in compatible_roomates.items() if value.country.lower() == country}
|
||||||
|
if len(roomless_by_country.items()) > 0:
|
||||||
|
code_to_add = list(roomless_by_country.keys())[0]
|
||||||
|
to_add.append(code_to_add)
|
||||||
|
del roomless_orders[code_to_add]
|
||||||
|
else:
|
||||||
|
# If not, add first roomless there is
|
||||||
|
code_to_add = list(compatible_roomates.keys())[0]
|
||||||
|
to_add.append(code_to_add)
|
||||||
|
del roomless_orders[code_to_add]
|
||||||
|
result_map[room.code] = {
|
||||||
|
'type': 'add_existing',
|
||||||
|
'to_add': to_add,
|
||||||
|
'count': count,
|
||||||
|
'previouslyPresent': alreadyPresent
|
||||||
|
}
|
||||||
|
|
||||||
|
generated_counter = 0
|
||||||
|
# Create additional rooms
|
||||||
|
while len(roomless_orders.items()) > 0:
|
||||||
|
room = list(roomless_orders.items())[0][1]
|
||||||
|
to_add = []
|
||||||
|
count = room.room_person_no
|
||||||
|
alreadyPresent = len(room.room_members)
|
||||||
|
missing_slots = count - alreadyPresent
|
||||||
|
for _ in range(missing_slots):
|
||||||
|
compatible_roomates = {key:value for key,value in roomless_orders.items() if value.bed_in_room == room.bed_in_room}
|
||||||
|
if len(compatible_roomates.items()) == 0: break
|
||||||
|
# Try picking a roomate that's from the same country and room type
|
||||||
|
country = room.country.lower()
|
||||||
|
roomless_by_country = {key:value for key,value in compatible_roomates.items() if value.country.lower() == country}
|
||||||
|
if len(roomless_by_country.items()) > 0:
|
||||||
|
code_to_add = list(roomless_by_country.keys())[0]
|
||||||
|
to_add.append(code_to_add)
|
||||||
|
del roomless_orders[code_to_add]
|
||||||
|
else:
|
||||||
|
# If not, add first roomless there is
|
||||||
|
code_to_add = list(compatible_roomates.keys())[0]
|
||||||
|
to_add.append(code_to_add)
|
||||||
|
del roomless_orders[code_to_add]
|
||||||
|
generated_counter += 1
|
||||||
|
result_map[room.code] = {
|
||||||
|
'type': 'new',
|
||||||
|
'room_name': f'Generated Room {generated_counter}',
|
||||||
|
'room_type': room.bed_in_room,
|
||||||
|
'to_add': to_add,
|
||||||
|
'count': count,
|
||||||
|
'previouslyPresent': alreadyPresent
|
||||||
|
}
|
||||||
|
|
||||||
|
result_map["infinite"] = { 'to_add': [] }
|
||||||
|
result_map = {k: v for k, v in sorted(result_map.items(), key=lambda x: ((x[1]["count"], x[1]["previouslyPresent"]) if("count" in x[1] and "previouslyPresent" in x[1]) else (4316, 0) ))}
|
||||||
|
tpl = request.app.ctx.tpl.get_template('wizard.html')
|
||||||
|
return html(tpl.render(order=order, all_orders=all_orders, unconfirmed_orders=orders, data=result_map, jsondata=json.dumps(result_map, skipkeys=True, ensure_ascii=False)))
|
||||||
|
|
||||||
|
@bp.post('/room/wizard/submit')
|
||||||
|
async def submit_from_room_wizard(request:Request, order:Order):
|
||||||
|
'''Will apply changes to the rooms'''
|
||||||
|
await clear_cache(request, order)
|
||||||
|
|
||||||
|
data = json.loads(request.body)
|
||||||
|
|
||||||
|
# Phase 1 - Delete all rooms in void
|
||||||
|
if 'void' in data:
|
||||||
|
for room_code in data['void']:
|
||||||
|
ppl = await get_people_in_room_by_code(request, room_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()
|
||||||
|
logger.info(f"Deleted rooms {', '.join(data['void'])}")
|
||||||
|
|
||||||
|
# Phase 2 - Join roomless to other rooms or add new rooms
|
||||||
|
for room_code, value in {key:value for key,value in data.items() if key.lower() not in ['void', 'infinite']}.items():
|
||||||
|
if not value['to_add'] or len(value['to_add']) == 0: continue
|
||||||
|
room_order = await request.app.ctx.om.get_order(code=room_code)
|
||||||
|
# Preconditions
|
||||||
|
if not room_order: raise exceptions.BadRequest(f"Order {room_code} does not exist.")
|
||||||
|
if room_order.daily == True: raise exceptions.BadRequest(f"Order {room_code} is daily.")
|
||||||
|
if room_order.status != 'paid': raise exceptions.BadRequest(f"Order {room_code} hasn't paid.")
|
||||||
|
if room_order.room_owner:
|
||||||
|
if room_order.room_person_no < len(room_order.room_members) + (len(value['to_add']) if value['to_add'] else 0):
|
||||||
|
raise exceptions.BadRequest(f"Input exceeds room {room_order.code} capacity.")
|
||||||
|
elif room_order.room_person_no < (len(value['to_add']) if value['to_add'] else 0):
|
||||||
|
raise exceptions.BadRequest(f"Input exceeds room {room_order.code} capacity.")
|
||||||
|
|
||||||
|
# Adding roomless orders to existing rooms
|
||||||
|
if value['type'] == 'add_existing' or value['type'] == 'new':
|
||||||
|
if value['type'] == 'new':
|
||||||
|
if room_order.room_owner: exceptions.BadRequest(f"Order {room_code} is already a room owner.")
|
||||||
|
# Create room data
|
||||||
|
await room_order.edit_answer('room_name', value['room_name'])
|
||||||
|
await room_order.edit_answer('room_id', room_order.code)
|
||||||
|
await room_order.edit_answer('room_secret', ''.join(choice('0123456789') for _ in range(6)))
|
||||||
|
elif not room_order.room_owner:
|
||||||
|
raise exceptions.BadRequest(f"Order {room_code} is not a room owner.")
|
||||||
|
# Add members
|
||||||
|
for new_member_code in value['to_add']:
|
||||||
|
pending_member = await request.app.ctx.om.get_order(code=new_member_code)
|
||||||
|
# Preconditions
|
||||||
|
if pending_member.daily == True: raise exceptions.BadRequest(f"Order {pending_member.code} is daily.")
|
||||||
|
#if pending_member.status != 'paid': raise exceptions.BadRequest(f"Order {new_member_code} hasn't paid.") # Since we don't confirm rooms anymore, we don't need to check if they're paid or not
|
||||||
|
if pending_member.bed_in_room != room_order.bed_in_room: raise exceptions.BadRequest(f"Order {new_member_code} has a different room type than {room_code}.")
|
||||||
|
if pending_member.room_owner: exceptions.BadRequest(f"Order {new_member_code} is already a room owner.")
|
||||||
|
if pending_member.room_id and pending_member.room_id not in data['void']: exceptions.BadRequest(f"Order {new_member_code} is in another room.")
|
||||||
|
await pending_member.edit_answer('room_id', room_order.code)
|
||||||
|
await pending_member.edit_answer('room_confirmed', "True")
|
||||||
|
await pending_member.edit_answer('pending_room', None)
|
||||||
|
await pending_member.send_answers()
|
||||||
|
logger.info(f"{'Created' if value['type'] == 'new' else 'Edited'} {str(room_order)}")
|
||||||
|
# Confirm members that were already inside the room
|
||||||
|
if value['type'] == 'add_existing':
|
||||||
|
for already_member in list(filter(lambda rm: rm.code in room_order.room_members and rm.code != room_order.code, request.app.ctx.om.cache.values())):
|
||||||
|
await already_member.edit_answer('room_confirmed', "True")
|
||||||
|
await already_member.send_answers()
|
||||||
|
else: raise exceptions.BadRequest(f"Unexpected type ({value['type']})")
|
||||||
|
await room_order.edit_answer('pending_room', None)
|
||||||
|
await room_order.edit_answer('pending_roommates', None)
|
||||||
|
# await room_order.edit_answer('room_confirmed', "True") Use the autoconfirm button in the admin panel
|
||||||
|
await room_order.edit_answer('room_members', ','.join(list(set([*room_order.room_members, room_order.code, *value['to_add']]))))
|
||||||
|
await room_order.send_answers()
|
||||||
|
await clear_cache(request, order)
|
||||||
|
return text('done', status=200)
|
||||||
|
|
||||||
|
|
||||||
@bp.get('/propic/remind')
|
@bp.get('/propic/remind')
|
||||||
async def propic_remind_missing(request, order:Order):
|
async def propic_remind_missing(request, order:Order):
|
||||||
await clear_cache(request, order)
|
await clear_cache(request, order)
|
||||||
|
@ -99,3 +305,80 @@ async def propic_remind_missing(request, order:Order):
|
||||||
await send_missing_propic_message(order, missingPropic, missingFursuitPropic)
|
await send_missing_propic_message(order, missingPropic, missingFursuitPropic)
|
||||||
|
|
||||||
return redirect(f'/manage/admin')
|
return redirect(f'/manage/admin')
|
||||||
|
|
||||||
|
@bp.get('/export/export')
|
||||||
|
async def export_export(request, order:Order):
|
||||||
|
await clear_cache(request, order)
|
||||||
|
|
||||||
|
data = StringIO()
|
||||||
|
w = csv.writer(data)
|
||||||
|
|
||||||
|
w.writerow(['Status', 'Code', 'First name', 'Last name', 'Nick', 'State', 'Card', 'Artist', 'Fursuiter', 'Sponsorhip', 'Early', 'Late', 'Daily', 'Daily days', 'Shirt', 'Room type', 'Room count', 'Room members', 'Payment', 'Price', 'Refunds', 'Staff'])
|
||||||
|
|
||||||
|
orders = request.app.ctx.om.cache.values()
|
||||||
|
order: Order
|
||||||
|
for order in orders:
|
||||||
|
w.writerow([
|
||||||
|
order.status,
|
||||||
|
order.code,
|
||||||
|
order.first_name,
|
||||||
|
order.last_name,
|
||||||
|
order.name,
|
||||||
|
order.country,
|
||||||
|
order.has_card,
|
||||||
|
order.is_artist,
|
||||||
|
order.is_fursuiter,
|
||||||
|
order.sponsorship,
|
||||||
|
order.has_early,
|
||||||
|
order.has_late,
|
||||||
|
order.daily,
|
||||||
|
','.join(order.dailyDays),
|
||||||
|
order.shirt_size,
|
||||||
|
ROOM_TYPE_NAMES[order.bed_in_room] if order.bed_in_room in ROOM_TYPE_NAMES else "-",
|
||||||
|
len(order.room_members),
|
||||||
|
','.join(order.room_members),
|
||||||
|
order.payment_provider,
|
||||||
|
order.total - order.fees,
|
||||||
|
order.refunds,
|
||||||
|
order.ans('staff_role') or 'attendee',
|
||||||
|
])
|
||||||
|
|
||||||
|
data.seek(0)
|
||||||
|
str = data.read() #data.read().decode("UTF-8")
|
||||||
|
data.flush()
|
||||||
|
data.close()
|
||||||
|
|
||||||
|
return raw(str, status=200, headers={'Content-Disposition': f'attachment; filename="export_{int(time.time())}.csv"', "Content-Type": "text/csv; charset=UTF-8"})
|
||||||
|
|
||||||
|
@bp.get('/export/hotel')
|
||||||
|
async def export_hotel(request, order:Order):
|
||||||
|
await clear_cache(request, order)
|
||||||
|
|
||||||
|
data = StringIO()
|
||||||
|
w = csv.writer(data)
|
||||||
|
|
||||||
|
w.writerow(['Room type', 'Room name', 'Room code', 'First name', 'Last name', 'Birthday', 'Address', 'Email', 'Phone number', 'Status'])
|
||||||
|
|
||||||
|
orders = sorted(request.app.ctx.om.cache.values(), key=lambda d: (d.room_id if d.room_id != None else "~"))
|
||||||
|
order: Order
|
||||||
|
for order in orders:
|
||||||
|
w.writerow([
|
||||||
|
ROOM_TYPE_NAMES[order.bed_in_room] if order.bed_in_room in ROOM_TYPE_NAMES else "-",
|
||||||
|
order.room_name,
|
||||||
|
order.room_id,
|
||||||
|
order.first_name,
|
||||||
|
order.last_name,
|
||||||
|
order.birth_date,
|
||||||
|
order.address,
|
||||||
|
order.email,
|
||||||
|
order.phone,
|
||||||
|
order.status,
|
||||||
|
order.code
|
||||||
|
])
|
||||||
|
|
||||||
|
data.seek(0)
|
||||||
|
str = data.read() #data.read().decode("UTF-8")
|
||||||
|
data.flush()
|
||||||
|
data.close()
|
||||||
|
|
||||||
|
return raw(str, status=200, headers={'Content-Disposition': f'attachment; filename="hotel_{int(time.time())}.csv"', "Content-Type": "text/csv; charset=UTF-8"})
|
30
api.py
30
api.py
|
@ -9,7 +9,9 @@ import random
|
||||||
import string
|
import string
|
||||||
import httpx
|
import httpx
|
||||||
import json
|
import json
|
||||||
|
import traceback
|
||||||
from sanic.log import logger
|
from sanic.log import logger
|
||||||
|
from email_util import send_app_login_attempt
|
||||||
|
|
||||||
bp = Blueprint("api", url_prefix="/manage/api")
|
bp = Blueprint("api", url_prefix="/manage/api")
|
||||||
|
|
||||||
|
@ -32,7 +34,8 @@ async def api_members(request):
|
||||||
'propic_fursuiter': o.ans('propic_fursuiter'),
|
'propic_fursuiter': o.ans('propic_fursuiter'),
|
||||||
'staff_role': o.ans('staff_role'),
|
'staff_role': o.ans('staff_role'),
|
||||||
'country': o.country,
|
'country': o.country,
|
||||||
'is_checked_in': False,
|
'room_id': o.room_id,
|
||||||
|
'is_checked_in': o.checked_in,
|
||||||
'points': random.randint(0,50) if random.random() > 0.3 else 0
|
'points': random.randint(0,50) if random.random() > 0.3 else 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -111,6 +114,10 @@ async def token_test(request):
|
||||||
|
|
||||||
return response.json({'ok': True, 'message': 'This token is valid :)'})
|
return response.json({'ok': True, 'message': 'This token is valid :)'})
|
||||||
|
|
||||||
|
@bp.get("/ping")
|
||||||
|
async def ping(request):
|
||||||
|
return response.text("pong")
|
||||||
|
|
||||||
@bp.get("/welcome")
|
@bp.get("/welcome")
|
||||||
async def welcome_app(request):
|
async def welcome_app(request):
|
||||||
|
|
||||||
|
@ -137,15 +144,18 @@ async def welcome_app(request):
|
||||||
'propic_fursuiter': o.ans('propic_fursuiter'),
|
'propic_fursuiter': o.ans('propic_fursuiter'),
|
||||||
'staff_role': o.ans('staff_role'),
|
'staff_role': o.ans('staff_role'),
|
||||||
'country': o.country,
|
'country': o.country,
|
||||||
'is_checked_in': False,
|
'is_checked_in': o.checked_in,
|
||||||
'points': random.randint(0,50) if random.random() > 0.3 else 0,
|
'points': random.randint(0,50) if random.random() > 0.3 else 0,
|
||||||
'can_scan_nfc': o.can_scan_nfc,
|
'can_scan_nfc': o.can_scan_nfc,
|
||||||
|
'room_id': o.room_id,
|
||||||
|
#'mail': o.email,
|
||||||
'actual_room_id': o.actual_room,
|
'actual_room_id': o.actual_room,
|
||||||
**ret
|
**ret
|
||||||
})
|
})
|
||||||
|
|
||||||
@bp.get("/scan/<nfc_id>")
|
@bp.get("/scan/<nfc_id>")
|
||||||
async def nfc_scan(request, nfc_id):
|
async def nfc_scan(request, nfc_id):
|
||||||
|
return response.text("Nope")
|
||||||
if not request.token:
|
if not request.token:
|
||||||
return response.json({'ok': False, 'error': 'You need to provide a token.'}, status=401)
|
return response.json({'ok': False, 'error': 'You need to provide a token.'}, status=401)
|
||||||
|
|
||||||
|
@ -172,11 +182,12 @@ async def nfc_scan(request, nfc_id):
|
||||||
'propic_fursuiter': o.ans('propic_fursuiter'),
|
'propic_fursuiter': o.ans('propic_fursuiter'),
|
||||||
'staff_role': o.ans('staff_role'),
|
'staff_role': o.ans('staff_role'),
|
||||||
'country': o.country,
|
'country': o.country,
|
||||||
'is_checked_in': False,
|
'is_checked_in': o.checked_in,
|
||||||
'points': random.randint(0,50) if random.random() > 0.3 else 0,
|
'points': random.randint(0,50) if random.random() > 0.3 else 0,
|
||||||
'comment': o.comment,
|
'comment': o.comment,
|
||||||
'actual_room_id': o.actual_room,
|
'actual_room_id': o.actual_room,
|
||||||
'phone': o.phone,
|
'phone': o.phone,
|
||||||
|
'room_id': o.room_id,
|
||||||
'telegram_username': o.telegram_username,
|
'telegram_username': o.telegram_username,
|
||||||
'roommates': {x: (await request.app.ctx.om.get_order(code=x, cached=True)).name for x in room_owner.room_members if x != o.code}
|
'roommates': {x: (await request.app.ctx.om.get_order(code=x, cached=True)).name for x in room_owner.room_members if x != o.code}
|
||||||
})
|
})
|
||||||
|
@ -221,16 +232,9 @@ async def get_token(request, code):
|
||||||
request.app.ctx.login_codes[code] = [''.join(random.choice(string.digits) for _ in range(6)), 3]
|
request.app.ctx.login_codes[code] = [''.join(random.choice(string.digits) for _ in range(6)), 3]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = MIMEText(f"Hello {user.name}!\n\nWe have received a request to login in the app. If you didn't do this, please ignore this email. Somebody is probably playing with you.\n\nYour login code is: {request.app.ctx.login_codes[code][0]}\n\nPlease do not tell this to anybody!")
|
await send_app_login_attempt(user, request.app.ctx.login_codes[code][0])
|
||||||
msg['Subject'] = '[Furizon] Your login code'
|
except Exception:
|
||||||
msg['From'] = 'Furizon <no-reply@furizon.net>'
|
logger.error(f"[API] [GET_TOKEN] Error while sending email.\n{traceback.format_exc()}")
|
||||||
msg['To'] = f"{user.name} <{user.email}>"
|
|
||||||
|
|
||||||
s = smtplib.SMTP_SSL(SMTP_HOST)
|
|
||||||
s.login(SMTP_USER, SMTP_PASSWORD)
|
|
||||||
s.sendmail(msg['From'], msg['to'], msg.as_string())
|
|
||||||
s.quit()
|
|
||||||
except:
|
|
||||||
return response.json({'ok': False, 'error': 'There has been an issue sending your e-mail. Please try again later or report to an admin.'}, status=500)
|
return response.json({'ok': False, 'error': 'There has been an issue sending your e-mail. Please try again later or report to an admin.'}, status=500)
|
||||||
|
|
||||||
return response.json({'ok': True, 'message': 'A login code has been sent to your email.'})
|
return response.json({'ok': True, 'message': 'A login code has been sent to your email.'})
|
||||||
|
|
12
app.py
12
app.py
|
@ -16,10 +16,12 @@ import requests
|
||||||
import sys
|
import sys
|
||||||
from sanic.log import logger, logging, access_logger
|
from sanic.log import logger, logging, access_logger
|
||||||
from metrics import *
|
from metrics import *
|
||||||
|
from utils import isSessionAdmin
|
||||||
from email_util import killSmptClient
|
from email_util import killSmptClient
|
||||||
import pretixClient
|
import pretixClient
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
app.static("/res", "res/")
|
app.static("/res", "res/")
|
||||||
|
|
||||||
|
@ -29,14 +31,13 @@ app.ext.add_dependency(Quotas, get_quotas)
|
||||||
from room import bp as room_bp
|
from room import bp as room_bp
|
||||||
from propic import bp as propic_bp
|
from propic import bp as propic_bp
|
||||||
from karaoke import bp as karaoke_bp
|
from karaoke import bp as karaoke_bp
|
||||||
from export import bp as export_bp
|
|
||||||
from stats import bp as stats_bp
|
from stats import bp as stats_bp
|
||||||
from api import bp as api_bp
|
from api import bp as api_bp
|
||||||
from carpooling import bp as carpooling_bp
|
from carpooling import bp as carpooling_bp
|
||||||
from checkin import bp as checkin_bp
|
from checkin import bp as checkin_bp
|
||||||
from admin import bp as admin_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, admin_bp])
|
app.blueprint([room_bp, karaoke_bp, propic_bp, stats_bp, api_bp, carpooling_bp, checkin_bp, admin_bp])
|
||||||
|
|
||||||
|
|
||||||
async def clear_session(response):
|
async def clear_session(response):
|
||||||
|
@ -50,12 +51,12 @@ async def handleException(request, exception):
|
||||||
statusCode = exception.status_code if hasattr(exception, 'status_code') else 500
|
statusCode = exception.status_code if hasattr(exception, 'status_code') else 500
|
||||||
try:
|
try:
|
||||||
tpl = app.ctx.tpl.get_template('error.html')
|
tpl = app.ctx.tpl.get_template('error.html')
|
||||||
r = html(tpl.render(exception=exception, status_code=statusCode))
|
r = html(tpl.render(exception=exception, status_code=statusCode), status=statusCode)
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
if statusCode == 403:
|
if statusCode == 403:
|
||||||
clear_session(r)
|
await clear_session(r)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ async def main_start(*_):
|
||||||
app.ctx.tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=True)
|
app.ctx.tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=True)
|
||||||
app.ctx.tpl.globals.update(time=time)
|
app.ctx.tpl.globals.update(time=time)
|
||||||
app.ctx.tpl.globals.update(PROPIC_DEADLINE=PROPIC_DEADLINE)
|
app.ctx.tpl.globals.update(PROPIC_DEADLINE=PROPIC_DEADLINE)
|
||||||
|
app.ctx.tpl.globals.update(ROOM_DEADLINE=ROOM_DEADLINE)
|
||||||
app.ctx.tpl.globals.update(LOCALES=LOCALES)
|
app.ctx.tpl.globals.update(LOCALES=LOCALES)
|
||||||
app.ctx.tpl.globals.update(ITEMS_ID_MAP=ITEMS_ID_MAP)
|
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(ITEM_VARIATIONS_MAP=ITEM_VARIATIONS_MAP)
|
||||||
|
@ -157,7 +159,7 @@ async def welcome(request, order: Order, quota: Quotas):
|
||||||
room_members.append(await app.ctx.om.get_order(code=member_id, cached=True))
|
room_members.append(await app.ctx.om.get_order(code=member_id, cached=True))
|
||||||
|
|
||||||
tpl = app.ctx.tpl.get_template('welcome.html')
|
tpl = app.ctx.tpl.get_template('welcome.html')
|
||||||
return html(tpl.render(order=order, quota=quota, room_members=room_members, pending_roommates=pending_roommates, ROOM_ERROR_MESSAGES=ROOM_ERROR_TYPES))
|
return html(tpl.render(order=order, quota=quota, room_members=room_members, pending_roommates=pending_roommates, ROOM_ERROR_MESSAGES=ROOM_ERROR_TYPES, isSessionAdmin=await isSessionAdmin(request, order)))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/manage/download_ticket")
|
@app.route("/manage/download_ticket")
|
||||||
|
|
|
@ -17,6 +17,8 @@ PROPIC_MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
|
||||||
PROPIC_MAX_SIZE = (2048, 2048) # (Width, Height)
|
PROPIC_MAX_SIZE = (2048, 2048) # (Width, Height)
|
||||||
PROPIC_MIN_SIZE = (125, 125) # (Width, Height)
|
PROPIC_MIN_SIZE = (125, 125) # (Width, Height)
|
||||||
|
|
||||||
|
ROOM_DEADLINE = 9999999999
|
||||||
|
|
||||||
# This is used for feedback sending inside of the app. Feedbacks will be sent to the specified chat using the bot api id.
|
# This is used for feedback sending inside of the app. Feedbacks will be sent to the specified chat using the bot api id.
|
||||||
TG_BOT_API = '123456789:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
|
TG_BOT_API = '123456789:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
|
||||||
TG_CHAT_ID = -1234567
|
TG_CHAT_ID = -1234567
|
||||||
|
@ -46,6 +48,8 @@ DEV_MODE = True
|
||||||
ACCESS_LOG = True
|
ACCESS_LOG = True
|
||||||
EXTRA_PRINTS = True
|
EXTRA_PRINTS = True
|
||||||
|
|
||||||
|
UNCONFIRM_ROOMS_ENABLE = True
|
||||||
|
|
||||||
METRICS_PATH = "/welcome/metrics"
|
METRICS_PATH = "/welcome/metrics"
|
||||||
|
|
||||||
# Additional configured locales.
|
# Additional configured locales.
|
||||||
|
@ -63,53 +67,58 @@ SPONSORSHIP_COLOR_MAP = {
|
||||||
'normal': (142, 36, 170)
|
'normal': (142, 36, 170)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Quotes
|
||||||
|
QUOTES_LIST = []
|
||||||
|
|
||||||
# Maps Products metadata name <--> ID
|
# Maps Products metadata name <--> ID
|
||||||
ITEMS_ID_MAP = {
|
ITEMS_ID_MAP = {
|
||||||
'early_bird_ticket': 126,
|
'early_bird_ticket': None,
|
||||||
'regular_ticket': 127,
|
'regular_ticket': None,
|
||||||
'staff_ticket': 155,
|
'staff_ticket': None,
|
||||||
'daily_ticket': 162,
|
'daily_ticket': None,
|
||||||
'sponsorship_item': 129,
|
'regular_bundle_sponsor_ticket': None,
|
||||||
'early_arrival_admission': 133,
|
'sponsorship_item': None,
|
||||||
'late_departure_admission': 134,
|
'early_arrival_admission': None,
|
||||||
'membership_card_item': 128,
|
'late_departure_admission': None,
|
||||||
'bed_in_room': 153,
|
'membership_card_item': None,
|
||||||
'room_type': 135,
|
'bed_in_room': None,
|
||||||
'room_guest': 136,
|
'room_type': None,
|
||||||
'daily_1': 163,
|
'room_guest': None,
|
||||||
'daily_2': 164,
|
'daily_1': None,
|
||||||
'daily_3': 165,
|
'daily_2': None,
|
||||||
'daily_4': 166,
|
'daily_3': None,
|
||||||
|
'daily_4': None,
|
||||||
'daily_5': None
|
'daily_5': None
|
||||||
}
|
}
|
||||||
|
|
||||||
# Maps Products' variants metadata name <--> ID
|
# Maps Products' variants metadata name <--> ID
|
||||||
ITEM_VARIATIONS_MAP = {
|
ITEM_VARIATIONS_MAP = {
|
||||||
'sponsorship_item': {
|
'sponsorship_item': {
|
||||||
'sponsorship_item_normal': 55,
|
'sponsorship_item_normal': None,
|
||||||
'sponsorship_item_super': 56
|
'sponsorship_item_super': None
|
||||||
},
|
},
|
||||||
'bed_in_room': {
|
'bed_in_room': {
|
||||||
'bed_in_room_main_1': 83,
|
'bed_in_room_no_room': None,
|
||||||
'bed_in_room_main_2': 67,
|
'bed_in_room_main_1': None,
|
||||||
'bed_in_room_main_3': 68,
|
'bed_in_room_main_2': None,
|
||||||
'bed_in_room_main_4': 69,
|
'bed_in_room_main_3': None,
|
||||||
'bed_in_room_main_5': 70,
|
'bed_in_room_main_4': None,
|
||||||
'bed_in_room_overflow1_2': 75,
|
'bed_in_room_main_5': None,
|
||||||
|
'bed_in_room_overflow1_2': None,
|
||||||
},
|
},
|
||||||
'room_type': {
|
'room_type': {
|
||||||
'single': 57,
|
'single': None,
|
||||||
'double': 58,
|
'double': None,
|
||||||
'triple': 59,
|
'triple': None,
|
||||||
'quadruple': 60,
|
'quadruple': None,
|
||||||
'quintuple': 61
|
'quintuple': None
|
||||||
},
|
},
|
||||||
'room_guest': {
|
'room_guest': {
|
||||||
'single': 57,
|
'single': None,
|
||||||
'double': 58,
|
'double': None,
|
||||||
'triple': 59,
|
'triple': None,
|
||||||
'quadruple': 60,
|
'quadruple': None,
|
||||||
'quintuple': 61
|
'quintuple': None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +138,9 @@ CATEGORIES_LIST_MAP = {
|
||||||
# Create a bunch of "room" items which will get added to the order once somebody gets a room.
|
# Create a bunch of "room" items which will get added to the order once somebody gets a room.
|
||||||
# Map item_name -> room capacity
|
# Map item_name -> room capacity
|
||||||
ROOM_CAPACITY_MAP = {
|
ROOM_CAPACITY_MAP = {
|
||||||
|
# Default
|
||||||
|
'bed_in_room_no_room': 0,
|
||||||
|
|
||||||
# SACRO CUORE
|
# SACRO CUORE
|
||||||
'bed_in_room_main_1': 1,
|
'bed_in_room_main_1': 1,
|
||||||
'bed_in_room_main_2': 2,
|
'bed_in_room_main_2': 2,
|
||||||
|
|
|
@ -15,30 +15,44 @@ def killSmptClient():
|
||||||
global sslLock
|
global sslLock
|
||||||
global sslTimer
|
global sslTimer
|
||||||
global smptSender
|
global smptSender
|
||||||
|
logger.info(f"[SMPT] killSmptClient: Lock status: {sslLock.locked()}")
|
||||||
sslTimer.cancel()
|
sslTimer.cancel()
|
||||||
sslLock.acquire()
|
sslLock.acquire()
|
||||||
|
exp = None
|
||||||
if(smptSender is not None):
|
if(smptSender is not None):
|
||||||
logger.debug('[SMPT] Closing smpt client')
|
logger.debug('[SMPT] Closing smpt client')
|
||||||
smptSender.quit() # it calls close() inside
|
try:
|
||||||
|
smptSender.quit() # it calls close() inside
|
||||||
|
except Exception as e:
|
||||||
|
exp = e
|
||||||
smptSender = None
|
smptSender = None
|
||||||
sslLock.release()
|
sslLock.release()
|
||||||
|
if(exp != None):
|
||||||
|
raise exp
|
||||||
|
|
||||||
async def openSmptClient():
|
async def openSmptClient():
|
||||||
global sslLock
|
global sslLock
|
||||||
global sslTimer
|
global sslTimer
|
||||||
global sslContext
|
global sslContext
|
||||||
global smptSender
|
global smptSender
|
||||||
|
logger.info(f"[SMPT] openSmptClient: Lock status: {sslLock.locked()}")
|
||||||
sslTimer.cancel()
|
sslTimer.cancel()
|
||||||
sslLock.acquire()
|
sslLock.acquire()
|
||||||
if(smptSender is None):
|
exp = None
|
||||||
logger.debug('[SMPT] Opening smpt client')
|
try:
|
||||||
client : smtplib.SMTP = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
|
if(smptSender is None):
|
||||||
client.starttls(context=sslContext)
|
logger.debug('[SMPT] Opening smpt client')
|
||||||
client.login(SMTP_USER, SMTP_PASSWORD)
|
client : smtplib.SMTP = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
|
||||||
smptSender = client
|
client.starttls(context=sslContext)
|
||||||
|
client.login(SMTP_USER, SMTP_PASSWORD)
|
||||||
|
smptSender = client
|
||||||
|
except Exception as e:
|
||||||
|
exp = e
|
||||||
sslLock.release()
|
sslLock.release()
|
||||||
sslTimer = createTimer()
|
sslTimer = createTimer()
|
||||||
sslTimer.start()
|
sslTimer.start()
|
||||||
|
if(exp != None):
|
||||||
|
raise exp
|
||||||
|
|
||||||
def createTimer():
|
def createTimer():
|
||||||
return Timer(SMPT_CLIENT_CLOSE_TIMEOUT, killSmptClient)
|
return Timer(SMPT_CLIENT_CLOSE_TIMEOUT, killSmptClient)
|
||||||
|
@ -48,11 +62,19 @@ sslContext : SSLContext = ssl.create_default_context()
|
||||||
smptSender : smtplib.SMTP = None
|
smptSender : smtplib.SMTP = None
|
||||||
|
|
||||||
async def sendEmail(message : MIMEMultipart):
|
async def sendEmail(message : MIMEMultipart):
|
||||||
|
message['From'] = f'{EMAIL_SENDER_NAME} <{EMAIL_SENDER_MAIL}>'
|
||||||
await openSmptClient()
|
await openSmptClient()
|
||||||
logger.debug(f"[SMPT] Sending mail {message['From']} -> {message['to']} '{message['Subject']}'")
|
logger.debug(f"[SMPT] Sending mail {message['From']} -> {message['to']} '{message['Subject']}'")
|
||||||
|
logger.info(f"[SMPT] sendEmail: Lock status: {sslLock.locked()}")
|
||||||
|
exp = None
|
||||||
sslLock.acquire()
|
sslLock.acquire()
|
||||||
smptSender.sendmail(message['From'], message['to'], message.as_string())
|
try:
|
||||||
|
smptSender.sendmail(message['From'], message['to'], message.as_string())
|
||||||
|
except Exception as e:
|
||||||
|
exp = e
|
||||||
sslLock.release()
|
sslLock.release()
|
||||||
|
if(exp != None):
|
||||||
|
raise exp
|
||||||
|
|
||||||
def render_email_template(title = "", body = ""):
|
def render_email_template(title = "", body = ""):
|
||||||
tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=False).get_template('email/comunication.html')
|
tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=False).get_template('email/comunication.html')
|
||||||
|
@ -79,17 +101,17 @@ async def send_unconfirm_message(room_order, orders):
|
||||||
issues_html += "</ul>"
|
issues_html += "</ul>"
|
||||||
|
|
||||||
for member in orders:
|
for member in orders:
|
||||||
plain_body = EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['plain'].format(member.name, room_order.room_name, issues_plain)
|
if(member.status != 'canceled'):
|
||||||
html_body = render_email_template(EMAILS_TEXT["ROOM_UNCONFIRM_TITLE"], EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['html'].format(member.name, room_order.room_name, issues_html))
|
plain_body = EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['plain'].format(member.name, room_order.room_name, issues_plain)
|
||||||
plain_text = MIMEText(plain_body, "plain")
|
html_body = render_email_template(EMAILS_TEXT["ROOM_UNCONFIRM_TITLE"], EMAILS_TEXT["ROOM_UNCONFIRM_TEXT"]['html'].format(member.name, room_order.room_name, issues_html))
|
||||||
html_text = MIMEText(html_body, "html")
|
plain_text = MIMEText(plain_body, "plain")
|
||||||
message = MIMEMultipart("alternative")
|
html_text = MIMEText(html_body, "html")
|
||||||
message.attach(plain_text)
|
message = MIMEMultipart("alternative")
|
||||||
message.attach(html_text)
|
message.attach(plain_text)
|
||||||
message['Subject'] = f'[{EMAIL_SENDER_NAME}] Your room cannot be confirmed'
|
message.attach(html_text)
|
||||||
message['From'] = f'{EMAIL_SENDER_NAME} <{EMAIL_SENDER_MAIL}>'
|
message['Subject'] = f'[{EMAIL_SENDER_NAME}] Your room cannot be confirmed'
|
||||||
message['To'] = f"{member.name} <{member.email}>"
|
message['To'] = f"{member.name} <{member.email}>"
|
||||||
memberMessages.append(message)
|
memberMessages.append(message)
|
||||||
|
|
||||||
if len(memberMessages) == 0: return
|
if len(memberMessages) == 0: return
|
||||||
|
|
||||||
|
@ -110,8 +132,14 @@ async def send_missing_propic_message(order, missingPropic, missingFursuitPropic
|
||||||
message.attach(plain_text)
|
message.attach(plain_text)
|
||||||
message.attach(html_text)
|
message.attach(html_text)
|
||||||
message['Subject'] = f"[{EMAIL_SENDER_NAME}] You haven't uploaded your badges yet!"
|
message['Subject'] = f"[{EMAIL_SENDER_NAME}] You haven't uploaded your badges yet!"
|
||||||
message['From'] = f'{EMAIL_SENDER_NAME} <{EMAIL_SENDER_MAIL}>'
|
|
||||||
message['To'] = f"{order.name} <{order.email}>"
|
message['To'] = f"{order.name} <{order.email}>"
|
||||||
|
|
||||||
await sendEmail(message)
|
await sendEmail(message)
|
||||||
|
|
||||||
|
async def send_app_login_attempt(user, loginCode):
|
||||||
|
#TODO: Format a proper email and add it to messages.py
|
||||||
|
msg = MIMEText(f"Hello {user.name}!\n\nWe have received a request to login in the app. If you didn't do this, please ignore this email. Somebody is probably playing with you.\n\nYour login code is: {loginCode}\n\nPlease do not tell this to anybody!")
|
||||||
|
msg['Subject'] = '[Furizon] Your login code'
|
||||||
|
msg['To'] = f"{user.name} <{user.email}>"
|
||||||
|
|
||||||
|
await sendEmail(msg)
|
87
export.py
87
export.py
|
@ -1,87 +0,0 @@
|
||||||
from sanic.response import text
|
|
||||||
from sanic import Blueprint, exceptions
|
|
||||||
from ext import *
|
|
||||||
from config import headers, ADMINS, ORGANIZER, EVENT_NAME
|
|
||||||
|
|
||||||
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 not order.isAdmin(): raise exceptions.Forbidden("Birichino :)")
|
|
||||||
|
|
||||||
page = 0
|
|
||||||
orders = {}
|
|
||||||
|
|
||||||
ret = 'status;code;nome;cognome;nick;nazione;tessera;artista;fursuiter;sponsorship;early;late;shirt;roomsize;roommembers;payment;price;refunds;staff\n'
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
r = httpx.get(f'https://reg.furizon.net/api/v1/organizers/{ORGANIZER}/events/{EVENT_NAME}/orders/?page={page}', headers=headers)
|
|
||||||
if r.status_code == 404: break
|
|
||||||
|
|
||||||
for r in r.json()['results']:
|
|
||||||
|
|
||||||
o = Order(r)
|
|
||||||
orders[o.code] = o
|
|
||||||
|
|
||||||
ret += (';'.join(map(lambda x: str(x),
|
|
||||||
[
|
|
||||||
o.status,
|
|
||||||
o.code,
|
|
||||||
o.first_name,
|
|
||||||
o.last_name,
|
|
||||||
o.name,
|
|
||||||
o.country,
|
|
||||||
o.has_card or '',
|
|
||||||
o.is_artist or '',
|
|
||||||
o.is_fursuiter or '',
|
|
||||||
o.sponsorship or '',
|
|
||||||
o.has_early or '',
|
|
||||||
o.has_late or '',
|
|
||||||
o.shirt_size,
|
|
||||||
len(o.room_members),
|
|
||||||
','.join(o.room_members),
|
|
||||||
o.payment_provider,
|
|
||||||
o.total-o.fees,
|
|
||||||
o.refunds,
|
|
||||||
o.ans('staff_role') or 'attendee',
|
|
||||||
]))) + "\n"
|
|
||||||
|
|
||||||
return text(ret)
|
|
||||||
|
|
||||||
@bp.route("/hotel_export.csv")
|
|
||||||
async def export_hotel_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 ['HWUC9','9YKGJ']: raise exceptions.Forbidden("Birichino :)")
|
|
||||||
|
|
||||||
page = 0
|
|
||||||
orders = {}
|
|
||||||
|
|
||||||
ret = 'code;nome;cognome;datanascita;posnascita;indirizzo;mail;status\n'
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
r = httpx.get(f'https://reg.furizon.net/api/v1/organizers/{ORGANIZER}/events/{EVENT_NAME}/orders/?page={page}', headers=headers)
|
|
||||||
if r.status_code == 404: break
|
|
||||||
|
|
||||||
for r in r.json()['results']:
|
|
||||||
|
|
||||||
o = Order(r)
|
|
||||||
orders[o.code] = o
|
|
||||||
|
|
||||||
ret += (';'.join(map(lambda x: str(x),
|
|
||||||
[
|
|
||||||
o.code,
|
|
||||||
o.first_name,
|
|
||||||
o.last_name,
|
|
||||||
o.birth_date,
|
|
||||||
o.birth_location,
|
|
||||||
o.address,
|
|
||||||
o.email,
|
|
||||||
o.status
|
|
||||||
]))) + "\n"
|
|
||||||
|
|
||||||
return text(ret)
|
|
114
ext.py
114
ext.py
|
@ -20,6 +20,10 @@ class Order:
|
||||||
|
|
||||||
self.time = time()
|
self.time = time()
|
||||||
self.data = data
|
self.data = data
|
||||||
|
if(len(self.data['positions']) == 0):
|
||||||
|
for fee in data['fees']:
|
||||||
|
if(fee['fee_type'] == "cancellation"):
|
||||||
|
self.data['status'] = 'c'
|
||||||
self.status = {'n': 'pending', 'p': 'paid', 'e': 'expired', 'c': 'canceled'}[self.data['status']]
|
self.status = {'n': 'pending', 'p': 'paid', 'e': 'expired', 'c': 'canceled'}[self.data['status']]
|
||||||
self.secret = data['secret']
|
self.secret = data['secret']
|
||||||
|
|
||||||
|
@ -35,20 +39,25 @@ class Order:
|
||||||
self.sponsorship = None
|
self.sponsorship = None
|
||||||
self.has_early = False
|
self.has_early = False
|
||||||
self.has_late = False
|
self.has_late = False
|
||||||
self.first_name = None
|
self.first_name = "None"
|
||||||
self.last_name = None
|
self.last_name = "None"
|
||||||
self.country = 'xx'
|
self.country = 'xx'
|
||||||
self.address = None
|
self.address = None
|
||||||
self.checked_in = False
|
self.checked_in = False
|
||||||
self.room_type = None
|
self.room_type = None
|
||||||
self.daily = False
|
self.daily = False
|
||||||
self.dailyDays = []
|
self.dailyDays = []
|
||||||
self.room_person_no = 0
|
self.bed_in_room = -1
|
||||||
|
self.room_person_no = -1
|
||||||
self.answers = []
|
self.answers = []
|
||||||
|
self.position_id = -1
|
||||||
|
self.position_positionid = -1
|
||||||
|
self.position_positiontypeid = -1
|
||||||
|
self.barcode = "None"
|
||||||
|
|
||||||
idata = data['invoice_address']
|
idata = data['invoice_address']
|
||||||
if idata:
|
if idata:
|
||||||
self.address = f"{idata['street']} - {idata['zipcode']} {idata['city']} - {idata['country']}"
|
self.address = f"{idata['street'].strip()} - {idata['zipcode'].strip()} {idata['city'].strip()} - {idata['country'].strip()}".replace("\n", "").replace("\r", "")
|
||||||
self.country = idata['country']
|
self.country = idata['country']
|
||||||
|
|
||||||
for p in self.data['positions']:
|
for p in self.data['positions']:
|
||||||
|
@ -88,7 +97,7 @@ class Order:
|
||||||
roomTypeLst = key_from_value(ITEM_VARIATIONS_MAP['bed_in_room'], p['variation'])
|
roomTypeLst = key_from_value(ITEM_VARIATIONS_MAP['bed_in_room'], p['variation'])
|
||||||
roomTypeId = roomTypeLst[0] if len(roomTypeLst) > 0 else None
|
roomTypeId = roomTypeLst[0] if len(roomTypeLst) > 0 else None
|
||||||
self.bed_in_room = p['variation']
|
self.bed_in_room = p['variation']
|
||||||
self.room_person_no = ROOM_CAPACITY_MAP[roomTypeId] if roomTypeId in ROOM_CAPACITY_MAP else None
|
self.room_person_no = ROOM_CAPACITY_MAP[roomTypeId] if roomTypeId in ROOM_CAPACITY_MAP else self.room_person_no
|
||||||
|
|
||||||
self.total = float(data['total'])
|
self.total = float(data['total'])
|
||||||
self.fees = 0
|
self.fees = 0
|
||||||
|
@ -106,6 +115,9 @@ class Order:
|
||||||
self.phone = data['phone']
|
self.phone = data['phone']
|
||||||
self.room_errors = []
|
self.room_errors = []
|
||||||
self.loadAns()
|
self.loadAns()
|
||||||
|
|
||||||
|
if(self.bed_in_room < 0 and not self.daily):
|
||||||
|
self.status = "canceled" # Must refer to the previous status assignment
|
||||||
def loadAns(self):
|
def loadAns(self):
|
||||||
self.shirt_size = self.ans('shirt_size')
|
self.shirt_size = self.ans('shirt_size')
|
||||||
self.is_artist = True if self.ans('is_artist') != 'No' else False
|
self.is_artist = True if self.ans('is_artist') != 'No' else False
|
||||||
|
@ -211,12 +223,17 @@ class Order:
|
||||||
# del self.answers[i]['options']
|
# del self.answers[i]['options']
|
||||||
# del self.answers[i]['option_identifiers']
|
# del self.answers[i]['option_identifiers']
|
||||||
|
|
||||||
res = await pretixClient.patch(f'orderpositions/{self.position_id}/', json={'answers': self.answers}, expectedStatusCodes=None)
|
ans = [] if self.status == "canceled" else self.answers
|
||||||
|
res = await pretixClient.patch(f'orderpositions/{self.position_id}/', json={'answers': ans}, expectedStatusCodes=None)
|
||||||
|
|
||||||
if res.status_code != 200:
|
if res.status_code != 200:
|
||||||
for ans, err in zip(self.answers, res.json()['answers']):
|
e = res.json()
|
||||||
if err:
|
if "answers" in e:
|
||||||
logger.error ('[ANSWERS SENDING] ERROR ON %s %s', ans, err)
|
for ans, err in zip(self.answers, res.json()['answers']):
|
||||||
|
if err:
|
||||||
|
logger.error ('[ANSWERS SENDING] ERROR ON %s %s', ans, err)
|
||||||
|
else:
|
||||||
|
logger.error("[ANSWERS SENDING] GENERIC ERROR. Response: '%s'", str(e))
|
||||||
|
|
||||||
raise exceptions.ServerError('There has been an error while updating this answers.')
|
raise exceptions.ServerError('There has been an error while updating this answers.')
|
||||||
|
|
||||||
|
@ -231,6 +248,47 @@ class Order:
|
||||||
def get_language(self):
|
def get_language(self):
|
||||||
return self.country.lower() if self.country.lower() in AVAILABLE_LOCALES else 'en'
|
return self.country.lower() if self.country.lower() in AVAILABLE_LOCALES else 'en'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
to_return = f"{'Room' if self.room_owner else 'Order'} {self.code}"
|
||||||
|
if self.room_owner == True:
|
||||||
|
to_return = f"{to_return} [ members = {self.room_members} ]"
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
to_return = f"{'Room' if self.room_owner == True else 'Order'} {self.code}"
|
||||||
|
if self.room_owner == True:
|
||||||
|
to_return = f"{to_return} [ members = {self.room_members} ]"
|
||||||
|
return to_return
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Quota:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.items = data['items'] if 'items' in data else []
|
||||||
|
self.variations = data['variations'] if 'variations' in data else []
|
||||||
|
self.available = data['available'] if 'available' in data else False
|
||||||
|
self.size = data['size'] if 'size' in data else 0
|
||||||
|
self.available_number = data['available_number'] if 'available_number' in data else 0
|
||||||
|
|
||||||
|
def has_item (self, id: int=-1, variation: int=None):
|
||||||
|
return id in self.items if not variation else (id in self.items and variation in self.variations)
|
||||||
|
|
||||||
|
def get_left (self):
|
||||||
|
return self.available_number
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'Quota [items={self.items}, variations={self.variations}] [{self.available_number}/{self.size}]'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Quota [items={self.items}, variations={self.variations}] [{self.available_number}/{self.size}]'
|
||||||
|
|
||||||
|
def get_quota(item: int, variation: int = None) -> Quota:
|
||||||
|
ret : Quota = None
|
||||||
|
for q in QUOTA_LIST:
|
||||||
|
if (q.has_item(item, variation)):
|
||||||
|
if(ret == None or (q.size != None and q.size < ret.size)):
|
||||||
|
ret = q
|
||||||
|
return ret
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Quotas:
|
class Quotas:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
|
@ -248,6 +306,21 @@ async def get_quotas(request: Request=None):
|
||||||
|
|
||||||
return Quotas(res)
|
return Quotas(res)
|
||||||
|
|
||||||
|
async def load_item_quotas() -> bool:
|
||||||
|
global QUOTA_LIST
|
||||||
|
QUOTA_LIST = []
|
||||||
|
logger.info ('[QUOTAS] Loading quotas...')
|
||||||
|
success = True
|
||||||
|
try:
|
||||||
|
res = await pretixClient.get('quotas/?order=id&with_availability=true')
|
||||||
|
res = res.json()
|
||||||
|
for quota_data in res['results']:
|
||||||
|
QUOTA_LIST.append (Quota(quota_data))
|
||||||
|
except Exception:
|
||||||
|
logger.warning(f"[QUOTAS] Error while loading quotas.\n{traceback.format_exc()}")
|
||||||
|
success = False
|
||||||
|
return success
|
||||||
|
|
||||||
async def get_order(request: Request=None):
|
async def get_order(request: Request=None):
|
||||||
await request.receive_body()
|
await request.receive_body()
|
||||||
return await request.app.ctx.om.get_order(request=request)
|
return await request.app.ctx.om.get_order(request=request)
|
||||||
|
@ -294,9 +367,24 @@ class OrderManager:
|
||||||
del cache[code]
|
del cache[code]
|
||||||
orderList.remove(code)
|
orderList.remove(code)
|
||||||
|
|
||||||
|
|
||||||
async def fill_cache(self, check_itemsQuestions=False) -> bool:
|
async def fill_cache(self, check_itemsQuestions=False) -> bool:
|
||||||
# Check cache lock
|
# Check cache lock
|
||||||
|
logger.info(f"[CACHE] Lock status: {self.updating.locked()}")
|
||||||
self.updating.acquire()
|
self.updating.acquire()
|
||||||
|
ret = False
|
||||||
|
exp = None
|
||||||
|
try:
|
||||||
|
ret = await self.fill_cache_INTERNAL(check_itemsQuestions=check_itemsQuestions)
|
||||||
|
except Exception as e:
|
||||||
|
exp = e
|
||||||
|
self.updating.release()
|
||||||
|
logger.info(f"[CACHE] Ret status: {ret}. Exp: {exp}")
|
||||||
|
if(exp != None):
|
||||||
|
raise exp
|
||||||
|
return ret
|
||||||
|
|
||||||
|
async def fill_cache_INTERNAL(self, check_itemsQuestions=False) -> bool:
|
||||||
start_time = time()
|
start_time = time()
|
||||||
logger.info("[CACHE] Filling cache...")
|
logger.info("[CACHE] Filling cache...")
|
||||||
# Index item's ids
|
# Index item's ids
|
||||||
|
@ -311,6 +399,12 @@ class OrderManager:
|
||||||
logger.error("[CACHE] Questions were not loading correctly. Aborting filling cache...")
|
logger.error("[CACHE] Questions were not loading correctly. Aborting filling cache...")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Load quotas
|
||||||
|
r = await load_item_quotas()
|
||||||
|
if(not r and check_itemsQuestions):
|
||||||
|
logger.error("[CACHE] Quotas were not loading correctly. Aborting filling cache...")
|
||||||
|
return False
|
||||||
|
|
||||||
cache = {}
|
cache = {}
|
||||||
orderList = []
|
orderList = []
|
||||||
success = True
|
success = True
|
||||||
|
@ -333,8 +427,6 @@ class OrderManager:
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error(f"[CACHE] Error while refreshing cache.\n{traceback.format_exc()}")
|
logger.error(f"[CACHE] Error while refreshing cache.\n{traceback.format_exc()}")
|
||||||
success = False
|
success = False
|
||||||
finally:
|
|
||||||
self.updating.release()
|
|
||||||
|
|
||||||
# Apply new cache if there were no errors
|
# Apply new cache if there were no errors
|
||||||
if(success):
|
if(success):
|
||||||
|
|
|
@ -3,6 +3,7 @@ from sanic import Blueprint, exceptions, response
|
||||||
from ext import *
|
from ext import *
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
from config import ADMINS
|
from config import ADMINS
|
||||||
|
from utils import isSessionAdmin
|
||||||
import json
|
import json
|
||||||
|
|
||||||
bp = Blueprint("karaoke", url_prefix="/manage/karaoke")
|
bp = Blueprint("karaoke", url_prefix="/manage/karaoke")
|
||||||
|
@ -10,7 +11,7 @@ bp = Blueprint("karaoke", url_prefix="/manage/karaoke")
|
||||||
@bp.get("/admin")
|
@bp.get("/admin")
|
||||||
async def show_songs(request, order: Order):
|
async def show_songs(request, order: Order):
|
||||||
|
|
||||||
if not order.isAdmin():
|
if not await isSessionAdmin(request, order):
|
||||||
raise exceptions.Forbidden("Birichino")
|
raise exceptions.Forbidden("Birichino")
|
||||||
|
|
||||||
orders = [x for x in request.app.ctx.om.cache.values() if x.karaoke_songs]
|
orders = [x for x in request.app.ctx.om.cache.values() if x.karaoke_songs]
|
||||||
|
@ -28,7 +29,7 @@ async def show_songs(request, order: Order):
|
||||||
@bp.post("/approve")
|
@bp.post("/approve")
|
||||||
async def approve_songs(request, order: Order):
|
async def approve_songs(request, order: Order):
|
||||||
|
|
||||||
if not order.isAdmin():
|
if not await isSessionAdmin(request, order):
|
||||||
raise exceptions.Forbidden("Birichino")
|
raise exceptions.Forbidden("Birichino")
|
||||||
|
|
||||||
for song in request.form:
|
for song in request.form:
|
||||||
|
@ -44,7 +45,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 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():
|
if not await isSessionAdmin(request, order):
|
||||||
raise exceptions.Forbidden("Birichino")
|
raise exceptions.Forbidden("Birichino")
|
||||||
|
|
||||||
songname = unquote(songname)
|
songname = unquote(songname)
|
||||||
|
|
|
@ -3,15 +3,16 @@ ROOM_ERROR_TYPES = {
|
||||||
'unpaid': "Somebody in your room has not paid for their reservation, yet.",
|
'unpaid': "Somebody in your room has not paid for their reservation, yet.",
|
||||||
'type_mismatch': "A member in your room has a ticket for a different type of room capacity. This happens when users swap their room types with others, without abandoning the room.",
|
'type_mismatch': "A member in your room has a ticket for a different type of room capacity. This happens when users swap their room types with others, without abandoning the room.",
|
||||||
'daily': "Some member in your room has a Daily ticket. These tickets do not include a hotel reservation.",
|
'daily': "Some member in your room has a Daily ticket. These tickets do not include a hotel reservation.",
|
||||||
'capacity_mismatch': "The number of people in your room mismatches your type of ticket."
|
'capacity_mismatch': "The number of people in your room mismatches your type of ticket.",
|
||||||
|
'canceled': "Someone in your room canceled his booking and it was removed from your room."
|
||||||
}
|
}
|
||||||
|
|
||||||
EMAILS_TEXT = {
|
EMAILS_TEXT = {
|
||||||
"ROOM_UNCONFIRM_TITLE": "Your room got unconfirmed",
|
"ROOM_UNCONFIRM_TITLE": "Your room got unconfirmed",
|
||||||
"ROOM_UNCONFIRM_TEXT": {
|
"ROOM_UNCONFIRM_TEXT": {
|
||||||
'html': "Hello <b>{0}</b><br>We had to <b>unconfirm</b> your room <i>'{1}'</i> due to the following issues:<br></p>{2}<br><p>Please contact your room's owner or contact our support for further informations at <a href=\"https://furizon.net/contact/\"> https://furizon.net/contact/</a>.<br>Thank you.<br><br><a class=\"link\" style=\"background-color: #1095c1; color: #fff;\" href=\"https://reg.furizon.net/manage/welcome\">Manage booking</a>",
|
'html': "Hello <b>{0}</b><br>We had to <b>unconfirm or change</b> your room <i>'{1}'</i> due to the following issues:<br></p>{2}<br><p>Please contact your room's owner or contact our support for further informations at <a href=\"https://furizon.net/contact/\"> https://furizon.net/contact/</a>.<br>Thank you.<br><br><a class=\"link\" style=\"background-color: #1095c1; color: #fff;\" href=\"https://reg.furizon.net/manage/welcome\">Manage booking</a>",
|
||||||
|
|
||||||
'plain': "Hello {0}\nWe had to unconfirm your room '{1}' due to the following issues:\n{2}\nPlease contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you\n\nTo manage your booking: https://reg.furizon.net/manage/welcome"
|
'plain': "Hello {0}\nWe had to unconfirm or change your room '{1}' due to the following issues:\n{2}\nPlease contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you\n\nTo manage your booking: https://reg.furizon.net/manage/welcome"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
18
metrics.py
18
metrics.py
|
@ -1,6 +1,7 @@
|
||||||
from sanic.log import logger, logging
|
from sanic.log import logger, logging
|
||||||
from logging import LogRecord
|
from logging import LogRecord
|
||||||
from config import *
|
from config import *
|
||||||
|
import traceback
|
||||||
|
|
||||||
METRICS_REQ_NO = 0
|
METRICS_REQ_NO = 0
|
||||||
METRICS_ERR_NO = 0 # Errors served to the clients
|
METRICS_ERR_NO = 0 # Errors served to the clients
|
||||||
|
@ -47,7 +48,7 @@ def getMetricsText():
|
||||||
|
|
||||||
def getRoomCountersText(request):
|
def getRoomCountersText(request):
|
||||||
out = []
|
out = []
|
||||||
try :
|
try:
|
||||||
daily = 0
|
daily = 0
|
||||||
counters = {}
|
counters = {}
|
||||||
counters_early = {}
|
counters_early = {}
|
||||||
|
@ -61,11 +62,13 @@ def getRoomCountersText(request):
|
||||||
if(order.daily):
|
if(order.daily):
|
||||||
daily += 1
|
daily += 1
|
||||||
else:
|
else:
|
||||||
counters[order.bed_in_room] += 1
|
# Order.status must reflect the one in the Order() constructor inside ext.py
|
||||||
if(order.has_early):
|
if(order.status in ["pending", "paid"] and hasattr(order, "bed_in_room") and order.bed_in_room in counters):
|
||||||
counters_early[order.bed_in_room] += 1
|
counters[order.bed_in_room] += 1
|
||||||
if(order.has_late):
|
if(order.has_early):
|
||||||
counters_late[order.bed_in_room] += 1
|
counters_early[order.bed_in_room] += 1
|
||||||
|
if(order.has_late):
|
||||||
|
counters_late[order.bed_in_room] += 1
|
||||||
|
|
||||||
for id, count in counters.items():
|
for id, count in counters.items():
|
||||||
out.append(f'webint_order_room_counter{{days="normal", label="{ROOM_TYPE_NAMES[id]}"}} {count}')
|
out.append(f'webint_order_room_counter{{days="normal", label="{ROOM_TYPE_NAMES[id]}"}} {count}')
|
||||||
|
@ -76,7 +79,8 @@ def getRoomCountersText(request):
|
||||||
out.append(f'webint_order_room_counter{{label="Daily"}} {daily}')
|
out.append(f'webint_order_room_counter{{label="Daily"}} {daily}')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(traceback.format_exc())
|
||||||
|
|
||||||
logger.warning("Error in loading metrics rooms")
|
logger.warning("Error in loading metrics rooms")
|
||||||
return "\n".join(out)
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from PIL import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from hashlib import sha224
|
from hashlib import sha224
|
||||||
from time import time
|
from time import time
|
||||||
|
from utils import isSessionAdmin
|
||||||
import os
|
import os
|
||||||
|
|
||||||
bp = Blueprint("propic", url_prefix="/manage/propic")
|
bp = Blueprint("propic", url_prefix="/manage/propic")
|
||||||
|
@ -38,7 +39,7 @@ async def upload_propic(request, order: Order):
|
||||||
if order.propic_locked:
|
if order.propic_locked:
|
||||||
raise exceptions.BadRequest("You have been limited from further editing the propic.")
|
raise exceptions.BadRequest("You have been limited from further editing the propic.")
|
||||||
|
|
||||||
if request.form.get('submit') != 'Upload' and time() > PROPIC_DEADLINE:
|
if request.form.get('submit') != 'Upload' and (time() > PROPIC_DEADLINE and not await isSessionAdmin(request, order)):
|
||||||
raise exceptions.BadRequest("The deadline has passed. You cannot modify the badges at this moment.")
|
raise exceptions.BadRequest("The deadline has passed. You cannot modify the badges at this moment.")
|
||||||
|
|
||||||
if request.form.get('submit') == 'Delete main image':
|
if request.form.get('submit') == 'Delete main image':
|
||||||
|
|
BIN
res/botbg2.png
BIN
res/botbg2.png
Binary file not shown.
Before Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
Before Width: | Height: | Size: 204 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 19C13 20.1 13.3 21.12 13.81 22H6C4.89 22 4 21.11 4 20V4C4 2.9 4.89 2 6 2H7V9L9.5 7.5L12 9V2H18C19.1 2 20 2.89 20 4V13.09C19.67 13.04 19.34 13 19 13C15.69 13 13 15.69 13 19M20 18V15H18V18H15V20H18V23H20V20H23V18H20Z" /></svg>
|
After Width: | Height: | Size: 297 B |
|
@ -0,0 +1,21 @@
|
||||||
|
function confirmAction (intent, sender) {
|
||||||
|
if (['propicReminder'].includes (intent) == false) return
|
||||||
|
let href = sender.getAttribute('action')
|
||||||
|
let intentTitle = document.querySelector("#intentText")
|
||||||
|
let intentDescription = document.querySelector("#intentDescription")
|
||||||
|
let intentEditPanel = document.querySelector("#intentEditPanel")
|
||||||
|
let intentFormAction = document.querySelector("#intentFormAction")
|
||||||
|
let intentSend = document.querySelector("#intentSend")
|
||||||
|
// Resetting ui
|
||||||
|
intentFormAction.setAttribute('method', 'GET')
|
||||||
|
intentEditPanel.style.display = 'none';
|
||||||
|
intentDescription.innerText = sender.title;
|
||||||
|
intentFormAction.setAttribute('action', href)
|
||||||
|
switch (intent){
|
||||||
|
case 'propicReminder':
|
||||||
|
intentTitle.innerText = "Send missing badge reminders";
|
||||||
|
intentSend.innerText = sender.innerText;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
document.getElementById('modalRoomconfirm').setAttribute('open', 'true');
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
function confirmAction (intent, sender) {
|
function confirmAction (intent, sender) {
|
||||||
if (['rename', 'unconfirm', 'delete'].includes (intent) == false) return
|
if (['rename', 'unconfirm', 'delete'].includes (intent) == false) return
|
||||||
let href = sender.getAttribute('action')
|
let href = sender.getAttribute('action')
|
||||||
let intentTitle = document.querySelector("#intentText")
|
let intentTitle = document.querySelector("#modalOrderEditDialog #intentText")
|
||||||
let intentEdit = document.querySelector("#intentRename")
|
let intentEdit = document.querySelector("#modalOrderEditDialog #intentRename")
|
||||||
let intentEditPanel = document.querySelector("#intentEditPanel")
|
let intentEditPanel = document.querySelector("#modalOrderEditDialog #intentEditPanel")
|
||||||
let intentFormAction = document.querySelector("#intentFormAction")
|
let intentFormAction = document.querySelector("#intentFormAction")
|
||||||
let intentSend = document.querySelector("#intentSend")
|
let intentSend = document.querySelector("#modalOrderEditDialog #intentSend")
|
||||||
// Resetting ui
|
// Resetting ui
|
||||||
intentEdit.removeAttribute('required')
|
intentEdit.removeAttribute('required')
|
||||||
intentEdit.removeAttribute('minlength')
|
intentEdit.removeAttribute('minlength')
|
||||||
|
@ -27,5 +27,5 @@ function confirmAction (intent, sender) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
document.getElementById('modalRoomconfirm').setAttribute('open', 'true');
|
document.getElementById('modalOrderEditDialog').setAttribute('open', 'true');
|
||||||
}
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
var draggingData = {
|
||||||
|
id: 0,
|
||||||
|
roomTypeId: 0,
|
||||||
|
parentRoomId: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowRedirect = false;
|
||||||
|
|
||||||
|
function initObjects (){
|
||||||
|
draggables = document.querySelectorAll("div.grid.people div.edit-drag");
|
||||||
|
rooms = document.querySelectorAll("main.container>div.room");
|
||||||
|
Array.from(draggables).forEach(element => {
|
||||||
|
element.addEventListener('dragstart', dragStart);
|
||||||
|
element.addEventListener('dragend', dragEnd);
|
||||||
|
});
|
||||||
|
Array.from(rooms).forEach(room => {
|
||||||
|
room.addEventListener('dragenter', dragEnter)
|
||||||
|
room.addEventListener('dragover', dragOver);
|
||||||
|
room.addEventListener('dragleave', dragLeave);
|
||||||
|
room.addEventListener('drop', drop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {DragEvent} e
|
||||||
|
*/
|
||||||
|
function dragStart(e) {
|
||||||
|
element = e.target;
|
||||||
|
room = element.closest('div.room')
|
||||||
|
setData(element.id, element.getAttribute('room-type'), room.id)
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
setTimeout(()=>toggleRoomSelection(true), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragEnd(e) {
|
||||||
|
toggleRoomSelection(false);
|
||||||
|
resetData ();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragEnter(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.target.classList.add('drag-over');
|
||||||
|
checkDragLocation (getData(), e.target);
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragOver(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.target.classList.add('drag-over');
|
||||||
|
checkDragLocation (getData(), e.target)
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Element} target
|
||||||
|
*/
|
||||||
|
function checkDragLocation (data, target) {
|
||||||
|
let toReturn = true;
|
||||||
|
const isInfinite = target.getAttribute("infinite");
|
||||||
|
const maxSizeReached = target.getAttribute("current-size") >= target.getAttribute("room-size");
|
||||||
|
const roomTypeMismatch = data.roomTypeId !== target.getAttribute("room-type");
|
||||||
|
if (!isInfinite && (maxSizeReached || roomTypeMismatch)) {
|
||||||
|
target.classList.add('drag-forbidden');
|
||||||
|
toReturn = false;
|
||||||
|
} else {
|
||||||
|
target.classList.remove('drag-forbidden');
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragLeave(e) {
|
||||||
|
e.target.classList.remove('drag-over');
|
||||||
|
e.target.classList.remove('drag-forbidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function drop(e) {
|
||||||
|
e.target.classList.remove('drag-over');
|
||||||
|
toggleRoomSelection(false);
|
||||||
|
if (checkDragLocation(getData(), e.target) === true) {
|
||||||
|
const data = getData();
|
||||||
|
let item = document.getElementById (data.id)
|
||||||
|
let oldParent = document.getElementById (data.parentRoomId)
|
||||||
|
let newParent = e.target;
|
||||||
|
if (moveToRoom (data.id, data.parentRoomId.replace('room-',''), newParent.id.replace('room-','')) === true) {
|
||||||
|
let newParentContainer = newParent.querySelector('div.grid.people')
|
||||||
|
newParentContainer.appendChild (item);
|
||||||
|
let oldParentQty = parseInt(oldParent.getAttribute("current-size")) - 1;
|
||||||
|
let newParentQty = parseInt(newParent.getAttribute("current-size")) + 1;
|
||||||
|
let newParentCapacity = parseInt(newParent.getAttribute("room-size"));
|
||||||
|
oldParent.setAttribute("current-size", oldParentQty);
|
||||||
|
newParent.setAttribute("current-size", newParentQty);
|
||||||
|
oldParent.classList.remove('complete');
|
||||||
|
if (newParentCapacity == newParentQty) newParent.classList.add('complete');
|
||||||
|
// if owner of room is being moved, assign a new owner
|
||||||
|
if (data.parentRoomId.replace('room-','') == data.id) {
|
||||||
|
// find first owner
|
||||||
|
if (model[data.id][toAdd] && model[data.id][toAdd].length <= 0) return;
|
||||||
|
newOwner = model[data.id][toAdd][0]
|
||||||
|
changeOwner (data.id, newOwner)
|
||||||
|
oldParent.id = "room-" + newOwner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRoomSelection(newStatus) {
|
||||||
|
rooms = document.querySelectorAll("div.room");
|
||||||
|
Array.from(rooms).forEach(room=>{
|
||||||
|
room.classList.toggle('interactless', newStatus);
|
||||||
|
room.classList.remove('drag-over');
|
||||||
|
room.classList.remove('drag-forbidden');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setData (id, roomType, parentRoomId) {
|
||||||
|
draggingData.id = id;
|
||||||
|
draggingData.roomTypeId = roomType;
|
||||||
|
draggingData.parentRoomId = parentRoomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetData (){ setData(0, 0, 0); }
|
||||||
|
|
||||||
|
function getData () { return draggingData; }
|
||||||
|
|
||||||
|
// This default onbeforeunload event
|
||||||
|
window.onbeforeunload = function(){
|
||||||
|
if (!allowRedirect) return "Any changes to the rooms will be discarded."
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Model managing */
|
||||||
|
|
||||||
|
var model = saveData;
|
||||||
|
|
||||||
|
const toAdd = "to_add";
|
||||||
|
|
||||||
|
function moveToRoom (order, from, to){
|
||||||
|
if (!model) { console.error("Model is null", order, from, to); return false; }
|
||||||
|
if (!model[from]) { console.error("Parent is null", order, from, to); return false; }
|
||||||
|
if (!model[to]) { console.error("Destination is null", order, from, to); return false; }
|
||||||
|
if (!model[from][toAdd] || !model[from][toAdd].includes(order)) { console.error("Order is not in parent", order, from, to); return false; }
|
||||||
|
if (!model[to][toAdd]) model[to][toAdd] = [];
|
||||||
|
// Delete order from the original room
|
||||||
|
model[from][toAdd] = model[from][toAdd].filter (itm=> itm !== order)
|
||||||
|
// Add it to the destination room
|
||||||
|
model[to][toAdd].push (order);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeOwner (from, to){
|
||||||
|
if (!model) { console.error("Model is null", from, to); return false; }
|
||||||
|
if (!model[from]) { console.error("Parent is null", from, to); return false; }
|
||||||
|
if (model[to]) { console.error("Destination already exist", from, to); return false; }
|
||||||
|
model[to] = {...model[from]}
|
||||||
|
delete model[from]
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSave (){
|
||||||
|
if (model['infinite'] && model['infinite'][toAdd] && model['infinite'][toAdd].length > 0) {
|
||||||
|
setTimeout(()=>{
|
||||||
|
let roomItem = document.querySelector("#room-infinite");
|
||||||
|
roomItem.scrollIntoView();
|
||||||
|
roomItem.classList.add('drag-forbidden');
|
||||||
|
setTimeout(()=>roomItem.classList.remove('drag-forbidden'), 3000);
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
document.getElementById('modalConfirmDialog').setAttribute('open', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Element} element
|
||||||
|
*/
|
||||||
|
function submitData (element){
|
||||||
|
if (element.ariaDisabled) return;
|
||||||
|
element.ariaDisabled = true;
|
||||||
|
element.setAttribute("aria-busy", true);
|
||||||
|
document.querySelector("#modalClose").setAttribute("disabled", true);
|
||||||
|
document.querySelector("#modalClose").style.display = 'none';
|
||||||
|
// Create request
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/manage/admin/room/wizard/submit', true);
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
let popoverText = document.querySelector("#popover-status-text");
|
||||||
|
let popoverStatus = document.querySelector("#popover-status");
|
||||||
|
popoverStatus.classList.remove('status-error');
|
||||||
|
popoverStatus.classList.remove('status-success');
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
// Handle correct redirect
|
||||||
|
popoverText.innerText = "Changes applied successfully. Redirecting..."
|
||||||
|
popoverStatus.classList.add('status-success');
|
||||||
|
} else {
|
||||||
|
// Handle errors
|
||||||
|
let error = xhr.statusText;
|
||||||
|
popoverText.innerText = "Could not apply changes: " + error;
|
||||||
|
console.error('Error submitting data:', error);
|
||||||
|
popoverStatus.classList.add('status-error');
|
||||||
|
}
|
||||||
|
popoverStatus.showPopover();
|
||||||
|
allowRedirect = true;
|
||||||
|
setTimeout(()=>window.location.assign('/manage/admin'), 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send(JSON.stringify(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
initObjects ();
|
|
@ -3,6 +3,18 @@ div.room-actions {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.admin-actions-header {
|
||||||
|
container-name: room-actions;
|
||||||
|
float: unset !important;
|
||||||
|
max-height: 2rem;
|
||||||
|
margin: 1rem 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.admin-actions-header img {
|
||||||
|
max-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
div.room-actions > a {
|
div.room-actions > a {
|
||||||
background-color: var(--card-background-color);
|
background-color: var(--card-background-color);
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
|
@ -17,3 +29,25 @@ div.room-actions > a:hover {
|
||||||
div.room-actions > a.act-del:hover {
|
div.room-actions > a.act-del:hover {
|
||||||
background-color: var(--del-color);
|
background-color: var(--del-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Spinning animation */
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform:rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform:rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3:has(.spin) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
animation-name: spin;
|
||||||
|
animation-duration: 500ms;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
max-height: 32px;
|
||||||
|
}
|
|
@ -27,6 +27,14 @@ summary:has(span.status) {
|
||||||
100% { background-position:57.75% 0%; }
|
100% { background-position:57.75% 0%; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Popover */
|
||||||
|
*[popover]:popover-open {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid #fff;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Dark theme */
|
/* Dark theme */
|
||||||
@media only screen and (prefers-color-scheme: dark) {
|
@media only screen and (prefers-color-scheme: dark) {
|
||||||
.icon {filter: invert(1);}
|
.icon {filter: invert(1);}
|
||||||
|
|
|
@ -14,7 +14,8 @@ nav#topbar {
|
||||||
top: 0rem;
|
top: 0rem;
|
||||||
transition: top 300ms;
|
transition: top 300ms;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
max-width:98vw;
|
max-width:100%;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav#topbar a {
|
nav#topbar a {
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
div.grid.people div.edit-disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
filter: grayscale(1);
|
||||||
|
user-select: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.grid.people div.edit-drag>div.propic-container {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.drag-over {
|
||||||
|
border-color: #000;
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.drag-forbidden {
|
||||||
|
border-color: #c92121aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.interactless > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.room.complete {
|
||||||
|
border-color: #21c929aa;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.room {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.room > h4 {
|
||||||
|
user-select: none;
|
||||||
|
margin-left: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.room:nth-child(2n) {
|
||||||
|
background-color: #ffffff55;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.room:nth-child(2n) {
|
||||||
|
background-color: #cccccc55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
background-color: #2e9147aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-error {
|
||||||
|
background-color: #912e2eaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme */
|
||||||
|
@media only screen and (prefers-color-scheme: dark) {
|
||||||
|
div.drag-over {
|
||||||
|
border-color: #fff;
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.drag-forbidden {
|
||||||
|
border-color: #c92121aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.room {
|
||||||
|
background-color: #16161655;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.room:nth-child(2n) {
|
||||||
|
background-color: #20202055;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
45
room.py
45
room.py
|
@ -5,9 +5,19 @@ from ext import *
|
||||||
from config import headers
|
from config import headers
|
||||||
import os
|
import os
|
||||||
from image_util import generate_room_preview, get_room
|
from image_util import generate_room_preview, get_room
|
||||||
|
from utils import confirm_room_by_order
|
||||||
|
from time import time
|
||||||
|
|
||||||
bp = Blueprint("room", url_prefix="/manage/room")
|
bp = Blueprint("room", url_prefix="/manage/room")
|
||||||
|
|
||||||
|
@bp.middleware
|
||||||
|
async def deadline_check(request: Request):
|
||||||
|
order = await get_order(request)
|
||||||
|
if not order:
|
||||||
|
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
|
||||||
|
if time() > ROOM_DEADLINE and not await isSessionAdmin(request, order):
|
||||||
|
raise exceptions.BadRequest("The deadline has passed. You cannot modify the room at this moment.")
|
||||||
|
|
||||||
@bp.post("/create")
|
@bp.post("/create")
|
||||||
async def room_create_post(request, order: Order):
|
async def room_create_post(request, order: Order):
|
||||||
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
|
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
|
||||||
|
@ -303,40 +313,7 @@ async def confirm_room(request, order: Order, quotas: Quotas):
|
||||||
#if quotas.get_left(len(order.room_members)) == 0:
|
#if quotas.get_left(len(order.room_members)) == 0:
|
||||||
# raise exceptions.BadRequest("There are no more rooms of this size to reserve.")
|
# 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
|
await confirm_room_by_order(order, request)
|
||||||
room_members = []
|
|
||||||
for m in order.room_members:
|
|
||||||
if m == order.code:
|
|
||||||
res = order
|
|
||||||
else:
|
|
||||||
res = await request.app.ctx.om.get_order(code=m)
|
|
||||||
|
|
||||||
if res.room_id != order.code:
|
|
||||||
raise exceptions.BadRequest("Please contact support: some of the members in your room are actually somewhere else")
|
|
||||||
|
|
||||||
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)
|
|
||||||
await rm.edit_answer('room_confirmed', "True")
|
|
||||||
await rm.edit_answer('pending_roommates', None)
|
|
||||||
await rm.edit_answer('pending_room', None)
|
|
||||||
|
|
||||||
for rm in room_members:
|
|
||||||
await rm.send_answers()
|
|
||||||
|
|
||||||
return redirect('/manage/welcome')
|
return redirect('/manage/welcome')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# python merda
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def a():
|
||||||
|
print("a")
|
||||||
|
|
||||||
|
def b():
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
print(loop)
|
||||||
|
|
||||||
|
b()
|
|
@ -2,6 +2,7 @@
|
||||||
{% block title %}Admin panel{% endblock %}
|
{% block title %}Admin panel{% endblock %}
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<main class="container">
|
<main class="container">
|
||||||
|
<script src="/res/scripts/adminManager.js"></script>
|
||||||
<header>
|
<header>
|
||||||
<picture>
|
<picture>
|
||||||
<source srcset="/res/furizon.png" media="(prefers-color-scheme:dark)">
|
<source srcset="/res/furizon.png" media="(prefers-color-scheme:dark)">
|
||||||
|
@ -10,11 +11,21 @@
|
||||||
</header>
|
</header>
|
||||||
<!-- Quick controls -->
|
<!-- Quick controls -->
|
||||||
<h2>Admin panel</h2>
|
<h2>Admin panel</h2>
|
||||||
|
<p>Data</p>
|
||||||
<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/admin/cache/clear" role="button" title="Reload the orders' data and the re-sync items' indexes from pretix">Clear cache</a>
|
||||||
|
<a download href="/manage/admin/export/export" role="button" title="Will export most of informations about the orders">Export CSV</a>
|
||||||
|
<a download href="/manage/admin/export/hotel" role="button" title="Will export a CSV for the hotel accomodation">Export hotel CSV</a>
|
||||||
|
<hr>
|
||||||
|
<p>Rooms</p>
|
||||||
<a href="/manage/nosecount" role="button" title="Shortcut to the nosecount's admin data">Manage rooms</a>
|
<a href="/manage/nosecount" role="button" title="Shortcut to the nosecount's admin data">Manage rooms</a>
|
||||||
<a href="/manage/admin/room/verify" role="button" title="Will unconfirm rooms that fail the default check. Useful when editing answers from Pretix">Verify Rooms</a>
|
<a href="/manage/admin/room/verify" role="button" title="Will unconfirm rooms that fail the default check. Useful when editing answers from Pretix">Verify Rooms</a>
|
||||||
<a href="/manage/admin/propic/remind" role="button" title="Will remind via mail all people who event uploaded a badge to do it">Remind badge upload</a>
|
<a href="/manage/admin/room/wizard" role="button" title="Auto fill unconfirmed rooms. You can review and edit matches before confirming.">Fill Rooms</a>
|
||||||
<hr>
|
<hr>
|
||||||
|
<p>Profiles</p>
|
||||||
|
<a href="#" onclick="confirmAction('propicReminder', this)" role="button" title="Will email all people who haven't uploaded a badge, yet" action="/manage/admin/propic/remind">Remind badge upload</a>
|
||||||
|
<a href="/manage/admin/room/autoconfirm" role="button" title="Will confirm all the full rooms that are still unconfirmed">Auto-confirm Rooms</a>
|
||||||
|
<hr>
|
||||||
|
{% include 'components/confirm_action_modal.html' %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if time() > PROPIC_DEADLINE %}
|
{% if time() > PROPIC_DEADLINE and not isSessionAdmin %}
|
||||||
<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>
|
<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 %}
|
{% else %}
|
||||||
<p><em>
|
<p><em>
|
||||||
|
@ -43,9 +43,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="grid grid_2x2">
|
<div class="grid grid_2x2">
|
||||||
<input style="grid-area: 1 / 1 / 2 / 3;" type="submit" name="submit" value="Upload" {{'disabled' if (order.ans('propic') and order.ans('propic_fursuiter')) else ''}} />
|
<input style="grid-area: 1 / 1 / 2 / 3;" type="submit" name="submit" value="Upload" {{'disabled' if ((order.ans('propic') and order.ans('propic_fursuiter'))) or (time() > PROPIC_DEADLINE and not isSessionAdmin) else ''}} />
|
||||||
<input style="grid-area: 2 / 1 / 3 / 2;" type="submit" name="submit" value="Delete main image" {{'disabled' if (time() > PROPIC_DEADLINE or not order.ans('propic')) else ''}} />
|
<input style="grid-area: 2 / 1 / 3 / 2;" type="submit" name="submit" value="Delete main image" {{'disabled' if ((time() > PROPIC_DEADLINE and not isSessionAdmin) or not order.ans('propic')) else ''}} />
|
||||||
<input style="grid-area: 2 / 2 / 3 / 3;" type="submit" name="submit" value="Delete fursuit image" {{'disabled' if (time() > PROPIC_DEADLINE or not order.ans('propic_fursuiter')) else ''}} />
|
<input style="grid-area: 2 / 2 / 3 / 3;" type="submit" name="submit" value="Delete fursuit image" {{'disabled' if ((time() > PROPIC_DEADLINE and not isSessionAdmin) or not order.ans('propic_fursuiter')) else ''}} />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<div aria-hidden="true" class="propic-border-animation"></div>
|
<div aria-hidden="true" class="propic-border-animation"></div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if current and current.isAdmin () and (not current.code == order.code )%}
|
{% if current and current.isAdmin () and (not current.code == order.code ) and not nologin %}
|
||||||
<a class="control-login-as" href="/manage/admin/loginas/{{order.code}}">
|
<a class="control-login-as" href="/manage/admin/loginas/{{order.code}}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<img alt="{{order.ans('fursona_name') if order.ans('fursona_name') else 'A user'}}'s profile picture" src="{{imgSrc}}" class="absolute propic {{(('propic-' + order.sponsorship) if not effects else '') if order.sponsorship else 'propic-base'}}"/>
|
<img alt="{{order.ans('fursona_name') if order.ans('fursona_name') else 'A user'}}'s profile picture" src="{{imgSrc}}" class="absolute propic {{(('propic-' + order.sponsorship) if not effects else '') if order.sponsorship else 'propic-base'}}"/>
|
||||||
|
|
|
@ -8,15 +8,22 @@
|
||||||
<p class="notice" style="background:#0881c0"><b><a href="/manage/nosecount?filter=capacity">Check here</a> for any fur who share your room type.</p>
|
<p class="notice" style="background:#0881c0"><b><a href="/manage/nosecount?filter=capacity">Check here</a> for any fur who share your room type.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Show alert if room owner has wrong people inside #}
|
{% if time() > ROOM_DEADLINE %}
|
||||||
|
<p class="notice">⚠️ The deadline to edit your room has passed. If your room is not full it will be subject to changes by the staff as we optimize for hotel capacity.</p>
|
||||||
|
|
||||||
{# {% if room_members and quota.get_left(len(room_members)) == 0 and (not order.room_confirmed) %} #}
|
{% else %}
|
||||||
{# <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 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 %} #}
|
||||||
|
|
||||||
|
{# Show alert if room was not confirmed #}
|
||||||
|
{% if order.room_id and not order.room_confirmed %}
|
||||||
|
<p class="notice">⚠️ Your room hasn't been confirmed yet. Unconfirmed rooms are subject to changes by the staff as we optimize for hotel capacity.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Show alert if room was not confirmed #}
|
|
||||||
{% if order.room_id and not order.room_confirmed %}
|
|
||||||
<p class="notice">⚠️ Your room hasn't been confirmed yet. Unconfirmed rooms are subject to changes by the staff as we optimize for hotel capacity.</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Show notice if the room is confirmed #}
|
{# Show notice if the room is confirmed #}
|
||||||
|
@ -40,7 +47,7 @@
|
||||||
{% if person.status == 'pending' %}
|
{% if person.status == 'pending' %}
|
||||||
<p><strong style="color:red;">UNPAID</strong></p>
|
<p><strong style="color:red;">UNPAID</strong></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if order.room_owner and person.code != order.code and (not order.room_confirmed) %}<a href="/manage/room/kick/{{person.code}}">KICK</a>{% endif %}
|
{% if order.room_owner and person.code != order.code and (not order.room_confirmed) and (time() <= ROOM_DEADLINE or isSessionAdmin) %}<a href="/manage/room/kick/{{person.code}}">KICK</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if person.status != 'paid' %}
|
{% if person.status != 'paid' %}
|
||||||
|
@ -51,7 +58,7 @@
|
||||||
{# {% 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 %}
|
{% if order.room_id == order.code and not order.room_confirmed and len(room_members) < order.room_person_no %}
|
||||||
<div>
|
<div>
|
||||||
<a href="javascript:document.getElementById('modal-roominvite').setAttribute('open', 'true');">
|
<a {% if time() <= ROOM_DEADLINE or isSessionAdmin %} href="javascript:document.getElementById('modal-roominvite').setAttribute('open', 'true');" {% else %} disabled {% endif %}>
|
||||||
<div class="propic-container">
|
<div class="propic-container">
|
||||||
<img class="propic" src="/res/new.png" />
|
<img class="propic" src="/res/new.png" />
|
||||||
<h3>Invite</h3>
|
<h3>Invite</h3>
|
||||||
|
@ -63,13 +70,13 @@
|
||||||
</div>
|
</div>
|
||||||
{% elif order.pending_room %}
|
{% elif order.pending_room %}
|
||||||
<p>You have have asked to join the room of another member. Wait for them to confirm or reject your request.</p>
|
<p>You have have asked to join the room of another member. Wait for them to confirm or reject your request.</p>
|
||||||
<a role="button" href="/manage/room/cancel_request">Cancel pending join request</a>
|
<a role="button" href="/manage/room/cancel_request" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}}>Cancel pending join request</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="notice">🎲 If you don't join a room or create your one within the room deadline, we will randomly put you into a room with free spots.</p>
|
<p class="notice">🎲 If you don't join a room or create your one within the room deadline, we will randomly put you into a room with free spots.</p>
|
||||||
<p>To join a room, ask somebody to send you their room code.</p>
|
<p>To join a room, ask somebody to send you their room code.</p>
|
||||||
<p class="grid">
|
<p class="grid">
|
||||||
<a role="button" href="/manage/room/create">Create a room</a>
|
<a role="button" href="/manage/room/create" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}}>Create a room</a>
|
||||||
<a role="button" href="javascript:document.getElementById('modal-joinroom').setAttribute('open', 'true');">Join a room</a>
|
<a role="button" href="javascript:document.getElementById('modal-joinroom').setAttribute('open', 'true');" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}}>Join a room</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -83,17 +90,17 @@
|
||||||
{% if order.room_owner %}
|
{% if order.room_owner %}
|
||||||
|
|
||||||
{% if not order.room_confirmed %}
|
{% if not order.room_confirmed %}
|
||||||
{# <a role="button" {% if not room.forbidden and quota.get_left(len(room_members)) > 0 %}href="javascript:document.getElementById('modal-roomconfirm').setAttribute('open', 'true');"{% endif %}>Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a> #}
|
{# <a role="button" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}} {% 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 style="grid-area: 1 / 1 / 2 / 2;" role="button" href="javascript:document.getElementById('modal-roomrename').setAttribute('open', 'true');">Rename room</a>
|
<a style="grid-area: 1 / 1 / 2 / 2;" role="button" href="javascript:document.getElementById('modal-roomrename').setAttribute('open', 'true');" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}}>Rename room</a>
|
||||||
<a style="grid-area: 1 / 2 / 2 / 3;" href="/manage/room/delete" role="button" {{'disabled' if (len(room_members) > 1) else ''}} >Delete room</a>
|
<a style="grid-area: 1 / 2 / 2 / 3;" href="/manage/room/delete" role="button" {{'disabled' if (len(room_members) > 1) or (time() > ROOM_DEADLINE and not isSessionAdmin) else ''}} >Delete room</a>
|
||||||
<a style="grid-area: 2 / 1 / 3 / 3; display:block;" 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 style="grid-area: 2 / 1 / 3 / 3; display:block;" role="button" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}} {% 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>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# <a style="grid-area: 1 / 1 / 2 / 2;" role="button" href="javascript:navigator.share({title: 'Furizon room', text:'Viewing room {{order.room_name}}', url: `${window.location.protocol}//${window.location.host}/manage/room/view/{{order.code}}}`});">Share</a> #}
|
{# <a style="grid-area: 1 / 1 / 2 / 2;" role="button" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}} href="javascript:navigator.share({title: 'Furizon room', text:'Viewing room {{order.room_name}}', url: `${window.location.protocol}//${window.location.host}/manage/room/view/{{order.code}}}`});">Share</a> #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if order.room_id and not order.room_confirmed %}
|
{% if order.room_id and not order.room_confirmed %}
|
||||||
<a href="/manage/room/leave" role="button">Leave room</a>
|
<a href="/manage/room/leave" role="button" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}}>Leave room</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
@ -111,8 +118,8 @@
|
||||||
<td><strong style="color:red;">UNPAID</strong></td>
|
<td><strong style="color:red;">UNPAID</strong></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if order.room_owner %}
|
{% 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/approve/{{person.code}}" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}}>Approve</a></td>
|
||||||
<td style="width:1%;white-space: nowrap;"><a role="button" href="/manage/room/reject/{{person.code}}">Reject</a></td>
|
<td style="width:1%;white-space: nowrap;"><a role="button" href="/manage/room/reject/{{person.code}}" {{'disabled' if time() > ROOM_DEADLINE and not isSessionAdmin else ''}}>Reject</a></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<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 action</h3>
|
||||||
|
<p id="intentDescription"></p>
|
||||||
|
<div id="intentEditPanel"></div>
|
||||||
|
<footer>
|
||||||
|
<input id="intentSend" type="submit" value="Confirm" />
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
</dialog>
|
||||||
|
</form>
|
|
@ -19,7 +19,9 @@
|
||||||
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
|
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
|
||||||
</picture>
|
</picture>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<p>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>
|
<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>
|
||||||
|
|
||||||
{% if filtered and order %}
|
{% if filtered and order %}
|
||||||
{% for person in filtered.values() %}
|
{% for person in filtered.values() %}
|
||||||
{% if loop.first %}
|
{% if loop.first %}
|
||||||
|
@ -135,7 +137,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<form id="intentFormAction" method="GET" action="">
|
<form id="intentFormAction" method="GET" action="">
|
||||||
<dialog id="modalRoomconfirm">
|
<dialog id="modalOrderEditDialog">
|
||||||
<article>
|
<article>
|
||||||
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
|
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
|
||||||
<h3 id="intentText">Confirm room edit</h3>
|
<h3 id="intentText">Confirm room edit</h3>
|
||||||
|
@ -150,6 +152,5 @@
|
||||||
</article>
|
</article>
|
||||||
</dialog>
|
</dialog>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -88,8 +88,10 @@
|
||||||
|
|
||||||
<details id="shuttle">
|
<details id="shuttle">
|
||||||
<summary role="button"><img src="/res/icons/bus.svg" class="icon" />Shuttle</summary>
|
<summary role="button"><img src="/res/icons/bus.svg" class="icon" />Shuttle</summary>
|
||||||
<p>This year, a shuttle service operated by the tourism company of Val di Fiemme will be available. The shuttle service will consist of a bus serving the convention, with scheduled stops at major airports and train stations. More informations <a href="https://furizon.net/furizon-overlord/furizon-overlord-shuttle-bus/">in the dedicated page.</a></p>
|
<p><b>Due to the low number of requests, the shuttle service managed by Trentino Trasporti will not be available. Those who have purchased a bus ticket will be refunded directly by the transport company</b></p>
|
||||||
<p style="text-align:right;"><a href="{{LOCALES['shuttle_link_url'][locale]}}" target="_blank" role="button">Book now!</a></p>
|
<p>On the Furizon Telegram group, there is an active topic dedicated to car sharing, and the staff is available to look for custom alternative solutions. We apologize for the inconvenience.</p>
|
||||||
|
<!--p>This year, a shuttle service operated by the tourism company of Val di Fiemme will be available. The shuttle service will consist of a bus serving the convention, with scheduled stops at major airports and train stations. More informations <a href="https://furizon.net/furizon-overlord/furizon-overlord-shuttle-bus/">in the dedicated page.</a></p>
|
||||||
|
<p style="text-align:right;"><a href="{{LOCALES['shuttle_link_url'][locale]}}" target="_blank" role="button">Book now!</a></p-->
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details id="barcard">
|
<details id="barcard">
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Room Wizard{% endblock %}
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="/res/styles/wizard.css">
|
||||||
|
{% endblock %}
|
||||||
|
{% block main %}
|
||||||
|
<main class="container">
|
||||||
|
<script src="/res/scripts/wizardManager.js" type="text/javascript" defer="defer"></script>
|
||||||
|
<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>
|
||||||
|
<!--order = current order login
|
||||||
|
unconfirmed_orders = all non confirmed rooms orders
|
||||||
|
all_orders = all orders
|
||||||
|
data = assigned rooms -->
|
||||||
|
<h2>Review rooms <a href="#popover-empty-room-tip" onclick="document.querySelector('#popover-wizard-tip').showPopover()">?</a></h2>
|
||||||
|
<div popover id="popover-wizard-tip">This is the preview page. Re-arrange users by dragging and dropping them in the rooms.<br>Once finished, scroll down to either <i>'Confirm'</i> changes or <i>'Undo'</i> them.</div>
|
||||||
|
<hr>
|
||||||
|
{% for room in data.items() %}
|
||||||
|
{% if room[0] in all_orders %}
|
||||||
|
{%with room_order = unconfirmed_orders[room[0]] %}
|
||||||
|
<div class="room" id="room-{{room_order.code}}" room-type="{{room_order.bed_in_room}}" room-size="{{room_order.room_person_no - len(room_order.room_members)}}" current-size="{{len(room[1]['to_add'])}}">
|
||||||
|
<h4 style="margin-top:1em;">
|
||||||
|
<span>{{room_order.room_name if room_order.room_name else room[1]['room_name'] if room[1] and room[1]['room_name'] else ''}} - {{room_order.room_person_no}} People max</span>
|
||||||
|
</h4>
|
||||||
|
<div class="grid people" style="padding-bottom:1em;">
|
||||||
|
{% for m in room_order.room_members %}
|
||||||
|
{% if m in all_orders %}
|
||||||
|
{% with person = all_orders[m] %}
|
||||||
|
<div class="edit-disabled" style="margin-bottom: 1em;">
|
||||||
|
{% with current=None, order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||||
|
{% include 'blocks/propic.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
<h5>{{person.ans('fursona_name')}}</h5>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% for m in room[1]['to_add'] %}
|
||||||
|
{% if m in unconfirmed_orders %}
|
||||||
|
{% with person = unconfirmed_orders[m] %}
|
||||||
|
<div class="edit-drag" id="{{person.code}}" room-type="{{person.bed_in_room}}" style="margin-bottom: 1em;" draggable="true">
|
||||||
|
{% with current=None, order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
|
||||||
|
{% include 'blocks/propic.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
<h5>{{person.ans('fursona_name')}}</h5>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="room" infinite="true" id="room-infinite" room-size="999999999" current-size="0">
|
||||||
|
<h4 style="margin-top:1em;">Empty room <a href="#popover-empty-room-tip" onclick="document.querySelector('#popover-empty-room-tip').showPopover()">?</a></button></h4>
|
||||||
|
<div popover id="popover-empty-room-tip">This is a placeholder room. Place users temporarily in order to free space and arrange rooms</div>
|
||||||
|
<div class="grid people" style="padding-bottom:1em;"></div>
|
||||||
|
</div>
|
||||||
|
<a href="/manage/admin" role="button" title="Discard all changes and go back to the admin page">Undo</a>
|
||||||
|
<a href="#" class="align-right" onclick="onSave()" role="button" title="Will try saving current changes.">Confirm changes</a>
|
||||||
|
|
||||||
|
<dialog id="modalConfirmDialog">
|
||||||
|
<article>
|
||||||
|
<a href="#close" id="modalClose" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
|
||||||
|
<h3 id="intentText">Confirm arrangement?</h3>
|
||||||
|
<p id="intentDescription">
|
||||||
|
Roomless guests will be moved around existing rooms and newly generated ones.<br>
|
||||||
|
This will also confirm all rooms.
|
||||||
|
</p>
|
||||||
|
<div popover id="popover-status"><span id="popover-status-text"></span></div>
|
||||||
|
<footer>
|
||||||
|
<button id="intentSend" onclick="submitData(this)">Confirm</button>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
let saveData = JSON.parse('{{jsondata|safe}}');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{% endblock %}
|
136
utils.py
136
utils.py
|
@ -29,7 +29,6 @@ QUESTION_TYPES = { #https://docs.pretix.eu/en/latest/api/resources/questions.htm
|
||||||
}
|
}
|
||||||
TYPE_OF_QUESTIONS = {} # maps questionId -> type
|
TYPE_OF_QUESTIONS = {} # maps questionId -> type
|
||||||
|
|
||||||
|
|
||||||
async def load_questions() -> bool:
|
async def load_questions() -> bool:
|
||||||
global TYPE_OF_QUESTIONS
|
global TYPE_OF_QUESTIONS
|
||||||
# TYPE_OF_QUESTIONS.clear() It should not be needed
|
# TYPE_OF_QUESTIONS.clear() It should not be needed
|
||||||
|
@ -84,7 +83,7 @@ async def load_items() -> bool:
|
||||||
ROOM_TYPE_NAMES[v['id']] = roomName
|
ROOM_TYPE_NAMES[v['id']] = roomName
|
||||||
# Adds itself to the category list
|
# Adds itself to the category list
|
||||||
categoryName = check_and_get_category ('item', q)
|
categoryName = check_and_get_category ('item', q)
|
||||||
if not categoryName: continue
|
if not categoryName or q['id'] in CATEGORIES_LIST_MAP[categoryName]: continue
|
||||||
CATEGORIES_LIST_MAP[categoryName].append(q['id'])
|
CATEGORIES_LIST_MAP[categoryName].append(q['id'])
|
||||||
if (EXTRA_PRINTS):
|
if (EXTRA_PRINTS):
|
||||||
logger.debug(f'Mapped Items: %s', ITEMS_ID_MAP)
|
logger.debug(f'Mapped Items: %s', ITEMS_ID_MAP)
|
||||||
|
@ -149,7 +148,43 @@ async def get_order_by_code(request, code, throwException=False):
|
||||||
async def get_people_in_room_by_code(request, code, om=None):
|
async def get_people_in_room_by_code(request, code, om=None):
|
||||||
if not om: om = request.app.ctx.om
|
if not om: om = request.app.ctx.om
|
||||||
await om.update_cache()
|
await om.update_cache()
|
||||||
return filter(lambda rm: rm.room_id == code, om.cache.values())
|
return list(filter(lambda rm: rm.room_id == code, om.cache.values()))
|
||||||
|
|
||||||
|
async def confirm_room_by_order(order, request):
|
||||||
|
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:
|
||||||
|
res = order
|
||||||
|
else:
|
||||||
|
res = await request.app.ctx.om.get_order(code=m)
|
||||||
|
|
||||||
|
if res.room_id != order.code:
|
||||||
|
raise exceptions.BadRequest("Please contact support: some of the members in your room are actually somewhere else")
|
||||||
|
|
||||||
|
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:
|
||||||
|
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)
|
||||||
|
await rm.edit_answer('room_confirmed', "True")
|
||||||
|
await rm.edit_answer('pending_roommates', None)
|
||||||
|
await rm.edit_answer('pending_room', None)
|
||||||
|
|
||||||
|
for rm in room_members:
|
||||||
|
await rm.send_answers()
|
||||||
|
|
||||||
async def unconfirm_room_by_order(order, room_members=None, throw=True, request=None, om=None):
|
async def unconfirm_room_by_order(order, room_members=None, throw=True, request=None, om=None):
|
||||||
if not om: om = request.app.ctx.om
|
if not om: om = request.app.ctx.om
|
||||||
|
@ -164,6 +199,17 @@ async def unconfirm_room_by_order(order, room_members=None, throw=True, request=
|
||||||
await p.edit_answer('room_confirmed', "False")
|
await p.edit_answer('room_confirmed', "False")
|
||||||
await p.send_answers()
|
await p.send_answers()
|
||||||
|
|
||||||
|
async def remove_members_from_room(order, removeMembers):
|
||||||
|
didSomething = False
|
||||||
|
for member in removeMembers:
|
||||||
|
if (member in order.room_members):
|
||||||
|
order.room_members.remove(member)
|
||||||
|
didSomething = True
|
||||||
|
if(didSomething):
|
||||||
|
await order.edit_answer("room_members", ','.join(order.room_members))
|
||||||
|
await order.send_answers()
|
||||||
|
return didSomething
|
||||||
|
|
||||||
async def validate_rooms(request, rooms, om):
|
async def validate_rooms(request, rooms, om):
|
||||||
logger.info('Validating rooms...')
|
logger.info('Validating rooms...')
|
||||||
if not om: om = request.app.ctx.om
|
if not om: om = request.app.ctx.om
|
||||||
|
@ -171,6 +217,7 @@ async def validate_rooms(request, rooms, om):
|
||||||
# rooms_to_unconfirm is the room that MUST be unconfirmed, room_with_errors is a less strict set containing all rooms with kind-ish errors
|
# rooms_to_unconfirm is the room that MUST be unconfirmed, room_with_errors is a less strict set containing all rooms with kind-ish errors
|
||||||
rooms_to_unconfirm = []
|
rooms_to_unconfirm = []
|
||||||
room_with_errors = []
|
room_with_errors = []
|
||||||
|
remove_members = []
|
||||||
|
|
||||||
# Validate rooms
|
# Validate rooms
|
||||||
for order in rooms:
|
for order in rooms:
|
||||||
|
@ -195,40 +242,71 @@ async def validate_rooms(request, rooms, om):
|
||||||
# Get confirmed rooms that fail validation
|
# Get confirmed rooms that fail validation
|
||||||
failed_confirmed_rooms = list(filter(lambda fr: (fr[0].room_confirmed == True), rooms_to_unconfirm))
|
failed_confirmed_rooms = list(filter(lambda fr: (fr[0].room_confirmed == True), rooms_to_unconfirm))
|
||||||
|
|
||||||
|
didSomething = False
|
||||||
|
|
||||||
if len(failed_confirmed_rooms) == 0:
|
if len(failed_confirmed_rooms) == 0:
|
||||||
logger.info('[ROOM VALIDATION] No rooms to unconfirm.')
|
logger.info('[ROOM VALIDATION] No rooms to unconfirm.')
|
||||||
return
|
else:
|
||||||
|
didSomething = True
|
||||||
|
logger.info(f"[ROOM VALIDATION] Trying to unconfirm {len(failed_confirmed_rooms)} rooms...")
|
||||||
|
|
||||||
logger.info(f"[ROOM VALIDATION] Trying to unconfirm {len(failed_confirmed_rooms)} rooms...")
|
# Try unconfirming them
|
||||||
|
for rtu in failed_confirmed_rooms:
|
||||||
|
order = rtu[0]
|
||||||
|
member_orders = rtu[2]
|
||||||
|
logger.warning(f"[ROOM VALIDATION] [UNCONFIRMING] Unconfirming room {order.code}...")
|
||||||
|
|
||||||
# Try unconfirming them
|
# Unconfirm and email users about the room
|
||||||
for rtu in failed_confirmed_rooms:
|
if UNCONFIRM_ROOMS_ENABLE:
|
||||||
order = rtu[0]
|
await unconfirm_room_by_order(order, member_orders, False, None, om)
|
||||||
member_orders = rtu[2]
|
|
||||||
|
|
||||||
# Unconfirm and email users about the room
|
for r in rooms_to_unconfirm:
|
||||||
await unconfirm_room_by_order(order, member_orders, False, None, om)
|
order = r[0]
|
||||||
|
removeMembers = r[3]
|
||||||
|
if len(removeMembers) > 0:
|
||||||
|
logger.warning(f"[ROOM VALIDATION] [REMOVING] Removing members '{','.join(removeMembers)}' from room {order.code}")
|
||||||
|
|
||||||
logger.info(f"[ROOM VALIDATION] Sending unconfirm notice to room members...")
|
if UNCONFIRM_ROOMS_ENABLE:
|
||||||
sent_count = 0
|
didSomething |= await remove_members_from_room(order, removeMembers)
|
||||||
# Send unconfirm notice via email
|
if(r not in failed_confirmed_rooms): failed_confirmed_rooms.append(r)
|
||||||
for rtu in failed_confirmed_rooms:
|
|
||||||
order = rtu[0]
|
|
||||||
member_orders = rtu[2]
|
|
||||||
try:
|
|
||||||
await send_unconfirm_message(order, member_orders)
|
|
||||||
sent_count += len(member_orders)
|
|
||||||
except Exception as ex:
|
|
||||||
if EXTRA_PRINTS: logger.exception(str(ex))
|
|
||||||
logger.info(f"[ROOM VALIDATION] Sent {sent_count} emails")
|
|
||||||
|
|
||||||
|
|
||||||
|
if(didSomething):
|
||||||
|
logger.info(f"[ROOM VALIDATION] Sending unconfirm notice to room members...")
|
||||||
|
sent_count = 0
|
||||||
|
# Send unconfirm notice via email
|
||||||
|
for rtu in failed_confirmed_rooms:
|
||||||
|
order = rtu[0]
|
||||||
|
member_orders = rtu[2]
|
||||||
|
try:
|
||||||
|
if UNCONFIRM_ROOMS_ENABLE:
|
||||||
|
await send_unconfirm_message(order, member_orders)
|
||||||
|
sent_count += len(member_orders)
|
||||||
|
except Exception as ex:
|
||||||
|
if EXTRA_PRINTS: logger.exception(str(ex))
|
||||||
|
logger.info(f"[ROOM VALIDATION] Sent {sent_count} emails")
|
||||||
|
|
||||||
|
|
||||||
|
# Returns true if the logged used is an admin OR if it's an admin logged as another user
|
||||||
|
async def isSessionAdmin(request, order):
|
||||||
|
if(order.isAdmin()): return True
|
||||||
|
|
||||||
|
orgCode = request.cookies.get("foxo_code_ORG")
|
||||||
|
orgSecret = request.cookies.get("foxo_secret_ORG")
|
||||||
|
if orgCode != None and orgSecret != None:
|
||||||
|
|
||||||
|
user = await request.app.ctx.om.get_order(code=orgCode)
|
||||||
|
if(user == None): return False
|
||||||
|
if(user.secret != orgSecret): raise exceptions.Forbidden("Birichino :)")
|
||||||
|
return user.isAdmin()
|
||||||
|
|
||||||
async def check_room(request, order, om=None):
|
async def check_room(request, order, om=None):
|
||||||
room_errors = []
|
room_errors = []
|
||||||
room_members = []
|
room_members = []
|
||||||
|
remove_members = []
|
||||||
use_cached = request == None
|
use_cached = request == None
|
||||||
if not om: om = request.app.ctx.om
|
if not om: om = request.app.ctx.om
|
||||||
if not order or not order.room_id or order.room_id != order.code: return (order, False, room_members)
|
if not order or not order.room_id or order.room_id != order.code: return (order, False, room_members, remove_members)
|
||||||
|
|
||||||
# This is not needed anymore you buy tickets already
|
# This is not needed anymore you buy tickets already
|
||||||
#if quotas.get_left(len(order.room_members)) == 0:
|
#if quotas.get_left(len(order.room_members)) == 0:
|
||||||
|
@ -247,7 +325,11 @@ async def check_room(request, order, om=None):
|
||||||
room_errors.append((res.code, 'room_id_mismatch'))
|
room_errors.append((res.code, 'room_id_mismatch'))
|
||||||
allOk = False
|
allOk = False
|
||||||
|
|
||||||
if res.status != 'paid':
|
if res.status == 'canceled':
|
||||||
|
room_errors.append((res.code, 'canceled'))
|
||||||
|
remove_members.append(res.code)
|
||||||
|
allOk = False
|
||||||
|
elif res.status != 'paid':
|
||||||
room_errors.append((res.code, 'unpaid'))
|
room_errors.append((res.code, 'unpaid'))
|
||||||
|
|
||||||
if res.bed_in_room != bed_in_room:
|
if res.bed_in_room != bed_in_room:
|
||||||
|
@ -262,9 +344,9 @@ async def check_room(request, order, om=None):
|
||||||
|
|
||||||
room_members.append(res)
|
room_members.append(res)
|
||||||
|
|
||||||
if len(room_members) != order.room_person_no and order.room_person_no != None:
|
if len(room_members) != order.room_person_no and order.room_person_no != None and order.room_person_no >= 0:
|
||||||
room_errors.append((None, 'capacity_mismatch'))
|
room_errors.append((None, 'capacity_mismatch'))
|
||||||
if order.room_confirmed:
|
if order.room_confirmed:
|
||||||
allOk = False
|
allOk = False
|
||||||
order.set_room_errors(room_errors)
|
order.set_room_errors(room_errors)
|
||||||
return (order, allOk, room_members)
|
return (order, allOk, room_members, remove_members)
|
Loading…
Reference in New Issue