handles list privacy in display

This commit is contained in:
Mouse Reeve 2021-01-31 08:41:11 -08:00
parent 69c2b192a4
commit d73a1b4ec1
7 changed files with 79 additions and 58 deletions

View file

@ -1,4 +1,4 @@
# Generated by Django 3.0.7 on 2021-01-31 05:00 # Generated by Django 3.0.7 on 2021-01-31 16:14
import bookwyrm.models.base_model import bookwyrm.models.base_model
import bookwyrm.models.fields import bookwyrm.models.fields
@ -24,7 +24,7 @@ class Migration(migrations.Migration):
('name', bookwyrm.models.fields.CharField(max_length=100)), ('name', bookwyrm.models.fields.CharField(max_length=100)),
('description', bookwyrm.models.fields.TextField(blank=True, null=True)), ('description', bookwyrm.models.fields.TextField(blank=True, null=True)),
('privacy', bookwyrm.models.fields.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255)), ('privacy', bookwyrm.models.fields.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255)),
('curation', bookwyrm.models.fields.CharField(choices=[('closed', 'Closed'), ('open', 'Open'), ('moderated', 'Moderated')], default='closed', max_length=255)), ('curation', bookwyrm.models.fields.CharField(choices=[('closed', 'Closed'), ('open', 'Open'), ('curated', 'Curated')], default='closed', max_length=255)),
], ],
options={ options={
'abstract': False, 'abstract': False,

View file

@ -11,7 +11,7 @@ from . import fields
CurationType = models.TextChoices('Curation', [ CurationType = models.TextChoices('Curation', [
'closed', 'closed',
'open', 'open',
'moderated', 'curated',
]) ])
class List(OrderedCollectionMixin, BookWyrmModel): class List(OrderedCollectionMixin, BookWyrmModel):

View file

@ -0,0 +1,2 @@
<h4><a href="{{ list.local_path }}">{{ list.name }}</a> {% include 'snippets/privacy-icons.html' with item=list %}</h4>
{% include 'snippets/trimmed_text.html' with full=list.description %}

View file

@ -6,18 +6,18 @@
</header> </header>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<section class="block"> <section class="block content">
<header class="columns"> <header class="columns">
<div class="column"> <div class="column">
<h2 class="title is-3">Your lists</h2> <h2 class="title">Your lists</h2>
</div> </div>
<div class="column is-narrow"> <div class="column is-narrow">
{% include 'snippets/toggle/open_button.html' with controls_text="create-list" icon="plus" text="Create new list" %} {% include 'snippets/toggle/open_button.html' with controls_text="create-list" icon="plus" text="Create new list" %}
</div> </div>
</header> </header>
<form name="create-list" method="post" action="{% url 'lists' %}" class="box hiddenx" id="create-list"> <form name="create-list" method="post" action="{% url 'lists' %}" class="box hidden" id="create-list">
<h3 class="title is-4">Create list</h3> <h3 class="title">Create list</h3>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="user" value="{{ request.user.id }}"> <input type="hidden" name="user" value="{{ request.user.id }}">
@ -68,7 +68,7 @@
<ul> <ul>
{% for list in request.user.list_set.all %} {% for list in request.user.list_set.all %}
<li> <li>
<a href="{{ list.local_path }}">{{ list.name }}</a> {% include 'lists/list-item.html' with list=list %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -77,12 +77,12 @@
{% endif %} {% endif %}
{% if lists.exists %} {% if lists.exists %}
<section class="block"> <section class="block content">
<h2 class="title is-2">Recent Lists</h2> <h2 class="title">Recent Lists</h2>
<ul> <ul>
{% for list in lists %} {% for list in lists %}
<li> <li>
<a href="{{ list.local_path }}">{{ list.name }}</a> {% include 'lists/list-item.html' with list=list %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View file

@ -1,24 +1,26 @@
{% load bookwyrm_tags %} {% load bookwyrm_tags %}
{% with 0|uuid as uuid %} {% with 0|uuid as uuid %}
{% if full %} {% if full %}
{% with full|to_markdown|safe as full %}
{% with full|to_markdown|safe|truncatewords_html:60 as trimmed %} {% with full|to_markdown|safe|truncatewords_html:60 as trimmed %}
{% if trimmed != full %} {% if trimmed != full %}
<div id="hide-full-{{ uuid }}"> <div id="hide-full-{{ uuid }}">
<blockquote class="content" id="trimmed-{{ uuid }}"><span dir="auto">{{ trimmed }}</span> <div class="content" id="trimmed-{{ uuid }}"><span dir="auto">{{ trimmed }}</span>
{% include 'snippets/toggle/open_button.html' with text="show more" controls_text="full" controls_uid=uuid class="is-small" %} {% include 'snippets/toggle/open_button.html' with text="show more" controls_text="full" controls_uid=uuid class="is-small" %}
</blockquote> </div>
</div> </div>
<div id="full-{{ uuid }}" class="hidden"> <div id="full-{{ uuid }}" class="hidden">
<blockquote class="content"><span dir="auto">{{ full | to_markdown | safe }}</span> <div class="content"><span dir="auto">{{ full }}</span>
{% include 'snippets/toggle/close_button.html' with text="show less" controls_text="full" controls_uid=uuid class="is-small" %} {% include 'snippets/toggle/close_button.html' with text="show less" controls_text="full" controls_uid=uuid class="is-small" %}
</blockquote> </div>
</div> </div>
{% else %} {% else %}
<blockquote class="content"><span dir="auto">{{ full | to_markdown | safe }}</span></blockquote> <div class="content"><span dir="auto">{{ full }}</span></div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
{% endwith %}
{% endif %} {% endif %}
{% endwith %} {% endwith %}

View file

@ -59,11 +59,55 @@ def object_visible_to_user(viewer, obj):
return True return True
return False return False
def privacy_filter(viewer, queryset, privacy_levels, following_only=False):
''' filter objects that have "user" and "privacy" fields '''
# exclude blocks from both directions
if not viewer.is_anonymous:
blocked = models.User.objects.filter(id__in=viewer.blocks.all()).all()
queryset = queryset.exclude(
Q(user__in=blocked) | Q(user__blocks=viewer))
# you can't see followers only or direct messages if you're not logged in
if viewer.is_anonymous:
privacy_levels = [p for p in privacy_levels if \
not p in ['followers', 'direct']]
# filter to only privided privacy levels
queryset = queryset.filter(privacy__in=privacy_levels)
# only include statuses the user follows
if following_only:
queryset = queryset.exclude(
~Q(# remove everythign except
Q(user__in=viewer.following.all()) | # user following
Q(user=viewer) |# is self
Q(mention_users=viewer)# mentions user
),
)
# exclude followers-only statuses the user doesn't follow
elif 'followers' in privacy_levels:
queryset = queryset.exclude(
~Q(# user isn't following and it isn't their own status
Q(user__in=viewer.following.all()) | Q(user=viewer)
),
privacy='followers' # and the status is followers only
)
# exclude direct messages not intended for the user
if 'direct' in privacy_levels:
queryset = queryset.exclude(
~Q(
Q(user=viewer) | Q(mention_users=viewer)
), privacy='direct'
)
return queryset
def get_activity_feed( def get_activity_feed(
user, privacy, local_only=False, following_only=False, user, privacy, local_only=False, following_only=False,
queryset=models.Status.objects): queryset=models.Status.objects):
''' get a filtered queryset of statuses ''' ''' get a filtered queryset of statuses '''
privacy = privacy if isinstance(privacy, list) else [privacy]
# if we're looking at Status, we need this. We don't if it's Comment # if we're looking at Status, we need this. We don't if it's Comment
if hasattr(queryset, 'select_subclasses'): if hasattr(queryset, 'select_subclasses'):
queryset = queryset.select_subclasses() queryset = queryset.select_subclasses()
@ -71,44 +115,10 @@ def get_activity_feed(
# exclude deleted # exclude deleted
queryset = queryset.exclude(deleted=True).order_by('-published_date') queryset = queryset.exclude(deleted=True).order_by('-published_date')
# exclude blocks from both directions # apply privacy filters
if not user.is_anonymous: privacy = privacy if isinstance(privacy, list) else [privacy]
blocked = models.User.objects.filter(id__in=user.blocks.all()).all() queryset = privacy_filter(
queryset = queryset.exclude( user, queryset, privacy, following_only=following_only)
Q(user__in=blocked) | Q(user__blocks=user))
# you can't see followers only or direct messages if you're not logged in
if user.is_anonymous:
privacy = [p for p in privacy if not p in ['followers', 'direct']]
# filter to only privided privacy levels
queryset = queryset.filter(privacy__in=privacy)
# only include statuses the user follows
if following_only:
queryset = queryset.exclude(
~Q(# remove everythign except
Q(user__in=user.following.all()) | # user follwoing
Q(user=user) |# is self
Q(mention_users=user)# mentions user
),
)
# exclude followers-only statuses the user doesn't follow
elif 'followers' in privacy:
queryset = queryset.exclude(
~Q(# user isn't following and it isn't their own status
Q(user__in=user.following.all()) | Q(user=user)
),
privacy='followers' # and the status is followers only
)
# exclude direct messages not intended for the user
if 'direct' in privacy:
queryset = queryset.exclude(
~Q(
Q(user=user) | Q(mention_users=user)
), privacy='direct'
)
# filter for only local status # filter for only local status
if local_only: if local_only:

View file

@ -9,7 +9,7 @@ from django.views import View
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from .helpers import is_api_request, object_visible_to_user from .helpers import is_api_request, object_visible_to_user, privacy_filter
# pylint: disable=no-self-use # pylint: disable=no-self-use
@ -18,7 +18,10 @@ class Lists(View):
def get(self, request): def get(self, request):
''' display a book list ''' ''' display a book list '''
user = request.user if request.user.is_authenticated else None user = request.user if request.user.is_authenticated else None
lists = models.List.objects.filter(~Q(user=user)).all() lists = models.List.objects.filter(
~Q(user=user),
).all()
lists = privacy_filter(request.user, lists, ['public', 'followers'])
data = { data = {
'title': 'Lists', 'title': 'Lists',
'lists': lists, 'lists': lists,
@ -30,7 +33,11 @@ class Lists(View):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def post(self, request): def post(self, request):
''' create a book_list ''' ''' create a book_list '''
book_list = None form = forms.ListForm(request.POST)
if not form.is_valid():
print(form.errors)
return redirect('lists')
book_list = form.save()
return redirect(book_list.local_path) return redirect(book_list.local_path)