Rafał Pitoń 10 years ago
parent
commit
b6fb94bc8c

+ 51 - 8
misago/threads/models/thread.py

@@ -1,5 +1,5 @@
 from django.core.urlresolvers import reverse
-from django.db import models
+from django.db import models, transaction
 from django.dispatch import receiver
 
 from misago.conf import settings
@@ -7,10 +7,25 @@ from misago.core.shortcuts import paginate
 from misago.core.utils import slugify
 
 
-__all__ = ['Thread', 'ThreadParticipant']
+__all__ = [
+    'PARTICIPANT_REMOVED',
+    'PARTICIPANT_ACTIVE',
+    'PARTICIPANT_OWNER',
+    'Thread',
+    'ThreadParticipant'
+]
 
 
-class Thread(models.Model):
+PARTICIPANT_REMOVED = 0
+PARTICIPANT_ACTIVE = 1
+PARTICIPANT_OWNER = 2
+
+
+class PrivateThreadMixin(object):
+    pass
+
+
+class Thread(models.Model, PrivateThreadMixin):
     forum = models.ForeignKey('misago_forums.Forum')
     label = models.ForeignKey('misago_threads.Label',
                               null=True, blank=True,
@@ -174,9 +189,37 @@ class Thread(models.Model):
             self.last_poster_slug = slugify(post.poster_name)
 
 
-PARTICIPANT_REMOVED = 0
-PARTICIPANT_ACTIVE = 1
-PARTICIPANT_OWNER = 2
+class ThreadParticipantManager(models.Manager):
+    def delete_participant(self, thread, user):
+        ThreadParticipant.objects.filter(thread=thread, user=user).delete()
+
+    @transaction.atomic
+    def set_owner(self, thread, user):
+        thread_owner = ThreadParticipant.objects.filter(
+            thread=thread, level=PARTICIPANT_OWNER)
+        thread_owner.update(level=PARTICIPANT_ACTIVE)
+
+        self.delete_participant(thread, user)
+        ThreadParticipant.objects.create(
+            thread=thread,
+            user=user,
+            level=PARTICIPANT_OWNER)
+
+    @transaction.atomic
+    def add_participant(self, thread, user):
+        self.delete_participant(thread, user)
+        ThreadParticipant.objects.create(
+            thread=thread,
+            user=user,
+            level=PARTICIPANT_ACTIVE)
+
+    @transaction.atomic
+    def remove_participant(self, thread, user):
+        self.delete_participant(thread, user)
+        ThreadParticipant.objects.create(
+            thread=thread,
+            user=user,
+            level=PARTICIPANT_REMOVED)
 
 
 class ThreadParticipant(models.Model):
@@ -184,6 +227,8 @@ class ThreadParticipant(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL)
     level = models.PositiveIntegerField(default=PARTICIPANT_ACTIVE)
 
+    objects = ThreadParticipantManager()
+
     @property
     def is_removed(self):
         return self.level == PARTICIPANT_REMOVED
@@ -195,5 +240,3 @@ class ThreadParticipant(models.Model):
     @property
     def is_owner(self):
         return self.level == PARTICIPANT_OWNER
-
-

+ 11 - 0
misago/threads/signals.py

@@ -106,3 +106,14 @@ def update_usernames(sender, **kwargs):
     Event.objects.filter(author=sender).update(
         author_name=sender.username,
         author_slug=sender.slug)
+
+
+from django.contrib.auth import get_user_model
+from django.db.models.signals import pre_delete
+@receiver(pre_delete, sender=get_user_model())
+def remove_unparticipated_private_threads(sender, **kwargs):
+    threads_qs = kwargs['instance'].private_thread_set.all()
+    for thread in batch_update(threads_qs, 50):
+        if thread.participants.count() == 1:
+            with transaction.atomic():
+                thread.delete()

+ 23 - 1
misago/threads/tests/test_thread_model.py

@@ -6,7 +6,7 @@ from django.utils import timezone
 
 from misago.forums.models import Forum
 
-from misago.threads.models import Thread, Post, Event
+from misago.threads.models import Thread, ThreadParticipant, Post, Event
 
 
 class ThreadModelTests(TestCase):
@@ -283,3 +283,25 @@ class ThreadModelTests(TestCase):
         self.assertEqual(self.thread.last_post_on, post.posted_on)
         self.assertEqual(self.thread.last_poster_name, "Admin")
         self.assertEqual(self.thread.last_poster_slug, "admin")
+
+    def test_delete_private_thread(self):
+        """
+        private thread gets deleted automatically
+        when there are no participants left in it
+        """
+        User = get_user_model()
+        user_a = User.objects.create_user(
+            "Bob", "bob@boberson.com", "Pass.123")
+        user_b = User.objects.create_user(
+            "Weebl", "weebl@weeblson.com", "Pass.123")
+
+        ThreadParticipant.objects.add_participant(self.thread, user_a)
+        ThreadParticipant.objects.add_participant(self.thread, user_b)
+        self.assertEqual(self.thread.participants.count(), 2)
+
+        user_a.delete()
+        Thread.objects.get(id=self.thread.id)
+
+        user_b.delete()
+        with self.assertRaises(Thread.DoesNotExist):
+            Thread.objects.get(id=self.thread.id)

+ 119 - 0
misago/threads/tests/test_threadparticipant_model.py

@@ -0,0 +1,119 @@
+from datetime import timedelta
+
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from django.utils import timezone
+
+from misago.forums.models import Forum
+
+from misago.threads.models import Thread, ThreadParticipant, Post
+
+
+class ThreadParticipantTests(TestCase):
+    def setUp(self):
+        datetime = timezone.now()
+
+        self.forum = Forum.objects.filter(role="forum")[:1][0]
+        self.thread = Thread(
+            forum=self.forum,
+            started_on=datetime,
+            starter_name='Tester',
+            starter_slug='tester',
+            last_post_on=datetime,
+            last_poster_name='Tester',
+            last_poster_slug='tester')
+
+        self.thread.set_title("Test thread")
+        self.thread.save()
+
+        post = Post.objects.create(
+            forum=self.forum,
+            thread=self.thread,
+            poster_name='Tester',
+            poster_ip='127.0.0.1',
+            original="Hello! I am test message!",
+            parsed="<p>Hello! I am test message!</p>",
+            checksum="nope",
+            posted_on=datetime,
+            updated_on=datetime)
+
+        self.thread.first_post = post
+        self.thread.last_post = post
+        self.thread.save()
+
+    def test_delete_participant(self):
+        """delete_participant deletes participant from thread"""
+        User = get_user_model()
+        user = User.objects.create_user(
+            "Bob", "bob@boberson.com", "Pass.123")
+        other_user = User.objects.create_user(
+            "Bob2", "bob2@boberson.com", "Pass.123")
+
+        ThreadParticipant.objects.add_participant(self.thread, user)
+        ThreadParticipant.objects.add_participant(self.thread, other_user)
+        self.assertEqual(self.thread.participants.count(), 2)
+
+        ThreadParticipant.objects.delete_participant(self.thread, user)
+        self.assertEqual(self.thread.participants.count(), 1)
+
+        with self.assertRaises(ThreadParticipant.DoesNotExist):
+            participation = ThreadParticipant.objects.get(
+                thread=self.thread, user=user)
+
+    def test_add_participant(self):
+        """add_participant adds participant to thread"""
+        User = get_user_model()
+        user = User.objects.create_user(
+            "Bob", "bob@boberson.com", "Pass.123")
+
+        ThreadParticipant.objects.add_participant(self.thread, user)
+        self.assertEqual(self.thread.participants.count(), 1)
+
+        participation = ThreadParticipant.objects.get(
+            thread=self.thread, user=user)
+        self.assertTrue(participation.is_active)
+        self.assertFalse(participation.is_removed)
+        self.assertFalse(participation.is_owner)
+        self.assertEqual(user, participation.user)
+
+        ThreadParticipant.objects.add_participant(self.thread, user)
+        self.assertEqual(self.thread.participants.count(), 1)
+
+    def test_set_owner(self):
+        """set_owner makes user thread owner"""
+        User = get_user_model()
+        user = User.objects.create_user(
+            "Bob", "bob@boberson.com", "Pass.123")
+
+        ThreadParticipant.objects.set_owner(self.thread, user)
+        self.assertEqual(self.thread.participants.count(), 1)
+
+        participation = ThreadParticipant.objects.get(
+            thread=self.thread, user=user)
+        self.assertFalse(participation.is_active)
+        self.assertFalse(participation.is_removed)
+        self.assertTrue(participation.is_owner)
+        self.assertEqual(user, participation.user)
+
+        other_user = User.objects.create_user(
+            "Bob2", "bob2@boberson.com", "Pass.123")
+        ThreadParticipant.objects.set_owner(self.thread, other_user)
+
+    def test_remove_participant(self):
+        """remove_participant flags participant as removed"""
+        User = get_user_model()
+        user = User.objects.create_user(
+            "Bob", "bob@boberson.com", "Pass.123")
+
+        ThreadParticipant.objects.add_participant(self.thread, user)
+        self.assertEqual(self.thread.participants.count(), 1)
+
+        ThreadParticipant.objects.remove_participant(self.thread, user)
+        self.assertEqual(self.thread.participants.count(), 1)
+
+        participation = ThreadParticipant.objects.get(
+            thread=self.thread, user=user)
+        self.assertFalse(participation.is_active)
+        self.assertTrue(participation.is_removed)
+        self.assertFalse(participation.is_owner)
+        self.assertEqual(user, participation.user)

+ 15 - 0
misago/users/models/user.py

@@ -299,6 +299,21 @@ class User(AbstractBaseUser, PermissionsMixin):
             return 0
 
     @property
+    def can_be_messaged_by_everyone(self):
+        preference = self.limits_private_thread_invites_to
+        return preference == LIMITS_PRIVATE_THREAD_INVITES_TO_NONE
+
+    @property
+    def can_be_messaged_by_followed(self):
+        preference = self.limits_private_thread_invites_to
+        return preference == LIMITS_PRIVATE_THREAD_INVITES_TO_FOLLOWED
+
+    @property
+    def can_be_messaged_by_nobody(self):
+        preference = self.limits_private_thread_invites_to
+        return preference == LIMITS_PRIVATE_THREAD_INVITES_TO_NOBODY
+
+    @property
     def has_valid_signature(self):
         return is_user_signature_valid(self)