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

[FORUM UPVOTE] préparation : déplacement du modèle CertifiedPost #356

Merged
merged 4 commits into from
Jul 20, 2023
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: 1 addition & 2 deletions lacommunaute/forum/tests/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@

from lacommunaute.forum.factories import CategoryForumFactory, ForumFactory
from lacommunaute.forum.views import ForumView
from lacommunaute.forum_conversation.factories import PostFactory, TopicFactory
from lacommunaute.forum_conversation.factories import CertifiedPostFactory, PostFactory, TopicFactory
from lacommunaute.forum_conversation.forms import PostForm
from lacommunaute.forum_conversation.models import Topic
from lacommunaute.forum_upvote.factories import CertifiedPostFactory
from lacommunaute.users.factories import UserFactory


Expand Down
15 changes: 15 additions & 0 deletions lacommunaute/forum_conversation/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.contrib import admin
from machina.apps.forum_conversation.admin import TopicAdmin as BaseTopicAdmin

from lacommunaute.forum_conversation.models import CertifiedPost


# TODO vincentporte, need to be fixed, likers field does not appear like a raw id fields
class TopicAdmin(BaseTopicAdmin):
Expand All @@ -8,3 +11,15 @@ class TopicAdmin(BaseTopicAdmin):
"subscribers",
"likers",
)


class CertifiedPostAdmin(admin.ModelAdmin):
list_display = ("topic", "post", "user")
raw_id_fields = (
"topic",
"post",
"user",
)


admin.site.register(CertifiedPost, CertifiedPostAdmin)
8 changes: 6 additions & 2 deletions lacommunaute/forum_conversation/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

from lacommunaute.forum.factories import ForumFactory
from lacommunaute.forum_conversation.forum_polls.factories import TopicPollVoteFactory
from lacommunaute.forum_conversation.models import Topic
from lacommunaute.forum_upvote.factories import CertifiedPostFactory
from lacommunaute.forum_conversation.models import CertifiedPost, Topic
from lacommunaute.users.factories import UserFactory


Expand All @@ -14,6 +13,11 @@ class PostFactory(BasePostFactory):
poster = factory.SubFactory(UserFactory)


class CertifiedPostFactory(factory.django.DjangoModelFactory):
class Meta:
model = CertifiedPost


class TopicFactory(BaseTopicFactory):
forum = factory.SubFactory(ForumFactory)
poster = factory.SubFactory(UserFactory)
Expand Down
79 changes: 79 additions & 0 deletions lacommunaute/forum_conversation/migrations/0007_certifiedpost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Generated by Django 4.2.3 on 2023-07-19 08:59

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


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("forum_conversation", "0006_topic_tags"),
("forum_upvote", "0002_certifiedpost"),
]

operations = [
migrations.CreateModel(
name="CertifiedPost",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created", models.DateTimeField(auto_now_add=True, verbose_name="Creation date")),
("updated", models.DateTimeField(auto_now=True, verbose_name="Update date")),
(
"post",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="certified_post",
to="forum_conversation.post",
verbose_name="Post",
),
),
(
"topic",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="certified_post",
to="forum_conversation.topic",
verbose_name="Topic",
),
),
(
"user",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="certified_posts",
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
],
options={
"ordering": ["-created"],
},
),
migrations.RunSQL(
"""
INSERT INTO forum_conversation_certifiedpost (
id, created, updated, post_id, topic_id, user_id
)
SELECT
id, created, updated, post_id, topic_id, user_id
FROM
forum_upvote_certifiedpost;
SELECT
setval(
pg_get_serial_sequence('"forum_conversation_certifiedpost"','id'),
coalesce(max("id"), 1),
max("id") IS NOT null
)
FROM
"forum_conversation_certifiedpost";
""",
reverse_sql="""
INSERT INTO forum_upvote_certifiedpost (id, created, updated, post_id, topic_id, user_id)
SELECT id, created, updated, post_id, topic_id, user_id FROM forum_conversation_certifiedpost;
""",
),
]
38 changes: 38 additions & 0 deletions lacommunaute/forum_conversation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.db.models import Count, Exists, OuterRef
from django.urls import reverse
from machina.apps.forum_conversation.abstract_models import AbstractPost, AbstractTopic
from machina.models.abstract_models import DatedModel
from taggit.managers import TaggableManager

from lacommunaute.forum_member.shortcuts import get_forum_member_display_name
Expand Down Expand Up @@ -90,3 +91,40 @@ def poster_display_name(self):
@property
def is_certified(self):
return hasattr(self, "certified_post")


class CertifiedPost(DatedModel):
topic = models.OneToOneField(
Topic,
related_name="certified_post",
on_delete=models.CASCADE,
verbose_name="Topic",
)

post = models.OneToOneField(
Post,
related_name="certified_post",
on_delete=models.CASCADE,
verbose_name="Post",
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="certified_posts",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name="User",
)

class Meta:
ordering = [
"-created",
]

def __str__(self):
return f"{self.post} - {self.topic} - {self.user}"

def save(self, *args, **kwargs):
if self.topic != self.post.topic:
raise ValueError("The post is not link to the topic")
super().save(*args, **kwargs)
9 changes: 9 additions & 0 deletions lacommunaute/forum_conversation/shortcuts.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.db.models import Count, Exists, OuterRef, Prefetch, Q, QuerySet

from lacommunaute.forum.enums import Kind as Forum_Kind
from lacommunaute.forum_conversation.models import Post, Topic
from lacommunaute.forum_upvote.models import UpVote
from lacommunaute.users.models import User
Expand Down Expand Up @@ -28,3 +29,11 @@ def get_posts_of_a_topic_except_first_one(topic: Topic, user: User) -> QuerySet[
upvotes_count=Count("upvotes"),
)
return qs.order_by("created")


def can_certify_post(forum, user):
return (
user.is_authenticated
and forum.kind == Forum_Kind.PUBLIC_FORUM
and (user.groups.filter(forum=forum).exists() or user.is_staff)
)
20 changes: 19 additions & 1 deletion lacommunaute/forum_conversation/tests/tests_models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.test import TestCase
from django.urls import reverse

from lacommunaute.forum.factories import ForumFactory
from lacommunaute.forum_conversation.factories import PostFactory, TopicFactory
from lacommunaute.forum_conversation.factories import CertifiedPostFactory, PostFactory, TopicFactory
from lacommunaute.forum_conversation.models import Post, Topic
from lacommunaute.forum_member.shortcuts import get_forum_member_display_name

Expand Down Expand Up @@ -124,3 +125,20 @@ def test_topic_types(self):
self.assertEqual(0, Topic.TOPIC_POST)
self.assertEqual(1, Topic.TOPIC_STICKY)
self.assertEqual(2, Topic.TOPIC_ANNOUNCE)


class CertifiedPostModelTest(TestCase):
def test_topic_is_unique(self):
topic = TopicFactory(with_certified_post=True)

with self.assertRaises(IntegrityError):
CertifiedPostFactory(topic=topic, post=topic.first_post, user=topic.poster)

def test_post_is_link_to_topic(self):
topic_to_be_certified = TopicFactory()
other_topic = TopicFactory(with_post=True)

with self.assertRaises(ValueError):
CertifiedPostFactory(
topic=topic_to_be_certified, post=other_topic.first_post, user=topic_to_be_certified.poster
)
32 changes: 31 additions & 1 deletion lacommunaute/forum_conversation/tests/tests_shortcuts.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from django.contrib.auth.models import AnonymousUser
from django.test import TestCase

from lacommunaute.forum.enums import Kind as ForumKind
from lacommunaute.forum.factories import ForumFactory
from lacommunaute.forum_conversation.factories import PostFactory, TopicFactory
from lacommunaute.forum_conversation.shortcuts import get_posts_of_a_topic_except_first_one
from lacommunaute.forum_conversation.shortcuts import can_certify_post, get_posts_of_a_topic_except_first_one
from lacommunaute.forum_upvote.factories import UpVoteFactory
from lacommunaute.users.factories import UserFactory


class GetPostsofaTopicExceptFirstOneTest(TestCase):
Expand Down Expand Up @@ -66,3 +69,30 @@ def test_topic_has_been_upvoted_by_the_user(self):
self.assertEqual(len(posts), 1)
self.assertEqual(post.upvotes_count, 1)
self.assertTrue(post.has_upvoted)


class CanCertifyPostShortcutTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = UserFactory.create()
cls.forum = ForumFactory.create()

def test_user_is_not_authenticated(self):
self.assertFalse(can_certify_post(self.forum, AnonymousUser()))

def test_forum_is_private(self):
self.assertFalse(can_certify_post(ForumFactory.create(kind=ForumKind.PRIVATE_FORUM), self.user))

def test_forum_is_newsfeed(self):
self.assertFalse(can_certify_post(ForumFactory.create(kind=ForumKind.NEWS), self.user))

def test_user_is_not_in_the_forum_members_group(self):
self.assertFalse(can_certify_post(self.forum, self.user))

def test_user_is_in_the_forum_members_group(self):
self.forum.members_group.user_set.add(self.user)
self.assertTrue(can_certify_post(self.forum, self.user))

def test_user_is_staff(self):
self.user.is_staff = True
self.assertTrue(can_certify_post(self.forum, self.user))
4 changes: 2 additions & 2 deletions lacommunaute/forum_conversation/tests/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
from lacommunaute.forum.enums import Kind as ForumKind
from lacommunaute.forum.factories import ForumFactory
from lacommunaute.forum_conversation.enums import Filters
from lacommunaute.forum_conversation.factories import PostFactory, TopicFactory
from lacommunaute.forum_conversation.factories import CertifiedPostFactory, PostFactory, TopicFactory
from lacommunaute.forum_conversation.forms import PostForm
from lacommunaute.forum_conversation.models import Topic
from lacommunaute.forum_conversation.views import PostDeleteView, TopicCreateView
from lacommunaute.forum_upvote.factories import CertifiedPostFactory, UpVoteFactory
from lacommunaute.forum_upvote.factories import UpVoteFactory
from lacommunaute.notification.factories import BouncedEmailFactory
from lacommunaute.users.factories import UserFactory

Expand Down
74 changes: 71 additions & 3 deletions lacommunaute/forum_conversation/tests/tests_views_htmx.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from machina.core.loading import get_class
from taggit.models import Tag

from lacommunaute.forum_conversation.factories import PostFactory, TopicFactory
from lacommunaute.forum_conversation.factories import CertifiedPostFactory, PostFactory, TopicFactory
from lacommunaute.forum_conversation.forms import PostForm
from lacommunaute.forum_conversation.models import Topic
from lacommunaute.forum_conversation.models import CertifiedPost, Topic
from lacommunaute.forum_conversation.views_htmx import PostListView
from lacommunaute.forum_upvote.factories import CertifiedPostFactory, UpVoteFactory
from lacommunaute.forum_upvote.factories import UpVoteFactory
from lacommunaute.notification.factories import BouncedEmailFactory
from lacommunaute.users.factories import UserFactory

Expand Down Expand Up @@ -468,3 +468,71 @@ def test_create_post_as_bounced_not_bounced_anonymous(self, *args):
self.topic.posts.values("content", "username", "approved").last(),
{"content": self.content, "username": username, "approved": False},
)


class CertifiedPostViewTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.topic = TopicFactory(with_post=True)
cls.user = cls.topic.poster
assign_perm("can_read_forum", cls.user, cls.topic.forum)
cls.url = reverse(
"forum_conversation_extension:certify",
kwargs={
"forum_pk": cls.topic.forum.pk,
"forum_slug": cls.topic.forum.slug,
"pk": cls.topic.pk,
"slug": cls.topic.slug,
},
)
cls.form_data = {"post_pk": cls.topic.last_post.pk}

def test_get(self):
self.client.force_login(self.user)
response = self.client.get(self.url)
self.assertEqual(response.status_code, 405)

def test_post_instance_doesnt_exist(self):
self.client.force_login(self.user)
response = self.client.post(self.url, data={"post_pk": 9999})
self.assertEqual(response.status_code, 404)

def test_certify_without_permission(self):
self.client.force_login(self.user)
response = self.client.post(self.url, data=self.form_data)
self.assertEqual(response.status_code, 403)

def test_certify_with_permission(self):
self.topic.forum.members_group.user_set.add(self.user)
self.topic.forum.members_group.save()
self.client.force_login(self.user)
response = self.client.post(self.url, data=self.form_data)

self.assertEqual(response.status_code, 200)
self.assertEqual(CertifiedPost.objects.count(), 1)
certified_post = CertifiedPost.objects.first()
self.assertEqual(certified_post.post, self.topic.last_post)
self.assertEqual(certified_post.user, self.user)
self.assertEqual(certified_post.topic, self.topic)
self.assertEqual(ForumReadTrack.objects.count(), 1)

def test_uncertify_with_permission(self):
self.topic.forum.members_group.user_set.add(self.user)
self.topic.forum.members_group.save()
CertifiedPost(topic=self.topic, post=self.topic.last_post, user=self.user).save()
self.client.force_login(self.user)
response = self.client.post(self.url, data=self.form_data)

self.assertEqual(response.status_code, 200)
self.assertEqual(CertifiedPost.objects.count(), 0)
self.assertEqual(ForumReadTrack.objects.count(), 1)

def test_rendered_content(self):
self.topic.forum.members_group.user_set.add(self.user)
self.topic.forum.members_group.save()
self.client.force_login(self.user)
response = self.client.post(self.url, data=self.form_data)

self.assertEqual(response.status_code, 200)
self.assertContains(response, f'<div id="showmorepostsarea{self.topic.pk}">')
self.assertTemplateUsed(response, "forum_conversation/partials/posts_list.html")
Loading