Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
890 views
in Technique[技术] by (71.8m points)

django - MultipleObjectsReturned: get() returned more than one Driver -- it returned 3

I have a DRF API that is supposed to feed a frontend Android and IOS app. The API has three types of users namely: Driver, Client and Admin.

The client is supposed to create a booking record and get assigned a driver automatically by the API. Based on availability days and times. At the moment every day (Sun, Mon, Tue, Wed, Thu, Fri) is a working day and working hours range from 7:00 am to 9:00 pm with working hours inclusive of transition being an hour. For example, booking of 9:00am will take an hour, hence finishing time will be 10:00am, all factors inclusive. Many drivers can have work at the same time. The app should give feedback to the user if they try booking already picked slots.

My problem at this time is to loop through already existing drivers from drivers table, left join them to the booking table and and assign them one by one. I was able to assign one driver a job. But when I added drivers, it became difficult. There is something I am not getting well and I do not know what it is.

Here are my models.

""" models.py """


    """ Helper function to check overlapping times """
    def check_time_overlap(self, fixed_start, fixed_end, new_start, new_end):
        time_overlap = False
        if new_start == fixed_end or new_end == fixed_start:  # edge case
            time_overlap = False
        elif (new_start >= fixed_start and new_start <= fixed_end) or 
                (new_end >= fixed_start and new_end <= fixed_end):  
                # innner limits
            time_overlap = True
        elif new_start <= fixed_start and new_end >= fixed_end: 
                # outter limits
            time_overlap = True

        return time_overlap

    """ Function to check overlapping bookings """
    def overlapping_bookings(self):
        if self.finishing_at <= self.booking_time:
            raise ValidationError(
                'Finishing times must be after booking times'
            )

        bookings = Booking.objects.filter(
            booking_date=self.booking_date, driver_id=self.driver
        )
        if bookings.exists():
            for booking in bookings:
                """ Check whether date and time overlaps """
                if self.check_time_overlap(
                    booking.booking_time, booking.finishing_at,
                    self.booking_time, self.finishing_at
                ):
                    """ If all drivers are allocated, raise an error 
                        message. """
                    raise ValidationError(
                        'All our drivers are booked at: ' +
                        str(booking.booking_date) + ', ' + str(
                            booking.booking_time) + '-' +
                        str(booking.finishing_at))

    def save(self, *args, **kwargs):
        self.calc_booking_total()
        self.calc_finishing_at()
        self.overlapping_bookings()
        super(Booking, self).save(*args, **kwargs)

From the above models, I have written a function to check overlapping times for the same booking_date, booking_time and finishing_at times. This is inspired by ALEXANDRE PINTO on >https://alexpnt.github.io/2017/07/15/django-calendar/

Below are the serializers



# BOOKING SERIALIZER
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––#


class BookingSerializer(serializers.ModelSerializer):
    package = serializers.SerializerMethodField()
    vehicle_type = serializers.SerializerMethodField()
    client_name = serializers.SerializerMethodField()
    client_phone = serializers.SerializerMethodField()

    class Meta:
        model = Booking
        # fields = '__all__'
        exclude = ('driver',)
        read_only_fields = (
            'booking_total', 'booking_status',
            'booked_at', 'finishing_at', 'client'
        )

    def get_package(self, obj):
        return obj.service.package_name

    def get_vehicle_type(self, obj):
        return obj.service.vehicle_category.name

    def get_client_name(self, obj):
        return obj.client.name

    def get_client_phone(self, obj):
        return str(obj.client.phone)

    """ Function to create booking based on authenticated client and available drivers """
    def create(self, validated_data):
        validated_data['client'] = self.context['request'].user
        validated_data['driver'] = Driver.objects.get(active=True)

        bookings = Booking.objects.create(**validated_data)
        return bookings

Here are my server logs:

Server Log

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

------------------------------------------------------------------------

Below is a solution I created to the following problem. Any one can perfect it. Note: It gets help from the function overlapping_bookings() in order to stop anymore bookings for a single date and time once all drivers are allocated. This throws in a ValidationError from overlapping_bookings() on save()

I hope it helps someone

In serializers.py, the function is overriding ModelViewSet create.

def create(self, validated_data):
        """ Function to create booking objects 
        and allocate drivers automatically. """

        validated_data['client'] = self.context['request'].user

        """ Variable to save all drivers (querysets) from the driver 
        table ordered by the booking date and time. --> maybe there is 
        a better way to do it. Avoid using .get()"""

        drivers = Driver.objects.filter(active=True).order_by(
            '-booking__booking_date', '-booking__booking_time').all()

        """ Check whether the drivers querysets (list) exists """
        if drivers.exists():

            """ For loop to isolate a single query set from list of 
            quersets (drivers) """
            for drv in drivers:

                """ Condition to check for inner join query between 
                driver and booking table carefully filtering them using 
                booking_date and booking_time. This code is helped by 
                the clean() function in models. Which after every active=True 
                driver is allocated a booking, raises a ValidationError. 

                It is subject to be made better. Trying to find out how it 
                will throw a HttpResponse error instead."""

                if Booking.objects.select_related('driver').filter(
                    booking_date=validated_data['booking_date'],
                    booking_time=validated_data['booking_time'],
                ).annotate(drv=F('driver__user_ptr')).exists():
                    continue
            try:
                return Booking.objects.create(driver=drv, **validated_data)
            except Booking.DoesNotExist:
                pass

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...