Notification emails #80
@ -1,6 +1,7 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
from factory.django import DjangoModelFactory
|
from factory.django import DjangoModelFactory
|
||||||
import factory
|
import factory
|
||||||
|
|
||||||
@ -42,3 +43,10 @@ class UserFactory(DjangoModelFactory):
|
|||||||
|
|
||||||
oauth_tokens = factory.RelatedFactoryList(OAuthUserTokenFactory, factory_related_name='user')
|
oauth_tokens = factory.RelatedFactoryList(OAuthUserTokenFactory, factory_related_name='user')
|
||||||
oauth_info = factory.RelatedFactory(OAuthUserInfoFactory, factory_related_name='user')
|
oauth_info = factory.RelatedFactory(OAuthUserInfoFactory, factory_related_name='user')
|
||||||
|
|
||||||
|
|
||||||
|
def create_moderator():
|
||||||
|
user = UserFactory()
|
||||||
|
moderators = Group.objects.get(name='moderators')
|
||||||
|
user.groups.add(moderators)
|
||||||
|
return user
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from actstream.actions import follow
|
from actstream.actions import follow, unfollow
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.db.models.signals import pre_save, post_save, post_delete
|
from django.db.models.signals import m2m_changed, pre_save, post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from constants.activity import Flag
|
from constants.activity import Flag
|
||||||
import extensions.models
|
from extensions.models import Extension, Preview, Version
|
||||||
import files.models
|
import files.models
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
@receiver(post_delete, sender=extensions.models.Preview)
|
|
||||||
def _delete_file(sender: object, instance: extensions.models.Preview, **kwargs: object) -> None:
|
@receiver(post_delete, sender=Preview)
|
||||||
|
def _delete_file(sender: object, instance: Preview, **kwargs: object) -> None:
|
||||||
instance.file.delete()
|
instance.file.delete()
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_save, sender=extensions.models.Extension)
|
@receiver(pre_save, sender=Extension)
|
||||||
@receiver(pre_save, sender=extensions.models.Version)
|
@receiver(pre_save, sender=Version)
|
||||||
def _record_changes(
|
def _record_changes(
|
||||||
sender: object,
|
sender: object,
|
||||||
instance: Union[extensions.models.Extension, extensions.models.Version],
|
instance: Union[Extension, Version],
|
||||||
**kwargs: object,
|
**kwargs: object,
|
||||||
) -> None:
|
) -> None:
|
||||||
was_changed, old_state = instance.pre_save_record()
|
was_changed, old_state = instance.pre_save_record()
|
||||||
@ -32,7 +35,7 @@ def _record_changes(
|
|||||||
instance.record_status_change(was_changed, old_state, **kwargs)
|
instance.record_status_change(was_changed, old_state, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=extensions.models.Extension)
|
@receiver(post_save, sender=Extension)
|
||||||
def _update_search_index(sender, instance, **kw):
|
def _update_search_index(sender, instance, **kw):
|
||||||
pass # TODO: update search index
|
pass # TODO: update search index
|
||||||
|
|
||||||
@ -46,18 +49,18 @@ def extension_should_be_listed(extension):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=extensions.models.Extension)
|
@receiver(post_save, sender=Extension)
|
||||||
@receiver(post_save, sender=extensions.models.Version)
|
@receiver(post_save, sender=Version)
|
||||||
@receiver(post_save, sender=files.models.File)
|
@receiver(post_save, sender=files.models.File)
|
||||||
def _set_is_listed(
|
def _set_is_listed(
|
||||||
sender: object,
|
sender: object,
|
||||||
instance: Union[extensions.models.Extension, extensions.models.Version, files.models.File],
|
instance: Union[Extension, Version, files.models.File],
|
||||||
*args: object,
|
*args: object,
|
||||||
**kwargs: object,
|
**kwargs: object,
|
||||||
) -> None:
|
) -> None:
|
||||||
if isinstance(instance, extensions.models.Extension):
|
if isinstance(instance, Extension):
|
||||||
extension = instance
|
extension = instance
|
||||||
elif isinstance(instance, extensions.models.Version):
|
elif isinstance(instance, Version):
|
||||||
extension = instance.extension
|
extension = instance.extension
|
||||||
else:
|
else:
|
||||||
# Some file types (e.g., image or video) have no version associated to them.
|
# Some file types (e.g., image or video) have no version associated to them.
|
||||||
@ -73,17 +76,17 @@ def _set_is_listed(
|
|||||||
if old_is_listed == new_is_listed:
|
if old_is_listed == new_is_listed:
|
||||||
return
|
return
|
||||||
|
|
||||||
if extension.status == extensions.models.Extension.STATUSES.APPROVED and not new_is_listed:
|
if extension.status == Extension.STATUSES.APPROVED and not new_is_listed:
|
||||||
extension.status = extensions.models.Extension.STATUSES.INCOMPLETE
|
extension.status = Extension.STATUSES.INCOMPLETE
|
||||||
|
|
||||||
extension.is_listed = new_is_listed
|
extension.is_listed = new_is_listed
|
||||||
extension.save()
|
extension.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=extensions.models.Extension)
|
@receiver(post_save, sender=Extension)
|
||||||
def _setup_followers(
|
def _setup_followers(
|
||||||
sender: object,
|
sender: object,
|
||||||
instance: extensions.models.Extension,
|
instance: Extension,
|
||||||
created: bool,
|
created: bool,
|
||||||
**kwargs: object,
|
**kwargs: object,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -94,3 +97,20 @@ def _setup_followers(
|
|||||||
follow(user, instance, send_action=False, flag=Flag.AUTHOR)
|
follow(user, instance, send_action=False, flag=Flag.AUTHOR)
|
||||||
for user in Group.objects.get(name='moderators').user_set.all():
|
for user in Group.objects.get(name='moderators').user_set.all():
|
||||||
follow(user, instance, send_action=False, flag=Flag.MODERATOR)
|
follow(user, instance, send_action=False, flag=Flag.MODERATOR)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(m2m_changed, sender=Extension.authors.through)
|
||||||
|
def _update_authors_follow(instance, action, model, reverse, pk_set, **kwargs):
|
||||||
|
if model == Extension and not reverse:
|
||||||
|
users = [instance]
|
||||||
|
extensions = Extension.objects.filter(pk__in=pk_set)
|
||||||
|
else:
|
||||||
|
extensions = [instance]
|
||||||
|
users = User.objects.filter(pk__in=pk_set)
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
for extension in extensions:
|
||||||
|
if action == 'post_remove':
|
||||||
|
unfollow(user, extension, send_action=False, flag=Flag.AUTHOR)
|
||||||
|
elif action == 'post_add':
|
||||||
|
follow(user, extension, send_action=False, flag=Flag.AUTHOR)
|
||||||
|
140
notifications/tests/test_follow_logic.py
Normal file
140
notifications/tests/test_follow_logic.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from common.tests.factories.extensions import create_approved_version, create_version
|
||||||
|
from common.tests.factories.files import FileFactory
|
||||||
|
from common.tests.factories.users import UserFactory, create_moderator
|
||||||
|
from files.models import File
|
||||||
|
from notifications.models import Notification
|
||||||
|
from reviewers.models import ApprovalActivity
|
||||||
|
|
||||||
|
TEST_FILES_DIR = Path(__file__).resolve().parent / '../../extensions/tests/files'
|
||||||
|
|
||||||
|
|
||||||
|
class TestTasks(TestCase):
|
||||||
|
fixtures = ['dev', 'licenses']
|
||||||
|
|
||||||
|
def test_ratings(self):
|
||||||
|
extension = create_approved_version(ratings=[]).extension
|
||||||
|
author = extension.authors.first()
|
||||||
|
notification_nr = Notification.objects.filter(recipient=author).count()
|
||||||
|
some_user = UserFactory()
|
||||||
|
self.client.force_login(some_user)
|
||||||
|
url = extension.get_rate_url()
|
||||||
|
response = self.client.post(url, {'score': 3, 'text': 'rating text'})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(extension.ratings.count(), 1)
|
||||||
|
new_notification_nr = Notification.objects.filter(recipient=author).count()
|
||||||
|
self.assertEqual(new_notification_nr, notification_nr + 1)
|
||||||
|
|
||||||
|
def test_abuse(self):
|
||||||
|
extension = create_approved_version(ratings=[]).extension
|
||||||
|
moderator = create_moderator()
|
||||||
|
notification_nr = Notification.objects.filter(recipient=moderator).count()
|
||||||
|
some_user = UserFactory()
|
||||||
|
self.client.force_login(some_user)
|
||||||
|
url = extension.get_report_url()
|
||||||
|
self.client.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'message': 'test message',
|
||||||
|
'reason': '127',
|
||||||
|
'version': '',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
new_notification_nr = Notification.objects.filter(recipient=moderator).count()
|
||||||
|
self.assertEqual(new_notification_nr, notification_nr + 1)
|
||||||
|
|
||||||
|
def test_new_extension_submitted(self):
|
||||||
|
moderator = create_moderator()
|
||||||
|
notification_nr = Notification.objects.filter(recipient=moderator).count()
|
||||||
|
some_user = UserFactory()
|
||||||
|
file_data = {
|
||||||
|
'metadata': {
|
||||||
|
'tagline': 'Get insight on the complexity of an edit',
|
||||||
|
'id': 'edit_breakdown',
|
||||||
|
'name': 'Edit Breakdown',
|
||||||
|
'version': '0.1.0',
|
||||||
|
'blender_version_min': '4.2.0',
|
||||||
|
'type': 'add-on',
|
||||||
|
'schema_version': "1.0.0",
|
||||||
|
},
|
||||||
|
'file_hash': 'sha256:4f3664940fc41641c7136a909270a024bbcfb2f8523a06a0d22f85c459b0b1ae',
|
||||||
|
'size_bytes': 53959,
|
||||||
|
'tags': ['Sequencer'],
|
||||||
|
'version_str': '0.1.0',
|
||||||
|
'slug': 'edit-breakdown',
|
||||||
|
}
|
||||||
|
file = FileFactory(
|
||||||
|
type=File.TYPES.BPY,
|
||||||
|
user=some_user,
|
||||||
|
original_hash=file_data['file_hash'],
|
||||||
|
hash=file_data['file_hash'],
|
||||||
|
metadata=file_data['metadata'],
|
||||||
|
)
|
||||||
|
create_version(
|
||||||
|
file=file,
|
||||||
|
extension__name=file_data['metadata']['name'],
|
||||||
|
extension__slug=file_data['metadata']['id'].replace("_", "-"),
|
||||||
|
extension__website=None,
|
||||||
|
tagline=file_data['metadata']['tagline'],
|
||||||
|
version=file_data['metadata']['version'],
|
||||||
|
blender_version_min=file_data['metadata']['blender_version_min'],
|
||||||
|
schema_version=file_data['metadata']['schema_version'],
|
||||||
|
)
|
||||||
|
self.client.force_login(some_user)
|
||||||
|
data = {
|
||||||
|
# Most of these values should come from the form's initial values, set in the template
|
||||||
|
# Version fields
|
||||||
|
'release_notes': 'initial release',
|
||||||
|
# Extension fields
|
||||||
|
'description': 'Rather long and verbose description',
|
||||||
|
'support': 'https://example.com/issues',
|
||||||
|
# Previews
|
||||||
|
'form-TOTAL_FORMS': ['2'],
|
||||||
|
'form-INITIAL_FORMS': ['0'],
|
||||||
|
'form-MIN_NUM_FORMS': ['0'],
|
||||||
|
'form-MAX_NUM_FORMS': ['1000'],
|
||||||
|
'form-0-id': '',
|
||||||
|
'form-0-caption': ['First Preview Caption Text'],
|
||||||
|
'form-1-id': '',
|
||||||
|
'form-1-caption': ['Second Preview Caption Text'],
|
||||||
|
# Submit for Approval.
|
||||||
|
'submit_draft': '',
|
||||||
|
}
|
||||||
|
file_name1 = 'test_preview_image_0001.png'
|
||||||
|
file_name2 = 'test_preview_image_0002.png'
|
||||||
|
with open(TEST_FILES_DIR / file_name1, 'rb') as fp1, open(
|
||||||
|
TEST_FILES_DIR / file_name2, 'rb'
|
||||||
|
) as fp2:
|
||||||
|
files = {
|
||||||
|
'form-0-source': fp1,
|
||||||
|
'form-1-source': fp2,
|
||||||
|
}
|
||||||
|
self.client.post(file.get_submit_url(), {**data, **files})
|
||||||
|
new_notification_nr = Notification.objects.filter(recipient=moderator).count()
|
||||||
|
self.assertEqual(new_notification_nr, notification_nr + 1)
|
||||||
|
|
||||||
|
def test_approval_queue_activity(self):
|
||||||
|
extension = create_approved_version(ratings=[]).extension
|
||||||
|
author = extension.authors.first()
|
||||||
|
moderator = create_moderator()
|
||||||
|
some_user = UserFactory()
|
||||||
|
notification_nrs = {}
|
||||||
|
for user in [author, moderator, some_user]:
|
||||||
|
notification_nrs[user.pk] = Notification.objects.filter(recipient=user).count()
|
||||||
|
self._leave_a_comment(some_user, extension, 'this is bad')
|
||||||
|
self._leave_a_comment(moderator, extension, 'thanks for the heads up')
|
||||||
|
new_notification_nrs = {}
|
||||||
|
for user in [author, moderator, some_user]:
|
||||||
|
new_notification_nrs[user.pk] = Notification.objects.filter(recipient=user).count()
|
||||||
|
self.assertEqual(new_notification_nrs[author.pk], notification_nrs[author.pk] + 2)
|
||||||
|
self.assertEqual(new_notification_nrs[moderator.pk], notification_nrs[moderator.pk] + 1)
|
||||||
|
self.assertEqual(new_notification_nrs[some_user.pk], notification_nrs[some_user.pk] + 1)
|
||||||
|
|
||||||
|
def _leave_a_comment(self, user, extension, text):
|
||||||
|
self.client.force_login(user)
|
||||||
|
url = reverse('reviewers:approval-comment', args=[extension.slug])
|
||||||
|
self.client.post(url, {'type': ApprovalActivity.ActivityType.COMMENT, 'message': text})
|
@ -61,9 +61,8 @@ def update_moderator_follows(instance, action, model, reverse, pk_set, **kwargs)
|
|||||||
users = User.objects.filter(pk__in=pk_set)
|
users = User.objects.filter(pk__in=pk_set)
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
if action == 'post_remove':
|
for extension in extensions:
|
||||||
for extension in extensions:
|
if action == 'post_remove':
|
||||||
unfollow(user, extension, send_action=False, flag=Flag.MODERATOR)
|
unfollow(user, extension, send_action=False, flag=Flag.MODERATOR)
|
||||||
elif action == 'post_add':
|
elif action == 'post_add':
|
||||||
for extension in extensions:
|
|
||||||
follow(user, extension, send_action=False, flag=Flag.MODERATOR)
|
follow(user, extension, send_action=False, flag=Flag.MODERATOR)
|
||||||
|
Loading…
Reference in New Issue
Block a user