Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 25 create friendship db model #42

Merged
merged 5 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion server/bingo/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib import admin
from .models import User, Challenge
from .models import User, Challenge, Friendship

# Register your models here.
admin.site.register(User)
admin.site.register(Challenge)
admin.site.register(Friendship)
45 changes: 45 additions & 0 deletions server/bingo/migrations/0003_friendshiptable.py
Original file line number Diff line number Diff line change
@@ -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,
),
),
],
),
]
Original file line number Diff line number Diff line change
@@ -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")},
),
]
17 changes: 17 additions & 0 deletions server/bingo/migrations/0005_rename_friendshiptable_friendship.py
Original file line number Diff line number Diff line change
@@ -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",
),
]
Original file line number Diff line number Diff line change
@@ -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"
),
),
]
45 changes: 45 additions & 0 deletions server/bingo/models.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -92,3 +94,46 @@ 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 Friendship(models.Model):
id = models.AutoField(primary_key=True)
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"
STATUS = [
(PENDING, "Pending"),
(ACCEPTED, "Accepted")
]
status = models.CharField(
max_length=10,
choices=STATUS,
default=PENDING
)

class Meta:
# 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.")

# 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()})"
71 changes: 70 additions & 1 deletion server/bingo/tests.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -39,3 +40,71 @@ 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()

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()
Loading