From 7c9c6d18226274a3cc3ea62b68e6e46af35ec816 Mon Sep 17 00:00:00 2001 From: abarg12 Date: Sun, 15 Nov 2020 15:51:23 -0500 Subject: [PATCH 1/6] Added new API endpoint for equipmentType filtering for time. Added new serializers to serializers.py as well to accomodate new filters of time. --- server/api/serializers.py | 21 ++++++++++++++++++ server/api/urls.py | 1 + server/api/views/equipment.py | 41 +++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/server/api/serializers.py b/server/api/serializers.py index 5c529a4c..a667a6b0 100644 --- a/server/api/serializers.py +++ b/server/api/serializers.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User, Group from rest_framework import serializers from .models import * +from api.views.equipment import check_availability """ The HyperlinkedModelSerializer class is similar to the ModelSerializer @@ -48,10 +49,30 @@ class Meta: model = EquipmentType fields = "__all__" +class EquipmentTypeSerializer(serializers.HyperlinkedModelSerializer): + items = EquipmentItemSerializerTime(many=True, required=False, read_only=True) + + class Meta: + model = EquipmentType + fields = "__all__" """ Serializers to support serializing EquipmentItem objects """ + +class EquipmentItemSerializerTime(serializers.HyperlinkedModelSerializer): + available = serializers.SerializerMethodField('is_available') + + def is_available(self): + if check_availability(self.context.get("request")): + return True + else: + return False + + class Meta: + model = EquipmentItem + fields = ['name', 'id', 'url', 'available'] + class EquipmentTypeSerializerSimple(serializers.HyperlinkedModelSerializer): class Meta: model = EquipmentType diff --git a/server/api/urls.py b/server/api/urls.py index 432c6c63..f5678511 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -9,6 +9,7 @@ # They support HTTP GET, PUT, PATCH, DELETE router.register(r'users', views.UserViewSet) router.register(r'equipment-types', views.EquipmentTypeViewSet) +router.register(r'equipment-types-time', views.EquipmentTypeTimeViewSet) router.register(r'equipment-items', views.EquipmentItemViewSet) router.register(r'equipment-categories', views.EquipmentCategoryViewSet) router.register(r'equipment-requests', views.EquipmentRequestViewSet) diff --git a/server/api/views/equipment.py b/server/api/views/equipment.py index 47b263e8..905c9b83 100644 --- a/server/api/views/equipment.py +++ b/server/api/views/equipment.py @@ -26,10 +26,23 @@ class EquipmentTypeViewSet(viewsets.ModelViewSet): queryset = EquipmentType.objects.all() serializer_class = EquipmentTypeSerializer +class EquipmentTypeTimeViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows Equipment Types to be viewed or edited and filters for time + """ + queryset = EquipmentType.objects.all() + serializer_class = EquipmentTypeTimeSerializer + class EquipmentItemViewSet(viewsets.ModelViewSet): """ API endpoint that allows Equipment Items to be viewed or edited. """ + # request_out = request.GET.get('out') + # request_in = request.GET.get('in') + + # request_out_fmt = datetime.datetime.strptime(request_out, "%Y-%m-%dT%H:%M:%S%z") + # request_in_fmt = datetime.datetime.strptime(request_in, "%Y-%m-%dT%H:%M:%S%z") + queryset = EquipmentItem.objects.all() serializer_class = EquipmentItemSerializer @@ -68,6 +81,34 @@ def get_availability(request): return JsonResponse(False, safe=False) return JsonResponse(True, safe=False) + +def check_availability(request): + request_out = request.GET.get('out') + request_in = request.GET.get('in') + equipment_item_id = request.GET.get('id') + if None in (request_out, request_in, equipment_item_id): + return JsonResponse("Incorrect query format. Format is ?out={}&in={}&id={}", status=status.HTTP_400_BAD_REQUEST, safe=False) + + request_out_fmt = datetime.datetime.strptime(request_out, "%Y-%m-%dT%H:%M:%S%z") + request_in_fmt = datetime.datetime.strptime(request_in, "%Y-%m-%dT%H:%M:%S%z") + + # Check if the item ID provided exists + # if EquipmentItem.objects.filter(id = equipment_item_id).count() == 0: + # return JsonResponse("Equipment not found", status=status.HTTP_400_BAD_REQUEST, safe=False) + # equipment_item = EquipmentItem.objects.get(pk=equipment_item_id) + + # Check if request out is earlier than request in + if request_out_fmt > request_in_fmt: + # raise TypeError("") + return JsonResponse("Request out cannot be later than request in", status=status.HTTP_400_BAD_REQUEST, safe=False) + + + # Return true if equipment is available during the specified time and + # false otherwise + for equipment_request in equipment_item.linked_requests.all(): + if (request_out_fmt < equipment_request.request_out) or (request_in_fmt > equipment_request.request_in): + return False + return True """ Partially update a request to reflect its signed out status Reference: https://stackoverflow.com/questions/50129567/django-rest-update-one-field From 67db69f4a1df74241b3869d4a21c42598aca85cf Mon Sep 17 00:00:00 2001 From: abarg12 Date: Sun, 29 Nov 2020 14:52:38 -0500 Subject: [PATCH 2/6] Initial draft of changes to equipment_item_request viewset --- server/api/serializers.py | 62 +++++++++++++++++++++++++++++------ server/api/urls.py | 2 +- server/api/views/equipment.py | 26 --------------- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/server/api/serializers.py b/server/api/serializers.py index 5876b46b..e2d25412 100644 --- a/server/api/serializers.py +++ b/server/api/serializers.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import User, Group from rest_framework import serializers from .models import * -from api.views.equipment import check_availability +import datetime """ The HyperlinkedModelSerializer class is similar to the ModelSerializer @@ -49,12 +49,7 @@ class Meta: model = EquipmentType fields = "__all__" -class EquipmentTypeSerializer(serializers.HyperlinkedModelSerializer): - items = EquipmentItemSerializerTime(many=True, required=False, read_only=True) - class Meta: - model = EquipmentType - fields = "__all__" """ Serializers to support serializing EquipmentItem objects @@ -63,15 +58,22 @@ class Meta: class EquipmentItemSerializerTime(serializers.HyperlinkedModelSerializer): available = serializers.SerializerMethodField('is_available') - def is_available(self): - if check_availability(self.context.get("request")): + def is_available(self, obj): + if check_availability(self.context.get("request"), obj.id): return True else: return False class Meta: model = EquipmentItem - fields = ['name', 'id', 'url', 'available'] + fields = ['id', 'url', 'available'] + +class EquipmentTypeTimeSerializer(serializers.HyperlinkedModelSerializer): + items = EquipmentItemSerializerTime(many=True, required=False, read_only=True) + + class Meta: + model = EquipmentType + fields = "__all__" class EquipmentTypeSerializerSimple(serializers.HyperlinkedModelSerializer): class Meta: @@ -99,4 +101,44 @@ class Meta: class EquipmentRequestSerializer(serializers.ModelSerializer): class Meta: model = EquipmentRequest - fields = '__all__' \ No newline at end of file + fields = '__all__' + + + + +def check_availability(request, eid): + request_out = request.GET.get('out') + request_in = request.GET.get('in') + equipment_item_id = eid + if None in (request_out, request_in, equipment_item_id): + return False + #return JsonResponse("Incorrect query format. Format is ?out={}&in={}&id={}", status=status.HTTP_400_BAD_REQUEST, safe=False) + print("item_id:", equipment_item_id) + print("request_out:", request_out) + print("request_in:", request_in) + print() + request_out_fmt = datetime.datetime.strptime(request_out, "%Y-%m-%dT%H:%M:%S%z") + request_in_fmt = datetime.datetime.strptime(request_in, "%Y-%m-%dT%H:%M:%S%z") + + # Check if the item ID provided exists + if EquipmentItem.objects.filter(id = equipment_item_id).count() == 0: + return False + #return JsonResponse("Equipment not found", status=status.HTTP_400_BAD_REQUEST, safe=False) + equipment_item = EquipmentItem.objects.get(pk=equipment_item_id) + + # Check if request out is earlier than request in + if request_out_fmt >= request_in_fmt: + return False + # raise TypeError("") + # return JsonResponse("Request out cannot be later than request in", status=status.HTTP_400_BAD_REQUEST, safe=False) + + + # Return true if equipment is available during the specified time and + # false otherwise + # if request_out happens within the equipment request period + # or if request_in happens within the equipment request period + for equipment_request in equipment_item.linked_requests.all(): + if ((request_out_fmt >= equipment_request.request_out and request_out_fmt <= equipment_request.request_in) + or (request_in_fmt <= equipment_request.request_in and request_in_fmt >= equipment_request.request_out)): + return False + return True \ No newline at end of file diff --git a/server/api/urls.py b/server/api/urls.py index 759b0411..06011d4b 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -17,7 +17,7 @@ urlpatterns = [ *router.urls, - # re_path(r'equipment', views.list_equipment), + #re_path(r'equipment', views.list_equipment), re_path(r'^availability/$', views.get_availability), path('signout//', views.sign_out_request.as_view()), path('return//', views.return_request.as_view()), diff --git a/server/api/views/equipment.py b/server/api/views/equipment.py index 4c81a8be..872f130f 100644 --- a/server/api/views/equipment.py +++ b/server/api/views/equipment.py @@ -109,33 +109,7 @@ def get_availability(request): return JsonResponse(True, safe=False) -def check_availability(request): - request_out = request.GET.get('out') - request_in = request.GET.get('in') - equipment_item_id = request.GET.get('id') - if None in (request_out, request_in, equipment_item_id): - return JsonResponse("Incorrect query format. Format is ?out={}&in={}&id={}", status=status.HTTP_400_BAD_REQUEST, safe=False) - - request_out_fmt = datetime.datetime.strptime(request_out, "%Y-%m-%dT%H:%M:%S%z") - request_in_fmt = datetime.datetime.strptime(request_in, "%Y-%m-%dT%H:%M:%S%z") - - # Check if the item ID provided exists - # if EquipmentItem.objects.filter(id = equipment_item_id).count() == 0: - # return JsonResponse("Equipment not found", status=status.HTTP_400_BAD_REQUEST, safe=False) - # equipment_item = EquipmentItem.objects.get(pk=equipment_item_id) - - # Check if request out is earlier than request in - if request_out_fmt > request_in_fmt: - # raise TypeError("") - return JsonResponse("Request out cannot be later than request in", status=status.HTTP_400_BAD_REQUEST, safe=False) - - # Return true if equipment is available during the specified time and - # false otherwise - for equipment_request in equipment_item.linked_requests.all(): - if (request_out_fmt < equipment_request.request_out) or (request_in_fmt > equipment_request.request_in): - return False - return True """ Partially update a request to reflect its signed out status Reference: https://stackoverflow.com/questions/50129567/django-rest-update-one-field From a1950be7262df395fd81afa3642b5e9b216e844d Mon Sep 17 00:00:00 2001 From: abarg12 Date: Sun, 29 Nov 2020 16:06:27 -0500 Subject: [PATCH 3/6] Added new viewset for equipment types filtered by availability based on a given timeframe. --- server/api/serializers.py | 24 ++++++++++++------------ server/api/urls.py | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/server/api/serializers.py b/server/api/serializers.py index e2d25412..45739a32 100644 --- a/server/api/serializers.py +++ b/server/api/serializers.py @@ -104,41 +104,41 @@ class Meta: fields = '__all__' +""" +Parameters: Takes in a request object (request) and also an equipment's id (eid) +Returns: True or False based on the availability of the equipment item in the + provided timeframe (request object) - +Assumes a properly formatted request parameter and does not return an error + if it is invalid (instead returns false). +""" def check_availability(request, eid): request_out = request.GET.get('out') request_in = request.GET.get('in') equipment_item_id = eid if None in (request_out, request_in, equipment_item_id): return False - #return JsonResponse("Incorrect query format. Format is ?out={}&in={}&id={}", status=status.HTTP_400_BAD_REQUEST, safe=False) - print("item_id:", equipment_item_id) - print("request_out:", request_out) - print("request_in:", request_in) - print() + request_out_fmt = datetime.datetime.strptime(request_out, "%Y-%m-%dT%H:%M:%S%z") request_in_fmt = datetime.datetime.strptime(request_in, "%Y-%m-%dT%H:%M:%S%z") # Check if the item ID provided exists if EquipmentItem.objects.filter(id = equipment_item_id).count() == 0: return False - #return JsonResponse("Equipment not found", status=status.HTTP_400_BAD_REQUEST, safe=False) equipment_item = EquipmentItem.objects.get(pk=equipment_item_id) # Check if request out is earlier than request in if request_out_fmt >= request_in_fmt: return False - # raise TypeError("") - # return JsonResponse("Request out cannot be later than request in", status=status.HTTP_400_BAD_REQUEST, safe=False) - # Return true if equipment is available during the specified time and # false otherwise # if request_out happens within the equipment request period # or if request_in happens within the equipment request period + # or if the request_out and request_in surround the equipment request period for equipment_request in equipment_item.linked_requests.all(): - if ((request_out_fmt >= equipment_request.request_out and request_out_fmt <= equipment_request.request_in) - or (request_in_fmt <= equipment_request.request_in and request_in_fmt >= equipment_request.request_out)): + if ((request_out_fmt > equipment_request.request_out and request_out_fmt < equipment_request.request_in) + or (request_in_fmt < equipment_request.request_in and request_in_fmt > equipment_request.request_out) + or (request_out_fmt <= equipment_request.request_out and request_in_fmt >= equipment_request.request_in)): return False return True \ No newline at end of file diff --git a/server/api/urls.py b/server/api/urls.py index 06011d4b..759b0411 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -17,7 +17,7 @@ urlpatterns = [ *router.urls, - #re_path(r'equipment', views.list_equipment), + # re_path(r'equipment', views.list_equipment), re_path(r'^availability/$', views.get_availability), path('signout//', views.sign_out_request.as_view()), path('return//', views.return_request.as_view()), From cfd1019de3201abbfa30e1469f98f4e0256a0efc Mon Sep 17 00:00:00 2001 From: abarg12 Date: Sun, 6 Dec 2020 16:03:52 -0500 Subject: [PATCH 4/6] confirm that user is authenticated before providing equipment-type-time viewset --- server/api/views/equipment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/api/views/equipment.py b/server/api/views/equipment.py index 872f130f..53cfaac4 100644 --- a/server/api/views/equipment.py +++ b/server/api/views/equipment.py @@ -31,6 +31,9 @@ class EquipmentTypeTimeViewSet(viewsets.ModelViewSet): """ API endpoint that allows Equipment Types to be viewed or edited and filters for time """ + # Confirm that user is logged in + permission_classes = [IsAuthenticated] + queryset = EquipmentType.objects.all() serializer_class = EquipmentTypeTimeSerializer From 3723ba92efe28b5c0cf71a6fb14ee94f0f153ec4 Mon Sep 17 00:00:00 2001 From: abarg12 Date: Thu, 6 Jan 2022 13:31:07 -0500 Subject: [PATCH 5/6] adding equipment serializer that takes time parameters and filters available item instances based on availability --- server/api/serializers.py | 77 +++++++++++++++++++++++++++++++++++ server/api/views/equipment.py | 33 ++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/server/api/serializers.py b/server/api/serializers.py index 8d010a66..3ffbf480 100644 --- a/server/api/serializers.py +++ b/server/api/serializers.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User, Group from rest_framework import serializers from .models import * +import datetime """ The HyperlinkedModelSerializer class is similar to the ModelSerializer @@ -67,6 +68,52 @@ class Meta: "num_instances", ] +class EquipmentItemSerializerWithTime(serializers.ModelSerializer): + """ + Helper class for EquipmentCategorySerializerWithTime + Has logic to find the number of item instances available based + on the time parameters in the GET request + """ + num_available = serializers.SerializerMethodField('get_num_available') + def get_num_available(self, obj): + available_count = 0 + item_inst = obj.instances() + n = obj.num_instances() + time_out_string = self.context.get('request_out') + time_in_string = self.context.get('request_in') + time_out = datetime.datetime.strptime(time_out_string, "%Y-%m-%dT%H:%M:%S%z") + time_in = datetime.datetime.strptime(time_in_string, "%Y-%m-%dT%H:%M:%S%z") + + for i in range(n): + instance = item_inst[i] + requests = instance.assoc_requests() + time_conflict = False + + # logic to see if existing request overlaps with GET request time + for assoc_request in requests: + if (assoc_request.request_out <= time_out): + if (assoc_request.request_in >= time_out): + time_conflict = True + if (assoc_request.request_out > time_out): + if (assoc_request.request_out <= time_in): + time_conflict = True + + if not time_conflict: + available_count += 1 + return available_count + + class Meta: + model = EquipmentItem + fields = [ + "id", + "name", + "description", + "image", + "product_url", + "equipment_type_FK", + "num_instances", + "num_available", + ] class EquipmentTypeSerializer(serializers.ModelSerializer): """ @@ -80,6 +127,22 @@ class Meta: fields = ["id", "name", "description", "equipment_category_FK", "items"] +class EquipmentTypeSerializerWithTime(serializers.ModelSerializer): + """ + Helper class for EquipmentCategorySerializerWithTime + Redefined __init__ allows for context to be passed through + """ + items = EquipmentItemSerializerWithTime(many=True, read_only=True) + + class Meta: + model = EquipmentType + fields = ["id", "name", "description", + "equipment_category_FK", "items"] + depth = 1 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['items'].context.update(self.context) class EquipmentCategorySerializer(serializers.ModelSerializer): """ @@ -92,6 +155,20 @@ class Meta: model = EquipmentCategory fields = ["id", "name", "description", "types"] +class EquipmentCategorySerializerWithTime(serializers.ModelSerializer): + """ + Serialize EquipmentCategory restricted by time availability of request + """ + types = EquipmentTypeSerializerWithTime(many=True, read_only=True) + + class Meta: + model = EquipmentCategory + fields = ["id", "name", "description", "types"] + depth = 2 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['types'].context.update(self.context) class EquipmentRequestItemQtySerializer(serializers.ModelSerializer): """ diff --git a/server/api/views/equipment.py b/server/api/views/equipment.py index 33518020..b8331c3d 100644 --- a/server/api/views/equipment.py +++ b/server/api/views/equipment.py @@ -13,14 +13,43 @@ from django.shortcuts import get_object_or_404 - +""" +request in EquipmentCategoryViewSet => context object => pass into +EquipmentCategorySerializerWithTime => pass into EquipmentTypeSerializerWithTime => +pass into EquipmentItemSerializerWithTime => EquipmentItemSerializerWithTime uses +SerializerMethodField to dynamically get num_available +""" class EquipmentCategoryViewSet(viewsets.ModelViewSet): """ API endpoint that allows Equipment Categories to be viewed or edited. """ queryset = EquipmentCategory.objects.all() - serializer_class = EquipmentCategorySerializer + + # Pass the GET request time parameters as context if they are present + def get_serializer_context(self, *args, **kwargs): + req_args = self.request.query_params + + # Check if time out and time in parameters are provided, if so + # then use a separate serializer to retrieve only available items + if ((req_args.get('out') == None) or (req_args.get('in') == None)): + return None + else: + return { + 'request_out': req_args.get('out'), + 'request_in' : req_args.get('in') + } + + # Use the time serializer if time parameters passed in GET request + def get_serializer_class(self): + req_args = self.request.query_params + + # Check if time out and time in parameters are provided, if so + # then use a separate serializer to retrieve only available items + if ((req_args.get('out') == None) or (req_args.get('in') == None)): + return EquipmentCategorySerializer + else: + return EquipmentCategorySerializerWithTime class EquipmentTypeViewSet(viewsets.ModelViewSet): From 7fee7b1468bca8de8bce216c2bacb356bd12ba42 Mon Sep 17 00:00:00 2001 From: abarg12 Date: Sun, 16 Jan 2022 16:53:36 -0500 Subject: [PATCH 6/6] merging changes serializers.py and urls.py --- server/api/serializers.py | 29 ----------------------------- server/api/urls.py | 10 ---------- 2 files changed, 39 deletions(-) diff --git a/server/api/serializers.py b/server/api/serializers.py index 9df47915..0374419d 100644 --- a/server/api/serializers.py +++ b/server/api/serializers.py @@ -123,35 +123,6 @@ class EquipmentTypeSerializer(serializers.ModelSerializer): items = EquipmentItemSerializer(many=True, read_only=True) -<<<<<<< HEAD -======= - -""" -Serializers to support serializing EquipmentItem objects -""" - -class EquipmentItemSerializerTime(serializers.HyperlinkedModelSerializer): - available = serializers.SerializerMethodField('is_available') - - def is_available(self, obj): - if check_availability(self.context.get("request"), obj.id): - return True - else: - return False - - class Meta: - model = EquipmentItem - fields = ['id', 'url', 'available'] - -class EquipmentTypeTimeSerializer(serializers.HyperlinkedModelSerializer): - items = EquipmentItemSerializerTime(many=True, required=False, read_only=True) - - class Meta: - model = EquipmentType - fields = "__all__" - -class EquipmentTypeSerializerSimple(serializers.HyperlinkedModelSerializer): ->>>>>>> cfd1019de3201abbfa30e1469f98f4e0256a0efc class Meta: model = EquipmentType fields = ["id", "name", "description", diff --git a/server/api/urls.py b/server/api/urls.py index f061a2a2..3c7b03f4 100644 --- a/server/api/urls.py +++ b/server/api/urls.py @@ -7,7 +7,6 @@ # These routes are registered for views defined with Django's ModelViewset. # They support HTTP GET, PUT, PATCH, DELETE -<<<<<<< HEAD router.register(r"users", views.UserViewSet) router.register(r"equipment-instances", views.EquipmentInstanceViewSet) router.register(r"equipment-items", views.EquipmentItemViewSet) @@ -18,15 +17,6 @@ ) router.register(r"admin/all-equipment-requests", views.EquipmentRequestViewSetAdmin) -======= -router.register(r'users', views.UserViewSet) -router.register(r'equipment-types', views.EquipmentTypeViewSet) -router.register(r'equipment-types-time', views.EquipmentTypeTimeViewSet) -router.register(r'equipment-items', views.EquipmentItemViewSet) -router.register(r'equipment-categories', views.EquipmentCategoryViewSet) -router.register(r'equipment-requests', views.EquipmentRequestViewSet,basename='EquipmentRequest') -router.register(r'equipment-requests-admin', views.EquipmentRequestViewSetAdmin) ->>>>>>> cfd1019de3201abbfa30e1469f98f4e0256a0efc urlpatterns = [ *router.urls,