4

So I got these tasks:

  • Creating meeting room

  • Get meeting room reservations and the possibility to filter by employee

  • Create reservation (Reservation has title, from and to dates, employees)

First of all I have created a meeting room, this is the model:

class MeetingRooms(models.Model):
    public_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, blank=False, null=False, max_length=36)

    creator = models.ForeignKey(Employees, on_delete=models.CASCADE)
    reservations = models.ManyToManyField(MeetingRoomsReservations, blank=True)
    secret_key = models.CharField(max_length=128, null=False, blank=False)
    date_created = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "MeetingRooms"

    def __str__(self):
        return str(self.id)

So as the task says I need to create some kind of reservations, so basically I was thinking that I could create reservations object and make it ManyToManyField, so it could serve many reservations for different users, imagine the meeting could have like 10 people.

Then I have created MeetingRoomsReservations model to handle all the reservations:

class MeetingRoomsReservations(models.Model):
    statusTypes = [
        (0, "Valid"),
        (1, "Cancelled")
    ]

    receiver = models.ForeignKey(Employees, on_delete=models.CASCADE)
    meeting_room = models.ForeignKey('MeetingRooms', on_delete=models.CASCADE, default=None)
    title = models.CharField(max_length=150, blank=False, null=False)
    status = models.IntegerField(choices=statusTypes, null=False, blank=False, default=0)

    date_from = models.DateTimeField()
    date_to = models.DateTimeField()


    class Meta:
        db_table = "MeetingRoomsReservations"

    def __str__(self):
        return str(self.id)

As far as I understand the logic, the MeetingRoomsReservations will handle all the reservations that were registered for the MeetingRoom.

MeetingRoom > Reservations > [ List Of Reservations ] = Each of the reservation has a time and etc..

Okay, now I have to create API endpoint :

from main.models import MeetingRooms, MeetingRoomsReservations
from rest_framework import viewsets, generics
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from .serializers import MeetingRoomSerializer, ReservationSerializer

from django.http import JsonResponse

class MeetingRoomViewSet(viewsets.ModelViewSet):

    permission_classes = [
        AllowAny
    ]

    queryset = MeetingRooms.objects.all()
    serializer_class = MeetingRoomSerializer

    def perform_create(self, serializer):
        serializer.save()


class ReservationViewSet(viewsets.ModelViewSet):

And right now what's left is to create a Serializer? Sound easy? Let's see..

from rest_framework import serializers from main.models import MeetingRooms, MeetingRoomsReservations

class ReservationSerializer(serializers.ModelSerializer):
  class Meta:
    model = MeetingRoomsReservations
    fields = ('receiver', 'meeting_room', 'status', 'title', 'date_from', 'date_to')


class MeetingRoomSerializer(serializers.ModelSerializer):
  class Meta:
    model = MeetingRooms
    fields = ('creator', 'public_id', 'creator', 'date_created', 'secret_key', 'reservations')
    extra_kwargs = {'secret_key': {'write_only': True, 'min_length': 8}}

Let's see our Rest Framework:

enter image description here

So now it's the part which I don't understand. To create a Meeting room is it enough to only post these values?

enter image description here

After creating a Meeting Room this is how the Reservations should be added?

enter image description here

After many hours of trying I wasn't able to correctly create reservations and assign them to specific Meeting Room. How can I correctly handle this operation?

user9745220
  • 213
  • 1
  • 2
  • 13
  • 1
    Don't expose `reservations` in `rooms` api, unless you want them to be read only. However I'll suggest to have a different endpoint `/rooms//reservations` where you get all reservations for meeting room with that id, also any other CRUD operation. – Gabriel Muj Nov 19 '20 at 07:44

1 Answers1

2

Let me help you get over the initial hurdle of designing the models because you seem to have some fields in some models that shouldn't be there. We start with 2 entities: employees and meeting rooms. Employees will have a basic employee ID, first and last name. A meeting room will have an ID for internal indexing, as well as a unique room number. This room number is analogous to room numbers that you would see in meeting rooms of corporate offices. The room number may have alphabetical characters so our field will be a CharField.

For the sake of following some of your design conventions, I will use UUIDField as the primary_key field in all the models. Also, for the sake of brevity, I'm adding only fields that demonstrate the entities as a minimum viable example. You are free to add more based on your needs.

class Employee(models.Model):
    employee_id = models.UUIDField(
        default=uuid.uuid4,
        primary_key=True,
        editable=False
    )
    first_name = models.CharField(max_length=64)
    last_name = models.CharField(max_length=64)


class MeetingRoom(models.Model):
    room_id = models.UUIDField(
        default=uuid.uuid4,
        primary_key=True,
        editable=False
    )
    room_number = models.CharField(
        max_length=16,
        unique=True
    )

Alright, now let's focus on the reservation implementation. Based on the requirement specs, employees should be able to make reservations and we should be able to fetch reservations and filter by employees. In addition, a reservation entails that an employee is organizing a meeting at that room at a range in time, and there will be employees as invitees. These invitees can have the option to accept the invite or not. Tracking whether the invitees accept the invite or not means that we also need a through table on the ManyToManyField. Let's make a Reservation model and the through table on the ManyToManyField for invitees.

class Reservation(models.Model):
    STATUS_VALID = 0
    STATUS_CANCELLED = 1
    STATUS_TYPES = [
        (STATUS_VALID, "Valid"),
        (STATUS_CANCELLED, "Cancelled"),
    ]

    meeting_id = models.UUIDField(
        default=uuid.uuid4,
        primary_key=True,
        editable=False
    )
    room = models.ForeignKey(
        MeetingRoom,
        related_name="reservations",
        on_delete=models.CASCADE
    )
    organizer = models.ForeignKey(
        Employee,
        related_name="organized_reservations",
        on_delete=models.CASCADE
    )
    invitees = models.ManyToManyField(
        Employee,
        through="ReservationInvitee"
    )
    title = models.CharField(max_length=150)
    status = models.IntegerField(
        choices=STATUS_TYPES,
        default=STATUS_VALID
    )
    date_from = models.DateTimeField()
    date_to = models.DateTimeField()


class ReservationInvitee(models.Model):
    IS_PENDING = -1
    IS_ATTENDING = 1
    IS_NOT_ATTENDING = 0
    ATTENDING_STATUSES = [
        (IS_PENDING, "Pending"),
        (IS_ATTENDING, "Attending"),
        (IS_NOT_ATTENDING, "Not attending")
    ]

    reservation = models.ForeignKey(
        Reservation,
        on_delete=models.CASCADE
    )
    employee = models.ForeignKey(
        Employee,
        related_name="invited_reservations",
        on_delete=models.CASCADE
    )
    status = models.IntegerField(
        choices=ATTENDING_STATUSES,
        default=IS_PENDING
    )

From here, it's defining the API serializer and views for Reservation and ReservationInvitee. I won't define the serializers and views for Employee and MeetingRoom because they aren't that important for this implementation. I'll leave that as an exercise for you to do.

class ReservationSerializer(serializers.ModelSerializer):
    class Meta:
        model = Reservation
        fields = (
            "meeting_id",
            "room",
            "organizer",
            "invitees",
            "title",
            "status",
            "date_from",
            "date_to",
        )
        read_only_fields = (
            "meeting_id",
            "invitees",
        )


class ReservationInviteeSerializer(serializers.ModelSerializer):
    class Meta:
        model = ReservationInvitee
        fields = "__all__"
        read_only_fields = ('id',)


class ReservationViewset(viewsets.ModelViewSet):
    queryset = Reservation.objects.all()
    serializer_class = ReservationSerializer


class ReservationInviteeViewset(viewsets.ModelViewSet):
    queryset = ReservationInvitee.objects.all()
    serializer_class = ReservationInviteeSerializer

Now, to add a reservation via the API, you do it in 2 steps:

  1. POST data to the ReservationViewset. This assumes that you already have Employee and MeetingRoom data in your database. After successfully POSTing the data, the response will return the meeting_id along with the other field values that you posted. The meeting_id is the pk for the reservation that you need to post in step 2.
  2. POST data to the ReservationInviteeViewset that includes the auto-generated pk of meeting_id from step 1, along with the invitee. POST data to this endpoint for as many invitees being invited to the meeting.

And as the invitees accept/reject the meeting invite, you can simply PUT/PATCH with the new payload onto the ReservationInviteeViewset endpoint.

Now a question about filtering for reservations by employee, I'm assuming by the employee who's organizing the meeting. In this case, you can use filtering techniques specified in the docs. I will use their filtering by query parameters as an example:

class ReservationViewset(viewsets.ModelViewSet):
    serializer_class = ReservationSerializer

    def get_queryset(self):
        queryset = Reservation.objects.all()
        employee = self.request.query_params.get('employee_id', None)
        if employee is not None:
            queryset = queryset.filter(organizer__employee_id=employee)
        return queryset

This should be a good starting point for you. There are many other things you can still enhance, such as showing the ReservationInvitee details for ReservationSerializer. You can read this post if you want to achieve that. Other enhancements include validation on the date_from and date_to fields so that they don't conflict with another reservation for that meeting room. This would mean you'd have to either override the clean or save method in your model, or the save method in your serializer.

Scratch'N'Purr
  • 9,959
  • 2
  • 35
  • 51
  • Can we continue in the chat? https://chat.stackoverflow.com/rooms/224820/dziugas – user9745220 Nov 20 '20 at 00:29
  • I want to add another option for allowing only the organizer to cancel a reservation is by setting object-level permissions. You can see an example [here](https://www.django-rest-framework.org/api-guide/permissions/#examples) – Scratch'N'Purr Nov 23 '20 at 03:59