from otree.api import Page from .models import Constants, Player from .drive_utils import upload_pdf_to_drive from reportlab.lib.utils import ImageReader import base64 import os import datetime from reportlab.pdfgen import canvas from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase import pdfmetrics from bidi.algorithm import get_display import arabic_reshaper from io import BytesIO from PIL import Image def reshape_text(text): """Reshape Hebrew text for correct RTL display""" reshaped = arabic_reshaper.reshape(text) bidi_text = get_display(reshaped) return bidi_text def generate_signed_consent_pdf(player: Player, signature_base64_data: str): filename = f"consent_form_{player.participant.code}_{datetime.date.today().strftime('%Y%m%d')}.pdf" # Register font that supports Hebrew font_path = os.path.join(os.path.dirname(__file__), 'fonts', 'DejaVuSans.ttf') pdfmetrics.registerFont(TTFont('DejaVu', font_path)) buffer = BytesIO() c = canvas.Canvas(buffer) c.setFont("DejaVu", 12) # Start drawing from the right side, Y from top downward width, height = 595, 842 # A4 size in points y = height - 50 # margin from top # Title (centered) title = reshape_text("קבלה") c.setFont("DejaVu", 16) c.drawCentredString(width / 2, y, title) c.setFont("DejaVu", 12) y -= 40 # Main declaration text1 = f"הנני מצהיר/ה כי השתתפתי כנבדק במחקרו של החוקר {Constants.RESEARCHER_NAME}." text1 = reshape_text(text1) c.drawRightString(width - 40, y, text1) y -= 20 text2 = "עבור השתתפותי כנבדק במחקר קיבלתי (נא לסמן במקום המתאים):" text2 = reshape_text(text2) c.drawRightString(width - 40, y, text2) y -= 20 payment_text = f"תשלום בסך {player.participant.vars['total_payout']:.2f} ש\"ח." payment_text = reshape_text(payment_text) c.drawRightString(width - 40, y, payment_text) y -= 40 # Previous payment declaration prev_header = "הנני מצהיר/ה כי בשנת המס הנוכחית:" prev_header = reshape_text(prev_header) c.drawRightString(width - 40, y, prev_header) y -= 20 if player.previous_payment_choice == 1: prev_amount_text = f"קבלתי גמול מסוג זה בסך {player.previous_payment_amount or '____'} ש\"ח" prev_amount_text = reshape_text(f"(*) {prev_amount_text}") c.drawRightString(width - 40, y, prev_amount_text) y -= 20 no_prev = reshape_text("( ) לא קבלתי גמול מסוג זה בגין השתתפות במחקר זה או אחר באוניברסיטה.") c.drawRightString(width - 40, y, no_prev) y -= 30 else: prev_blank = reshape_text("( ) קבלתי גמול מסוג זה בסך ________ ש\"ח") c.drawRightString(width - 40, y, prev_blank) y -= 20 no_prev = reshape_text("(*) לא קבלתי גמול מסוג זה בגין השתתפות במחקר זה או אחר באוניברסיטה.") c.drawRightString(width - 40, y, no_prev) y -= 30 # Disclaimer disclaimer_text1 = "ידוע לי כי קבלת גמול זה בגין השתתפותי במחקר אין משמעותה קיום יחסי עובד - מעביד" disclaimer_text1 = reshape_text(disclaimer_text1) c.drawRightString(width - 40, y, disclaimer_text1) y -= 20 disclaimer_text2 = "ולא תהיה לי כל תביעה בהקשר זה." disclaimer_text2 = reshape_text(disclaimer_text2) c.drawRightString(width - 40, y, disclaimer_text2) y -= 50 # Name, ID and Phone number name_text = reshape_text(f"שם: {player.participant_full_name or ''}") c.drawRightString(width - 40, y, name_text) y -= 20 id_text = reshape_text(f"ת.ז. {player.participant_id_number or ''}") c.drawRightString(width - 40, y, id_text) y -= 20 phone_text = reshape_text(f"מספר טלפון לקבלת התשלום (יועבר בביט): {player.phone_number or ''}") c.drawRightString(width - 40, y, phone_text) y -= 50 # Date and signature line date_text = reshape_text(f"תאריך: {datetime.date.today().strftime('%d/%m/%Y')}") signature_line = reshape_text("חתימה: _______________") c.drawRightString(width / 2 - 10, y, date_text) c.drawRightString(width - 40, y, signature_line) # Add signature image if present if signature_base64_data: try: if "," in signature_base64_data: _, encoded_data = signature_base64_data.split(',', 1) else: encoded_data = signature_base64_data img_data = base64.b64decode(encoded_data) img_buffer = BytesIO(img_data) img = Image.open(img_buffer) img = img.convert("RGBA") background = Image.new("RGBA", img.size, (255, 255, 255, 255)) background.paste(img, mask=img) img_rgb = background.convert("RGB") final_img_buffer = BytesIO() img_rgb.save(final_img_buffer, format='PNG') final_img_buffer.seek(0) img_reader = ImageReader(final_img_buffer) c.drawImage(img_reader, width - 160, y, width=80, height=40, mask='auto') except Exception as e: print(f"Error adding signature to PDF: {e}") error_text = reshape_text("[Signature not captured or invalid]") c.drawRightString(width - 40, y, error_text) c.showPage() c.save() buffer.seek(0) return buffer, filename class ValueCheck(Page): def vars_for_template(self): if "phone_number" in self.participant.vars: self.player.phone_number = self.participant.vars["phone_number"] if "total_payout" in self.participant.vars: self.player.payment_amount = int(self.participant.vars["total_payout"]) return {} def get_timeout_seconds(self): return 0.01 class SignaturePage(Page): form_model = 'player' form_fields = ['participant_full_name', 'participant_id_number', 'previous_payment_choice', 'previous_payment_amount', 'signature_base64', 'phone_number'] def vars_for_template(self): player = self.player full_name = player.field_maybe_none('participant_full_name') signature_data = player.field_maybe_none('signature_base64') pdf_path = player.field_maybe_none('signed_consent_pdf_path') return { 'pdf_path': pdf_path or '', 'payment_amount': player.payment_amount, 'payment_amount_display': f"{float(player.payment_amount):.2f}", 'full_name': full_name or '', 'has_pdf': bool(pdf_path), 'has_signature': bool(signature_data) } def error_message(self, values): errors = {} display = '' if not values.get('participant_full_name'): errors['participant_full_name'] = '.נא למלא שם מלא' if not values.get('participant_id_number'): errors['participant_id_number'] = '.נא למלא מספר תעודת זהות' if len(values.get('participant_id_number')) != 9: errors['participant_id_number'] = '.מספר תעודת זהות צריך להיות בעל 9 ספרות' if not values.get('signature_base64'): errors['signature_base64'] = '.נא לחתום על הטופס' if values.get('previous_payment_choice') == 1 and not values.get('previous_payment_amount'): errors['previous_payment_amount'] = '.נא למלא סכום גמול קודם' if not values.get('phone_number'): errors['phone_number'] = '.נא למלא מספר טלפון' if len(values.get('phone_number')) != 10: errors['phone_number'] = '.מספר טלפון נדרש להיות עם 10 ספרות' for key in errors: display = display + errors.get(key) + ' ' return display def before_next_page(self): player = self.player if player.signature_base64: try: pdf_stream, filename = generate_signed_consent_pdf(player, player.signature_base64) pdf_bytes = pdf_stream.getvalue() print(f"Uploading PDF {filename}, size={len(pdf_bytes)} bytes") try: file_id = upload_pdf_to_drive(pdf_bytes, filename, folder_id="1I3VoayP17AX-tq9I2CYJO94XMmo6xhQX") print(f"Uploaded file ID: {file_id}") except Exception as e: print(f"Error uploading PDF: {e}") pdf_stream.close() except Exception as e: import traceback traceback.print_exc() else: print(f"No signature data found for participant {player.participant.code}.") class MyResultsPage(Page): def vars_for_template(self): player = self.player full_name = player.field_maybe_none('participant_full_name') signature_data = player.field_maybe_none('signature_base64') pdf_path = player.field_maybe_none('signed_consent_pdf_path') return { 'pdf_path': pdf_path or '', 'payment_amount': player.payment_amount, 'full_name': full_name or '', 'has_pdf': bool(pdf_path), 'has_signature': bool(signature_data) } page_sequence = [ValueCheck, SignaturePage, MyResultsPage]