Skip to content

Commit

Permalink
Merge pull request #2074 from ResearchHub/new-follow
Browse files Browse the repository at this point in the history
feat: Add new follow model
  • Loading branch information
gzurowski authored Jan 17, 2025
2 parents 8c1a5db + a86ab00 commit 51e1d05
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/user/migrations/0130_follow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Generated by Django 5.1.4 on 2025-01-17 11:41

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


class Migration(migrations.Migration):

dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("user", "0129_delete_follow"),
]

operations = [
migrations.CreateModel(
name="Follow",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_date", models.DateTimeField(auto_now_add=True)),
("updated_date", models.DateTimeField(auto_now=True)),
("object_id", models.PositiveIntegerField()),
(
"content_type",
models.ForeignKey(
limit_choices_to={"model__in": ["hub", "paper", "user"]},
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="following",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"unique_together": {("user", "content_type", "object_id")},
},
),
]
2 changes: 2 additions & 0 deletions src/user/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .related_models.action_model import Action
from .related_models.author_model import Author
from .related_models.follow_model import Follow
from .related_models.gatekeeper_model import Gatekeeper
from .related_models.organization_model import Organization
from .related_models.profile_image_storage import ProfileImageStorage
Expand All @@ -12,6 +13,7 @@
migratables = (
Action,
Author,
Follow,
Major,
ProfileImageStorage,
University,
Expand Down
35 changes: 35 additions & 0 deletions src/user/related_models/follow_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models

from utils.models import DefaultModel


class Follow(DefaultModel):
ALLOWED_FOLLOW_MODELS = ["hub", "paper", "user"]

user = models.ForeignKey(
"user.User",
on_delete=models.CASCADE,
related_name="following",
)
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
limit_choices_to={"model__in": ALLOWED_FOLLOW_MODELS},
)
object_id = models.PositiveIntegerField()
followed_object = GenericForeignKey("content_type", "object_id")

class Meta:
unique_together = ("user", "content_type", "object_id")

def clean(self):
if self.content_type.model not in self.ALLOWED_FOLLOW_MODELS:
raise ValidationError(f"Invalid follow model: {self.content_type.model}")
super().clean()

def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
51 changes: 51 additions & 0 deletions src/user/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.test import TestCase

from paper.related_models.authorship_model import Authorship
from paper.related_models.paper_model import Paper
from user.related_models.follow_model import Follow
from user.related_models.user_model import User
from user.tests.helpers import create_user


Expand Down Expand Up @@ -39,3 +43,50 @@ def test_open_access_pct_property(self):

def test_achievements(self):
self.assertIn("CITED_AUTHOR", self.user.author_profile.achievements)


class FollowModelTests(TestCase):
def setUp(self):
self.user = create_user(
email="[email protected]",
first_name="random",
last_name="user",
)

def test_follow_user(self):
# Arrange & Act
follow = Follow.objects.create(
user=self.user,
content_type=ContentType.objects.get_for_model(User),
object_id=self.user.id,
)

# Assert
self.assertEqual(follow.user, self.user)
self.assertEqual(follow.content_type, ContentType.objects.get_for_model(User))
self.assertEqual(follow.object_id, self.user.id)

def test_follow_paper(self):
# Arrange
paper = Paper.objects.create(title="title1", citations=10, is_open_access=True)

# Act
follow = Follow.objects.create(
user=self.user,
content_type=ContentType.objects.get_for_model(Paper),
object_id=paper.id,
)

# Assert
self.assertEqual(follow.user, self.user)
self.assertEqual(follow.content_type, ContentType.objects.get_for_model(Paper))
self.assertEqual(follow.object_id, paper.id)

def test_follow_unsupported_model(self):
# Arrange
with self.assertRaises(ValidationError):
Follow.objects.create(
user=self.user,
content_type=ContentType.objects.get_for_model(Authorship),
object_id=1,
)

0 comments on commit 51e1d05

Please sign in to comment.