diff --git a/app.py b/app.py
index d53b49b..f100edb 100644
--- a/app.py
+++ b/app.py
@@ -139,8 +139,10 @@ async def welcome(request, order: Order, quota: Quotas):
else:
room_members.append(await app.ctx.om.get_order(code=member_id, cached=True))
+ print (order.room_errors)
+
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_MESSAGES))
+ return html(tpl.render(order=order, quota=quota, room_members=room_members, pending_roommates=pending_roommates, ROOM_ERROR_MESSAGES=ROOM_ERROR_TYPES))
@app.route("/manage/download_ticket")
diff --git a/config.example.py b/config.example.py
index c39f559..6034cfa 100644
--- a/config.example.py
+++ b/config.example.py
@@ -117,5 +117,6 @@ TG_CHAT_ID = -1234567
ADMINS = ['XXXXX', 'YYYYY']
SMTP_HOST = 'host'
+SMTP_PORT = 0
SMTP_USER = 'user'
SMTP_PASSWORD = 'pw'
diff --git a/email_util.py b/email_util.py
new file mode 100644
index 0000000..461d3ea
--- /dev/null
+++ b/email_util.py
@@ -0,0 +1,37 @@
+from email.mime.text import MIMEText
+from email.mime.multipart import MIMEMultipart
+from messages import ROOM_ERROR_TYPES
+import smtplib
+from messages import *
+from config import SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_ADMIN_PASS
+
+def send_unconfirm_message (room_order, room_errors, orders, title):
+ memberMessages = []
+
+ issues_plain = ""
+ issues_html = "
"
+
+ for err in room_errors:
+ if err in ROOM_ERROR_TYPES.keys():
+ issues_plain += f"{ROOM_ERROR_TYPES[err]}"
+ issues_html += f"- {ROOM_ERROR_TYPES[err]}
"
+ issues_html += "
"
+
+ for member in orders:
+ plain_body = ROOM_UNCONFIRM_TEXT['plain'].format(member.name, room_order.room_name, issues_plain)
+ html_body = ROOM_UNCONFIRM_TEXT['html'].format(member.name, room_order.room_name, issues_html)
+ plain_text = MIMEText(plain_body, "plain")
+ html_text = MIMEText(html_body, "html")
+ message = MIMEMultipart("alternative")
+ message.attach(plain_text)
+ message.attach(html_text)
+ message['Subject'] = '[Furizon] Your room cannot be confirmed'
+ message['From'] = 'Furizon '
+ message['To'] = f"{member.name} <{member.email}>"
+ memberMessages.append(message)
+
+ if len(memberMessages) == 0: return
+
+def render_email_template(title = "", body = ""):
+ tpl = app.ctx.tpl.get_template('email/comunication.html')
+ return str(tpl.render(title=title, body=body))
\ No newline at end of file
diff --git a/ext.py b/ext.py
index e9afd1d..6c09b9c 100644
--- a/ext.py
+++ b/ext.py
@@ -98,7 +98,7 @@ class Order:
self.payment_provider = data['payment_provider']
self.comment = data['comment']
self.phone = data['phone']
- self.room_issues = []
+ self.room_errors = []
self.loadAns()
def loadAns(self):
self.shirt_size = self.ans('shirt_size')
@@ -133,7 +133,7 @@ class Order:
return self.data[var]
def set_room_errors (self, to_set):
- for s in to_set: self.room_issues.append (s)
+ for s in to_set: self.room_errors.append (s)
def ans(self, name):
for p in self.data['positions']:
diff --git a/image_util.py b/image_util.py
new file mode 100644
index 0000000..09d7f58
--- /dev/null
+++ b/image_util.py
@@ -0,0 +1,90 @@
+from config import *
+from PIL import Image, ImageDraw, ImageFont
+from sanic import Blueprint, exceptions
+import textwrap
+
+jobs = []
+
+def draw_profile (source, member, position, font, size=(170, 170), border_width=5):
+ idraw = ImageDraw.Draw(source)
+ source_size = source.size
+ main_fill = (187, 198, 206)
+ propic_x = position[0]
+ propic_y = (source_size[1] // 2) - (size[1] // 2)
+ border_loc = (propic_x, propic_y, propic_x + size[0] + border_width * 2, propic_y + size[1] + border_width *2)
+ profile_location = (propic_x + border_width, propic_y + border_width)
+ propic_name_y = propic_y + size[1] + border_width + 20
+ border_color = SPONSORSHIP_COLOR_MAP[member['sponsorship']] if member['sponsorship'] in SPONSORSHIP_COLOR_MAP.keys() else (84, 110, 122)
+ # Draw border
+ idraw.rounded_rectangle(border_loc, border_width, border_color)
+ # Draw profile picture
+ with Image.open(f'res/propic/{member['propic'] or 'default.png'}') as to_add:
+ source.paste(to_add.resize (size), profile_location)
+ name_len = idraw.textlength(str(member['name']), font)
+ calc_size = 0
+ if name_len > size[0]:
+ calc_size = size[0] * 20 / name_len if name_len > size[0] else 20
+ font = ImageFont.truetype(font.path, calc_size)
+ name_len = idraw.textlength(str(member['name']), font)
+ name_loc = (position[0] + ((size[0] / 2) - name_len / 2), propic_name_y + (calc_size/2))
+ name_color = SPONSORSHIP_COLOR_MAP[member['sponsorship']] if member['sponsorship'] in SPONSORSHIP_COLOR_MAP.keys() else main_fill
+ idraw.text(name_loc, str(member['name']), font=font, fill=name_color)
+
+async def generate_room_preview(request, code, room_data):
+ font_path = f'res/font/NotoSans-Bold.ttf'
+ main_fill = (187, 198, 206)
+ propic_size = (170, 170)
+ logo_size = (200, 43)
+ border_width = 5
+ propic_gap = 50
+ propic_width = propic_size[0] + (border_width * 2)
+ propic_total_width = propic_width + propic_gap
+ jobs.append(code)
+ try:
+ room_data = await get_room(request, code) if not room_data else room_data
+ if not room_data: return
+ width = max([(propic_width + propic_gap) * int(room_data['capacity']) + propic_gap, 670])
+ height = int(width * 0.525)
+ font = ImageFont.truetype(font_path, 20)
+
+ # Recalculate gap
+ propic_gap = (width - (propic_width * int(room_data['capacity']))) // (int(room_data['capacity']) + 1)
+ propic_total_width = propic_width + propic_gap
+
+ # Define output image
+ with Image.new('RGB', (width, height), (17, 25, 31)) as source:
+ # Draw logo
+ with (Image.open('res/furizon.png') as logo, logo.resize(logo_size).convert('RGBA') as resized_logo):
+ source.paste(resized_logo, ((source.size[0] // 2) - (logo_size[0] // 2), 10), resized_logo)
+ i_draw = ImageDraw.Draw(source)
+ # Draw room's name
+ room_name_len = i_draw.textlength(room_data['name'], font)
+ i_draw.text((((width / 2) - room_name_len / 2), 55), room_data['name'], font=font, fill=main_fill)
+ # Draw members
+ for m in range (room_data['capacity']):
+ member = room_data['members'][m] if m < len(room_data['members']) else { 'name': 'Empty', 'propic': '../new.png', 'sponsorship': None }
+ font = ImageFont.truetype(font_path, 20)
+ draw_profile(source, member, (propic_gap + (propic_total_width * m), 63), font, propic_size, border_width)
+ source.save(f'res/rooms/{code}.jpg', 'JPEG', quality=60)
+ except Exception as err:
+ if EXTRA_PRINTS: print(err)
+ finally:
+ # Remove fault job
+ if len(jobs) > 0: jobs.pop()
+ if not room_data:
+ raise exceptions.SanicException("There's no room with that code.", status_code=404)
+
+async def get_room (request, code):
+ order_data = await request.app.ctx.om.get_order(code=code)
+ if not order_data or not order_data.room_owner: return None
+ members_map = [{'name': order_data.name, 'propic': order_data.propic, 'sponsorship': order_data.sponsorship}]
+ for member_code in order_data.room_members:
+ if member_code == order_data.code: continue
+ member_order = await request.app.ctx.om.get_order(code=member_code)
+ if not member_order: continue
+ members_map.append ({'name': member_order.name, 'propic': member_order.propic, 'sponsorship': member_order.sponsorship})
+ return {'name': order_data.room_name,
+ 'confirmed': order_data.room_confirmed,
+ 'capacity': order_data.room_person_no,
+ 'free_spots': order_data.room_person_no - len(members_map),
+ 'members': members_map}
\ No newline at end of file
diff --git a/messages.py b/messages.py
index a9a190a..7051486 100644
--- a/messages.py
+++ b/messages.py
@@ -1,6 +1,13 @@
-ROOM_ERROR_MESSAGES = {
+ROOM_ERROR_TYPES = {
'room_id_mismatch': "There's a member in your room that is actually in another room, too. Please contact us as soon as possible in order to fix this issue.",
'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.",
- '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."
+}
+
+ROOM_UNCONFIRM_TITLE = "Your room got unconfirmed"
+ROOM_UNCONFIRM_TEXT = {
+ 'html': "Hello {0}
We had to unconfirm your room '{1}' due to the following problem/s:
{2}
Please contact your room's owner or contact our support for further informations at https://furizon.net/contact/.
Thank you",
+ 'plain': "Hello {0}\nWe had to unconfirm your room '{1}' due to the following problem/s:\n{2}\nPlease contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you"
}
\ No newline at end of file
diff --git a/room.py b/room.py
index 8141c3a..226024e 100644
--- a/room.py
+++ b/room.py
@@ -3,11 +3,8 @@ from sanic import Blueprint, exceptions
from random import choice
from ext import *
from config import headers
-from PIL import Image, ImageDraw, ImageFont
-import textwrap
import os
-
-jobs = []
+from image_util import generate_room_preview, get_room
bp = Blueprint("room", url_prefix="/manage/room")
@@ -357,21 +354,6 @@ async def confirm_room(request, order: Order, quotas: Quotas):
return redirect('/manage/welcome')
-async def get_room (request, code):
- order_data = await request.app.ctx.om.get_order(code=code)
- if not order_data or not order_data.room_owner: return None
- members_map = [{'name': order_data.name, 'propic': order_data.propic, 'sponsorship': order_data.sponsorship}]
- for member_code in order_data.room_members:
- if member_code == order_data.code: continue
- member_order = await request.app.ctx.om.get_order(code=member_code)
- if not member_order: continue
- members_map.append ({'name': member_order.name, 'propic': member_order.propic, 'sponsorship': member_order.sponsorship})
- return {'name': order_data.room_name,
- 'confirmed': order_data.room_confirmed,
- 'capacity': order_data.room_person_no,
- 'free_spots': order_data.room_person_no - len(members_map),
- 'members': members_map}
-
async def get_room_with_order (request, code):
order_data = await request.app.ctx.om.get_order(code=code)
if not order_data or not order_data.room_owner: return None
@@ -383,75 +365,6 @@ def remove_room_preview(code):
except Exception as ex:
if (EXTRA_PRINTS): print(ex)
-def draw_profile (source, member, position, font, size=(170, 170), border_width=5):
- idraw = ImageDraw.Draw(source)
- source_size = source.size
- main_fill = (187, 198, 206)
- propic_x = position[0]
- propic_y = (source_size[1] // 2) - (size[1] // 2)
- border_loc = (propic_x, propic_y, propic_x + size[0] + border_width * 2, propic_y + size[1] + border_width *2)
- profile_location = (propic_x + border_width, propic_y + border_width)
- propic_name_y = propic_y + size[1] + border_width + 20
- border_color = SPONSORSHIP_COLOR_MAP[member['sponsorship']] if member['sponsorship'] in SPONSORSHIP_COLOR_MAP.keys() else (84, 110, 122)
- # Draw border
- idraw.rounded_rectangle(border_loc, border_width, border_color)
- # Draw profile picture
- with Image.open(f'res/propic/{member['propic'] or 'default.png'}') as to_add:
- source.paste(to_add.resize (size), profile_location)
- name_len = idraw.textlength(str(member['name']), font)
- calc_size = 0
- if name_len > size[0]:
- calc_size = size[0] * 20 / name_len if name_len > size[0] else 20
- font = ImageFont.truetype(font.path, calc_size)
- name_len = idraw.textlength(str(member['name']), font)
- name_loc = (position[0] + ((size[0] / 2) - name_len / 2), propic_name_y + (calc_size/2))
- name_color = SPONSORSHIP_COLOR_MAP[member['sponsorship']] if member['sponsorship'] in SPONSORSHIP_COLOR_MAP.keys() else main_fill
- idraw.text(name_loc, str(member['name']), font=font, fill=name_color)
-
-async def generate_room_preview(request, code, room_data):
- font_path = f'res/font/NotoSans-Bold.ttf'
- main_fill = (187, 198, 206)
- propic_size = (170, 170)
- logo_size = (200, 43)
- border_width = 5
- propic_gap = 50
- propic_width = propic_size[0] + (border_width * 2)
- propic_total_width = propic_width + propic_gap
- jobs.append(code)
- try:
- room_data = await get_room(request, code) if not room_data else room_data
- if not room_data: return
- width = max([(propic_width + propic_gap) * int(room_data['capacity']) + propic_gap, 670])
- height = int(width * 0.525)
- font = ImageFont.truetype(font_path, 20)
-
- # Recalculate gap
- propic_gap = (width - (propic_width * int(room_data['capacity']))) // (int(room_data['capacity']) + 1)
- propic_total_width = propic_width + propic_gap
-
- # Define output image
- with Image.new('RGB', (width, height), (17, 25, 31)) as source:
- # Draw logo
- with (Image.open('res/furizon.png') as logo, logo.resize(logo_size).convert('RGBA') as resized_logo):
- source.paste(resized_logo, ((source.size[0] // 2) - (logo_size[0] // 2), 10), resized_logo)
- i_draw = ImageDraw.Draw(source)
- # Draw room's name
- room_name_len = i_draw.textlength(room_data['name'], font)
- i_draw.text((((width / 2) - room_name_len / 2), 55), room_data['name'], font=font, fill=main_fill)
- # Draw members
- for m in range (room_data['capacity']):
- member = room_data['members'][m] if m < len(room_data['members']) else { 'name': 'Empty', 'propic': '../new.png', 'sponsorship': None }
- font = ImageFont.truetype(font_path, 20)
- draw_profile(source, member, (propic_gap + (propic_total_width * m), 63), font, propic_size, border_width)
- source.save(f'res/rooms/{code}.jpg', 'JPEG', quality=60)
- except Exception as err:
- if EXTRA_PRINTS: print(err)
- finally:
- # Remove fault job
- if len(jobs) > 0: jobs.pop()
- if not room_data:
- raise exceptions.SanicException("There's no room with that code.", status_code=404)
-
@bp.route("/view/")
async def get_view(request, code):
room_file_name = f"res/rooms/{code}.jpg"
@@ -460,5 +373,4 @@ async def get_view(request, code):
if not os.path.exists(room_file_name) and code not in jobs:
await generate_room_preview(request, code, room_data)
tpl = request.app.ctx.tpl.get_template('view_room.html')
- return html(tpl.render(preview=room_file_name, room_data=room_data))
-
\ No newline at end of file
+ return html(tpl.render(preview=room_file_name, room_data=room_data))
\ No newline at end of file
diff --git a/tpl/blocks/room.html b/tpl/blocks/room.html
index 82a0cb0..8d0b839 100644
--- a/tpl/blocks/room.html
+++ b/tpl/blocks/room.html
@@ -14,7 +14,8 @@
{% if order.room_id and not order.room_confirmed %}
⚠️ Your room hasn't been confirmed yet. Unconfirmed rooms are subject to changes by the staff as we optimize for hotel capacity.
{% endif %}
- {% if order.room_id and len(order.room_issues) > 0 %}
+ {{len(order.room_errors)}}
+ {% if order.room_id and len(order.room_errors) > 0 %}
⚠️ There are some issues with your room:
{% for issue in issues %}
diff --git a/tpl/email/comunication.html b/tpl/email/comunication.html
new file mode 100644
index 0000000..06598af
--- /dev/null
+++ b/tpl/email/comunication.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ Simple Transactional Email
+
+
+
+
+
+
+
+
+ {{title}}
+ {{body}}
+
+
\ No newline at end of file
diff --git a/tpl/view_room.html b/tpl/view_room.html
index 4ecadd9..b252840 100644
--- a/tpl/view_room.html
+++ b/tpl/view_room.html
@@ -17,7 +17,7 @@
diff --git a/utils.py b/utils.py
index 9b088d3..fe8994b 100644
--- a/utils.py
+++ b/utils.py
@@ -3,7 +3,7 @@ from sanic import exceptions
from config import *
import httpx
from email.mime.text import MIMEText
-from messages import ROOM_ERROR_MESSAGES
+from messages import ROOM_ERROR_TYPES
import smtplib
METADATA_TAG = "meta_data"
@@ -164,7 +164,8 @@ async def validate_room(request, order, om):
check, room_errors, member_orders = await check_room(request, order, om)
if check == True: return
print(f'[ROOM VALIDATION FAILED] {order.code} has failed room validation.', room_errors)
- order.set_room_errors(room_errors)
+ order.room_errors = room_errors
+ om.add_cache (order)
# End here if room is not confirmed
if not order.room_confirmed: return
@@ -175,25 +176,10 @@ async def validate_room(request, order, om):
# Build message
issues_str = ""
for err in room_errors:
- if err in ROOM_ERROR_MESSAGES:
- issues_str += f" - {ROOM_ERROR_MESSAGES[err]}"
-
- memberMessages = []
+ if err in ROOM_ERROR_TYPES:
+ issues_str += f" - {ROOM_ERROR_TYPES[err]}"
- for member in member_orders:
- msg_text = f"Hello {member.name}!\n\n"
- msg_text += f"We had to unconfirm your room '{order.room_name}'"
- msg_text += f" due to th{'ese issues' if len(room_errors) > 1 else 'is issue'}:\n{issues_str}\n\n"
- msg_text += f"Please contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you"
- msg = MIMEText(msg_text)
- msg['Subject'] = '[Furizon] Your room cannot be confirmed'
- msg['From'] = 'Furizon '
- msg['To'] = f"{member.name} <{member.email}>"
- memberMessages.append(msg)
-
- if len(memberMessages) == 0: return
-
- s = smtplib.SMTP_SSL(SMTP_HOST, 587)
+ s = smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT)
s.login(SMTP_USER, SMTP_PASSWORD)
for message in memberMessages:
s.sendmail(message['From'], message['to'], message.as_string())