[Solved]-Create objects based on date and time

6👍

My approach is a little bit different. You said in the question tag that you are using django-rest-framework.. So where are the serializers? 🙂

Lets create two serializers, one for user data validation (Because we don’t trust the USER!) and one for multi data insert.

I haven’t checked the code! But you can use it was an example…

class ShowEventSerializer(serializers.Serializer):
    event_id = serializers.IntegerField()
    start_date = serializers.DateField(required=True)
    end_date = serializers.DateField(required=True)
    time_list = serializers.ListField(
        child=serializers.TimeField()
    )

    class Meta:
        fields = ('event_id', 'start_date', 'end_date', 'time_list')

class ShowSerializer(serializers.Serializer):
    date_time = serializers.DateTimeField()

    class Meta:
        model = Show
        fields = ('event', 'date_time')

Now, with the serializers, we are going to validate the user data and then to create a json data object:

def create_show_by_datetime(self, request):

    show_event_serializer = ShowEventSerializer(data=request.data)
    if not show_event_serializer.is_valid():
        return Response({'error': show_event_serializer.errors},status=status.HTTP_400_BAD_REQUEST)

    event_id = show_event_serializer.data['event_id']
    try:
        event = Event.objects.get(id=event_id)
    except Event.DoesNotExist:
        return Response({'error': 'event with id: %s does not exist.' % event_id},status=status.HTTP_400_BAD_REQUEST)

    start_date = show_event_serializer.data['start_date']
    end_date = show_event_serializer.data['end_date']
    time_list = show_event_serializer.data['time_list']
    date_format = '%Y-%m-%d'
    time_format = "%I:%M %p"

    try:
        datetime.strptime(start_date, date_format)
        datetime.strptime(end_date, date_format)
        for i in range(len(time_list)):
            time = datetime.strptime(time_list[i], time_format)
    except ValueError as e:
        return Response(
            {'error': 'Time was not in a supported format. %s' % e},
            status=status.HTTP_400_BAD_REQUEST
        )

    delta_days = datetime.strptime(end_date, date_format).date() - datetime.strptime(start_date, date_format).date()
    delta_days = delta_days.days + 1
    dt = None
    show_data = []
    for i in range(delta_days):
        day = datetime.strptime(start_date, date_format) + timedelta(days=i)
        for i in range(len(time_list)):
            hrs = datetime.strptime(time_list[i], time_format).hour
            mins = datetime.strptime(time_list[i], time_format).minute
            dt = day + timedelta(hours=hrs, minutes=mins)
            show_data.append({
                "event": event,
                "date_time": dt
            })

    try:
        with transaction.atomic():
            show_serializer = ShowSerializer(data=show_data, many=True)
            if show_serializer.is_valid():
                show_serializer.save()

            return Response({"data": 'Post succesfull'}, status=status.HTTP_201_CREATED)
    except IntegrityError as e:
        return Response(
            {
                'error': "event with date and time already exsits. %s-%s-%s at %s:%s" % (
                    dt.day, dt.month, dt.year, dt.hour, dt.minute),
                'detail': str(e)
            }, status=status.HTTP_400_BAD_REQUEST

So this code is basically the same as yours, with the difference of the way the objects are saved using the DRF. Look the show_data variable.

This solution is just a different way of looking at the question.

GOOD LUCK!

http://www.django-rest-framework.org/api-guide/serializers/

http://www.django-rest-framework.org/api-guide/fields/

4👍

Then your Show model should look like

from django.contrib.postgres.fields import ArrayField

class Show(models.Model):
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    start_date = models.DateField(blank=True, null=True)
    end_date = models.DateField(blank=True, null=True)
    board = ArrayField(
        models.TimeField(blank=True, null=True),
        size=10, # specify max array size
    )

so you’ll have model Show with specified DateFields and array of TimeFields.

django docs: Array Field, Time Field, Date Field

4👍

There are some tools in the datetime library that can give you a more streamlined approach to generating your times. You can use toordinal to turn a date into an integer and fromordinal to turn an integer back into a date; this makes a nice way to create a range of dates. And you can use combine to merge a date object and a time object into a datetime. I’d create the following function:

from datetime import datetime, date

def get_showtimes(post):
    start = datetime.strptime(post['start_date'], '%Y-%m-%d')
    end = datetime.strptime(post['end_date'], '%Y-%m-%d')
    times = [datetime.strptime(t, '%I:%M %p').time() for t in post['time_list']]
    for ordinal in range(start.toordinal(), end.toordinal() + 1):
        date = date.fromordinal(date)
        for time in times:
            yield datetime.combine(date, time)

then, in your code, replace the second try: except: block and what follows it with:

try:
    showtimes = list(get_showtimes(post))
except ValueError as e:
    return Response(
        {'error': 'Time was not in a supported format. %s' % e},
        status=status.HTTP_400_BAD_REQUEST
    )

try:
    with transaction.atomic():
        for showtime in showtimes:
            show = Show.objects.create(event=event, date_time=showtime)
except IntegrityError as e:
    # etc.

3👍

I am leaving the validation part and only focusing on generating Show objects from the given data:

data = request.data

date_format = '%Y-%m-%d'
time_format = "%I:%M %p"
show_time_format = f"{date_format} {time_format}"

# get the total number of days by parsing start and end dates
start_date = datetime.strptime(data['start_date'], date_format)
end_date = datetime.strptime(data['end_date'], date_format)
total_days = (end_date - start_date).days + 1

# Get the timings for the first day.
# We will use this to generate the timings for the rest of the days.
first_day_timings = [
    datetime.strptime(f"{data['start_date']} {show_time}", show_time_format)
    for show_time in data['time_list']
]

# generate all show objects using list comprehension and bulk create later
show_objects = [
    Show(event=event, date_time=first_day_timing + timedelta(days=day_cnt))
    for day_cnt in range(total_days)
    for first_day_timing in first_day_timings
]

Show.objects.bulk_create(show_objects)

Enhancements made on the existing code:

  1. Reduced the number of instances date/time is getting parsed and hours/minutes are getting computed.
  2. Use list comprehension to generate show objects and bulk create instead of creating one object at a time in a transaction.
👤AKS

Leave a comment