diff --git a/app/__init__.py b/app/__init__.py index 02e52f48d8ea9cc603f856575a447bd5cbae28e6..6d2b047da24fe98ebc941b4c9aea32393ea0cd43 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -84,7 +84,6 @@ def create_database_if_not_exists(db_host, db_user, db_password, db_name): connection.close() - def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) @@ -153,6 +152,12 @@ def create_app(config_class=Config): 'user_permission': g.user_permission, 'super_admin_permission': g.super_admin_permission } + + # Prevent site being loaded using iFrames + @app.after_request + def set_x_frame_options(response): + response.headers['X-Frame-Options'] = 'SAMEORIGIN' + return response # @app.errorhandler(Exception) # def handle_exception(e): diff --git a/app/bookings/routes.py b/app/bookings/routes.py index 102b87647dc2488c9691733f13d06a7aeef2fda7..14f6a991d234731d8e29ba59839349c1c72e3a85 100644 --- a/app/bookings/routes.py +++ b/app/bookings/routes.py @@ -245,6 +245,7 @@ def checkout_post(): total_cost = per_person_cost * num_seats depart_date = cache['depart_date'] + last_four_card_nums = card_number[-4:] # Convert depart_date to date object depart_date_obj = datetime.strptime(depart_date, '%Y-%m-%d').date() @@ -255,7 +256,7 @@ def checkout_post(): return redirect(url_for('bookings.listing', id=listing_id)) try: - booking = Bookings.create_booking(listing_id, user_id, total_cost, seat_type, num_seats) + booking = Bookings.create_booking(listing_id, user_id, total_cost, seat_type, num_seats, depart_date, last_four_card_nums) if booking: # Update availability ListingAvailability.update_availability(listing_id, depart_date_obj, seat_type, num_seats) @@ -268,6 +269,7 @@ def checkout_post(): db.session.rollback() error_logger.debug(f"Error processing booking: {e}") flash('Booking failed. Please try again.', 'error') + return redirect(url_for('bookings.checkout')) return redirect(url_for('bookings.payment_complete', id=booking.id)) @@ -361,7 +363,7 @@ def payment_success(booking_id): def generate_receipt(id): booking = Bookings.search_booking(id) listing = Listings.search_listing(booking.listing_id) - pdf = create_receipt(1, booking.seat_type, '2024-02-24', listing) + pdf = create_receipt(booking, listing) return send_file(pdf, as_attachment=True, download_name='receipt.pdf') @@ -370,6 +372,6 @@ def generate_ticket(id): booking = Bookings.search_booking(id) listing = Listings.search_listing(booking.listing_id) - pdf = create_plane_ticket(booking.seat_num, booking.seat_type, booking.depart_date, listing, booking_id) + pdf = create_plane_ticket(booking, listing) return send_file(pdf, as_attachment=True, download_name='plane_ticket.pdf') diff --git a/app/main/utils.py b/app/main/utils.py index 949e0da34c13d3d4be45422444b39e63785eebe7..756b2ca4452885eee50b889124c71cc3e04a6597 100644 --- a/app/main/utils.py +++ b/app/main/utils.py @@ -4,12 +4,13 @@ from flask import current_app from datetime import time, datetime from datetime import datetime from fpdf import FPDF -import qrcode import barcode from barcode.writer import ImageWriter +from PyPDF2 import PdfMerger from io import BytesIO import os from PIL import Image +from pystrich.datamatrix import DataMatrixEncoder, DataMatrixRenderer def allowed_image_files(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS'] @@ -55,48 +56,51 @@ def pretty_time(unformatted_time, to_12_hour=True): def get_static_files(*path_parts): return os.path.join(current_app.root_path, 'static', *path_parts) -def create_receipt(seat_num, seat_type, depart_date, listing): +import os +import tempfile +from PyPDF2 import PdfMerger +from PIL import Image +import barcode +from barcode.writer import ImageWriter +from pystrich.datamatrix import DataMatrixEncoder, DataMatrixRenderer + +def create_receipt(booking, listing): + formatted_depart_date = booking.depart_date.strftime('%d-%m-%Y') + formatted_booking_date = booking.booking_date.strftime('%d-%m-%Y') + formatted_depart_time = pretty_time(listing.depart_time) + formatted_arrival_time = pretty_time(listing.destination_time) + + os.makedirs('/tmp', exist_ok=True) + pdf = FPDF() pdf.add_page() - pdf.set_font("Arial", size=12) - # Add company logo - company_logo_path = get_static_files('core', '1.jpg') + company_logo_path = get_static_files('core', 'logo.jpeg') pdf.image(company_logo_path, x=10, y=8, w=33) - # Adjust y position to avoid overlapping - pdf.set_xy(10, 45) + pdf.set_font("Arial", size=40, style='B') pdf.cell(200, 10, txt="Receipt", ln=True, align='C') + pdf.set_xy(10, 45) - pdf.cell(200, 10, txt=f"Seat Number: {seat_num}", ln=True) - pdf.cell(200, 10, txt=f"Seat Type: {seat_type}", ln=True) - pdf.cell(200, 10, txt=f"Departure Date: {depart_date}", ln=True) + pdf.set_font("Arial", size=12) + pdf.cell(200, 10, txt=f"Number of Seats: {booking.num_seats}", ln=True) + pdf.cell(200, 10, txt=f"Seat Type: {booking.seat_type.capitalize()}", ln=True) + pdf.cell(200, 10, txt=f"Departure Date: {formatted_depart_date}", ln=True) pdf.cell(200, 10, txt=f"Departure Location: {listing.depart_location}", ln=True) + pdf.cell(200, 10, txt=f"Departure Time: {formatted_depart_time}", ln=True) pdf.cell(200, 10, txt=f"Destination Location: {listing.destination_location}", ln=True) + pdf.cell(200, 10, txt=f"Arrival Time: {formatted_arrival_time}", ln=True) + pdf.cell(200, 10, txt=f"Booking Date: {formatted_booking_date}", ln=True) - if seat_type.lower() == 'economy': + if booking.seat_type.lower() == 'economy': cost = listing.economy_fair_cost - elif seat_type.lower() == 'business': + elif booking.seat_type.lower() == 'business': cost = listing.business_fair_cost - pdf.cell(200, 10, txt=f"Total Cost: {cost}", ln=True) - - # Generate barcode - receipt_data = f"{seat_num},{seat_type},{depart_date},{listing.depart_location},{listing.destination_location},{cost}" - code128 = barcode.get('code128', receipt_data, writer=ImageWriter()) - barcode_image = BytesIO() - code128.write(barcode_image) - barcode_image.seek(0) - - # Save barcode image - with open("barcode.png", "wb") as f: - f.write(barcode_image.read()) - - pdf.image("barcode.png", x=150, y=8, w=50) - - # Add thank you message + pdf.cell(200, 10, txt=f"Total Cost: £{cost}", ln=True) + pdf.set_font("Arial", size=16, style='B') - pdf.set_xy(10, 250) + pdf.set_xy(10, 200) pdf.cell(200, 10, txt="Thanks for choosing Horizon Travels!", ln=True, align='C') output = BytesIO() @@ -105,53 +109,75 @@ def create_receipt(seat_num, seat_type, depart_date, listing): return output +def create_plane_ticket(booking, listing): + merger = PdfMerger() + temp_files = [] + + os.makedirs('/tmp', exist_ok=True) -def create_plane_ticket(seat_num, seat_type, depart_date, listing, booking_id): - for i in range(seat_num): - seat_number = f"{seat_num + 1 - i:02d}" - ticket_number = f"{booking_id}-{seat_number}" + for seat_num in range(booking.num_seats): + seat_number = f"{seat_num + 1:02d}" + ticket_number = f"{booking.id}{listing.id}{booking.user_id}0000001PASS000000{seat_number}" pdf = FPDF() pdf.add_page() - pdf.set_font("Arial", size=12) + pdf.set_font("Arial", size=30, style='B') - pdf.cell(200, 10, txt="Plane Ticket", ln=True, align='C') + pdf.cell(200, 10, txt="Boarding Pass", ln=True, align='C') + pdf.set_font("Arial", size=12) pdf.cell(200, 10, txt=f"Seat Number: {seat_number}", ln=True) - pdf.cell(200, 10, txt=f"Seat Type: {seat_type}", ln=True) - pdf.cell(200, 10, txt=f"Departure Date: {depart_date}", ln=True) + pdf.cell(200, 10, txt=f"Seat Type: {booking.seat_type.capitalize()}", ln=True) + pdf.cell(200, 10, txt=f"Departure Date: {booking.depart_date.strftime('%d-%m-%Y')}", ln=True) pdf.cell(200, 10, txt=f"Departure Location: {listing.depart_location}", ln=True) - pdf.cell(200, 10, txt=f"Departure Time: {listing.depart_time}", ln=True) + pdf.cell(200, 10, txt=f"Departure Time: {pretty_time(listing.depart_time)}", ln=True) pdf.cell(200, 10, txt=f"Destination Location: {listing.destination_location}", ln=True) - pdf.cell(200, 10, txt=f"Destination Time: {listing.destination_time}", ln=True) + pdf.cell(200, 10, txt=f"Arrival Time: {pretty_time(listing.destination_time)}", ln=True) pdf.cell(200, 10, txt=f"Transport Type: {listing.transport_type}", ln=True) - qr_img = create_qr_code(ticket_number) - qr_img_path = f"qr_code_{ticket_number}.png" - qr_img.save(qr_img_path) - pdf.image(qr_img_path, x=10, y=120, w=50) + datamatrix_img = create_datamatrix(ticket_number) + datamatrix_img_path = f"/tmp/datamatrix_{ticket_number}.png" + datamatrix_img.save(datamatrix_img_path) + temp_files.append(datamatrix_img_path) + pdf.image(datamatrix_img_path, x=100, y=30, w=75) barcode_img = create_barcode(ticket_number) - barcode_img_path = f"barcode_{ticket_number}.png" + barcode_img_path = f"/tmp/barcode_{ticket_number}.png" barcode_img.save(barcode_img_path) - pdf.image(barcode_img_path, x=80, y=120, w=100) + temp_files.append(barcode_img_path) + pdf.image(barcode_img_path, x=60, y=120, w=100) output = BytesIO() pdf.output(output) output.seek(0) - return output + merger.append(output) + + final_output = BytesIO() + merger.write(final_output) + final_output.seek(0) + + for file_path in temp_files: + os.remove(file_path) + + return final_output -def create_qr_code(data): - qr = qrcode.QRCode(version=1, box_size=10, border=5) - qr.add_data(data) - qr.make(fit=True) - img = qr.make_image(fill='black', back_color='white') +def create_datamatrix(data): + encoder = DataMatrixEncoder(data) + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file: + encoder.save(temp_file.name) + temp_file.seek(0) + img = Image.open(temp_file.name) return img def create_barcode(data): CODE128 = barcode.get_barcode_class('code128') + options = { + 'write_text': False + } code = CODE128(data, writer=ImageWriter()) buffer = BytesIO() - code.write(buffer) - return Image.open(buffer) \ No newline at end of file + code.write(buffer, options=options) + buffer.seek(0) + img = Image.open(buffer) + return img \ No newline at end of file diff --git a/app/models/bookings.py b/app/models/bookings.py index 34b1551faa5f80a1f08d7358acbd05c39755dfb6..4881e91ee5f3ab415f6e45d00accd33711a9fa4d 100644 --- a/app/models/bookings.py +++ b/app/models/bookings.py @@ -1,5 +1,7 @@ from app import db from flask_login import UserMixin +from app.logger import error_logger +from datetime import datetime class Bookings(UserMixin, db.Model): __tablename__ = 'bookings' @@ -11,26 +13,32 @@ class Bookings(UserMixin, db.Model): seat_type = db.Column(db.String(50), nullable=False) num_seats = db.Column(db.Integer, nullable=False) cancelled = db.Column(db.Boolean, default=False) + cancelled_date = db.Column(db.Date, nullable=False) booking_date = db.Column(db.Date, nullable=False) + depart_date = db.Column(db.Date, nullable=False) last_four_card_nums = db.Column(db.String(4), nullable=False) - @staticmethod - def create_booking(listing_id, user_id, amount_paid, seat_type, num_seats): + @classmethod + def create_booking(cls, listing_id, user_id, amount_paid, seat_type, num_seats, depart_date, last_four_card_nums): + today_date = datetime.now().strftime('%Y-%m-%d') try: - new_booking = Bookings( + new_booking = cls( user_id=user_id, listing_id=listing_id, amount_paid=amount_paid, seat_type=seat_type, num_seats=num_seats, - cancelled=False + cancelled=False, + booking_date=today_date, + depart_date=depart_date, + last_four_card_nums=last_four_card_nums ) db.session.add(new_booking) db.session.commit() return new_booking except Exception as e: db.session.rollback() - print(f"Error creating booking: {e}") + error_logger.error(f"Error creating booking: {e}") return False @classmethod diff --git a/app/static/core/1.jpg b/app/static/core/1.jpg deleted file mode 100644 index 7efd61d2d445c696b4f4689a845e8274123d2656..0000000000000000000000000000000000000000 Binary files a/app/static/core/1.jpg and /dev/null differ diff --git a/app/static/core/logo.jpeg b/app/static/core/logo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c244ed9c4f6fd3f3c4bb8a0ab8ac12d7727d0c73 Binary files /dev/null and b/app/static/core/logo.jpeg differ diff --git a/barcode.png b/barcode.png deleted file mode 100644 index 6316549fa9149ba0f79c580fc4d8ab0adf42cdb3..0000000000000000000000000000000000000000 Binary files a/barcode.png and /dev/null differ diff --git a/requirements.txt b/requirements.txt index f8a540d7b7b5aa0c6dafef7ae94021d436a69393..abc42a16dc9ad6c8ffa4310f5301ed5cfe74a71f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,6 @@ flask-principal setuptools fpdf2 qrcode[pil] -python-barcode \ No newline at end of file +python-barcode +pypdf2 +pystrich \ No newline at end of file