Merge pull request #780 from mouse-reeve/directory

Adds opt-in directory of users
This commit is contained in:
Mouse Reeve 2021-03-21 17:38:45 -07:00 committed by GitHub
commit 1025a2e4da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 194 additions and 13 deletions

View file

@ -30,5 +30,5 @@ class Person(ActivityObject):
icon: Image = field(default_factory=lambda: {}) icon: Image = field(default_factory=lambda: {})
bookwyrmUser: bool = False bookwyrmUser: bool = False
manuallyApprovesFollowers: str = False manuallyApprovesFollowers: str = False
discoverable: str = True discoverable: str = False
type: str = "Person" type: str = "Person"

View file

@ -3,7 +3,6 @@ import datetime
from collections import defaultdict from collections import defaultdict
from django import forms from django import forms
from django.core.exceptions import ValidationError
from django.forms import ModelForm, PasswordInput, widgets from django.forms import ModelForm, PasswordInput, widgets
from django.forms.widgets import Textarea from django.forms.widgets import Textarea
from django.utils import timezone from django.utils import timezone
@ -130,8 +129,9 @@ class EditUserForm(CustomForm):
"name", "name",
"email", "email",
"summary", "summary",
"manually_approves_followers",
"show_goal", "show_goal",
"manually_approves_followers",
"discoverable",
] ]
help_texts = {f: None for f in fields} help_texts = {f: None for f in fields}

View file

@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-03-21 21:44
import bookwyrm.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0056_auto_20210321_0303"),
]
operations = [
migrations.AddField(
model_name="user",
name="discoverable",
field=bookwyrm.models.fields.BooleanField(default=False),
),
]

View file

@ -103,6 +103,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
last_active_date = models.DateTimeField(auto_now=True) last_active_date = models.DateTimeField(auto_now=True)
manually_approves_followers = fields.BooleanField(default=False) manually_approves_followers = fields.BooleanField(default=False)
show_goal = models.BooleanField(default=True) show_goal = models.BooleanField(default=True)
discoverable = fields.BooleanField(default=False)
name_field = "username" name_field = "username"

View file

@ -0,0 +1,67 @@
{% extends 'layout.html' %}
{% load i18n %}
{% load bookwyrm_tags %}
{% load humanize %}
{% block title %}{% trans "Directory" %}{% endblock %}
{% block content %}
<header class="block">
<h1 class="title">
{% trans "Directory" %}
</h1>
</header>
<div class="columns is-multiline">
{% for user in users %}
<div class="column is-one-third">
<div class="card block">
<div class="card-content">
<div class="media">
<span class="media-left">
{% include 'snippets/avatar.html' with user=user large=True %}
</span>
<div class="media-content">
<a href="{{ user.local_path }}" class="is-block mb-2">
<span class="title is-4 is-block">{{ user.display_name }}</span>
<span class="subtitle is-6 is-block">@{{ user|username }}</span>
</a>
{% include 'snippets/follow_button.html' with user=user %}
</div>
</div>
{% if user.summary %}
<div class="content">
{{ user.summary | to_markdown | safe | truncatechars_html:40 }}
</div>
{% endif %}
</div>
<footer class="card-footer content">
<div class="card-footer-item">
<div class="has-text-centered">
<p class="title is-6 mb-0">{{ user.shelfbook_set.count }}</p>
<p class="help">{% trans "books shelved" %}</p>
</div>
</div>
<div class="card-footer-item">
<div class="has-text-centered">
<p class="title is-6 mb-0">{{ user.status_set.count|intword }}</p>
<p class="help">{% trans "posts" %}</p>
</div>
</div>
<div class="card-footer-item">
<div class="has-text-centered">
<p class="title is-6 mb-0">{{ user.last_active_date|naturalday }}</p>
<p class="help">{% trans "last active" %}</p>
</div>
</div>
</footer>
</div>
</div>
{% endfor %}
</div>
<div>
{% include 'snippets/pagination.html' with page=users path="/directory" %}
</div>
{% endblock %}

View file

@ -2,11 +2,11 @@
{% load i18n %} {% load i18n %}
{% load humanize %} {% load humanize %}
{% block title %}{% trans "Edit Author" %}: {{ author.name }}{% endblock %} {% block title %}{% trans "Edit Author:" %} {{ author.name }}{% endblock %}
{% block content %} {% block content %}
<header class="block"> <header class="block">
<h1 class="title level-left"> <h1 class="title">
Edit "{{ author.name }}" Edit "{{ author.name }}"
</h1> </h1>
<div> <div>

View file

@ -83,18 +83,18 @@
</a> </a>
<ul class="navbar-dropdown" id="navbar-dropdown"> <ul class="navbar-dropdown" id="navbar-dropdown">
<li> <li>
<a href="/direct-messages" class="navbar-item"> <a href="{% url 'direct-messages' %}" class="navbar-item">
{% trans "Direct Messages" %} {% trans "Direct Messages" %}
</a> </a>
</li> </li>
<li> <li>
<a href="/user/{{request.user.localname}}" class="navbar-item"> <a href="{{ request.user.local_path }}" class="navbar-item">
{% trans 'Profile' %} {% trans 'Profile' %}
</a> </a>
</li> </li>
<li> <li>
<a href="/preferences/profile" class="navbar-item"> <a href="{% url 'directory' %}" class="navbar-item">
{% trans 'Settings' %} {% trans 'Directory' %}
</a> </a>
</li> </li>
<li> <li>
@ -102,12 +102,17 @@
{% trans 'Import Books' %} {% trans 'Import Books' %}
</a> </a>
</li> </li>
<li>
<a href="/preferences/profile" class="navbar-item">
{% trans 'Settings' %}
</a>
</li>
{% if perms.bookwyrm.create_invites or perms.bookwyrm.edit_instance_settings%} {% if perms.bookwyrm.create_invites or perms.bookwyrm.edit_instance_settings%}
<li class="navbar-divider" role="presentation"></li> <li class="navbar-divider" role="presentation"></li>
{% endif %} {% endif %}
{% if perms.bookwyrm.create_invites %} {% if perms.bookwyrm.create_invites %}
<li> <li>
<a href="{% url 'settings-invites' %}" class="navbar-item"> <a href="{% url 'settings-invite-requests' %}" class="navbar-item">
{% trans 'Invites' %} {% trans 'Invites' %}
</a> </a>
</li> </li>

View file

@ -53,6 +53,14 @@
{{ form.manually_approves_followers }} {{ form.manually_approves_followers }}
</label> </label>
</div> </div>
<div class="block">
<label class="checkbox label" for="id_discoverable">
{% trans "Show this account in suggested users:" %}
{{ form.discoverable }}
</label>
{% url 'directory' as path %}
<p class="help">{% blocktrans %}Your account will show up in the <a href="{{ path }}">directory</a>, and may be recommended to other BookWyrm users.{% endblocktrans %}</p>
</div>
<button class="button is-primary" type="submit">{% trans "Save" %}</button> <button class="button is-primary" type="submit">{% trans "Save" %}</button>
</form> </form>
{% endblock %} {% endblock %}

View file

@ -1,3 +1,3 @@
{% load bookwyrm_tags %} {% load bookwyrm_tags %}
<img class="avatar image {% if large %}is-96x96{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" {% if ariaHide %}aria-hidden="true"{% endif %} alt="{{ user.alt_text }}"> <img class="avatar image {% if large %}is-96x96{% elif medium %}is-48x48{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" {% if ariaHide %}aria-hidden="true"{% endif %} alt="{{ user.alt_text }}">

View file

@ -28,6 +28,9 @@
}, },
"bookwyrmUser": true, "bookwyrmUser": true,
"manuallyApprovesFollowers": false, "manuallyApprovesFollowers": false,
"discoverable": true,
"devices": "https://friend.camp/users/tripofmice/collections/devices",
"tag": [],
"icon": { "icon": {
"type": "Image", "type": "Image",
"mediaType": "image/png", "mediaType": "image/png",

View file

@ -77,7 +77,7 @@ class User(TestCase):
self.assertEqual(activity["inbox"], self.user.inbox) self.assertEqual(activity["inbox"], self.user.inbox)
self.assertEqual(activity["outbox"], self.user.outbox) self.assertEqual(activity["outbox"], self.user.outbox)
self.assertEqual(activity["bookwyrmUser"], False) self.assertEqual(activity["bookwyrmUser"], False)
self.assertEqual(activity["discoverable"], True) self.assertEqual(activity["discoverable"], False)
self.assertEqual(activity["type"], "Person") self.assertEqual(activity["type"], "Person")
def test_activitypub_outbox(self): def test_activitypub_outbox(self):

View file

@ -0,0 +1,43 @@
""" test for app action functionality """
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import models, views
# pylint: disable=unused-argument
class DirectoryViews(TestCase):
""" tag views"""
def setUp(self):
""" we need basic test data and mocks """
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.com",
"mouseword",
local=True,
localname="mouse",
remote_id="https://example.com/users/mouse",
)
self.rat = models.User.objects.create_user(
"rat@local.com",
"rat@rat.com",
"ratword",
local=True,
localname="rat",
remote_id="https://example.com/users/rat",
discoverable=True,
)
models.SiteSettings.objects.create()
def test_directory_page(self):
""" there are so many views, this just makes sure it LOADS """
view = views.Directory.as_view()
request = self.factory.get("")
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)

View file

@ -789,6 +789,7 @@ class Inbox(TestCase):
self.assertEqual(user.name, "MOUSE?? MOUSE!!") self.assertEqual(user.name, "MOUSE?? MOUSE!!")
self.assertEqual(user.username, "mouse@example.com") self.assertEqual(user.username, "mouse@example.com")
self.assertEqual(user.localname, "mouse") self.assertEqual(user.localname, "mouse")
self.assertTrue(user.discoverable)
def test_handle_update_edition(self): def test_handle_update_edition(self):
""" update an existing edition """ """ update an existing edition """

View file

@ -96,6 +96,7 @@ urlpatterns = [
path("", views.Home.as_view(), name="landing"), path("", views.Home.as_view(), name="landing"),
re_path(r"^discover/?$", views.Discover.as_view()), re_path(r"^discover/?$", views.Discover.as_view()),
re_path(r"^notifications/?$", views.Notifications.as_view()), re_path(r"^notifications/?$", views.Notifications.as_view()),
re_path(r"^directory/?", views.Directory.as_view(), name="directory"),
# feeds # feeds
re_path(r"^(?P<tab>home|local|federated)/?$", views.Feed.as_view()), re_path(r"^(?P<tab>home|local|federated)/?$", views.Feed.as_view()),
re_path( re_path(

View file

@ -4,6 +4,7 @@ from .author import Author, EditAuthor
from .block import Block, unblock from .block import Block, unblock
from .books import Book, EditBook, ConfirmEditBook, Editions from .books import Book, EditBook, ConfirmEditBook, Editions
from .books import upload_cover, add_description, switch_edition, resolve_book from .books import upload_cover, add_description, switch_edition, resolve_book
from .directory import Directory
from .error import not_found_page, server_error_page from .error import not_found_page, server_error_page
from .federation import Federation from .federation import Federation
from .feed import DirectMessage, Feed, Replies, Status from .feed import DirectMessage, Feed, Replies, Status

View file

@ -0,0 +1,32 @@
""" who all's here? """
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.template.response import TemplateResponse
from django.views import View
from django.utils.decorators import method_decorator
from bookwyrm import models
# pylint: disable=no-self-use
@method_decorator(login_required, name="dispatch")
class Directory(View):
""" display of known bookwyrm users """
def get(self, request):
""" lets see your cute faces """
try:
page = int(request.GET.get("page", 1))
except ValueError:
page = 1
users = models.User.objects.filter(
discoverable=True,
bookwyrm_user=True,
is_active=True,
).order_by("-last_active_date")
paginated = Paginator(users, 12)
data = {
"users": paginated.page(page),
}
return TemplateResponse(request, "directory.html", data)

View file

@ -2,7 +2,7 @@
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.views import View from django.views import View
from bookwyrm import forms, models from bookwyrm import forms
from .feed import Feed from .feed import Feed
from . import helpers from . import helpers