From 4c960c8abf0d6463526ec6632ae19ecf61c6d43d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 1 Oct 2019 18:24:33 +0300 Subject: [PATCH] Booking (squashed commit) --- apps/booking/__init__.py | 0 apps/booking/admin.py | 8 + apps/booking/apps.py | 7 + apps/booking/migrations/0001_initial.py | 27 ++++ apps/booking/migrations/0002_booking_user.py | 21 +++ .../migrations/0003_auto_20190916_1533.py | 61 ++++++++ .../migrations/0004_auto_20190916_1646.py | 33 ++++ .../migrations/0005_auto_20190918_1308.py | 23 +++ .../migrations/0006_booking_country_code.py | 18 +++ .../migrations/0007_booking_booking_id.py | 18 +++ .../migrations/0008_auto_20190919_2008.py | 18 +++ apps/booking/migrations/0009_booking_user.py | 21 +++ .../migrations/0010_auto_20190920_1206.py | 45 ++++++ apps/booking/migrations/__init__.py | 0 apps/booking/models/__init__.py | 0 apps/booking/models/models.py | 67 +++++++++ apps/booking/models/services.py | 141 ++++++++++++++++++ apps/booking/serializers/__init__.py | 0 apps/booking/serializers/web.py | 47 ++++++ apps/booking/tests.py | 3 + apps/booking/urls.py | 14 ++ apps/booking/views.py | 115 ++++++++++++++ .../migrations/0020_auto_20190916_1532.py | 23 +++ apps/establishment/models.py | 4 + apps/establishment/serializers/back.py | 4 +- bin/manage | 3 + project/settings/base.py | 7 + project/settings/production.py | 6 + project/urls/web.py | 1 + 29 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 apps/booking/__init__.py create mode 100644 apps/booking/admin.py create mode 100644 apps/booking/apps.py create mode 100644 apps/booking/migrations/0001_initial.py create mode 100755 apps/booking/migrations/0002_booking_user.py create mode 100644 apps/booking/migrations/0003_auto_20190916_1533.py create mode 100644 apps/booking/migrations/0004_auto_20190916_1646.py create mode 100644 apps/booking/migrations/0005_auto_20190918_1308.py create mode 100644 apps/booking/migrations/0006_booking_country_code.py create mode 100644 apps/booking/migrations/0007_booking_booking_id.py create mode 100644 apps/booking/migrations/0008_auto_20190919_2008.py create mode 100644 apps/booking/migrations/0009_booking_user.py create mode 100644 apps/booking/migrations/0010_auto_20190920_1206.py create mode 100644 apps/booking/migrations/__init__.py create mode 100644 apps/booking/models/__init__.py create mode 100644 apps/booking/models/models.py create mode 100644 apps/booking/models/services.py create mode 100644 apps/booking/serializers/__init__.py create mode 100644 apps/booking/serializers/web.py create mode 100644 apps/booking/tests.py create mode 100644 apps/booking/urls.py create mode 100644 apps/booking/views.py create mode 100644 apps/establishment/migrations/0020_auto_20190916_1532.py create mode 100755 bin/manage diff --git a/apps/booking/__init__.py b/apps/booking/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/admin.py b/apps/booking/admin.py new file mode 100644 index 00000000..59e56238 --- /dev/null +++ b/apps/booking/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from .models import models + + +@admin.register(models.Booking) +class BookingModelAdmin(admin.ModelAdmin): + """Model admin for model Comment""" \ No newline at end of file diff --git a/apps/booking/apps.py b/apps/booking/apps.py new file mode 100644 index 00000000..5319bb5b --- /dev/null +++ b/apps/booking/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class BookingConfig(AppConfig): + name = 'booking' + verbose_name = _('Booking') diff --git a/apps/booking/migrations/0001_initial.py b/apps/booking/migrations/0001_initial.py new file mode 100644 index 00000000..f7d5e919 --- /dev/null +++ b/apps/booking/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-09-12 17:55 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Booking', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('type', models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/booking/migrations/0002_booking_user.py b/apps/booking/migrations/0002_booking_user.py new file mode 100755 index 00000000..0ee387b7 --- /dev/null +++ b/apps/booking/migrations/0002_booking_user.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-09-14 14:47 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('booking', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='user', + field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='User'), + ), + ] diff --git a/apps/booking/migrations/0003_auto_20190916_1533.py b/apps/booking/migrations/0003_auto_20190916_1533.py new file mode 100644 index 00000000..448d94e7 --- /dev/null +++ b/apps/booking/migrations/0003_auto_20190916_1533.py @@ -0,0 +1,61 @@ +# Generated by Django 2.2.4 on 2019-09-16 15:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0002_booking_user'), + ] + + operations = [ + migrations.AlterModelOptions( + name='booking', + options={'verbose_name': 'Booking', 'verbose_name_plural': 'Booking'}, + ), + migrations.RemoveField( + model_name='booking', + name='user', + ), + migrations.AddField( + model_name='booking', + name='booked_persons_number', + field=models.PositiveIntegerField(default=2, verbose_name='persons number'), + ), + migrations.AddField( + model_name='booking', + name='booking_date', + field=models.DateField(default=None, verbose_name='booking date'), + ), + migrations.AddField( + model_name='booking', + name='booking_time', + field=models.TimeField(default=None, verbose_name='booking time'), + ), + migrations.AddField( + model_name='booking', + name='booking_user_locale', + field=models.CharField(default='en', max_length=10, verbose_name='booking locale'), + ), + migrations.AddField( + model_name='booking', + name='first_name', + field=models.CharField(default=None, max_length=200, verbose_name='booking first name'), + ), + migrations.AddField( + model_name='booking', + name='last_name', + field=models.CharField(default=None, max_length=200, verbose_name='booking last name'), + ), + migrations.AddField( + model_name='booking', + name='phone', + field=models.CharField(default=None, max_length=20, verbose_name='booking phone'), + ), + migrations.AddField( + model_name='booking', + name='restaurant_id', + field=models.PositiveIntegerField(default=None, verbose_name='booking service establishment id'), + ), + ] diff --git a/apps/booking/migrations/0004_auto_20190916_1646.py b/apps/booking/migrations/0004_auto_20190916_1646.py new file mode 100644 index 00000000..66471d26 --- /dev/null +++ b/apps/booking/migrations/0004_auto_20190916_1646.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.4 on 2019-09-16 16:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0003_auto_20190916_1533'), + ] + + operations = [ + migrations.AlterField( + model_name='booking', + name='first_name', + field=models.CharField(default=None, max_length=200, null=True, verbose_name='booking first name'), + ), + migrations.AlterField( + model_name='booking', + name='last_name', + field=models.CharField(default=None, max_length=200, null=True, verbose_name='booking last name'), + ), + migrations.AlterField( + model_name='booking', + name='phone', + field=models.CharField(default=None, max_length=20, null=True, verbose_name='booking phone'), + ), + migrations.AlterField( + model_name='booking', + name='type', + field=models.CharField(choices=[('L', 'Lastable'), ('G', 'GuestOnline')], max_length=2, verbose_name='Guestonline or Lastable'), + ), + ] diff --git a/apps/booking/migrations/0005_auto_20190918_1308.py b/apps/booking/migrations/0005_auto_20190918_1308.py new file mode 100644 index 00000000..eae448d2 --- /dev/null +++ b/apps/booking/migrations/0005_auto_20190918_1308.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2019-09-18 13:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0004_auto_20190916_1646'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='email', + field=models.EmailField(default=None, max_length=254, null=True, verbose_name='Booking email'), + ), + migrations.AddField( + model_name='booking', + name='pending_booking_id', + field=models.TextField(default=None, verbose_name='external service pending booking'), + ), + ] diff --git a/apps/booking/migrations/0006_booking_country_code.py b/apps/booking/migrations/0006_booking_country_code.py new file mode 100644 index 00000000..aed2951e --- /dev/null +++ b/apps/booking/migrations/0006_booking_country_code.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-18 14:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0005_auto_20190918_1308'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='country_code', + field=models.CharField(default=None, max_length=10, null=True, verbose_name='Country code'), + ), + ] diff --git a/apps/booking/migrations/0007_booking_booking_id.py b/apps/booking/migrations/0007_booking_booking_id.py new file mode 100644 index 00000000..a96aecd1 --- /dev/null +++ b/apps/booking/migrations/0007_booking_booking_id.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-19 20:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0006_booking_country_code'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='booking_id', + field=models.TextField(default=None, null=True, verbose_name='external service booking id'), + ), + ] diff --git a/apps/booking/migrations/0008_auto_20190919_2008.py b/apps/booking/migrations/0008_auto_20190919_2008.py new file mode 100644 index 00000000..59255422 --- /dev/null +++ b/apps/booking/migrations/0008_auto_20190919_2008.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-19 20:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0007_booking_booking_id'), + ] + + operations = [ + migrations.AlterField( + model_name='booking', + name='booking_id', + field=models.TextField(db_index=True, default=None, null=True, verbose_name='external service booking id'), + ), + ] diff --git a/apps/booking/migrations/0009_booking_user.py b/apps/booking/migrations/0009_booking_user.py new file mode 100644 index 00000000..d24162f9 --- /dev/null +++ b/apps/booking/migrations/0009_booking_user.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-09-19 21:18 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('booking', '0008_auto_20190919_2008'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='user', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='booking owner'), + ), + ] diff --git a/apps/booking/migrations/0010_auto_20190920_1206.py b/apps/booking/migrations/0010_auto_20190920_1206.py new file mode 100644 index 00000000..95a0ae97 --- /dev/null +++ b/apps/booking/migrations/0010_auto_20190920_1206.py @@ -0,0 +1,45 @@ +# Generated by Django 2.2.4 on 2019-09-20 12:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('booking', '0009_booking_user'), + ] + + operations = [ + migrations.RemoveField( + model_name='booking', + name='booked_persons_number', + ), + migrations.RemoveField( + model_name='booking', + name='booking_date', + ), + migrations.RemoveField( + model_name='booking', + name='booking_time', + ), + migrations.RemoveField( + model_name='booking', + name='country_code', + ), + migrations.RemoveField( + model_name='booking', + name='email', + ), + migrations.RemoveField( + model_name='booking', + name='first_name', + ), + migrations.RemoveField( + model_name='booking', + name='last_name', + ), + migrations.RemoveField( + model_name='booking', + name='phone', + ), + ] diff --git a/apps/booking/migrations/__init__.py b/apps/booking/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/models/__init__.py b/apps/booking/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/models/models.py b/apps/booking/models/models.py new file mode 100644 index 00000000..203189b8 --- /dev/null +++ b/apps/booking/models/models.py @@ -0,0 +1,67 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers +from utils.models import ProjectBaseMixin +from booking.models.services import LastableService, GuestonlineService +from account.models import User + + +class BookingManager(models.QuerySet): + def by_user(self, user: User): + return self.filter(user=user) + + +class Booking(ProjectBaseMixin): + LASTABLE = 'L' + GUESTONLINE = 'G' + AVAILABLE_SERVICES = ( + (LASTABLE, 'Lastable'), + (GUESTONLINE, 'GuestOnline') + ) + type = models.CharField(max_length=2, choices=AVAILABLE_SERVICES, verbose_name=_('Guestonline or Lastable')) + restaurant_id = models.PositiveIntegerField(verbose_name=_('booking service establishment id'), default=None) + booking_user_locale = models.CharField(verbose_name=_('booking locale'), default='en', max_length=10) + pending_booking_id = models.TextField(verbose_name=_('external service pending booking'), default=None) + booking_id = models.TextField(verbose_name=_('external service booking id'), default=None, null=True, + db_index=True, ) + user = models.ForeignKey( + 'account.User', verbose_name=_('booking owner'), null=True, + related_name='bookings', + blank=True, default=None, on_delete=models.CASCADE) + objects = BookingManager.as_manager() + + @property + def accept_email_spam(self): + return False + + @property + def accept_sms_spam(self): + return False + + @classmethod + def get_service_by_type(cls, type): + if type == cls.GUESTONLINE: + return GuestonlineService() + elif type == cls.LASTABLE: + return LastableService() + else: + return None + + @classmethod + def get_booking_id_by_type(cls, establishment, type): + if type == cls.GUESTONLINE: + return establishment.guestonline_id + elif type == cls.LASTABLE: + return establishment.lastable_id + else: + return None + + def delete(self, using=None, keep_parents=False): + service = self.get_service_by_type(self.type) + if not service.cancel_booking(self.booking_id): + raise serializers.ValidationError(detail='Something went wrong! Unable to cancel.') + super().delete(using, keep_parents) + + class Meta: + verbose_name = _('Booking') + verbose_name_plural = _('Booking') diff --git a/apps/booking/models/services.py b/apps/booking/models/services.py new file mode 100644 index 00000000..1c117f56 --- /dev/null +++ b/apps/booking/models/services.py @@ -0,0 +1,141 @@ +from abc import ABC, abstractmethod +import json +import requests +from django.conf import settings +from rest_framework import status +import booking.models.models as models +from rest_framework import serializers + + +class AbstractBookingService(ABC): + """ Abstract class for Guestonline && Lastable booking services""" + + def __init__(self, service): + self.service = None + self.response = None + if service not in [models.Booking.LASTABLE, models.Booking.GUESTONLINE]: + raise Exception('Service %s is not implemented yet' % service) + self.service = service + if service == models.Booking.GUESTONLINE: + self.token = settings.GUESTONLINE_TOKEN + self.url = settings.GUESTONLINE_SERVICE + elif service == models.Booking.LASTABLE: + self.token = settings.LASTABLE_TOKEN + self.url = settings.LASTABLE_SERVICE + + @staticmethod + def get_certain_keys(d: dict, keys_to_preserve: set) -> dict: + """ Helper """ + return {key: d[key] for key in d.keys() & keys_to_preserve} + + @abstractmethod + def check_whether_booking_available(self, restaurant_id, date): + """ checks whether booking is available """ + pass + + @abstractmethod + def cancel_booking(self, payload): + """ cancels booking and returns the result """ + pass + + @abstractmethod + def create_pending_booking(self, payload): + """ returns pending booking id if created. otherwise False """ + pass + + @abstractmethod + def update_pending_booking(self, payload): + """ updates pending booking with contacts """ + pass + + @abstractmethod + def get_common_headers(self): + pass + + @abstractmethod + def get_booking_details(self, payload): + """ fetches booking details from external service """ + pass + + +class GuestonlineService(AbstractBookingService): + def __init__(self): + super().__init__(models.Booking.GUESTONLINE) + + def get_common_headers(self): + return {'X-Token': self.token, 'Content-type': 'application/json', 'Accept': 'application/json'} + + def check_whether_booking_available(self, restaurant_id, date: str): + url = f'{self.url}v1/runtime_services' + params = {'restaurant_id': restaurant_id, 'date': date, 'expands[]': 'table_availabilities'} + r = requests.get(url, headers=self.get_common_headers(), params=params) + if not status.is_success(r.status_code): + return False + response = json.loads(r.content)['runtime_services'] + keys_to_preserve = {'left_seats', 'table_availabilities', 'closed', 'start_time', 'end_time', 'last_booking'} + response = map(lambda x: self.get_certain_keys(x, keys_to_preserve), response) + self.response = response + return True + + def commit_booking(self, payload): + url = self.url + 'v1/pending_bookings/' + payload + '/commit' + r = requests.put(url, headers=self.get_common_headers()) + self.response = json.loads(r.content) + if status.is_success(r.status_code) and self.response is None: + raise serializers.ValidationError(detail='Booking already committed.') + return status.is_success(r.status_code) + + def update_pending_booking(self, payload): + booking_id = payload.pop('pending_booking_id') + url = self.url + 'v1/pending_bookings/' + booking_id + payload['lastname'] = payload.pop('last_name') + payload['firstname'] = payload.pop('first_name') + payload['mobile_phone'] = payload.pop('phone') + headers = self.get_common_headers() + r = requests.put(url, headers=headers, data=json.dumps({'contact_info': payload})) + return status.is_success(r.status_code) + + def create_pending_booking(self, payload): + url = self.url + 'v1/pending_bookings' + payload['hour'] = payload.pop('booking_time') + payload['persons'] = payload.pop('booked_persons_number') + payload['date'] = payload.pop('booking_date') + r = requests.post(url, headers=self.get_common_headers(), data=json.dumps(payload)) + return json.loads(r.content)['id'] if status.is_success(r.status_code) else False + + def cancel_booking(self, payload): + url = f'{self.url}v1/pending_bookings/{payload}' + r = requests.delete(url, headers=self.get_common_headers()) + return status.is_success(r.status_code) + + def get_booking_details(self, payload): + url = f'{self.url}v1/bookings/{payload}' + r = requests.get(url, headers=self.get_common_headers()) + return json.loads(r.content) + + +class LastableService(AbstractBookingService): + def __init__(self): + super().__init__(models.Booking.LASTABLE) + + def create_pending_booking(self, payload): + pass + + def get_common_headers(self): + return {'Authorization': f'Bearer {self.token}', 'Content-type': 'application/json', + 'Accept': 'application/json'} + + def check_whether_booking_available(self, restaurant_id, date): + return False + + def commit_booking(self, payload): + return False + + def update_pending_booking(self, payload): + return False + + def cancel_booking(self, payload): + return False + + def get_booking_details(self, payload): + return {} diff --git a/apps/booking/serializers/__init__.py b/apps/booking/serializers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/booking/serializers/web.py b/apps/booking/serializers/web.py new file mode 100644 index 00000000..f5b142e0 --- /dev/null +++ b/apps/booking/serializers/web.py @@ -0,0 +1,47 @@ +from rest_framework import serializers +from booking.models import models + + +class BookingSerializer(serializers.ModelSerializer): + class Meta: + model = models.Booking + fields = ( + 'id', + 'type', + ) + + +class PendingBookingSerializer(serializers.ModelSerializer): + restaurant_id = serializers.IntegerField(min_value=0, ) + id = serializers.ReadOnlyField() + + class Meta: + model = models.Booking + fields = ( + 'id', + 'type', + 'restaurant_id', + 'pending_booking_id', + 'user', + ) + + +class UpdateBookingSerializer(serializers.ModelSerializer): + id = serializers.ReadOnlyField() + + class Meta: + model = models.Booking + fields = ('booking_id', 'id') + + +class GetBookingSerializer(serializers.ModelSerializer): + details = serializers.SerializerMethodField() + + def get_details(self, obj): + booking = self.instance + service = booking.get_service_by_type(booking.type) + return service.get_booking_details(booking.booking_id) + + class Meta: + model = models.Booking + fields = '__all__' diff --git a/apps/booking/tests.py b/apps/booking/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/booking/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/booking/urls.py b/apps/booking/urls.py new file mode 100644 index 00000000..a86178ff --- /dev/null +++ b/apps/booking/urls.py @@ -0,0 +1,14 @@ +"""Booking app urls.""" +from django.urls import path +from booking import views + +app = 'booking' + +urlpatterns = [ + path('/check/', views.CheckWhetherBookingAvailable.as_view(), name='booking-check'), + path('/create/', views.CreatePendingBooking.as_view(), name='create-pending-booking'), + path('', views.UpdatePendingBooking.as_view(), name='update-pending-booking'), + path('/cancel', views.CancelBooking.as_view(), name='cancel-existing-booking'), + path('last/', views.LastBooking.as_view(), name='last_booking-for-authorizer-user'), + path('retrieve/', views.GetBookingById.as_view(), name='retrieves-booking-by-id'), +] diff --git a/apps/booking/views.py b/apps/booking/views.py new file mode 100644 index 00000000..2bb5ec2b --- /dev/null +++ b/apps/booking/views.py @@ -0,0 +1,115 @@ +from rest_framework import generics, permissions, status + +from django.shortcuts import get_object_or_404 +from establishment.models import Establishment +from booking.models.models import Booking, GuestonlineService, LastableService +from rest_framework.response import Response +from booking.serializers.web import (PendingBookingSerializer, + UpdateBookingSerializer, GetBookingSerializer) +from utils.serializers import EmptySerializer + + +class CheckWhetherBookingAvailable(generics.GenericAPIView): + """ Checks which service to use if establishmend is managed by any """ + + permission_classes = (permissions.AllowAny,) + serializer_class = EmptySerializer + pagination_class = None + + def get(self, request, *args, **kwargs): + is_booking_available = False + establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id']) + service = None + date = request.query_params.get('date') + g_service = GuestonlineService() + l_service = LastableService() + if not establishment.lastable_id is None and l_service \ + .check_whether_booking_available(establishment.lastable_id, date): + is_booking_available = True + service = l_service + service.service_id = establishment.lastable_id + elif not establishment.guestonline_id is None and g_service \ + .check_whether_booking_available(establishment.guestonline_id, date): + is_booking_available = True + service = g_service + service.service_id = establishment.guestonline_id + + response = { + 'available': is_booking_available, + 'type': service.service, + } + response.update({'details': service.response} if service.response else {}) + return Response(data=response, status=200) + + +class CreatePendingBooking(generics.CreateAPIView): + """ Creates pending booking """ + permission_classes = (permissions.AllowAny,) + serializer_class = PendingBookingSerializer + + def post(self, request, *args, **kwargs): + data = request.data.copy() + establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id']) + data['restaurant_id'] = Booking.get_booking_id_by_type(establishment, data.get('type')) + service = Booking.get_service_by_type(request.data.get('type')) + data['user'] = request.user.pk if request.user else None + data['pending_booking_id'] = service.create_pending_booking(service.get_certain_keys(data, { + 'restaurant_id', + 'booking_time', + 'booking_date', + 'booked_persons_number', + })) + if not data['pending_booking_id']: + return Response(status=status.HTTP_403_FORBIDDEN, data='Unable to create booking') + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_201_CREATED, data=serializer.data) + + +class UpdatePendingBooking(generics.UpdateAPIView): + """ Update pending booking with contacts """ + queryset = Booking.objects.all() + permission_classes = (permissions.AllowAny,) + serializer_class = UpdateBookingSerializer + + def patch(self, request, *args, **kwargs): + instance = self.get_object() + data = request.data.copy() + service = Booking.get_service_by_type(instance.type) + data['pending_booking_id'] = instance.pending_booking_id + service.update_pending_booking(service.get_certain_keys(data, { + 'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', + })) + service.commit_booking(data['pending_booking_id']) + data = { + 'booking_id': service.response.get('id'), + 'id': instance.pk, + } + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + serializer.update(instance, data) + return Response(status=status.HTTP_200_OK, data=serializer.data) + + +class CancelBooking(generics.DestroyAPIView): + """ Cancel existing booking """ + queryset = Booking.objects.all() + permission_classes = (permissions.AllowAny,) + + +class LastBooking(generics.RetrieveAPIView): + """ Get last booking by user credentials """ + permission_classes = (permissions.IsAuthenticated,) + serializer_class = GetBookingSerializer + lookup_field = None + + def get_object(self): + return Booking.objects.by_user(self.request.user).latest('modified') + + +class GetBookingById(generics.RetrieveAPIView): + """ Returns booking by its id""" + permission_classes = (permissions.AllowAny,) + serializer_class = GetBookingSerializer + queryset = Booking.objects.all() diff --git a/apps/establishment/migrations/0020_auto_20190916_1532.py b/apps/establishment/migrations/0020_auto_20190916_1532.py new file mode 100644 index 00000000..d22fad07 --- /dev/null +++ b/apps/establishment/migrations/0020_auto_20190916_1532.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2019-09-16 15:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0019_establishment_is_publish'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='guestonline_id', + field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='guestonline id'), + ), + migrations.AddField( + model_name='establishment', + name='lastable_id', + field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='lastable id'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 589c2e22..d809d9b5 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -260,6 +260,10 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Twitter URL')) lafourchette = models.URLField(blank=True, null=True, default=None, verbose_name=_('Lafourchette URL')) + guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'), + null=True, default=None,) + lastable_id = models.PositiveIntegerField(blank=True, verbose_name=_('lastable id'), + null=True, default=None,) booking = models.URLField(blank=True, null=True, default=None, verbose_name=_('Booking URL')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index d0c70b2f..8bd09e85 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -39,7 +39,9 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): 'image_url', 'slug', # TODO: check in admin filters - 'is_publish' + 'is_publish', + 'guestonline_id', + 'lastable_id', ] diff --git a/bin/manage b/bin/manage new file mode 100755 index 00000000..62d0ec94 --- /dev/null +++ b/bin/manage @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +docker-compose run --rm gm_app python manage.py "$@" \ No newline at end of file diff --git a/project/settings/base.py b/project/settings/base.py index cfea18a5..9f72ecde 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -55,6 +55,7 @@ PROJECT_APPS = [ 'advertisement.apps.AdvertisementConfig', 'account.apps.AccountConfig', 'authorization.apps.AuthorizationConfig', + 'booking.apps.BookingConfig', 'collection.apps.CollectionConfig', 'establishment.apps.EstablishmentConfig', 'gallery.apps.GalleryConfig', @@ -262,6 +263,12 @@ SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email', } +# Booking API configuration +GUESTONLINE_SERVICE = 'https://api-preprod.guestonline.fr/' +GUESTONLINE_TOKEN = 'iiReiYpyojshpPjpmczS' +LASTABLE_SERVICE = '' +LASTABLE_TOKEN = '' + # SMS Settings SMS_EXPIRATION = 5 SMS_SEND_DELAY = 30 diff --git a/project/settings/production.py b/project/settings/production.py index e491a1fb..76dc31f0 100644 --- a/project/settings/production.py +++ b/project/settings/production.py @@ -1,2 +1,8 @@ """Production settings.""" from .base import * + +# Booking API configuration +GUESTONLINE_SERVICE = 'https://api.guestonline.fr/' +GUESTONLINE_TOKEN = '' +LASTABLE_SERVICE = '' +LASTABLE_TOKEN = '' \ No newline at end of file diff --git a/project/urls/web.py b/project/urls/web.py index 0a81672d..71b8f84f 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -19,6 +19,7 @@ app_name = 'web' urlpatterns = [ path('account/', include('account.urls.web')), + path('booking/', include('booking.urls')), path('re_blocks/', include('advertisement.urls.web')), path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')),