From cb7a22550ef2b7adc1a8f83aa79e6b260798f465 Mon Sep 17 00:00:00 2001 From: 23348918 <23348918@student.uwa.edu.au> Date: Sat, 14 Dec 2024 03:41:19 +0000 Subject: [PATCH 1/5] created friendship model --- server/bingo/models.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/server/bingo/models.py b/server/bingo/models.py index fa71560..cb0874a 100644 --- a/server/bingo/models.py +++ b/server/bingo/models.py @@ -92,3 +92,24 @@ class Challenge(models.Model): def __str__(self): # Format when printed: Challenge ID: Name (Challenge Type) return f"Challenge {self.id}: {self.name} ({self.challenge_type.capitalize()})" + + +class FriendshipTable(models.Model): + id = models.AutoField(primary_key=True) + requester = models.ForeignKey(User, on_delete=models.CASCADE) + receiver = models.ForeignKey(User, on_delete=models.CASCADE) + + PENDING = "pending" + ACCEPTED = "accepted" + STATUS = [ + (PENDING, "Pending"), + (ACCEPTED, "Accepted") + ] + status = models.CharField( + max_length=10, + choices=STATUS, + default=PENDING + ) + + def __str__(self): + return f"Friend request from {self.requester} to {self.receiver} ({self.status.capitalize()})" From 0413d197bd1062d60b7eed91cc8a6ff9dd11831b Mon Sep 17 00:00:00 2001 From: 23348918 <23348918@student.uwa.edu.au> Date: Sat, 14 Dec 2024 03:54:58 +0000 Subject: [PATCH 2/5] registered friendship table and added related name --- server/bingo/admin.py | 3 +- .../bingo/migrations/0003_friendshiptable.py | 45 +++++++++++++++++++ server/bingo/models.py | 6 ++- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 server/bingo/migrations/0003_friendshiptable.py diff --git a/server/bingo/admin.py b/server/bingo/admin.py index 72d7b36..e20bf73 100644 --- a/server/bingo/admin.py +++ b/server/bingo/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from .models import User, Challenge +from .models import User, Challenge, FriendshipTable # Register your models here. admin.site.register(User) admin.site.register(Challenge) +admin.site.register(FriendshipTable) diff --git a/server/bingo/migrations/0003_friendshiptable.py b/server/bingo/migrations/0003_friendshiptable.py new file mode 100644 index 0000000..122683a --- /dev/null +++ b/server/bingo/migrations/0003_friendshiptable.py @@ -0,0 +1,45 @@ +# Generated by Django 5.1 on 2024-12-14 03:52 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bingo", "0002_challenge"), + ] + + operations = [ + migrations.CreateModel( + name="FriendshipTable", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "status", + models.CharField( + choices=[("pending", "Pending"), ("accepted", "Accepted")], + default="pending", + max_length=10, + ), + ), + ( + "receiver", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="received_requests", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "requester", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="sent_requests", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/server/bingo/models.py b/server/bingo/models.py index cb0874a..489d9e6 100644 --- a/server/bingo/models.py +++ b/server/bingo/models.py @@ -96,8 +96,10 @@ def __str__(self): class FriendshipTable(models.Model): id = models.AutoField(primary_key=True) - requester = models.ForeignKey(User, on_delete=models.CASCADE) - receiver = models.ForeignKey(User, on_delete=models.CASCADE) + requester = models.ForeignKey( + User, related_name="sent_requests", on_delete=models.CASCADE) + receiver = models.ForeignKey( + User, related_name="received_requests", on_delete=models.CASCADE) PENDING = "pending" ACCEPTED = "accepted" From ac5f1c029647074e0091393b5befb032e1657a4a Mon Sep 17 00:00:00 2001 From: 23348918 <23348918@student.uwa.edu.au> Date: Sat, 14 Dec 2024 04:08:10 +0000 Subject: [PATCH 3/5] renamed to Frienship, added unique_together Meta --- server/bingo/admin.py | 4 ++-- ...004_alter_friendshiptable_unique_together.py | 17 +++++++++++++++++ .../0005_rename_friendshiptable_friendship.py | 17 +++++++++++++++++ server/bingo/models.py | 5 ++++- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 server/bingo/migrations/0004_alter_friendshiptable_unique_together.py create mode 100644 server/bingo/migrations/0005_rename_friendshiptable_friendship.py diff --git a/server/bingo/admin.py b/server/bingo/admin.py index e20bf73..86ac0a0 100644 --- a/server/bingo/admin.py +++ b/server/bingo/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin -from .models import User, Challenge, FriendshipTable +from .models import User, Challenge, Friendship # Register your models here. admin.site.register(User) admin.site.register(Challenge) -admin.site.register(FriendshipTable) +admin.site.register(Friendship) diff --git a/server/bingo/migrations/0004_alter_friendshiptable_unique_together.py b/server/bingo/migrations/0004_alter_friendshiptable_unique_together.py new file mode 100644 index 0000000..847ccdf --- /dev/null +++ b/server/bingo/migrations/0004_alter_friendshiptable_unique_together.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-12-14 04:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bingo", "0003_friendshiptable"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="friendshiptable", + unique_together={("requester", "receiver")}, + ), + ] diff --git a/server/bingo/migrations/0005_rename_friendshiptable_friendship.py b/server/bingo/migrations/0005_rename_friendshiptable_friendship.py new file mode 100644 index 0000000..233e7cf --- /dev/null +++ b/server/bingo/migrations/0005_rename_friendshiptable_friendship.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-12-14 04:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bingo", "0004_alter_friendshiptable_unique_together"), + ] + + operations = [ + migrations.RenameModel( + old_name="FriendshipTable", + new_name="Friendship", + ), + ] diff --git a/server/bingo/models.py b/server/bingo/models.py index 489d9e6..f309f5c 100644 --- a/server/bingo/models.py +++ b/server/bingo/models.py @@ -94,7 +94,7 @@ def __str__(self): return f"Challenge {self.id}: {self.name} ({self.challenge_type.capitalize()})" -class FriendshipTable(models.Model): +class Friendship(models.Model): id = models.AutoField(primary_key=True) requester = models.ForeignKey( User, related_name="sent_requests", on_delete=models.CASCADE) @@ -113,5 +113,8 @@ class FriendshipTable(models.Model): default=PENDING ) + class Meta: + unique_together = ('requester', 'receiver') + def __str__(self): return f"Friend request from {self.requester} to {self.receiver} ({self.status.capitalize()})" From 2c809fa13bd4eb2154c361842c2b60299cf49705 Mon Sep 17 00:00:00 2001 From: 23348918 <23348918@student.uwa.edu.au> Date: Sat, 14 Dec 2024 05:23:58 +0000 Subject: [PATCH 4/5] Adjusted duplicate friendship constraint to not allow reverse relations, added test cases for Friendship --- server/bingo/models.py | 16 ++++++++++- server/bingo/tests.py | 65 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/server/bingo/models.py b/server/bingo/models.py index f309f5c..f58a971 100644 --- a/server/bingo/models.py +++ b/server/bingo/models.py @@ -1,6 +1,8 @@ from django.db import models from django.contrib.auth.models import PermissionsMixin, AbstractBaseUser, BaseUserManager from datetime import date +from django.db.models import Q +from django.core.exceptions import ValidationError class UserManager(BaseUserManager): @@ -114,7 +116,19 @@ class Friendship(models.Model): ) class Meta: - unique_together = ('requester', 'receiver') + # Ensure the combination of requester and receiver is unique + constraints = [ + models.UniqueConstraint( + fields=["requester", "receiver"], name="unique_friendship" + ) + ] + + def clean(self): + # Ensure no reverse friendships exist + if Friendship.objects.filter( + Q(requester=self.receiver, receiver=self.requester) + ).exists(): + raise ValidationError("A reverse friendship already exists.") def __str__(self): return f"Friend request from {self.requester} to {self.receiver} ({self.status.capitalize()})" diff --git a/server/bingo/tests.py b/server/bingo/tests.py index 9660547..66f9cdc 100644 --- a/server/bingo/tests.py +++ b/server/bingo/tests.py @@ -1,5 +1,6 @@ +from django.db.utils import IntegrityError from django.test import TestCase -from .models import Challenge +from .models import User, Challenge, Friendship from django.core.exceptions import ValidationError @@ -39,3 +40,65 @@ def test_default_total_completions(self): points=15, ) self.assertEqual(challenge.total_completions, 0) + + +class FriendshipTest(TestCase): + def setUp(self): + # Create test users + self.user1 = User.objects.create_user( + username="user1", email="user1@example.com", password="password") + self.user2 = User.objects.create_user( + username="user2", email="user2@example.com", password="password") + self.user3 = User.objects.create_user( + username="user3", email="user3@example.com", password="password") + + def test_create_friendship(self): + # Tests whether a frienship can be created + friendship = Friendship.objects.create( + requester=self.user1, receiver=self.user2) + self.assertEqual(friendship.requester, self.user1) + self.assertEqual(friendship.receiver, self.user2) + # Checks default status is pending + self.assertEqual(friendship.status, Friendship.PENDING) + + def test_unique_together_constraint(self): + # Create a friendship + Friendship.objects.create(requester=self.user1, receiver=self.user2) + + # Attempt to create a duplicate friendship + with self.assertRaises(IntegrityError): + Friendship.objects.create( + requester=self.user1, receiver=self.user2) + + def test_status_choices(self): + # Test creating a friendship with "accepted" status + friendship = Friendship.objects.create( + requester=self.user1, receiver=self.user2, status=Friendship.ACCEPTED) + self.assertEqual(friendship.status, Friendship.ACCEPTED) + + # Test invalid status + with self.assertRaises(ValidationError): + Friendship.objects.create( + requester=self.user1, receiver=self.user3, status="invalid").full_clean() + + def test_str_representation(self): + # Test the string representation of a friendship + friendship = Friendship.objects.create( + requester=self.user1, receiver=self.user2) + expected_str = f"Friend request from { + self.user1} to {self.user2} (Pending)" + self.assertEqual(str(friendship), expected_str) + + def test_delete_user_cascades(self): + # Test that deleting a user cascades to related friendships + Friendship.objects.create(requester=self.user1, receiver=self.user2) + self.user1.delete() + self.assertEqual(Friendship.objects.count(), 0) + + def test_reverse_friendship(self): + # Test whether two reverse friendship can be created + Friendship.objects.create(requester=self.user1, receiver=self.user2) + + with self.assertRaises(ValidationError): + Friendship.objects.create( + requester=self.user2, receiver=self.user1).full_clean() From b221832d38cfeb324e105a7b285b2ab4e3c503df Mon Sep 17 00:00:00 2001 From: 23348918 <23348918@student.uwa.edu.au> Date: Sat, 14 Dec 2024 05:46:22 +0000 Subject: [PATCH 5/5] added so there can't be a friendship with the same sender and receiver --- ...ter_friendship_unique_together_and_more.py | 23 +++++++++++++++++++ server/bingo/models.py | 5 ++++ server/bingo/tests.py | 6 +++++ 3 files changed, 34 insertions(+) create mode 100644 server/bingo/migrations/0006_alter_friendship_unique_together_and_more.py diff --git a/server/bingo/migrations/0006_alter_friendship_unique_together_and_more.py b/server/bingo/migrations/0006_alter_friendship_unique_together_and_more.py new file mode 100644 index 0000000..015d45b --- /dev/null +++ b/server/bingo/migrations/0006_alter_friendship_unique_together_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1 on 2024-12-14 05:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bingo", "0005_rename_friendshiptable_friendship"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="friendship", + unique_together=set(), + ), + migrations.AddConstraint( + model_name="friendship", + constraint=models.UniqueConstraint( + fields=("requester", "receiver"), name="unique_friendship" + ), + ), + ] diff --git a/server/bingo/models.py b/server/bingo/models.py index f58a971..4e81bf6 100644 --- a/server/bingo/models.py +++ b/server/bingo/models.py @@ -130,5 +130,10 @@ def clean(self): ).exists(): raise ValidationError("A reverse friendship already exists.") + # Ensure requester and receiver are not the same + if self.requester == self.receiver: + raise ValidationError( + "Requester and receiver cannot be the same user.") + def __str__(self): return f"Friend request from {self.requester} to {self.receiver} ({self.status.capitalize()})" diff --git a/server/bingo/tests.py b/server/bingo/tests.py index 66f9cdc..2d5d7dc 100644 --- a/server/bingo/tests.py +++ b/server/bingo/tests.py @@ -102,3 +102,9 @@ def test_reverse_friendship(self): with self.assertRaises(ValidationError): Friendship.objects.create( requester=self.user2, receiver=self.user1).full_clean() + + def test_friends_with_self(self): + # Test that a user cannot have a friendship with themselves + with self.assertRaises(ValidationError): + Friendship.objects.create( + requester=self.user1, receiver=self.user1).full_clean()