Skip to content

Fix issue #192: [Claude] User groups #193

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
77 changes: 77 additions & 0 deletions photo/migrations/0005_group_usergroup_group_members_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generated by Django 4.2.8 on 2025-02-06 15:21

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):
dependencies = [
("photo", "0004_alter_contest_prize"),
]

operations = [
migrations.CreateModel(
name="Group",
fields=[
("is_deleted", models.BooleanField(default=False)),
(
"id",
models.UUIDField(
default=uuid.uuid4, primary_key=True, serialize=False
),
),
("name", models.TextField()),
("description", models.TextField(blank=True, null=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="UserGroup",
fields=[
("is_deleted", models.BooleanField(default=False)),
(
"id",
models.UUIDField(
default=uuid.uuid4, primary_key=True, serialize=False
),
),
("role", models.TextField()),
("joined_at", models.DateTimeField(auto_now_add=True)),
(
"group",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="photo.group"
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.AddField(
model_name="group",
name="members",
field=models.ManyToManyField(
related_name="user_groups",
through="photo.UserGroup",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AddConstraint(
model_name="usergroup",
constraint=models.UniqueConstraint(
condition=models.Q(("is_deleted", False)),
fields=("user", "group"),
name="unique_user_group",
),
),
]
32 changes: 32 additions & 0 deletions photo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,38 @@ class Meta:
abstract = True


class Group(SoftDeleteModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.TextField()
description = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
members = models.ManyToManyField(
"User",
through="UserGroup",
related_name="user_groups",
)

def __str__(self):
return self.name


class UserGroup(SoftDeleteModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey("User", on_delete=models.CASCADE)
group = models.ForeignKey("Group", on_delete=models.CASCADE)
role = models.TextField()
joined_at = models.DateTimeField(auto_now_add=True)

class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "group"],
condition=models.Q(is_deleted=False),
name="unique_user_group",
)
]


class User(AbstractUser, SoftDeleteModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
email = models.TextField(unique=True)
Expand Down
102 changes: 102 additions & 0 deletions photo/tests/test_database/test_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import pytest
from django.db.utils import IntegrityError
from django.forms import ValidationError

from photo.models import Group, User, UserGroup
from photo.tests.factories import UserFactory


@pytest.mark.django_db
class TestGroup:
def test_create_group(self):
group = Group.objects.create(
name="Test Group",
description="A test group",
)
assert group.name == "Test Group"
assert group.description == "A test group"
assert group.created_at is not None

def test_add_user_to_group(self):
user = UserFactory()
group = Group.objects.create(name="Test Group")
user_group = UserGroup.objects.create(
user=user,
group=group,
role="member",
)
assert user_group.user == user
assert user_group.group == group
assert user_group.role == "member"
assert user_group.joined_at is not None

def test_add_multiple_users_to_group(self):
users = [UserFactory() for _ in range(3)]
group = Group.objects.create(name="Test Group")

for user in users:
UserGroup.objects.create(
user=user,
group=group,
role="member",
)

assert group.members.count() == 3
for user in users:
assert user in group.members.all()

def test_user_cannot_join_group_twice(self):
user = UserFactory()
group = Group.objects.create(name="Test Group")
UserGroup.objects.create(
user=user,
group=group,
role="member",
)

with pytest.raises(IntegrityError):
UserGroup.objects.create(
user=user,
group=group,
role="admin",
)

def test_user_can_be_in_multiple_groups(self):
user = UserFactory()
group1 = Group.objects.create(name="Group 1")
group2 = Group.objects.create(name="Group 2")

UserGroup.objects.create(
user=user,
group=group1,
role="member",
)
UserGroup.objects.create(
user=user,
group=group2,
role="admin",
)

assert user.user_groups.count() == 2
assert group1 in user.user_groups.all()
assert group2 in user.user_groups.all()

def test_soft_delete_group(self):
group = Group.objects.create(name="Test Group")
group.delete()
assert group.is_deleted
assert not Group.objects.filter(name="Test Group").exists()
assert Group.all_objects.filter(name="Test Group").exists()

def test_soft_delete_user_group(self):
user = UserFactory()
group = Group.objects.create(name="Test Group")
user_group = UserGroup.objects.create(
user=user,
group=group,
role="member",
)
user_group.delete()
assert user_group.is_deleted
assert not UserGroup.objects.filter(user=user, group=group).exists()
assert UserGroup.all_objects.filter(user=user, group=group).exists()