Local-only posting
This commit is contained in:
		
							parent
							
								
									c758858392
								
							
						
					
					
						commit
						849c221aee
					
				| @ -60,6 +60,7 @@ class Migration(migrations.Migration): | ||||
|                     models.IntegerField( | ||||
|                         choices=[ | ||||
|                             (0, "Public"), | ||||
|                             (4, "Local Only"), | ||||
|                             (1, "Unlisted"), | ||||
|                             (2, "Followers"), | ||||
|                             (3, "Mentioned"), | ||||
|  | ||||
| @ -64,6 +64,7 @@ class Post(StatorModel): | ||||
| 
 | ||||
|     class Visibilities(models.IntegerChoices): | ||||
|         public = 0 | ||||
|         local_only = 4 | ||||
|         unlisted = 1 | ||||
|         followers = 2 | ||||
|         mentioned = 3 | ||||
| @ -261,6 +262,9 @@ class Post(StatorModel): | ||||
|                     mentions.add(identity) | ||||
|             if reply_to: | ||||
|                 mentions.add(reply_to.author) | ||||
|                 # Maintain local-only for replies | ||||
|                 if reply_to.visibility == reply_to.Visibilities.local_only: | ||||
|                     visibility = reply_to.Visibilities.local_only | ||||
|             # Strip all HTML and apply linebreaks filter | ||||
|             content = linebreaks_filter(strip_html(content)) | ||||
|             # Make the Post object | ||||
| @ -361,11 +365,12 @@ class Post(StatorModel): | ||||
|         reply_post = await self.ain_reply_to_post() | ||||
|         if reply_post: | ||||
|             targets.add(reply_post.author) | ||||
|         # If this is a remote post, filter to only include local identities | ||||
|         if not self.local: | ||||
|         # If this is a remote post or local-only, filter to only include | ||||
|         # local identities | ||||
|         if not self.local or self.visibility == Post.Visibilities.local_only: | ||||
|             targets = {target for target in targets if target.local} | ||||
|         # If it's a local post, include the author | ||||
|         else: | ||||
|         if self.local: | ||||
|             targets.add(self.author) | ||||
|         return targets | ||||
| 
 | ||||
|  | ||||
| @ -172,6 +172,7 @@ class Compose(FormView): | ||||
|         visibility = forms.ChoiceField( | ||||
|             choices=[ | ||||
|                 (Post.Visibilities.public, "Public"), | ||||
|                 (Post.Visibilities.local_only, "Local Only"), | ||||
|                 (Post.Visibilities.unlisted, "Unlisted"), | ||||
|                 (Post.Visibilities.followers, "Followers & Mentioned Only"), | ||||
|                 (Post.Visibilities.mentioned, "Mentioned Only"), | ||||
| @ -207,7 +208,7 @@ class Compose(FormView): | ||||
|         ] = self.request.identity.config_identity.default_post_visibility | ||||
|         if self.reply_to: | ||||
|             initial["reply_to"] = self.reply_to.pk | ||||
|             initial["visibility"] = Post.Visibilities.unlisted | ||||
|             initial["visibility"] = self.reply_to.visibility | ||||
|             initial["text"] = f"@{self.reply_to.author.handle} " | ||||
|         return initial | ||||
| 
 | ||||
|  | ||||
| @ -15,6 +15,8 @@ | ||||
|             <i class="visibility fa-solid fa-lock" title="Followers Only"></i> | ||||
|         {% elif post.visibility == 3 %} | ||||
|             <i class="visibility fa-solid fa-at" title="Mentioned Only"></i> | ||||
|         {% elif post.visibility == 4 %} | ||||
|         <i class="visibility fa-solid fa-link-slash" title="Local Only"></i> | ||||
|         {% endif %} | ||||
|         <a href="{{ post.url }}"> | ||||
|             {% if post.published %} | ||||
|  | ||||
							
								
								
									
										107
									
								
								tests/activities/models/test_post_targets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								tests/activities/models/test_post_targets.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| import pytest | ||||
| from asgiref.sync import async_to_sync | ||||
| 
 | ||||
| from activities.models import Post | ||||
| from users.models import Follow | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.django_db | ||||
| def test_post_targets_simple(identity, other_identity, remote_identity): | ||||
|     """ | ||||
|     Tests that a simple top level post returns the correct targets. | ||||
|     """ | ||||
|     # Test a post with no mentions targets author | ||||
|     post = Post.objects.create( | ||||
|         content="<p>Hello</p>", | ||||
|         author=identity, | ||||
|         local=True, | ||||
|     ) | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     assert targets == {identity} | ||||
| 
 | ||||
|     # Test remote reply targets original post author | ||||
|     Post.objects.create( | ||||
|         content="<p>Reply</p>", | ||||
|         author=remote_identity, | ||||
|         local=False, | ||||
|         in_reply_to=post.absolute_object_uri(), | ||||
|     ) | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     assert targets == {identity} | ||||
| 
 | ||||
|     # Test a post with local and remote mentions | ||||
|     post = Post.objects.create( | ||||
|         content="<p>Hello @test and @other</p>", | ||||
|         author=identity, | ||||
|         local=True, | ||||
|     ) | ||||
|     # Mentions are targeted | ||||
|     post.mentions.add(remote_identity) | ||||
|     post.mentions.add(other_identity) | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     # Targets everyone | ||||
|     assert targets == {identity, other_identity, remote_identity} | ||||
| 
 | ||||
|     # Test remote post with mentions | ||||
|     post.local = False | ||||
|     post.save() | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     # Only targets locals | ||||
|     assert targets == {identity, other_identity} | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.django_db | ||||
| def test_post_local_only(identity, other_identity, remote_identity): | ||||
|     """ | ||||
|     Tests that a simple top level post returns the correct targets. | ||||
|     """ | ||||
|     # Test a short username (remote) | ||||
|     post = Post.objects.create( | ||||
|         content="<p>Hello @test and @other</p>", | ||||
|         author=identity, | ||||
|         local=True, | ||||
|         visibility=Post.Visibilities.local_only, | ||||
|     ) | ||||
|     post.mentions.add(remote_identity) | ||||
|     post.mentions.add(other_identity) | ||||
| 
 | ||||
|     # Remote mention is not targeted | ||||
|     post.mentions.add(remote_identity) | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     assert targets == {identity, other_identity} | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.django_db | ||||
| def test_post_followers(identity, other_identity, remote_identity): | ||||
| 
 | ||||
|     Follow.objects.create(source=other_identity, target=identity) | ||||
|     Follow.objects.create(source=remote_identity, target=identity) | ||||
| 
 | ||||
|     # Test Public post w/o mentions targets self and followers | ||||
|     post = Post.objects.create( | ||||
|         content="<p>Hello</p>", | ||||
|         author=identity, | ||||
|         local=True, | ||||
|         visibility=Post.Visibilities.public, | ||||
|     ) | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     assert targets == {identity, other_identity, remote_identity} | ||||
| 
 | ||||
|     # Remote post only targets local followers | ||||
|     post.local = False | ||||
|     post.save() | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     assert targets == {identity, other_identity} | ||||
| 
 | ||||
|     # Local Only post only targets local followers | ||||
|     post.local = True | ||||
|     post.visibility = Post.Visibilities.local_only | ||||
|     post.save() | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     assert targets == {identity, other_identity} | ||||
| 
 | ||||
|     # Mentioned posts do not target unmentioned followers | ||||
|     post.visibility = Post.Visibilities.mentioned | ||||
|     post.save() | ||||
|     targets = async_to_sync(post.aget_targets)() | ||||
|     assert targets == {identity} | ||||
| @ -68,11 +68,16 @@ def user() -> User: | ||||
| 
 | ||||
| @pytest.fixture | ||||
| @pytest.mark.django_db | ||||
| def identity(user): | ||||
| def domain() -> Domain: | ||||
|     return Domain.objects.create(domain="example.com", local=True, public=True) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| @pytest.mark.django_db | ||||
| def identity(user, domain) -> Identity: | ||||
|     """ | ||||
|     Creates a basic test identity with a user and domain. | ||||
|     """ | ||||
|     domain = Domain.objects.create(domain="example.com", local=True, public=True) | ||||
|     identity = Identity.objects.create( | ||||
|         actor_uri="https://example.com/@test@example.com/", | ||||
|         username="test", | ||||
| @ -84,9 +89,25 @@ def identity(user): | ||||
|     return identity | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| def other_identity(user, domain) -> Identity: | ||||
|     """ | ||||
|     Creates a different basic test identity with a user and domain. | ||||
|     """ | ||||
|     identity = Identity.objects.create( | ||||
|         actor_uri="https://example.com/@other@example.com/", | ||||
|         username="other", | ||||
|         domain=domain, | ||||
|         name="Other User", | ||||
|         local=True, | ||||
|     ) | ||||
|     identity.users.set([user]) | ||||
|     return identity | ||||
| 
 | ||||
| 
 | ||||
| @pytest.fixture | ||||
| @pytest.mark.django_db | ||||
| def remote_identity(): | ||||
| def remote_identity() -> Identity: | ||||
|     """ | ||||
|     Creates a basic remote test identity with a domain. | ||||
|     """ | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Michael Manfre
						Michael Manfre