Hotel Booking App: Passing context throgh templates

Hi, I am making a hotel app and I want to pass the search information from index view all the way until the user makes a reservation. I am having massive problems doing this. I either get reverse errors or queryset errors when I try to pass it as a list. Here is my views.py. In short, if I were making a website for a hotel and wanted user to input dates on the index view, how can I take them through the other pages all the way to the make_reservation view where they will finalize the reservation? Thanks! :slight_smile:

def index(request):
    if request.method == 'GET':
        return render(request, 'index.html')
    return render(request, 'index.html')



def search_results(request):
    if request.method == 'GET':
        check_in_date = request.GET.get('check_in_date')
        check_out_date = request.GET.get('check_out_date')
        guests = request.GET.get('guests')
        check_in_date = datetime.strptime(check_in_date, '%Y-%m-%d').date()
        check_out_date = datetime.strptime(check_out_date, '%Y-%m-%d').date()
        available_rooms = Room.objects.filter(available=True)
        booked_rooms = Reservation.objects.filter(
            check_in_date__lte=check_out_date,
            check_out_date__gt=check_in_date
        ).values_list('room__room_no', flat=True)
        available_rooms = available_rooms.exclude(id__in=booked_rooms)
        
        available_rooms_list = [
            {'room_no': room.room_no, 'type': room.type, 'price': room.price, 'available': room.available}
            for room in available_rooms
        ]
        
        available_room_types = available_rooms.values_list('type', flat=True).distinct()

        request.session['reservation_context'] = {
            'check_in_date': str(check_in_date),
            'check_out_date': str(check_out_date),
            'guests': guests,
            'available_rooms': available_rooms_list,
            'available_room_types': available_room_types,
        }
        return redirect('make_res')

    return render(request, 'results.html')


def make_res(request):
    context = request.session.pop('reservation_context', None)
    return render(request, 'make_res.html', context)

Here is models

class Room(models.Model):
    
    TYPES = [
        ('double', 'Double'),
        ('king', 'King'),
        ('two_double', 'Two Double'),
        ('suite', 'Suite')
    ]
    
    room_no = models.IntegerField(unique=True)
    type = models.CharField(max_length=20, choices=TYPES)    
    price = models.IntegerField()
    available = models.BooleanField(default=True) 

    def __str__(self):
        return f"Room: {self.room_no} - ${self.price}"

    def mark_as_booked(self):
        self.available = False
        self.save()

    def mark_as_available(self):
        self.available = True
        self.save()


class Customer(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    phone_number = models.CharField(max_length=15, blank=True)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"
    
class Reservation(models.Model):
    room = models.ForeignKey(Room, on_delete=models.CASCADE)
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    check_in_date = models.DateField()
    check_out_date = models.DateField()
    seats_reserved = models.PositiveIntegerField(default=1)  

    def __str__(self):
        return f"Reservation for {self.customer} - Room No {self.room.room_no}"



Can you also include your urls.py file, and examples of some of the errors you’re getting?

I think you have some good ideas there involving sessions, but I’d like to see more of your project’s structure and the errors before I suggest anything. I think you might be over-complicating things :stuck_out_tongue:

Sure. Here they are

urlpatterns = [
    path('', views.index, name='index'),
    path('search_results/', views.search_results, name='search_results'),
    path('make_res/', views.make_res, name='make_res')
]

You’re probably right, I am probably overcomplicating things lol.

Can you also include some errors / tracebacks?

This is a side note after a quick glance at your models: is this an app for your own hotel where you will be setting prices? If not, you probably don’t want to use IntegerField for the price, because it’s pretty much guaranteed at some point a client will want a price with decimal points, ie 299.99. I’d recommend DecimalField there instead.

As a general rule, when you need to pass more than a single element across multiple views, you want to persist the data and pass the “key” allowing you to access that data later.

For example, you could store it in session. That has the benefit that the data goes away when the session is expired and removed.

Or, you could create a Model to hold the “reservation in progress”, passing the primary key between views. This provides the benefit that an authenticated user could possibly retrieve the pending reservation at a later time (subsequent login), or possibly even on a different computer.

Otherwise, trying to continuously pass data structures across views leads to some awkward and usually “fragile” code.

Thanks Ken. That totally makes sense. That’s what I was trying to do here:

        request.session['reservation_context'] = {
            'check_in_date': str(check_in_date),
            'check_out_date': str(check_out_date),
            'guests': guests,
            'available_rooms': available_rooms_list,
            'available_room_types': available_room_types,
        }
        return redirect('make_res')

When I run the code, I get this error:
Object of type QuerySet is not JSON serializable

So I guess I am not passing something to the session correctly?

The variable available_room_types is a QuerySet object. Do you need that in the reservation context you’ve saved to the session?

Edit: and as I look at further, available_rooms is too.

Correct. If you really need to keep that data (and can’t retrieve it when needed later), you need to force the queryset to be evaluated and converted to a serializable structure (A list perhaps?)
However, to avoid race conditions, I would suggest you not try to save that data. Rerun the queries when needed.

Thanks! Ok, so this is stuff that really shouldn’t be forced into the queryset. I guess I am confused about if/how a search can be executed on the index view and everything about the search gets carried to a final reservation view. Like if you’re on a website and you search the rooms, see the rooms, and select the room type. I am guessing all of this should be stored in a session at the index view. I’ll keep playing around with it to see if I can get it.

Just a quick spitball here, if it were me I would probably create something like a “tentative reservation” model, that relates to a new intermediary model (that will capture prices etc at the time of creation, since prices will likely fluctuate often), and that intermediary model would relate to the search results so they can be referenced later. And if the customer completes the booking then transpose the “tentative reservation” object into a final reservation object. And of course the “tentative reservation” object’s PK gets stored in the session, since they will probably not be logged in. If they return to the site later and the session is still valid you get the “tentative reservation” model and all of your quoted prices are available, like a snapshot. And of course eventually expire the “tentative reservation” model, you don’t want the client to have the quoted prices forever. This way also allows the client to easily review tentative models to see how many result in sale conversions, dropoffs, etc., which they’d probably appreciate for analytics.

And further to @KenWhitesell point about race conditions, that’s something you’ll really want to be careful to prevent in general.

For example, if you had two customers trying to book the same room at the same time, you have to make sure:

  • You don’t double-book a room
  • You don’t book one client to a room and then immediately book the other customer in place of the original booking
  • If you’re processing payments you don’t take payment from both but only book one of them to the room

You’ll want to block booking on a room for date ranges when there’s a tentative or pending booking. You will probably also need to make use of row locking.