From 22b9ebb5952dcb12ec54da439e32761dd874d34f Mon Sep 17 00:00:00 2001 From: Abubukker Chaudhary Date: Sun, 18 Jun 2023 12:53:29 -0400 Subject: [PATCH] (FEAT): Add reason_for_order field to Order model --- hackathon_site/event/test_api.py | 17 ++++++++- hackathon_site/hardware/admin.py | 2 +- .../migrations/0011_order_reason_for_order.py | 18 +++++++++ hackathon_site/hardware/models.py | 18 +++++++++ hackathon_site/hardware/serializers.py | 2 + hackathon_site/hardware/test_api.py | 28 ++++++++++++++ hackathon_site/hardware/tests.py | 37 +++++++++++++++++++ 7 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 hackathon_site/hardware/migrations/0011_order_reason_for_order.py diff --git a/hackathon_site/event/test_api.py b/hackathon_site/event/test_api.py index cbc12d478..49e08d317 100644 --- a/hackathon_site/event/test_api.py +++ b/hackathon_site/event/test_api.py @@ -180,6 +180,7 @@ def test_cannot_leave_with_order(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create(order=order, hardware=hardware) @@ -271,6 +272,7 @@ def test_leave_with_order(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create(order=order, hardware=hardware) @@ -680,7 +682,10 @@ def setUp(self): super().setUp() self.team = Team.objects.create() self.order = Order.objects.create( - status="Cart", team=self.team, request={"hardware": []} + status="Cart", + team=self.team, + request={"hardware": []}, + reason_for_order="Creating a Robot", ) self.hardware = Hardware.objects.create( name="name", @@ -711,7 +716,10 @@ def setUp(self): # making extra data to test if team data is being filtered self.team2 = Team.objects.create(team_code="ABCDE") self.order_2 = Order.objects.create( - status="Submitted", team=self.team2, request={"hardware": []} + status="Submitted", + team=self.team2, + request={"hardware": []}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create( order=self.order_2, hardware=self.hardware, @@ -754,6 +762,7 @@ def setUp(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) self.hardware = Hardware.objects.create( @@ -803,6 +812,7 @@ def test_post_another_team_incident(self): status="Cart", team=self.team2, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) self.order_item2 = OrderItem.objects.create( order=self.order2, hardware=self.other_hardware, @@ -898,6 +908,7 @@ def setUp(self): status="Submitted", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create(order=order, hardware=hardware) self.pk = order.id @@ -938,6 +949,7 @@ def test_failed_beginning_status(self): status="Picked Up", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) response = self.client.patch(self._build_view(order.id), self.request_data) self.assertEqual( @@ -954,6 +966,7 @@ def test_cannot_change_other_team_order(self): status="Submitted", team=self.team2, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) response = self.client.patch(self._build_view(order.id), self.request_data) self.assertEqual( diff --git a/hackathon_site/hardware/admin.py b/hackathon_site/hardware/admin.py index 832a353ef..e768f0851 100644 --- a/hackathon_site/hardware/admin.py +++ b/hackathon_site/hardware/admin.py @@ -269,7 +269,7 @@ class OrderAdmin(admin.ModelAdmin): "id", "get_team_code", ) - fields = ("team", "status") + fields = ("team", "status", "reason_for_order") search_fields = ("id", "team__team_code") inlines = ( OrderItemInline, diff --git a/hackathon_site/hardware/migrations/0011_order_reason_for_order.py b/hackathon_site/hardware/migrations/0011_order_reason_for_order.py new file mode 100644 index 000000000..827db79f1 --- /dev/null +++ b/hackathon_site/hardware/migrations/0011_order_reason_for_order.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.15 on 2023-06-18 15:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("hardware", "0010_alter_order_status"), + ] + + operations = [ + migrations.AddField( + model_name="order", + name="reason_for_order", + field=models.CharField(default="Hackathon Project", max_length=350), + ), + ] diff --git a/hackathon_site/hardware/models.py b/hackathon_site/hardware/models.py index 2fc71f0bd..c47b98335 100644 --- a/hackathon_site/hardware/models.py +++ b/hackathon_site/hardware/models.py @@ -1,5 +1,6 @@ from django.db import models from django.db.models import Count, F, Q +from django.core.exceptions import ValidationError from event.models import Team as TeamEvent @@ -115,10 +116,27 @@ class Order(models.Model): max_length=64, choices=STATUS_CHOICES, default="Submitted" ) request = models.JSONField(null=False) + reason_for_order = models.CharField( + max_length=350, null=False, blank=False, default=None + ) created_at = models.DateTimeField(auto_now_add=True, null=False) updated_at = models.DateTimeField(auto_now=True, null=False) + def validate_reason_for_order_length(self): + # If field is none, DB will raise IntegrityError automatically + if self.reason_for_order is None: + return + max_length = self._meta.get_field("reason_for_order").max_length + if len(self.reason_for_order) > max_length: + raise ValidationError( + f"Reason for order must be {max_length} characters or less." + ) + + def save(self, *args, **kwargs): + self.validate_reason_for_order_length() + super().save(*args, **kwargs) + def __str__(self): return f"{self.id}" diff --git a/hackathon_site/hardware/serializers.py b/hackathon_site/hardware/serializers.py index 878972b2c..941bc2716 100644 --- a/hackathon_site/hardware/serializers.py +++ b/hackathon_site/hardware/serializers.py @@ -126,6 +126,7 @@ class Meta: "team_id", "team_code", "status", + "reason_for_order", "created_at", "updated_at", "request", @@ -312,6 +313,7 @@ def create(self, validated_data): team=self.context["request"].user.profile.team, status="Submitted", request=serialized_requested_hardware, + reason_for_order="Hackathon Project", ) response_data["order_id"] = new_order.id order_items += [ diff --git a/hackathon_site/hardware/test_api.py b/hackathon_site/hardware/test_api.py index 09a62ae77..505a918b3 100644 --- a/hackathon_site/hardware/test_api.py +++ b/hackathon_site/hardware/test_api.py @@ -79,6 +79,7 @@ def setUp(self): {"id": 3, "quantity": 2}, ] }, + reason_for_order="Creating a Robot", ) def _build_filter_url(self, **kwargs): @@ -349,6 +350,7 @@ def setUp(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) self.hardware = Hardware.objects.create( name="name", @@ -471,6 +473,7 @@ def setUp(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) self.hardware = Hardware.objects.create( name="name", @@ -501,6 +504,7 @@ def setUp(self): status="Submitted", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create( order=self.order_2, hardware=self.hardware, @@ -512,6 +516,7 @@ def setUp(self): status="Cancelled", team=self.team2, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create( order=self.order_3, hardware=self.hardware, @@ -520,6 +525,7 @@ def setUp(self): status="Submitted", team=self.team2, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create( order=self.order_4, hardware=self.hardware, @@ -662,6 +668,7 @@ def setUp(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) self.hardware = Hardware.objects.create( name="name", @@ -692,6 +699,7 @@ def setUp(self): status="Submitted", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) self.order_item_3 = OrderItem.objects.create( order=self.order_2, hardware=self.hardware, @@ -703,6 +711,7 @@ def setUp(self): status="Cancelled", team=self.team2, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) self.order_item_5 = OrderItem.objects.create( order=self.order_3, hardware=self.hardware, @@ -711,6 +720,7 @@ def setUp(self): status="Submitted", team=self.team2, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) self.order_item_6 = OrderItem.objects.create( order=self.order_4, hardware=self.hardware, @@ -813,6 +823,7 @@ def setUp(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) self.permissions = Permission.objects.filter( content_type__app_label="hardware", codename="add_incident" @@ -875,6 +886,7 @@ def setUp(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 1}]}, + reason_for_order="Creating a Robot", ) self.hardware = Hardware.objects.create( name="name", @@ -996,6 +1008,7 @@ def create_order(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2},]}, + reason_for_order="Creating a Robot", ) self.category1 = Category.objects.create(name="category1", max_per_team=4) @@ -1181,6 +1194,7 @@ def test_invalid_input_hardware_limit_past_orders(self): team=self.user.profile.team, status="Submitted", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) submitted_order_item = OrderItem.objects.create( order=submitted_order, hardware=hardware @@ -1190,6 +1204,7 @@ def test_invalid_input_hardware_limit_past_orders(self): team=self.user.profile.team, status="Ready for Pickup", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) ready_order_item = OrderItem.objects.create( order=ready_order, hardware=hardware @@ -1199,6 +1214,7 @@ def test_invalid_input_hardware_limit_past_orders(self): team=self.user.profile.team, status="Picked Up", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) picked_up_order_item = OrderItem.objects.create( order=picked_up_order, hardware=hardware @@ -1239,6 +1255,7 @@ def test_hardware_limit_returned_orders(self): team=self.user.profile.team, status="Picked Up", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) healthy_order_item = OrderItem.objects.create( order=order, hardware=hardware, part_returned_health="Healthy" @@ -1291,6 +1308,7 @@ def test_hardware_limit_cancelled_orders(self): team=self.user.profile.team, status="Cancelled", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) order_item = OrderItem.objects.create(order=order, hardware=hardware) @@ -1363,6 +1381,7 @@ def test_invalid_input_category_limit_past_orders(self): team=self.user.profile.team, status="Submitted", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) submitted_order_item = OrderItem.objects.create( order=submitted_order, hardware=hardware @@ -1372,6 +1391,7 @@ def test_invalid_input_category_limit_past_orders(self): team=self.user.profile.team, status="Ready for Pickup", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) ready_order_item = OrderItem.objects.create( order=ready_order, hardware=hardware @@ -1381,6 +1401,7 @@ def test_invalid_input_category_limit_past_orders(self): team=self.user.profile.team, status="Picked Up", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) picked_up_order_item = OrderItem.objects.create( order=picked_up_order, hardware=hardware @@ -1421,6 +1442,7 @@ def test_category_limit_returned_orders(self): team=self.user.profile.team, status="Picked Up", request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) healthy_order_item = OrderItem.objects.create( order=order, hardware=hardware, part_returned_health="Healthy" @@ -1473,6 +1495,7 @@ def test_category_limit_cancelled_orders(self): team=self.user.profile.team, status="Cancelled", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) order_item = OrderItem.objects.create(order=order, hardware=hardware) @@ -1690,6 +1713,7 @@ def test_limited_by_remaining_quantities(self): team=self.user.profile.team, status="Submitted", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.bulk_create( [ @@ -1761,6 +1785,7 @@ def test_no_remaining_quantities(self): team=self.user.profile.team, status="Submitted", request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) order_item = OrderItem.objects.create(order=order, hardware=hardware) @@ -1827,6 +1852,7 @@ def setUp(self): status="Submitted", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create(order=order, hardware=hardware) self.pk = order.id @@ -1876,6 +1902,7 @@ def test_failed_beginning_status(self): status="Picked Up", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) response = self.client.patch(self._build_view(order.id), request_data) self.assertEqual( @@ -1897,6 +1924,7 @@ def setUp(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) self.permissions = Permission.objects.filter( content_type__app_label="hardware", codename="change_order" diff --git a/hackathon_site/hardware/tests.py b/hackathon_site/hardware/tests.py index bdf798e12..9dbc517b6 100644 --- a/hackathon_site/hardware/tests.py +++ b/hackathon_site/hardware/tests.py @@ -1,5 +1,7 @@ from django.test import TestCase from rest_framework import serializers +from django.db.utils import IntegrityError +from django.core.exceptions import ValidationError from hardware.models import Hardware, Category, Order, OrderItem, Incident from event.models import Team @@ -56,6 +58,7 @@ def test_some_items_cancelled(self): status="Cancelled", team=team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) order_item_1 = OrderItem.objects.create(order=order, hardware=self.hardware,) @@ -85,6 +88,7 @@ def test_some_items_returned(self): status="Picked Up", team=team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) order_item_1 = OrderItem.objects.create( order=order, hardware=self.hardware, part_returned_health="Healthy" @@ -135,6 +139,7 @@ def test_some_items_none_returned(self): status="Picked Up", team=team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) order_item_1 = OrderItem.objects.create(order=order, hardware=self.hardware,) order_item_2 = OrderItem.objects.create(order=order, hardware=self.hardware,) @@ -148,6 +153,7 @@ def test_some_items_returned_healthy(self): status="Picked Up", team=team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) order_item_1 = OrderItem.objects.create( order=order, hardware=self.hardware, part_returned_health="Healthy" @@ -163,6 +169,7 @@ def test_some_items_returned_not_healthy(self): status="Picked Up", team=team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) OrderItem.objects.create( order=order, hardware=self.hardware, part_returned_health="Broken" @@ -180,6 +187,7 @@ def test_some_items_cancelled(self): status="Cancelled", team=team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) order_item_1 = OrderItem.objects.create(order=order, hardware=self.hardware,) order_item_2 = OrderItem.objects.create(order=order, hardware=self.hardware,) @@ -232,6 +240,7 @@ def setUp(self): status="Picked Up", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}]}, + reason_for_order="Creating a Robot", ) self.category = Category.objects.create(name="category", max_per_team=4) @@ -319,6 +328,7 @@ def test_empty_order(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) order_serializer = OrderListSerializer(order).data expected_response = { @@ -326,6 +336,7 @@ def test_empty_order(self): "team_id": self.team.id, "team_code": self.team.team_code, "status": "Cart", + "reason_for_order": "Creating a Robot", "items": [], "created_at": serializers.DateTimeField().to_representation( order.created_at @@ -345,6 +356,7 @@ def test_hardware_set(self): status="Cart", team=self.team, request={"hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}]}, + reason_for_order="Creating a Robot", ) item_1 = OrderItem.objects.create( order=order, hardware=self.hardware, part_returned_health="Healthy" @@ -358,6 +370,7 @@ def test_hardware_set(self): "team_id": self.team.id, "team_code": self.team.team_code, "status": "Cart", + "reason_for_order": "Creating a Robot", "items": [ { "id": item_1.id, @@ -381,3 +394,27 @@ def test_hardware_set(self): }, } self.assertEqual(order_serializer, expected_response) + + def test_order_with_no_reason_for_order_field(self): + with self.assertRaises(IntegrityError): + Order.objects.create( + status="Cart", + team=self.team, + request={ + "hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}] + }, + ) + + def test_order_with_large_reason_for_order(self): + with self.assertRaises(ValidationError) as context: + Order.objects.create( + status="Cart", + team=self.team, + request={ + "hardware": [{"id": 1, "quantity": 2}, {"id": 2, "quantity": 3}] + }, + reason_for_order="*" * 351, + ) + # Formatted like this since ValidationError __str__ method returns in this format + expected_error_message = "['Reason for order must be 350 characters or less.']" + self.assertEqual(str(context.exception), expected_error_message)