The new updates of partners and user

Made-with: Cursor
This commit is contained in:
Vivek P Prakash
2026-03-15 00:29:17 +05:30
parent 88b3aafb0b
commit c04395afc9
65 changed files with 5242 additions and 341 deletions

View File

@@ -0,0 +1,101 @@
# Generated by Django 4.2.27 on 2026-03-13 16:55
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('events', '0007_event_gst_percentage_1_event_gst_percentage_2_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Booking',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('booking_id', models.CharField(max_length=250)),
('quantity', models.IntegerField()),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('created_date', models.DateField(auto_now_add=True)),
('updated_date', models.DateField(auto_now=True)),
('transaction_id', models.CharField(blank=True, max_length=250, null=True)),
],
),
migrations.CreateModel(
name='TicketMeta',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ticket_name', models.CharField(max_length=250)),
('maximum_quantity', models.IntegerField()),
('available_quantity', models.IntegerField(default=0)),
('is_active', models.BooleanField(default=True)),
('created_date', models.DateField(auto_now_add=True)),
('updated_date', models.DateField(auto_now=True)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.event')),
],
),
migrations.CreateModel(
name='TicketType',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ticket_type', models.CharField(max_length=250)),
('ticket_type_description', models.TextField()),
('ticket_type_quantity', models.IntegerField()),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('is_active', models.BooleanField(default=True)),
('created_date', models.DateField(auto_now_add=True)),
('updated_date', models.DateField(auto_now=True)),
('is_offer', models.BooleanField(default=False)),
('offer_percentage', models.IntegerField(default=0)),
('offer_price', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
('offer_start_date', models.DateField(blank=True, null=True)),
('offer_end_date', models.DateField(blank=True, null=True)),
('ticket_meta', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.ticketmeta')),
],
),
migrations.CreateModel(
name='Ticket',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ticket_id', models.CharField(max_length=250)),
('is_checked_in', models.BooleanField(default=False)),
('checked_in_date_time', models.DateTimeField(blank=True, null=True)),
('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.booking')),
],
),
migrations.CreateModel(
name='Cart',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField()),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('is_active', models.BooleanField(default=True)),
('created_date', models.DateField(auto_now_add=True)),
('updated_date', models.DateField(auto_now=True)),
('ticket_meta', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.ticketmeta')),
('ticket_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.tickettype')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='booking',
name='ticket_meta',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.ticketmeta'),
),
migrations.AddField(
model_name='booking',
name='ticket_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.tickettype'),
),
migrations.AddField(
model_name='booking',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -4,10 +4,9 @@ from events.models import Event
from accounts.models import User
# Create your models here.
class Ticket(models.Model):
class TicketMeta(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
ticket_name = models.CharField(max_length=250)
price_per_ticket = models.DecimalField(max_digits=10, decimal_places=2)
maximum_quantity = models.IntegerField()
available_quantity = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
@@ -19,11 +18,13 @@ class Ticket(models.Model):
class TicketType(models.Model):
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
ticket_meta = models.ForeignKey(TicketMeta, on_delete=models.CASCADE)
ticket_type = models.CharField(max_length=250)
ticket_type_description = models.TextField()
quantity = models.IntegerField()
ticket_type_quantity = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
is_active = models.BooleanField(default=True)
created_date = models.DateField(auto_now_add=True)
updated_date = models.DateField(auto_now=True)
@@ -41,10 +42,11 @@ class TicketType(models.Model):
class Cart(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
ticket_meta = models.ForeignKey(TicketMeta, on_delete=models.CASCADE)
ticket_type = models.ForeignKey(TicketType, on_delete=models.CASCADE)
quantity = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
is_active = models.BooleanField(default=True)
created_date = models.DateField(auto_now_add=True)
updated_date = models.DateField(auto_now=True)
@@ -55,7 +57,7 @@ class Cart(models.Model):
class Booking(models.Model):
booking_id = models.CharField(max_length=250)
user = models.ForeignKey(User, on_delete=models.CASCADE)
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
ticket_meta = models.ForeignKey(TicketMeta, on_delete=models.CASCADE)
ticket_type = models.ForeignKey(TicketType, on_delete=models.CASCADE)
quantity = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
@@ -70,4 +72,26 @@ class Booking(models.Model):
super().save(*args, **kwargs)
def __str__(self):
return self.booking_id
return self.booking_id
class Ticket(models.Model):
booking = models.ForeignKey(Booking, on_delete=models.CASCADE)
ticket_id = models.CharField(max_length=250)
is_checked_in = models.BooleanField(default=False)
checked_in_date_time = models.DateTimeField(blank=True, null=True)
def __save__(self):
if not self.ticket_id:
self.ticket_id = str(self.booking.ticket_meta.event.name[:3].upper()) + str(uuid.uuid4().hex[:10]).upper()
super().save(*args, **kwargs)
def __str__(self):
return self.ticket_id
def check_in(self, ticket_id):
if self.ticket_id == ticket_id:
self.is_checked_in = True
self.checked_in_date_time = datetime.now()
self.save()
return True
return False

61
bookings/services.py Normal file
View File

@@ -0,0 +1,61 @@
from typing import List
import uuid
from django.utils import timezone
from bookings.models import Booking, Ticket
def _generate_ticket_id(booking: Booking) -> str:
"""
Generate a ticket_id based on the event name and a random UUID segment.
Pattern: <EVT><RANDOM_HEX>
- EVT: first 3 characters of event name (uppercase), or 'EVT' fallback
- RANDOM_HEX: first 10 chars of uuid4 hex (uppercase)
"""
event = getattr(booking.ticket_meta, "event", None)
if event and getattr(event, "name", None):
prefix = (event.name or "EVT")[:3].upper()
else:
prefix = "EVT"
return prefix + uuid.uuid4().hex[:10].upper()
def generate_tickets_for_booking(booking: Booking) -> List[Ticket]:
"""
Generate Ticket instances for a given Booking based on its quantity.
This function does NOT perform any payment or business-rule validation.
It simply creates one Ticket per quantity on the booking.
Args:
booking: Booking instance for which tickets should be generated.
Returns:
List[Ticket]: List of created Ticket instances.
"""
if not isinstance(booking, Booking):
raise TypeError("booking must be a Booking instance")
if booking.quantity <= 0:
return []
tickets: List[Ticket] = []
for _ in range(booking.quantity):
tickets.append(
Ticket(
booking=booking,
ticket_id=_generate_ticket_id(booking),
is_checked_in=False,
checked_in_date_time=None,
)
)
# Bulk create for efficiency
Ticket.objects.bulk_create(tickets)
# Refresh from DB to ensure we have primary keys and any defaults
return list[Ticket](Ticket.objects.filter(booking=booking).order_by("id"))

View File

@@ -0,0 +1,2 @@
from . import ticket_meta_type
from . import booking_api

View File

@@ -1,301 +0,0 @@
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.forms.models import model_to_dict
from rest_framework.views import APIView
from bookings.models import Ticket
from events.models import Event
from mobile_api.utils import validate_token_and_get_user
def _ticket_to_dict(ticket):
"""
Helper to serialise a Ticket instance into a simple dict suitable for JSON.
"""
data = model_to_dict(
ticket,
fields=[
"id",
"ticket_name",
"price_per_ticket",
"maximum_quantity",
"available_quantity",
"is_active",
"created_date",
"updated_date",
],
)
data["event_id"] = ticket.event_id
return data
@method_decorator(csrf_exempt, name="dispatch")
class TicketCreateAPI(APIView):
"""
Create a new Ticket.
Expected JSON body (along with token & username used across mobile_api):
{
"token": "...",
"username": "...",
"event_id": 1,
"ticket_name": "VIP",
"price_per_ticket": 1000.0,
"maximum_quantity": 50,
"available_quantity": 50, # optional, defaults to maximum_quantity
"is_active": true # optional, defaults to true
}
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
event_id = data.get("event_id")
ticket_name = data.get("ticket_name")
price_per_ticket = data.get("price_per_ticket")
maximum_quantity = data.get("maximum_quantity")
available_quantity = data.get("available_quantity")
is_active = data.get("is_active", True)
if not event_id or not ticket_name or price_per_ticket is None or maximum_quantity is None:
return JsonResponse(
{
"status": "error",
"message": "event_id, ticket_name, price_per_ticket and maximum_quantity are required.",
},
status=400,
)
try:
event = Event.objects.get(id=event_id)
except Event.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "Event not found."},
status=404,
)
try:
price_per_ticket = float(price_per_ticket)
maximum_quantity = int(maximum_quantity)
if available_quantity is not None:
available_quantity = int(available_quantity)
else:
available_quantity = maximum_quantity
except (TypeError, ValueError):
return JsonResponse(
{
"status": "error",
"message": "price_per_ticket, maximum_quantity and available_quantity must be numeric.",
},
status=400,
)
if maximum_quantity <= 0:
return JsonResponse(
{
"status": "error",
"message": "maximum_quantity must be greater than zero.",
},
status=400,
)
ticket = Ticket.objects.create(
event=event,
ticket_name=ticket_name,
price_per_ticket=price_per_ticket,
maximum_quantity=maximum_quantity,
available_quantity=available_quantity,
is_active=bool(is_active),
)
return JsonResponse(
{"status": "success", "ticket": _ticket_to_dict(ticket)},
status=201,
)
except Exception as e:
return JsonResponse(
{"status": "error", "message": str(e)},
status=500,
)
@method_decorator(csrf_exempt, name="dispatch")
class TicketListAPI(APIView):
"""
List tickets, optionally filtered by event_id.
Expected JSON body:
{
"token": "...",
"username": "...",
"event_id": 1 # optional
}
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
event_id = data.get("event_id")
tickets_qs = Ticket.objects.all().order_by("-created_date")
if event_id:
tickets_qs = tickets_qs.filter(event_id=event_id)
tickets = [_ticket_to_dict(t) for t in tickets_qs]
return JsonResponse(
{"status": "success", "tickets": tickets},
status=200,
)
except Exception as e:
return JsonResponse(
{"status": "error", "message": str(e)},
status=500,
)
@method_decorator(csrf_exempt, name="dispatch")
class TicketUpdateAPI(APIView):
"""
Update an existing Ticket.
Expected JSON body:
{
"token": "...",
"username": "...",
"ticket_id": 1,
"ticket_name": "...", # optional
"price_per_ticket": 1000.0, # optional
"maximum_quantity": 50, # optional
"available_quantity": 50, # optional
"is_active": true # optional
}
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
ticket_id = data.get("ticket_id")
if not ticket_id:
return JsonResponse(
{"status": "error", "message": "ticket_id is required."},
status=400,
)
try:
ticket = Ticket.objects.get(id=ticket_id)
except Ticket.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "Ticket not found."},
status=404,
)
# Optional updates
ticket_name = data.get("ticket_name")
price_per_ticket = data.get("price_per_ticket")
maximum_quantity = data.get("maximum_quantity")
available_quantity = data.get("available_quantity")
is_active = data.get("is_active")
if ticket_name is not None:
ticket.ticket_name = ticket_name
try:
if price_per_ticket is not None:
ticket.price_per_ticket = float(price_per_ticket)
if maximum_quantity is not None:
maximum_quantity = int(maximum_quantity)
if maximum_quantity <= 0:
return JsonResponse(
{
"status": "error",
"message": "maximum_quantity must be greater than zero.",
},
status=400,
)
ticket.maximum_quantity = maximum_quantity
if available_quantity is not None:
ticket.available_quantity = int(available_quantity)
except (TypeError, ValueError):
return JsonResponse(
{
"status": "error",
"message": "price_per_ticket, maximum_quantity and available_quantity must be numeric.",
},
status=400,
)
if is_active is not None:
ticket.is_active = bool(is_active)
ticket.save()
return JsonResponse(
{"status": "success", "ticket": _ticket_to_dict(ticket)},
status=200,
)
except Exception as e:
return JsonResponse(
{"status": "error", "message": str(e)},
status=500,
)
@method_decorator(csrf_exempt, name="dispatch")
class TicketDeleteAPI(APIView):
"""
Delete an existing Ticket.
Expected JSON body:
{
"token": "...",
"username": "...",
"ticket_id": 1
}
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
ticket_id = data.get("ticket_id")
if not ticket_id:
return JsonResponse(
{"status": "error", "message": "ticket_id is required."},
status=400,
)
try:
ticket = Ticket.objects.get(id=ticket_id)
except Ticket.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "Ticket not found."},
status=404,
)
ticket.delete()
return JsonResponse(
{"status": "success", "message": "Ticket deleted successfully."},
status=200,
)
except Exception as e:
return JsonResponse(
{"status": "error", "message": str(e)},
status=500,
)

View File

@@ -0,0 +1,369 @@
import uuid
from decimal import Decimal
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.forms.models import model_to_dict
from django.utils import timezone
from datetime import date
from rest_framework.views import APIView
from bookings.models import Cart, TicketType, TicketMeta, Booking, Ticket
from mobile_api.utils import validate_token_and_get_user
from banking_operations.services import transaction_initiate
from eventify_logger.services import log
def _cart_to_dict(cart):
"""Serialize Cart for JSON."""
data = model_to_dict(
cart,
fields=["id", "quantity", "price", "created_date", "updated_date"],
)
data["user_id"] = cart.user_id
data["ticket_meta_id"] = cart.ticket_meta_id
data["ticket_type_id"] = cart.ticket_type_id
return data
@method_decorator(csrf_exempt, name="dispatch")
class AddToCartAPI(APIView):
"""
Add TicketType to Cart (when customer clicks plus button).
Body: token, username, ticket_type_id, quantity (optional, defaults to 1).
If cart item already exists for this user + ticket_type, quantity is incremented.
Price is taken from TicketType (offer_price if offer is active, else price).
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
ticket_type_id = data.get("ticket_type_id")
quantity = data.get("quantity", 1) # Default to 1 for plus button click
if not ticket_type_id:
return JsonResponse(
{"status": "error", "message": "ticket_type_id is required."},
status=400,
)
try:
ticket_type = TicketType.objects.select_related("ticket_meta").get(id=ticket_type_id)
except TicketType.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "TicketType not found."},
status=404,
)
if not ticket_type.is_active:
return JsonResponse(
{"status": "error", "message": "TicketType is not active."},
status=400,
)
try:
quantity = int(quantity)
if quantity <= 0:
return JsonResponse(
{"status": "error", "message": "quantity must be greater than zero."},
status=400,
)
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "quantity must be a positive integer."},
status=400,
)
# Determine price: use offer_price if offer is active, else regular price
price = ticket_type.price
if ticket_type.is_offer:
# Check if offer is currently valid (if dates are set)
today = date.today()
offer_valid = True
if ticket_type.offer_start_date and ticket_type.offer_start_date > today:
offer_valid = False
if ticket_type.offer_end_date and ticket_type.offer_end_date < today:
offer_valid = False
if offer_valid and ticket_type.offer_price > 0:
price = ticket_type.offer_price
# Check if cart item already exists for this user + ticket_type
cart_item, created = Cart.objects.get_or_create(
user=user,
ticket_type=ticket_type,
defaults={
"ticket_meta": ticket_type.ticket_meta,
"quantity": quantity,
"price": price,
},
)
if not created:
# Update existing cart item: increment quantity
cart_item.quantity += quantity
cart_item.price = price # Update price in case offer changed
cart_item.save()
return JsonResponse(
{
"status": "success",
"message": "TicketType added to cart." if created else "Cart item updated.",
"cart_item": _cart_to_dict(cart_item),
},
status=201 if created else 200,
)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class DeleteFromCartAPI(APIView):
"""
Remove or decrement TicketType from Cart (when customer clicks minus button).
Body: token, username, ticket_type_id, quantity (optional, defaults to 1).
Decrements quantity by 1 (or by given quantity). If quantity becomes 0 or less,
the cart item is deleted.
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
ticket_type_id = data.get("ticket_type_id")
quantity = data.get("quantity", 1) # Default to 1 for minus button click
if not ticket_type_id:
return JsonResponse(
{"status": "error", "message": "ticket_type_id is required."},
status=400,
)
try:
quantity = int(quantity)
if quantity <= 0:
return JsonResponse(
{"status": "error", "message": "quantity must be greater than zero."},
status=400,
)
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "quantity must be a positive integer."},
status=400,
)
try:
cart_item = Cart.objects.get(user=user, ticket_type_id=ticket_type_id)
except Cart.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "Cart item not found for this ticket type."},
status=404,
)
cart_item.quantity -= quantity
if cart_item.quantity <= 0:
cart_item.delete()
return JsonResponse(
{
"status": "success",
"message": "TicketType removed from cart.",
"cart_item": None,
"removed": True,
},
status=200,
)
cart_item.save()
return JsonResponse(
{
"status": "success",
"message": "Cart quantity updated.",
"cart_item": _cart_to_dict(cart_item),
"removed": False,
},
status=200,
)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class CheckoutAPI(APIView):
"""
Checkout the authenticated user's cart: create one Booking per cart line,
call transaction_initiate in banking_operations, then clear the checked-out cart items.
Body: token, username.
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
cart_items = list(
Cart.objects.filter(user=user, is_active=True).select_related(
"ticket_meta", "ticket_type", "ticket_meta__event"
)
)
if not cart_items:
return JsonResponse(
{"status": "error", "message": "Cart is empty. Add ticket types before checkout."},
status=400,
)
total_amount = Decimal("0")
created_bookings = []
cart_ids_to_clear = []
for item in cart_items:
event_name = (
(item.ticket_meta.event.name or "EVT")[:3].upper()
if item.ticket_meta.event_id else "EVT"
)
booking_id = event_name + uuid.uuid4().hex[:10].upper()
line_total = Decimal(str(item.price)) * item.quantity
total_amount += line_total
booking = Booking.objects.create(
booking_id=booking_id,
user=user,
ticket_meta=item.ticket_meta,
ticket_type=item.ticket_type,
quantity=item.quantity,
price=item.price,
)
created_bookings.append(booking)
cart_ids_to_clear.append(item.id)
reference_id = ",".join(b.booking_id for b in created_bookings)
result = transaction_initiate(
request=request,
user=user,
amount=float(total_amount),
currency="INR",
reference_type="checkout",
reference_id=reference_id,
bookings=created_bookings,
extra_data=None,
)
if not result.get("success"):
for b in created_bookings:
b.delete()
return JsonResponse(
{
"status": "error",
"message": result.get("message", "Transaction initiation failed."),
},
status=502,
)
transaction_id = result.get("transaction_id")
if transaction_id:
for b in created_bookings:
b.transaction_id = transaction_id
b.save(update_fields=["transaction_id"])
Cart.objects.filter(id__in=cart_ids_to_clear).delete()
log("info", "Checkout complete", request=request, user=user, logger_data={
"booking_ids": [b.booking_id for b in created_bookings],
"total_amount": str(total_amount),
})
response_payload = {
"status": "success",
"message": "Checkout complete. Proceed to payment.",
"booking_ids": [b.booking_id for b in created_bookings],
"total_amount": str(total_amount),
"transaction_id": result.get("transaction_id"),
"payment_url": result.get("payment_url"),
}
return JsonResponse(response_payload, status=200)
except Exception as e:
log("error", "Checkout exception", request=request, logger_data={"error": str(e)})
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class CheckInAPI(APIView):
"""
Check-in a ticket by scanning QR code (ticket_id).
Body: token, username, ticket_id (required).
Looks up Ticket by ticket_id. If found and not already checked in,
sets is_checked_in=True and checked_in_date_time=now. Returns success or
appropriate error (ticket not found / already checked in).
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True)
if error_response:
return error_response
ticket_id = data.get("ticket_id")
if not ticket_id or not str(ticket_id).strip():
return JsonResponse(
{"status": "error", "message": "ticket_id is required."},
status=400,
)
ticket_id = str(ticket_id).strip()
try:
ticket = Ticket.objects.select_related(
"booking", "booking__ticket_meta", "booking__ticket_meta__event", "booking__ticket_type"
).get(ticket_id=ticket_id)
except Ticket.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "Ticket not found."},
status=404,
)
if ticket.is_checked_in:
log("info", "Check-in duplicate - ticket already checked in", request=request, user=user, logger_data={"ticket_id": ticket_id})
return JsonResponse(
{
"status": "success",
"message": "Ticket already checked in.",
"ticket_id": ticket.ticket_id,
"is_checked_in": True,
"checked_in_date_time": ticket.checked_in_date_time.isoformat() if ticket.checked_in_date_time else None,
"event_name": ticket.booking.ticket_meta.event.name if ticket.booking.ticket_meta_id else None,
"booking_id": ticket.booking.booking_id,
},
status=200,
)
ticket.is_checked_in = True
ticket.checked_in_date_time = timezone.now()
ticket.save(update_fields=["is_checked_in", "checked_in_date_time"])
log("info", "Check-in successful", request=request, user=user, logger_data={"ticket_id": ticket_id, "booking_id": ticket.booking.booking_id})
return JsonResponse(
{
"status": "success",
"message": "Check-in successful.",
"ticket_id": ticket.ticket_id,
"is_checked_in": True,
"checked_in_date_time": ticket.checked_in_date_time.isoformat(),
"event_name": ticket.booking.ticket_meta.event.name if ticket.booking.ticket_meta_id else None,
"booking_id": ticket.booking.booking_id,
},
status=200,
)
except Exception as e:
log("error", "Check-in exception", request=request, logger_data={"error": str(e)})
return JsonResponse({"status": "error", "message": str(e)}, status=500)

View File

@@ -0,0 +1,489 @@
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.forms.models import model_to_dict
from rest_framework.views import APIView
from bookings.models import TicketMeta, TicketType
from events.models import Event
from mobile_api.utils import validate_token_and_get_user
def _ticket_meta_to_dict(meta):
"""Serialize TicketMeta for JSON."""
data = model_to_dict(
meta,
fields=[
"id",
"ticket_name",
"maximum_quantity",
"available_quantity",
"is_active",
"created_date",
"updated_date",
],
)
data["event_id"] = meta.event_id
return data
def _ticket_type_to_dict(tt):
"""Serialize TicketType for JSON."""
data = model_to_dict(
tt,
fields=[
"id",
"ticket_type",
"ticket_type_description",
"quantity",
"price",
"is_active",
"created_date",
"updated_date",
"is_offer",
"offer_percentage",
"offer_price",
"offer_start_date",
"offer_end_date",
],
)
data["ticket_meta_id"] = tt.ticket_meta_id
return data
# ---------- TicketMeta (event-level ticket config) CRUD ----------
@method_decorator(csrf_exempt, name="dispatch")
class TicketMetaCreateAPI(APIView):
"""
Create a new TicketMeta (event-level ticket config).
Body: token, username, event_id, ticket_name, maximum_quantity,
available_quantity (optional, defaults to maximum_quantity), is_active (optional, default true).
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
event_id = data.get("event_id")
ticket_name = data.get("ticket_name")
maximum_quantity = data.get("maximum_quantity")
available_quantity = data.get("available_quantity")
is_active = data.get("is_active", True)
if not event_id or not ticket_name or maximum_quantity is None:
return JsonResponse(
{
"status": "error",
"message": "event_id, ticket_name and maximum_quantity are required.",
},
status=400,
)
try:
event = Event.objects.get(id=event_id)
except Event.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "Event not found."},
status=404,
)
try:
maximum_quantity = int(maximum_quantity)
available_quantity = int(available_quantity) if available_quantity is not None else maximum_quantity
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "maximum_quantity and available_quantity must be integers."},
status=400,
)
if maximum_quantity <= 0:
return JsonResponse(
{"status": "error", "message": "maximum_quantity must be greater than zero."},
status=400,
)
meta = TicketMeta.objects.create(
event=event,
ticket_name=ticket_name,
maximum_quantity=maximum_quantity,
available_quantity=available_quantity,
is_active=bool(is_active),
)
return JsonResponse(
{"status": "success", "ticket_meta": _ticket_meta_to_dict(meta)},
status=201,
)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketMetaListAPI(APIView):
"""List TicketMeta, optionally filtered by event_id. Body: token, username, event_id (optional)."""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
event_id = data.get("event_id")
qs = TicketMeta.objects.filter(is_active=True).order_by("-created_date")
if event_id:
qs = qs.filter(event_id=event_id)
items = [_ticket_meta_to_dict(m) for m in qs]
return JsonResponse({"status": "success", "ticket_metas": items}, status=200)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketMetaUpdateAPI(APIView):
"""
Update TicketMeta. Body: token, username, ticket_meta_id (required);
ticket_name, maximum_quantity, available_quantity, is_active (optional).
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
pk = data.get("ticket_meta_id")
if not pk:
return JsonResponse({"status": "error", "message": "ticket_meta_id is required."}, status=400)
try:
meta = TicketMeta.objects.get(id=pk)
except TicketMeta.DoesNotExist:
return JsonResponse({"status": "error", "message": "TicketMeta not found."}, status=404)
if data.get("ticket_name") is not None:
meta.ticket_name = data["ticket_name"]
if data.get("maximum_quantity") is not None:
try:
val = int(data["maximum_quantity"])
if val <= 0:
return JsonResponse(
{"status": "error", "message": "maximum_quantity must be greater than zero."},
status=400,
)
meta.maximum_quantity = val
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "maximum_quantity must be an integer."},
status=400,
)
if data.get("available_quantity") is not None:
try:
meta.available_quantity = int(data["available_quantity"])
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "available_quantity must be an integer."},
status=400,
)
if data.get("is_active") is not None:
meta.is_active = bool(data["is_active"])
meta.save()
return JsonResponse({"status": "success", "ticket_meta": _ticket_meta_to_dict(meta)}, status=200)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketMetaDeleteAPI(APIView):
"""Delete TicketMeta. Body: token, username, ticket_meta_id."""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
pk = data.get("ticket_meta_id")
if not pk:
return JsonResponse({"status": "error", "message": "ticket_meta_id is required."}, status=400)
try:
meta = TicketMeta.objects.get(id=pk)
except TicketMeta.DoesNotExist:
return JsonResponse({"status": "error", "message": "TicketMeta not found."}, status=404)
meta.delete()
return JsonResponse({"status": "success", "message": "TicketMeta deleted successfully."}, status=200)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketMetaDeactivateAPI(APIView):
"""Deactivate a TicketMeta (set is_active=False). Body: token, username, ticket_meta_id."""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
pk = data.get("ticket_meta_id")
if not pk:
return JsonResponse({"status": "error", "message": "ticket_meta_id is required."}, status=400)
try:
meta = TicketMeta.objects.get(id=pk)
except TicketMeta.DoesNotExist:
return JsonResponse({"status": "error", "message": "TicketMeta not found."}, status=404)
meta.is_active = False
meta.save()
return JsonResponse(
{"status": "success", "message": "TicketMeta deactivated.", "ticket_meta": _ticket_meta_to_dict(meta)},
status=200,
)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
# ---------- TicketType CRUD ----------
@method_decorator(csrf_exempt, name="dispatch")
class TicketTypeCreateAPI(APIView):
"""
Create a new TicketType.
Body: token, username, ticket_meta_id, ticket_type, ticket_type_description, quantity, price (required);
is_active (optional, default true); is_offer, offer_percentage, offer_price, offer_start_date, offer_end_date (optional).
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
ticket_meta_id = data.get("ticket_meta_id")
ticket_type_name = data.get("ticket_type")
ticket_type_description = data.get("ticket_type_description")
quantity = data.get("quantity")
price = data.get("price")
is_active = data.get("is_active", True)
is_offer = data.get("is_offer", False)
offer_percentage = data.get("offer_percentage", 0)
offer_price = data.get("offer_price", 0)
offer_start_date = data.get("offer_start_date")
offer_end_date = data.get("offer_end_date")
if not ticket_meta_id or not ticket_type_name or quantity is None or price is None:
return JsonResponse(
{
"status": "error",
"message": "ticket_meta_id, ticket_type, ticket_type_description, quantity and price are required.",
},
status=400,
)
try:
ticket_meta = TicketMeta.objects.get(id=ticket_meta_id)
except TicketMeta.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "TicketMeta not found."},
status=404,
)
try:
quantity = int(quantity)
price = float(price)
offer_percentage = int(offer_percentage) if offer_percentage is not None else 0
offer_price = float(offer_price) if offer_price is not None else 0
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "quantity, price, offer_percentage and offer_price must be numeric."},
status=400,
)
if quantity <= 0:
return JsonResponse(
{"status": "error", "message": "quantity must be greater than zero."},
status=400,
)
tt = TicketType.objects.create(
ticket_meta=ticket_meta,
ticket_type=ticket_type_name,
ticket_type_description=ticket_type_description or "",
quantity=quantity,
price=price,
is_active=bool(is_active),
is_offer=bool(is_offer),
offer_percentage=offer_percentage,
offer_price=offer_price,
offer_start_date=offer_start_date,
offer_end_date=offer_end_date,
)
return JsonResponse(
{"status": "success", "ticket_type": _ticket_type_to_dict(tt)},
status=201,
)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketTypeListAPI(APIView):
"""List TicketType, optionally filtered by ticket_meta_id. Body: token, username, ticket_meta_id (optional)."""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
ticket_meta_id = data.get("ticket_meta_id")
qs = TicketType.objects.filter(is_active=True).order_by("-created_date")
if ticket_meta_id:
qs = qs.filter(ticket_meta_id=ticket_meta_id)
items = [_ticket_type_to_dict(tt) for tt in qs]
return JsonResponse({"status": "success", "ticket_types": items}, status=200)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketTypeUpdateAPI(APIView):
"""
Update TicketType. Body: token, username, ticket_type_id (required);
ticket_type, ticket_type_description, quantity, price, is_active,
is_offer, offer_percentage, offer_price, offer_start_date, offer_end_date (optional).
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
pk = data.get("ticket_type_id")
if not pk:
return JsonResponse({"status": "error", "message": "ticket_type_id is required."}, status=400)
try:
tt = TicketType.objects.get(id=pk)
except TicketType.DoesNotExist:
return JsonResponse({"status": "error", "message": "TicketType not found."}, status=404)
if data.get("ticket_type") is not None:
tt.ticket_type = data["ticket_type"]
if data.get("ticket_type_description") is not None:
tt.ticket_type_description = data["ticket_type_description"]
if data.get("quantity") is not None:
try:
val = int(data["quantity"])
if val <= 0:
return JsonResponse(
{"status": "error", "message": "quantity must be greater than zero."},
status=400,
)
tt.quantity = val
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "quantity must be an integer."},
status=400,
)
if data.get("price") is not None:
try:
tt.price = float(data["price"])
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "price must be numeric."},
status=400,
)
if data.get("is_active") is not None:
tt.is_active = bool(data["is_active"])
if data.get("is_offer") is not None:
tt.is_offer = bool(data["is_offer"])
if data.get("offer_percentage") is not None:
try:
tt.offer_percentage = int(data["offer_percentage"])
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "offer_percentage must be an integer."},
status=400,
)
if data.get("offer_price") is not None:
try:
tt.offer_price = float(data["offer_price"])
except (TypeError, ValueError):
return JsonResponse(
{"status": "error", "message": "offer_price must be numeric."},
status=400,
)
if "offer_start_date" in data:
tt.offer_start_date = data["offer_start_date"]
if "offer_end_date" in data:
tt.offer_end_date = data["offer_end_date"]
tt.save()
return JsonResponse({"status": "success", "ticket_type": _ticket_type_to_dict(tt)}, status=200)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketTypeDeactivateAPI(APIView):
"""Deactivate a TicketType (set is_active=False). Body: token, username, ticket_type_id."""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
pk = data.get("ticket_type_id")
if not pk:
return JsonResponse({"status": "error", "message": "ticket_type_id is required."}, status=400)
try:
tt = TicketType.objects.get(id=pk)
except TicketType.DoesNotExist:
return JsonResponse({"status": "error", "message": "TicketType not found."}, status=404)
tt.is_active = False
tt.save()
return JsonResponse(
{"status": "success", "message": "TicketType deactivated.", "ticket_type": _ticket_type_to_dict(tt)},
status=200,
)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class TicketTypeDeleteAPI(APIView):
"""Delete TicketType. Body: token, username, ticket_type_id."""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
pk = data.get("ticket_type_id")
if not pk:
return JsonResponse({"status": "error", "message": "ticket_type_id is required."}, status=400)
try:
tt = TicketType.objects.get(id=pk)
except TicketType.DoesNotExist:
return JsonResponse({"status": "error", "message": "TicketType not found."}, status=404)
tt.delete()
return JsonResponse({"status": "success", "message": "TicketType deleted successfully."}, status=200)
except Exception as e:
return JsonResponse({"status": "error", "message": str(e)}, status=500)

View File

@@ -1,17 +1,34 @@
from django.urls import path
from bookings.tickets_view.api import (
TicketCreateAPI,
TicketListAPI,
TicketUpdateAPI,
TicketDeleteAPI,
from bookings.tickets_view.ticket_meta_type import (
TicketMetaCreateAPI,
TicketMetaListAPI,
TicketMetaUpdateAPI,
TicketMetaDeleteAPI,
TicketMetaDeactivateAPI,
TicketTypeCreateAPI,
TicketTypeListAPI,
TicketTypeUpdateAPI,
TicketTypeDeleteAPI,
TicketTypeDeactivateAPI,
)
from bookings.tickets_view.booking_api import AddToCartAPI, DeleteFromCartAPI, CheckoutAPI, CheckInAPI
urlpatterns = [
path("tickets/create/", TicketCreateAPI.as_view(), name="ticket_create"),
path("tickets/list/", TicketListAPI.as_view(), name="ticket_list"),
path("tickets/update/", TicketUpdateAPI.as_view(), name="ticket_update"),
path("tickets/delete/", TicketDeleteAPI.as_view(), name="ticket_delete"),
path("ticket-meta/create/", TicketMetaCreateAPI.as_view(), name="ticket_meta_create"),
path("ticket-meta/list/", TicketMetaListAPI.as_view(), name="ticket_meta_list"),
path("ticket-meta/update/", TicketMetaUpdateAPI.as_view(), name="ticket_meta_update"),
path("ticket-meta/delete/", TicketMetaDeleteAPI.as_view(), name="ticket_meta_delete"),
path("ticket-meta/deactivate/", TicketMetaDeactivateAPI.as_view(), name="ticket_meta_deactivate"),
path("ticket-type/create/", TicketTypeCreateAPI.as_view(), name="ticket_type_create"),
path("ticket-type/list/", TicketTypeListAPI.as_view(), name="ticket_type_list"),
path("ticket-type/update/", TicketTypeUpdateAPI.as_view(), name="ticket_type_update"),
path("ticket-type/delete/", TicketTypeDeleteAPI.as_view(), name="ticket_type_delete"),
path("ticket-type/deactivate/", TicketTypeDeactivateAPI.as_view(), name="ticket_type_deactivate"),
path("cart/add/", AddToCartAPI.as_view(), name="add_to_cart"),
path("cart/delete/", DeleteFromCartAPI.as_view(), name="delete_from_cart"),
path("checkout/", CheckoutAPI.as_view(), name="checkout"),
path("check-in/", CheckInAPI.as_view(), name="check_in"),
]