• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gcivil-nyu-org / team3-wed-spring25 / #632597538

04 May 2025 06:18PM UTC coverage: 95.672%. First build
#632597538

Pull #450

travis-ci

Pull Request #450: Develop to Main

435 of 476 new or added lines in 5 files covered. (91.39%)

5770 of 6031 relevant lines covered (95.67%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.44
/booking/views.py
1
# bookings/views.py
2
from django.shortcuts import render, redirect, get_object_or_404
1✔
3
from django.contrib.auth.decorators import login_required
1✔
4
import datetime as dt
1✔
5
from django.utils import timezone
1✔
6
from django.http import JsonResponse
1✔
7
from .models import Booking, BookingSlot
1✔
8
from .forms import (
1✔
9
    BookingForm,
10
    BookingSlotFormSet,
11
    BookingSlotForm,
12
)
13
from listings.models import Listing
1✔
14
from listings.forms import ReviewForm, HALF_HOUR_CHOICES
1✔
15
from django.db import transaction
1✔
16
from .utils import (
1✔
17
    block_out_booking,
18
    restore_booking_availability,
19
    generate_recurring_dates,
20
    generate_booking_slots,
21
)
22
from accounts.models import Notification
1✔
23

24

25
@login_required
1✔
26
def available_times(request):
1✔
27
    listing_id = request.GET.get("listing_id")
1✔
28
    date_str = request.GET.get("date")
1✔
29
    ref_date_str = request.GET.get("ref_date")
1✔
30
    max_time_str = request.GET.get("max_time")
1✔
31
    min_time_str = request.GET.get("min_time")
1✔
32
    if not listing_id or not date_str:
1✔
33
        return JsonResponse({"times": []})
1✔
34
    try:
1✔
35
        booking_date = dt.datetime.strptime(date_str, "%Y-%m-%d").date()
1✔
36
    except ValueError:
1✔
37
        return JsonResponse({"times": []})
1✔
38
    listing = get_object_or_404(Listing, pk=listing_id)
1✔
39

40
    ref_slot = None
1✔
41
    if ref_date_str:
1✔
42
        try:
1✔
43
            ref_date = dt.datetime.strptime(ref_date_str, "%Y-%m-%d").date()
1✔
44
        except ValueError:
×
45
            ref_date = None
×
46
        if ref_date:
1✔
47
            ref_slots = listing.slots.filter(
1✔
48
                start_date__lte=ref_date, end_date__gte=ref_date
49
            )
50
            if ref_slots.exists():
1✔
51
                ref_slot = ref_slots.first()
1✔
52

53
    slots = listing.slots.filter(
1✔
54
        start_date__lte=booking_date, end_date__gte=booking_date
55
    )
56
    if ref_slot:
1✔
57
        slots = slots.filter(pk=ref_slot.pk)
1✔
58

59
    valid_times = set()
1✔
60
    for slot in slots:
1✔
61
        if slot.start_time == slot.end_time:
1✔
62
            current_dt = dt.datetime.combine(booking_date, dt.time(0, 0))
×
63
            end_dt = current_dt + dt.timedelta(days=1)
×
64
        else:
65
            if booking_date == slot.start_date:
1✔
66
                current_dt = dt.datetime.combine(booking_date, slot.start_time)
1✔
67
            else:
68
                current_dt = dt.datetime.combine(booking_date, dt.time(0, 0))
×
69
            if booking_date == slot.end_date:
1✔
70
                end_dt = dt.datetime.combine(booking_date, slot.end_time)
1✔
71
            else:
72
                end_dt = dt.datetime.combine(
×
73
                    booking_date, dt.time(0, 0)
74
                ) + dt.timedelta(days=1)
75
        while current_dt <= end_dt:
1✔
76
            valid_times.add(current_dt.strftime("%H:%M"))
1✔
77
            current_dt += dt.timedelta(minutes=30)
1✔
78
    times = sorted(valid_times)
1✔
79

80
    # Filter out past times for today's date
81
    today = dt.date.today()
1✔
82
    if booking_date == today:
1✔
83
        # Get current time
NEW
84
        now = dt.datetime.now()
×
85

86
        # Calculate the next available time slot
NEW
87
        current_minutes = now.minute
×
NEW
88
        current_hour = now.hour
×
89

90
        # Round up to the nearest half hour
NEW
91
        if current_minutes < 30:
×
NEW
92
            next_slot_minutes = 30
×
NEW
93
            next_slot_hour = current_hour
×
94
        else:
NEW
95
            next_slot_minutes = 0
×
NEW
96
            next_slot_hour = current_hour + 1
×
97

98
        # Format as HH:MM string
NEW
99
        next_slot_str = f"{next_slot_hour:02d}:{next_slot_minutes:02d}"
×
100

101
        # Filter out times before the next valid slot
NEW
102
        times = [t for t in times if t >= next_slot_str]
×
103

104
    if max_time_str:
1✔
105
        times = [t for t in times if t <= max_time_str]
1✔
106
    if min_time_str:
1✔
107
        times = [t for t in times if t >= min_time_str]
1✔
108
    print(
1✔
109
        "Returning times for listing",
110
        listing_id,
111
        "on",
112
        booking_date,
113
        "ref_date=",
114
        ref_date_str,
115
        ":",
116
        times,
117
    )
118
    return JsonResponse({"times": times})
1✔
119

120

121
@login_required
1✔
122
def book_listing(request, listing_id):
1✔
123
    listing = get_object_or_404(Listing, pk=listing_id)
1✔
124
    error_messages = []
1✔
125
    success_messages = []
1✔
126

127
    if request.user == listing.user:
1✔
128
        error_messages.append("You cannot book your own parking spot.")
1✔
129
        return redirect("view_listings")
1✔
130

131
    # Create initial data with user's email
132
    initial_data = {}
1✔
133
    if request.user.is_authenticated:
1✔
134
        initial_data["email"] = request.user.email
1✔
135

136
    # Create form with initial data
137
    if request.method == "POST":
1✔
138
        booking_form = BookingForm(request.POST)
1✔
139
        is_recurring = request.POST.get("is_recurring") == "true"
1✔
140

141
        if booking_form.is_valid():
1✔
142
            try:
1✔
143
                with transaction.atomic():
1✔
144
                    if not is_recurring:
1✔
145
                        # Handle regular (non-recurring) booking
146
                        booking = booking_form.save(commit=False)
1✔
147
                        booking.user = request.user
1✔
148
                        booking.listing = listing
1✔
149
                        booking.status = "PENDING"
1✔
150
                        booking.save()  # Save so we can use it for formset instance
1✔
151

152
                        slot_formset = BookingSlotFormSet(
1✔
153
                            request.POST,
154
                            instance=booking,
155
                            form_kwargs={"listing": listing},
156
                            prefix="form",
157
                        )
158
                        for form in slot_formset.forms:
1✔
159
                            form.listing = listing
1✔
160

161
                        if slot_formset.is_valid():
1✔
162
                            slot_formset.save()
1✔
163

164
                            if booking.slots.exists():
1✔
165
                                tz = timezone.get_current_timezone()
1✔
166

167
                                def combine_slot(date, time):
1✔
168
                                    dt_obj = dt.datetime.combine(date, time)
1✔
169
                                    return timezone.make_aware(dt_obj, tz)
1✔
170

171
                                overall_start = min(
1✔
172
                                    combine_slot(s.start_date, s.start_time)
173
                                    for s in booking.slots.all()
174
                                )
175
                                overall_end = max(
1✔
176
                                    combine_slot(s.end_date, s.end_time)
177
                                    for s in booking.slots.all()
178
                                )
179
                                valid = False
1✔
180
                                for avail in listing.slots.all():
1✔
181
                                    avail_start = combine_slot(
1✔
182
                                        avail.start_date, avail.start_time
183
                                    )
184
                                    avail_end = combine_slot(
1✔
185
                                        avail.end_date, avail.end_time
186
                                    )
187
                                    if (
1✔
188
                                        overall_start >= avail_start
189
                                        and overall_end <= avail_end
190
                                    ):
191
                                        valid = True
1✔
192
                                        break
1✔
193
                                if not valid:
1✔
194
                                    raise ValueError(
×
195
                                        "Booking must be within a single availability slot."
196
                                    )
197

198
                            total_hours = 0
1✔
199
                            for slot in booking.slots.all():
1✔
200
                                start_dt = dt.datetime.combine(
1✔
201
                                    slot.start_date, slot.start_time
202
                                )
203
                                end_dt = dt.datetime.combine(
1✔
204
                                    slot.end_date, slot.end_time
205
                                )
206
                                duration = (end_dt - start_dt).total_seconds() / 3600.0
1✔
207
                                total_hours += duration
1✔
208
                            booking.total_price = total_hours * float(
1✔
209
                                listing.rent_per_hour
210
                            )
211
                            booking.save()
1✔
212

213
                            # Create notification for the listing owner
214
                            Notification.objects.create(
1✔
215
                                sender=request.user,
216
                                recipient=listing.user,
217
                                subject=f"New Booking Request for {listing.title}",
218
                                content=f"{request.user.username} requested to book your spot '{listing.title}'. "
219
                                f"Please review and approve or decline this booking.",
220
                                notification_type="BOOKING",
221
                            )
222

223
                            success_messages.append("Booking request created!")
1✔
224
                            return redirect("my_bookings")
1✔
225
                        else:
226
                            raise ValueError(
×
227
                                "Please fix the errors in the booking form."
228
                            )
229
                    else:
230
                        # Handle recurring booking
231
                        start_date = request.POST.get("recurring-start_date")
1✔
232
                        start_time = request.POST.get("recurring-start_time")
1✔
233
                        end_time = request.POST.get("recurring-end_time")
1✔
234
                        pattern = request.POST.get("recurring_pattern", "daily")
1✔
235
                        is_overnight = request.POST.get("recurring-overnight") == "on"
1✔
236

237
                        if pattern == "daily":
1✔
238
                            end_date = request.POST.get("recurring-end_date")
1✔
239
                            if not all([start_date, start_time, end_time, end_date]):
1✔
240
                                raise ValueError(
1✔
241
                                    "Start date, end date, start time, end time required for recurring bookings."
242
                                )
243
                            start_date = dt.datetime.strptime(
1✔
244
                                start_date, "%Y-%m-%d"
245
                            ).date()
246
                            start_time = dt.datetime.strptime(
1✔
247
                                start_time, "%H:%M"
248
                            ).time()
249
                            end_time = dt.datetime.strptime(end_time, "%H:%M").time()
1✔
250
                            end_date = dt.datetime.strptime(end_date, "%Y-%m-%d").date()
1✔
251
                            if end_date < start_date:
1✔
252
                                raise ValueError(
1✔
253
                                    "End date must be on or after start date."
254
                                )
255
                            dates = generate_recurring_dates(
1✔
256
                                start_date, "daily", end_date=end_date
257
                            )
258
                        elif pattern == "weekly":
1✔
259
                            if not all([start_date, start_time, end_time]):
1✔
260
                                raise ValueError(
×
261
                                    "Start date, start time, end time required for recurring bookings."
262
                                )
263
                            start_date = dt.datetime.strptime(
1✔
264
                                start_date, "%Y-%m-%d"
265
                            ).date()
266
                            start_time = dt.datetime.strptime(
1✔
267
                                start_time, "%H:%M"
268
                            ).time()
269
                            end_time = dt.datetime.strptime(end_time, "%H:%M").time()
1✔
270
                            weeks_str = request.POST.get("recurring-weeks")
1✔
271
                            if not weeks_str:
1✔
272
                                raise ValueError(
×
273
                                    "Number of weeks is required for weekly recurring pattern."
274
                                )
275
                            weeks = int(weeks_str)
1✔
276
                            if weeks <= 0 or weeks > 52:
1✔
277
                                raise ValueError(
×
278
                                    "Number of weeks must be between 1 and 52."
279
                                )
280
                            dates = generate_recurring_dates(
1✔
281
                                start_date, "weekly", weeks=weeks
282
                            )
283

284
                        if start_time >= end_time and not is_overnight:
1✔
285
                            raise ValueError(
1✔
286
                                "Start time must be before end time unless overnight booking is selected."
287
                            )
288

289
                        booking_slots = generate_booking_slots(
1✔
290
                            dates, start_time, end_time, is_overnight
291
                        )
292

293
                        unavailable_dates = []
1✔
294
                        for slot in booking_slots:
1✔
295
                            start_dt = dt.datetime.combine(
1✔
296
                                slot["start_date"], slot["start_time"]
297
                            )
298
                            end_dt = dt.datetime.combine(
1✔
299
                                slot["end_date"], slot["end_time"]
300
                            )
301
                            start_dt = timezone.make_aware(start_dt)
1✔
302
                            end_dt = timezone.make_aware(end_dt)
1✔
303
                            if not listing.is_available_for_range(start_dt, end_dt):
1✔
304
                                date_str = slot["start_date"].strftime("%Y-%m-%d")
1✔
305
                                unavailable_dates.append(date_str)
1✔
306
                        if unavailable_dates:
1✔
307
                            error_msg = "Some of those times unavailable. Please review timeslots and try again."
1✔
308
                            raise ValueError(error_msg)
1✔
309

310
                        booking = booking_form.save(commit=False)
1✔
311
                        booking.user = request.user
1✔
312
                        booking.listing = listing
1✔
313
                        booking.status = "PENDING"
1✔
314
                        booking.save()
1✔
315

316
                        total_hours = 0
1✔
317
                        for slot_data in booking_slots:
1✔
318
                            slot = BookingSlot(
1✔
319
                                booking=booking,
320
                                start_date=slot_data["start_date"],
321
                                start_time=slot_data["start_time"],
322
                                end_date=slot_data["end_date"],
323
                                end_time=slot_data["end_time"],
324
                            )
325
                            slot.save()
1✔
326
                            start_dt = dt.datetime.combine(
1✔
327
                                slot_data["start_date"], slot_data["start_time"]
328
                            )
329
                            end_dt = dt.datetime.combine(
1✔
330
                                slot_data["end_date"], slot_data["end_time"]
331
                            )
332
                            duration = (end_dt - start_dt).total_seconds() / 3600.0
1✔
333
                            total_hours += duration
1✔
334
                        booking.total_price = total_hours * float(listing.rent_per_hour)
1✔
335
                        booking.save()
1✔
336

337
                        # Create notification for the listing owner
338
                        Notification.objects.create(
1✔
339
                            sender=request.user,
340
                            recipient=listing.user,
341
                            subject=f"New Recurring Booking Request for {listing.title}",
342
                            content=f"User {request.user.username} has requested \
343
                                a recurring booking for your parking spot '{listing.title}'. "
344
                            f"This booking includes {len(booking_slots)} dates. "
345
                            f"Please review and approve or decline this booking.",
346
                            notification_type="BOOKING",
347
                        )
348

349
                        success_messages.append(
1✔
350
                            f"Recurring booking created successfully for {len(booking_slots)} dates!"
351
                        )
352
                        return redirect("my_bookings")
1✔
353

354
            except ValueError as e:
1✔
355
                error_messages.append(str(e))
1✔
356
                slot_formset = BookingSlotFormSet(
1✔
357
                    form_kwargs={"listing": listing}, prefix="form"
358
                )
359
            except Exception as e:
×
360
                error_messages.append(f"An error occurred: {str(e)}")
×
361
                slot_formset = BookingSlotFormSet(
×
362
                    form_kwargs={"listing": listing}, prefix="form"
363
                )
364
        else:
365
            slot_formset = BookingSlotFormSet(
1✔
366
                request.POST, form_kwargs={"listing": listing}, prefix="form"
367
            )
368
            error_messages.append("Please fix the errors below.")
1✔
369
    else:
370
        booking_form = BookingForm(initial=initial_data)
1✔
371
        slot_formset = BookingSlotFormSet(
1✔
372
            form_kwargs={"listing": listing}, prefix="form"
373
        )
374

375
    recurring_form = BookingSlotForm(prefix="recurring", listing=listing)
1✔
376

377
    return render(
1✔
378
        request,
379
        "booking/book_listing.html",
380
        {
381
            "listing": listing,
382
            "booking_form": booking_form,
383
            "slot_formset": slot_formset,
384
            "recurring_form": recurring_form,
385
            "half_hour_choices": HALF_HOUR_CHOICES,
386
            "error_messages": error_messages,
387
            "success_messages": success_messages,
388
        },
389
    )
390

391

392
@login_required
1✔
393
def cancel_booking(request, booking_id):
1✔
394
    booking = get_object_or_404(Booking, pk=booking_id, user=request.user)
1✔
395

396
    # Create notification for the listing owner about the cancellation
397
    Notification.objects.create(
1✔
398
        sender=request.user,
399
        recipient=booking.listing.user,
400
        subject=f"Booking Canceled for {booking.listing.title}",
401
        content=f"User {request.user.username} canceled their booking for your spot '{booking.listing.title}'.",
402
        notification_type="BOOKING",
403
    )
404

405
    if booking.status == "APPROVED":
1✔
406
        restore_booking_availability(booking.listing, booking)
×
407
    booking.delete()
1✔
408
    return redirect("my_bookings")
1✔
409

410

411
@login_required
1✔
412
def manage_booking(request, booking_id, action):
1✔
413
    booking = get_object_or_404(Booking, pk=booking_id)
1✔
414
    if request.user != booking.listing.user:
1✔
415
        return redirect("my_bookings")
1✔
416

417
    if action == "approve":
1✔
418
        # First check if there are any conflicting bookings
419
        conflicts_found = False
1✔
420
        conflicting_bookings = []
1✔
421

422
        # Get all pending bookings for this listing except the current one
423
        other_pending_bookings = Booking.objects.filter(
1✔
424
            listing=booking.listing, status="PENDING"
425
        ).exclude(pk=booking_id)
426

427
        # Get all slots for the current booking
428
        current_booking_slots = booking.slots.all()
1✔
429

430
        # For each of the current booking's slots, check for conflicts with other pending bookings
431
        for current_slot in current_booking_slots:
1✔
432
            current_start = timezone.make_aware(
1✔
433
                dt.datetime.combine(current_slot.start_date, current_slot.start_time)
434
            )
435
            current_end = timezone.make_aware(
1✔
436
                dt.datetime.combine(current_slot.end_date, current_slot.end_time)
437
            )
438

439
            # Check each pending booking for conflicts
440
            for other_booking in other_pending_bookings:
1✔
441
                # Skip bookings we've already marked as conflicting
442
                if other_booking in conflicting_bookings:
×
443
                    continue
×
444

445
                # Check each slot in the other booking
446
                for other_slot in other_booking.slots.all():
×
447
                    other_start = timezone.make_aware(
×
448
                        dt.datetime.combine(
449
                            other_slot.start_date, other_slot.start_time
450
                        )
451
                    )
452
                    other_end = timezone.make_aware(
×
453
                        dt.datetime.combine(other_slot.end_date, other_slot.end_time)
454
                    )
455

456
                    # Check if the intervals overlap
457
                    if other_start < current_end and current_start < other_end:
×
458
                        conflicts_found = True
×
459
                        if other_booking not in conflicting_bookings:
×
460
                            conflicting_bookings.append(other_booking)
×
461
                        break  # No need to check other slots in this booking
×
462

463
        # Now approve the current booking
464
        booking.status = "APPROVED"
1✔
465
        booking.save()
1✔
466
        block_out_booking(booking.listing, booking)
1✔
467

468
        # Create notification for the user that their booking was approved
469
        Notification.objects.create(
1✔
470
            sender=request.user,
471
            recipient=booking.user,
472
            subject=f"Booking Approved for {booking.listing.title}",
473
            content=f"Your booking request for the parking spot '{booking.listing.title}' has been approved."
474
            f"You can now use this spot according to your booking schedule.",
475
            notification_type="BOOKING",
476
        )
477

478
        # If conflicts were found, decline those conflicting bookings
479
        if conflicts_found:
1✔
480
            for conflicting_booking in conflicting_bookings:
×
481
                # Skip if somehow this booking was already approved or declined
482
                if conflicting_booking.status != "PENDING":
×
483
                    continue
×
484

485
                # Mark the booking as declined
486
                conflicting_booking.status = "DECLINED"
×
487
                conflicting_booking.save()
×
488

489
                # Notify the user that their booking was declined due to conflict
490
                Notification.objects.create(
×
491
                    sender=request.user,
492
                    recipient=conflicting_booking.user,
493
                    subject=f"Booking Declined for {booking.listing.title}",
494
                    content=f"Your booking request for the parking spot '{booking.listing.title}' \
495
                        has been declined because another booking for the same time slot was approved first.",
496
                    notification_type="BOOKING",
497
                )
498

499
    elif action == "decline":
1✔
500
        if booking.status == "APPROVED":
1✔
501
            restore_booking_availability(booking.listing, booking)
1✔
502
        booking.status = "DECLINED"
1✔
503
        booking.save()
1✔
504

505
        # Create notification for the user that their booking was declined
506
        Notification.objects.create(
1✔
507
            sender=request.user,
508
            recipient=booking.user,
509
            subject=f"Booking Declined for {booking.listing.title}",
510
            content=f"Your booking request for the parking spot '{booking.listing.title}' has been declined."
511
            f"Please check for other available spots or contact the owner for more information.",
512
            notification_type="BOOKING",
513
        )
514

515
    return redirect("manage_listings")
1✔
516

517

518
@login_required
1✔
519
def review_booking(request, booking_id):
1✔
520
    booking = get_object_or_404(Booking, pk=booking_id, user=request.user)
1✔
521
    earliest_slot = min(
1✔
522
        (
523
            dt.datetime.combine(slot.start_date, slot.start_time)
524
            for slot in booking.slots.all()
525
        ),
526
        default=None,
527
    )
528
    if earliest_slot:
1✔
529
        booking_datetime = timezone.make_aware(
1✔
530
            earliest_slot, timezone.get_current_timezone()
531
        )
532
        if timezone.now() < booking_datetime:
1✔
533
            return redirect("my_bookings")
1✔
534
    else:
535
        return redirect("my_bookings")
×
536

537
    if hasattr(booking, "review"):
1✔
538
        return redirect("my_bookings")
1✔
539

540
    if request.method == "POST":
1✔
541
        form = ReviewForm(request.POST)
1✔
542
        if form.is_valid():
1✔
543
            review = form.save(commit=False)
1✔
544
            review.booking = booking
1✔
545
            review.listing = booking.listing
1✔
546
            review.user = request.user
1✔
547
            review.save()
1✔
548

549
            # Notify the listing owner about the new review
550
            notify_owner_listing_reviewed(review)
1✔
551

552
            return redirect("my_bookings")
1✔
553
    else:
554
        form = ReviewForm()
1✔
555
    return render(
1✔
556
        request, "booking/review_booking.html", {"form": form, "booking": booking}
557
    )
558

559

560
@login_required
1✔
561
def my_bookings(request):
1✔
562
    # Get all bookings for current user
563
    all_bookings = Booking.objects.filter(user=request.user)
1✔
564

565
    # Create separate lists for different priorities
566
    approved_unreviewed = []
1✔
567
    other_bookings = []
1✔
568
    approved_reviewed = []
1✔
569

570
    # Sort bookings into categories
571
    for booking in all_bookings:
1✔
572
        # Check if booking has been reviewed
573
        has_review = hasattr(booking, "review")
1✔
574

575
        if booking.status == "APPROVED":
1✔
576
            if has_review:
1✔
577
                # Lowest priority: Approved bookings that have been reviewed
578
                approved_reviewed.append(booking)
1✔
579
            else:
580
                # Highest priority: Approved bookings that haven't been reviewed
581
                approved_unreviewed.append(booking)
1✔
582
        else:
583
            # Medium priority: Other bookings (pending/declined)
584
            other_bookings.append(booking)
1✔
585

586
    # Sort each category
587
    approved_unreviewed.sort(key=lambda x: x.updated_at, reverse=True)
1✔
588
    other_bookings.sort(key=lambda x: x.created_at, reverse=True)
1✔
589
    approved_reviewed.sort(key=lambda x: x.updated_at, reverse=True)
1✔
590

591
    # Combine all lists in priority order
592
    sorted_bookings = approved_unreviewed + other_bookings + approved_reviewed
1✔
593

594
    # Process booking slots
595
    for booking in sorted_bookings:
1✔
596
        # Get all slots for this booking
597
        slots = booking.slots.all().order_by("start_date", "start_time")
1✔
598

599
        # Format the slots information for display
600
        slots_info = []
1✔
601
        for slot in slots:
1✔
602
            slot_info = {
1✔
603
                "date": slot.start_date,
604
                "start_time": slot.start_time,
605
                "end_time": slot.end_time,
606
            }
607
            slots_info.append(slot_info)
1✔
608

609
        # Add slots_info attribute to the booking object
610
        booking.slots_info = slots_info
1✔
611

612
    return render(request, "booking/my_bookings.html", {"bookings": sorted_bookings})
1✔
613

614

615
def notify_owner_booking_created(booking):
1✔
616
    """Create a notification for the owner when a booking is created."""
617
    owner = booking.listing.user
×
618
    listing_title = booking.listing.title
×
619
    booker_username = booking.user.username
×
620

621
    # Create notification for the owner
622
    Notification.objects.create(
×
623
        sender=booking.user,
624
        recipient=owner,
625
        subject=f"New Booking Request for {listing_title}",
626
        content=f"User {booker_username} has requested to book your parking spot '{listing_title}'. "
627
        f"Please review and approve or decline this booking.",
628
        notification_type="BOOKING",
629
    )
630

631

632
def notify_owner_booking_canceled(booking):
1✔
633
    """Create a notification for the owner when a booking is canceled."""
634
    owner = booking.listing.user
×
635
    listing_title = booking.listing.title
×
636
    booker_username = booking.user.username
×
637

638
    # Create notification for the owner
639
    Notification.objects.create(
×
640
        sender=booking.user,
641
        recipient=owner,
642
        subject=f"Booking Canceled for {listing_title}",
643
        content=f"User {booker_username} has canceled their booking for your parking spot '{listing_title}'.",
644
        notification_type="BOOKING",
645
    )
646

647

648
def notify_user_booking_approved(booking):
1✔
649
    """Create a notification for the user when their booking is approved."""
650
    listing_title = booking.listing.title
×
651
    owner_username = booking.listing.user.username
×
652

653
    # Create notification for the booker
654
    Notification.objects.create(
×
655
        sender=booking.listing.user,
656
        recipient=booking.user,
657
        subject=f"Booking Approved for {listing_title}",
658
        content=f"Your booking request for the spot '{listing_title}' by {owner_username} has been approved.",
659
        notification_type="BOOKING",
660
    )
661

662

663
def notify_owner_listing_reviewed(review):
1✔
664
    """Create a notification for the owner when their listing is reviewed."""
665
    listing = review.listing
1✔
666
    owner = listing.user
1✔
667
    reviewer_username = review.user.username
1✔
668
    review_rating = review.rating
1✔
669

670
    # Create notification for the owner
671
    Notification.objects.create(
1✔
672
        sender=review.user,
673
        recipient=owner,
674
        subject=f"New Review for {listing.title}",
675
        content=f"User {reviewer_username} left a {review_rating}-star review for your listing '{listing.title}'.",
676
        notification_type="BOOKING",
677
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc