forked from mirrors/bookwyrm
handles list privacy in display
This commit is contained in:
parent
69c2b192a4
commit
d73a1b4ec1
7 changed files with 79 additions and 58 deletions
|
@ -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,
|
|
@ -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):
|
||||||
|
|
2
bookwyrm/templates/lists/list-item.html
Normal file
2
bookwyrm/templates/lists/list-item.html
Normal 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 %}
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue