diff --git a/hackathon_site/event/test_api.py b/hackathon_site/event/test_api.py index a19e15c71..d14a78ef0 100644 --- a/hackathon_site/event/test_api.py +++ b/hackathon_site/event/test_api.py @@ -181,6 +181,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) @@ -272,6 +273,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) @@ -681,7 +683,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", @@ -712,7 +717,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, @@ -755,6 +763,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( @@ -804,6 +813,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, @@ -1006,6 +1016,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 @@ -1046,6 +1057,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( @@ -1062,6 +1074,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 29888d210..605712777 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 241e5ed9e..294cf626f 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 130ab7468..c9f1226a9 100644 --- a/hackathon_site/hardware/serializers.py +++ b/hackathon_site/hardware/serializers.py @@ -148,6 +148,7 @@ class Meta: "team_id", "team_code", "status", + "reason_for_order", "created_at", "updated_at", "request", @@ -334,6 +335,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 2b525222f..b75798ac4 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", @@ -1084,6 +1096,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) @@ -1269,6 +1282,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 @@ -1278,6 +1292,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 @@ -1287,6 +1302,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 @@ -1327,6 +1343,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" @@ -1379,6 +1396,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) @@ -1451,6 +1469,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 @@ -1460,6 +1479,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 @@ -1469,6 +1489,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 @@ -1509,6 +1530,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" @@ -1561,6 +1583,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) @@ -1778,6 +1801,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( [ @@ -1849,6 +1873,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) @@ -1915,6 +1940,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 @@ -1964,6 +1990,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( @@ -1985,6 +2012,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)