mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-10-31 22:19:00 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
7aff486a59
31 changed files with 247 additions and 167 deletions
|
@ -1,4 +1,5 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
""" What you need in the database to make it work """
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
@ -7,12 +8,14 @@ from bookwyrm.settings import DOMAIN
|
|||
|
||||
|
||||
def init_groups():
|
||||
"""permission levels"""
|
||||
groups = ["admin", "moderator", "editor"]
|
||||
for group in groups:
|
||||
Group.objects.create(name=group)
|
||||
|
||||
|
||||
def init_permissions():
|
||||
"""permission types"""
|
||||
permissions = [
|
||||
{
|
||||
"codename": "edit_instance_settings",
|
||||
|
@ -69,6 +72,7 @@ def init_permissions():
|
|||
|
||||
|
||||
def init_connectors():
|
||||
"""access book data sources"""
|
||||
Connector.objects.create(
|
||||
identifier=DOMAIN,
|
||||
name="Local",
|
||||
|
@ -130,7 +134,11 @@ def init_federated_servers():
|
|||
|
||||
|
||||
def init_settings():
|
||||
SiteSettings.objects.create()
|
||||
"""info about the instance"""
|
||||
SiteSettings.objects.create(
|
||||
support_link="https://www.patreon.com/bookwyrm",
|
||||
support_title="Patreon",
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
|
18
bookwyrm/migrations/0073_sitesettings_footer_item.py
Normal file
18
bookwyrm/migrations/0073_sitesettings_footer_item.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2 on 2021-04-30 17:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0072_remove_work_default_edition"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="sitesettings",
|
||||
name="footer_item",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -19,19 +19,28 @@ class SiteSettings(models.Model):
|
|||
max_length=150, default="Social Reading and Reviewing"
|
||||
)
|
||||
instance_description = models.TextField(default="This instance has no description.")
|
||||
|
||||
# about page
|
||||
registration_closed_text = models.TextField(
|
||||
default="Contact an administrator to get an invite"
|
||||
)
|
||||
code_of_conduct = models.TextField(default="Add a code of conduct here.")
|
||||
privacy_policy = models.TextField(default="Add a privacy policy here.")
|
||||
|
||||
# registration
|
||||
allow_registration = models.BooleanField(default=True)
|
||||
allow_invite_requests = models.BooleanField(default=True)
|
||||
|
||||
# images
|
||||
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
logo_small = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
favicon = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
|
||||
# footer
|
||||
support_link = models.CharField(max_length=255, null=True, blank=True)
|
||||
support_title = models.CharField(max_length=100, null=True, blank=True)
|
||||
admin_email = models.EmailField(max_length=255, null=True, blank=True)
|
||||
footer_item = models.TextField(null=True, blank=True)
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
|
|
|
@ -150,6 +150,19 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
"""for consistent naming"""
|
||||
return not self.is_active
|
||||
|
||||
@property
|
||||
def unread_notification_count(self):
|
||||
"""count of notifications, for the templates"""
|
||||
return self.notification_set.filter(read=False).count()
|
||||
|
||||
@property
|
||||
def has_unread_mentions(self):
|
||||
"""whether any of the unread notifications are conversations"""
|
||||
return self.notification_set.filter(
|
||||
read=False,
|
||||
notification_type__in=["REPLY", "MENTION", "TAG", "REPORT"],
|
||||
).exists()
|
||||
|
||||
activity_serializer = activitypub.Person
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -97,10 +97,12 @@ let BookWyrm = new class {
|
|||
updateCountElement(counter, data) {
|
||||
const currentCount = counter.innerText;
|
||||
const count = data.count;
|
||||
const hasMentions = data.has_mentions;
|
||||
|
||||
if (count != currentCount) {
|
||||
this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-hidden', count < 1);
|
||||
counter.innerText = count;
|
||||
this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-danger', hasMentions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'user/user_layout.html' %}
|
||||
{% extends 'user/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block header %}
|
||||
|
|
|
@ -135,8 +135,11 @@
|
|||
<span class="is-sr-only">{% trans "Notifications" %}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="{% if not request.user|notification_count %}is-hidden {% endif %}tag is-danger is-medium transition-x" data-poll-wrapper>
|
||||
<span data-poll="notifications">{{ request.user | notification_count }}</span>
|
||||
<span
|
||||
class="{% if not request.user.unread_notification_count %}is-hidden {% elif request.user.has_unread_mentions %}is-danger {% endif %}tag is-medium transition-x"
|
||||
data-poll-wrapper
|
||||
>
|
||||
<span data-poll="notifications">{{ request.user.unread_notification_count }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -190,7 +193,7 @@
|
|||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="column is-one-fifth">
|
||||
<p>
|
||||
<a href="/about">{% trans "About this server" %}</a>
|
||||
</p>
|
||||
|
@ -199,16 +202,26 @@
|
|||
<a href="mailto:{{ site.admin_email }}">{% trans "Contact site admin" %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<a href="https://docs.joinbookwyrm.com/">{% trans "Documentation" %}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column content is-two-fifth">
|
||||
{% if site.support_link %}
|
||||
<div class="column">
|
||||
<p>
|
||||
<span class="icon icon-heart"></span>
|
||||
{% blocktrans with site_name=site.name support_link=site.support_link support_title=site.support_title %}Support {{ site_name }} on <a href="{{ support_link }}" target="_blank">{{ support_title }}</a>{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
{% trans 'BookWyrm is open source software. You can contribute or report issues on <a href="https://github.com/mouse-reeve/bookwyrm">GitHub</a>.' %}
|
||||
</p>
|
||||
</div>
|
||||
{% if site.footer_item %}
|
||||
<div class="column">
|
||||
<p>{{ site.footer_item|safe }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="column">
|
||||
{% trans 'BookWyrm is open source software. You can contribute or report issues on <a href="https://github.com/mouse-reeve/bookwyrm">GitHub</a>.' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
@ -44,8 +44,15 @@
|
|||
</div>
|
||||
|
||||
<div class="column ml-3">
|
||||
<span>{% include 'snippets/book_titleby.html' %}</span>
|
||||
<p>
|
||||
{% include 'snippets/book_titleby.html' %}
|
||||
</p>
|
||||
<p>
|
||||
{% include 'snippets/stars.html' with rating=item.book|rating:request.user %}
|
||||
</p>
|
||||
<p>
|
||||
{{ book|book_description|to_markdown|default:""|safe|truncatewords_html:20 }}
|
||||
</p>
|
||||
{% include 'snippets/shelve_button/shelve_button.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,16 +37,16 @@
|
|||
|
||||
<section class="block" id="images">
|
||||
<h2 class="title is-4">{% trans "Images" %}</h2>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<label class="label" for="id_logo">{% trans "Logo:" %}</label>
|
||||
{{ site_form.logo }}
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="column">
|
||||
<label class="label" for="id_logo_small">{% trans "Logo small:" %}</label>
|
||||
{{ site_form.logo_small }}
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="column">
|
||||
<label class="label" for="id_favicon">{% trans "Favicon:" %}</label>
|
||||
{{ site_form.favicon }}
|
||||
</div>
|
||||
|
@ -69,6 +69,10 @@
|
|||
<label class="label" for="id_admin_email">{% trans "Admin email:" %}</label>
|
||||
{{ site_form.admin_email }}
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="label" for="id_footer_item">{% trans "Additional info:" %}</label>
|
||||
{{ site_form.footer_item }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr aria-hidden="true">
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
{% extends 'user/user_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block header %}
|
||||
<h1 class="title">
|
||||
{% trans "User Profile" %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="block">
|
||||
<h2 class="title">{% trans "Followers" %}</h2>
|
||||
{% for follower in followers %}
|
||||
<div class="block columns">
|
||||
<div class="column">
|
||||
<a href="{{ follower.local_path }}">
|
||||
{% include 'snippets/avatar.html' with user=follower %}
|
||||
{{ follower.display_name }}
|
||||
</a>
|
||||
({{ follower.username }})
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
{% include 'snippets/follow_button.html' with user=follower %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not followers.count %}
|
||||
<div>{% blocktrans with username=user.display_name %}{{ username }} has no followers{% endblocktrans %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'snippets/pagination.html' with page=followers path=request.path %}
|
||||
{% endblock %}
|
|
@ -1,34 +0,0 @@
|
|||
{% extends 'user/user_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block header %}
|
||||
<h1 class="title">
|
||||
{% trans "User Profile" %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="block">
|
||||
<h2 class="title">{% trans "Following" %}</h2>
|
||||
{% for follower in user.following.all %}
|
||||
<div class="block columns">
|
||||
<div class="column">
|
||||
<a href="{{ follower.local_path }}">
|
||||
{% include 'snippets/avatar.html' with user=follower %}
|
||||
{{ follower.display_name }}
|
||||
</a>
|
||||
({{ follower.username }})
|
||||
</div>
|
||||
<div class="column">
|
||||
{% include 'snippets/follow_button.html' with user=follower %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not following.count %}
|
||||
<div>{% blocktrans with username=user|username %}{{ username }} isn't following any users{% endblocktrans %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'snippets/pagination.html' with page=following path=request.path %}
|
||||
{% endblock %}
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
{% block content %}
|
||||
<header class="block">
|
||||
{% block header %}{% endblock %}
|
||||
{% block header %}
|
||||
<h1 class="title">
|
||||
{% trans "User Profile" %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
</header>
|
||||
|
||||
{# user bio #}
|
||||
|
@ -41,8 +45,9 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% block tabs %}
|
||||
{% with user|username as username %}
|
||||
{% if 'user/'|add:username|add:'/books' not in request.path and 'user/'|add:username|add:'/shelf' not in request.path %}
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
{% url 'user-feed' user|username as url %}
|
||||
|
@ -70,8 +75,8 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}{% endblock %}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'user/user_layout.html' %}
|
||||
{% extends 'user/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block header %}
|
||||
|
@ -23,7 +23,7 @@
|
|||
|
||||
|
||||
{% block panel %}
|
||||
<section class="block content">
|
||||
<section class="block">
|
||||
<form name="create-list" method="post" action="{% url 'lists' %}" class="box is-hidden" id="create-list">
|
||||
<header class="columns">
|
||||
<h3 class="title column">{% trans "Create list" %}</h3>
|
||||
|
|
14
bookwyrm/templates/user/relationships/followers.html
Normal file
14
bookwyrm/templates/user/relationships/followers.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends 'user/relationships/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block header %}
|
||||
<h1 class="title">
|
||||
{% trans "Followers" %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block nullstate %}
|
||||
<div>
|
||||
{% blocktrans with username=user.display_name %}{{ username }} has no followers{% endblocktrans %}
|
||||
</div>
|
||||
{% endblock %}
|
14
bookwyrm/templates/user/relationships/following.html
Normal file
14
bookwyrm/templates/user/relationships/following.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends 'user/relationships/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block header %}
|
||||
<h1 class="title">
|
||||
{% trans "Following" %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block nullstate %}
|
||||
<div>
|
||||
{% blocktrans with username=user.display_name %}{{ username }} isn't following any users{% endblocktrans %}
|
||||
</div>
|
||||
{% endblock %}
|
46
bookwyrm/templates/user/relationships/layout.html
Normal file
46
bookwyrm/templates/user/relationships/layout.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
{% extends 'user/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block tabs %}
|
||||
{% with user|username as username %}
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
{% url 'user-followers' user|username as url %}
|
||||
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Followers" %}</a>
|
||||
</li>
|
||||
{% url 'user-following' user|username as url %}
|
||||
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Following" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="block">
|
||||
{% for follow in follow_list %}
|
||||
<div class="block columns">
|
||||
<div class="column">
|
||||
<a href="{{ follower.local_path }}">
|
||||
{% include 'snippets/avatar.html' with user=follow %}
|
||||
{{ follow.display_name }}
|
||||
</a>
|
||||
({{ follow.username }})
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
{% include 'snippets/follow_button.html' with user=follow %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if not follow_list %}
|
||||
{% block nullstate %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'snippets/pagination.html' with page=follow_list path=request.path %}
|
||||
{% endblock %}
|
|
@ -1,21 +1,21 @@
|
|||
{% extends 'user/user_layout.html' %}
|
||||
{% extends 'user/layout.html' %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load humanize %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% include 'user/books_header.html' %}
|
||||
{% include 'user/shelf/books_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<header class="columns">
|
||||
<h1 class="title">
|
||||
{% include 'user/books_header.html' %}
|
||||
{% include 'user/shelf/books_header.html' %}
|
||||
</h1>
|
||||
</header>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% block tabs %}
|
||||
<div class="block columns">
|
||||
<div class="column">
|
||||
<div class="tabs">
|
||||
|
@ -39,9 +39,11 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="block">
|
||||
{% include 'user/create_shelf_form.html' with controls_text='create-shelf-form' %}
|
||||
{% include 'user/shelf/create_shelf_form.html' with controls_text='create-shelf-form' %}
|
||||
</div>
|
||||
|
||||
<div class="block columns is-mobile">
|
||||
|
@ -62,7 +64,7 @@
|
|||
</div>
|
||||
|
||||
<div class="block">
|
||||
{% include 'user/edit_shelf_form.html' with controls_text="edit-shelf-form" %}
|
||||
{% include 'user/shelf/edit_shelf_form.html' with controls_text="edit-shelf-form" %}
|
||||
</div>
|
||||
|
||||
<div class="block">
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'user/user_layout.html' %}
|
||||
{% extends 'user/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
{% if is_self %}
|
||||
<div class="column is-narrow">
|
||||
<a href="/preferences/profile">
|
||||
<a href="{% url 'prefs-profile' %}">
|
||||
<span class="icon icon-pencil" title="Edit profile">
|
||||
<span class="is-sr-only">{% trans "Edit profile" %}</span>
|
||||
</span>
|
||||
|
@ -25,7 +25,7 @@
|
|||
{% if user.bookwyrm_user %}
|
||||
<div class="block">
|
||||
<h2 class="title">
|
||||
{% include 'user/books_header.html' %}
|
||||
{% include 'user/shelf/books_header.html' %}
|
||||
</h2>
|
||||
<div class="columns">
|
||||
{% for shelf in shelves %}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
<div class="media block">
|
||||
<div class="media-left">
|
||||
|
@ -12,8 +13,19 @@
|
|||
<p><a href="{{ user.remote_id }}">{{ user.username }}</a></p>
|
||||
<p>{% blocktrans with date=user.created_date|naturaltime %}Joined {{ date }}{% endblocktrans %}</p>
|
||||
<p>
|
||||
<a href="{{ user.local_path }}/followers">{% blocktrans count counter=user.followers.count %}{{ counter }} follower{% plural %}{{ counter }} followers{% endblocktrans %}</a>,
|
||||
<a href="{{ user.local_path }}/following">{% blocktrans with counter=user.following.count %}{{ counter }} following{% endblocktrans %}</a>
|
||||
{% if is_self %}
|
||||
|
||||
<a href="{% url 'user-followers' user|username %}">{% blocktrans count counter=user.followers.count %}{{ counter }} follower{% plural %}{{ counter }} followers{% endblocktrans %}</a>,
|
||||
<a href="{% url 'user-following' user|username %}">{% blocktrans with counter=user.following.count %}{{ counter }} following{% endblocktrans %}</a>
|
||||
|
||||
{% elif request.user.is_authenticated %}
|
||||
|
||||
{% mutuals_count user as mutuals %}
|
||||
<a href="{% url 'user-followers' user|username %}">
|
||||
{% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %}
|
||||
</a>
|
||||
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -235,3 +235,12 @@ def get_lang():
|
|||
"""get current language, strip to the first two letters"""
|
||||
language = utils.translation.get_language()
|
||||
return language[0 : language.find("-")]
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def mutuals_count(context, user):
|
||||
"""how many users that you follow, follow them"""
|
||||
viewer = context["request"].user
|
||||
if not viewer.is_authenticated:
|
||||
return None
|
||||
return user.followers.filter(id__in=viewer.following.all()).count()
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
from unittest.mock import patch
|
||||
import pathlib
|
||||
from django.db.models import Q
|
||||
from django.http import Http404
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
import responses
|
||||
|
@ -67,7 +68,7 @@ class ViewsHelpers(TestCase):
|
|||
views.helpers.get_user_from_username(self.local_user, "mouse@local.com"),
|
||||
self.local_user,
|
||||
)
|
||||
with self.assertRaises(models.User.DoesNotExist):
|
||||
with self.assertRaises(Http404):
|
||||
views.helpers.get_user_from_username(self.local_user, "mojfse@example.com")
|
||||
|
||||
def test_is_api_request(self, _):
|
||||
|
|
|
@ -6,6 +6,7 @@ from PIL import Image
|
|||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.http.response import Http404
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
@ -76,8 +77,8 @@ class UserViews(TestCase):
|
|||
self.rat.blocks.add(self.local_user)
|
||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, "rat")
|
||||
self.assertEqual(result.status_code, 404)
|
||||
with self.assertRaises(Http404):
|
||||
view(request, "rat")
|
||||
|
||||
def test_followers_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
|
@ -105,8 +106,8 @@ class UserViews(TestCase):
|
|||
self.rat.blocks.add(self.local_user)
|
||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, "rat")
|
||||
self.assertEqual(result.status_code, 404)
|
||||
with self.assertRaises(Http404):
|
||||
view(request, "rat")
|
||||
|
||||
def test_following_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
|
@ -134,8 +135,8 @@ class UserViews(TestCase):
|
|||
self.rat.blocks.add(self.local_user)
|
||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, "rat")
|
||||
self.assertEqual(result.status_code, 404)
|
||||
with self.assertRaises(Http404):
|
||||
view(request, "rat")
|
||||
|
||||
def test_edit_user_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
|
@ -166,7 +167,6 @@ class UserViews(TestCase):
|
|||
self.assertEqual(self.local_user.name, "New Name")
|
||||
self.assertEqual(self.local_user.email, "wow@email.com")
|
||||
|
||||
# idk how to mock the upload form, got tired of triyng to make it work
|
||||
def test_edit_user_avatar(self):
|
||||
"""use a form to update a user"""
|
||||
view = views.EditUser.as_view()
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.http import HttpResponseNotFound, Http404
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
|
@ -62,7 +62,7 @@ class DirectMessage(View):
|
|||
if username:
|
||||
try:
|
||||
user = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
except Http404:
|
||||
pass
|
||||
if user:
|
||||
queryset = queryset.filter(Q(user=user) | Q(mention_users=user))
|
||||
|
@ -94,7 +94,7 @@ class Status(View):
|
|||
status = models.Status.objects.select_subclasses().get(
|
||||
id=status_id, deleted=False
|
||||
)
|
||||
except (ValueError, models.Status.DoesNotExist, models.User.DoesNotExist):
|
||||
except (ValueError, models.Status.DoesNotExist):
|
||||
return HttpResponseNotFound()
|
||||
|
||||
# the url should have the poster's username in it
|
||||
|
|
|
@ -14,10 +14,7 @@ from .helpers import get_user_from_username
|
|||
def follow(request):
|
||||
"""follow another user, here or abroad"""
|
||||
username = request.POST["user"]
|
||||
try:
|
||||
to_follow = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
models.UserFollowRequest.objects.create(
|
||||
|
@ -35,10 +32,7 @@ def follow(request):
|
|||
def unfollow(request):
|
||||
"""unfollow a user"""
|
||||
username = request.POST["user"]
|
||||
try:
|
||||
to_unfollow = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
models.UserFollows.objects.get(
|
||||
|
@ -63,10 +57,7 @@ def unfollow(request):
|
|||
def accept_follow_request(request):
|
||||
"""a user accepts a follow request"""
|
||||
username = request.POST["user"]
|
||||
try:
|
||||
requester = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
follow_request = models.UserFollowRequest.objects.get(
|
||||
|
@ -85,10 +76,7 @@ def accept_follow_request(request):
|
|||
def delete_follow_request(request):
|
||||
"""a user rejects a follow request"""
|
||||
username = request.POST["user"]
|
||||
try:
|
||||
requester = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
follow_request = models.UserFollowRequest.objects.get(
|
||||
|
|
|
@ -3,6 +3,7 @@ import re
|
|||
from requests import HTTPError
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models import Count, Max, Q
|
||||
from django.http import Http404
|
||||
|
||||
from bookwyrm import activitypub, models
|
||||
from bookwyrm.connectors import ConnectorException, get_data
|
||||
|
@ -12,11 +13,17 @@ from bookwyrm.utils import regex
|
|||
|
||||
def get_user_from_username(viewer, username):
|
||||
"""helper function to resolve a localname or a username to a user"""
|
||||
# raises DoesNotExist if user is now found
|
||||
# raises 404 if the user isn't found
|
||||
try:
|
||||
return models.User.viewer_aware_objects(viewer).get(localname=username)
|
||||
except models.User.DoesNotExist:
|
||||
pass
|
||||
|
||||
# if the localname didn't match, try the username
|
||||
try:
|
||||
return models.User.viewer_aware_objects(viewer).get(username=username)
|
||||
except models.User.DoesNotExist:
|
||||
raise Http404()
|
||||
|
||||
|
||||
def is_api_request(request):
|
||||
|
|
|
@ -25,10 +25,7 @@ class Shelf(View):
|
|||
|
||||
def get(self, request, username, shelf_identifier=None):
|
||||
"""display a shelf"""
|
||||
try:
|
||||
user = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
shelves = privacy_filter(request.user, user.shelf_set)
|
||||
|
||||
|
@ -68,7 +65,7 @@ class Shelf(View):
|
|||
"books": paginated.get_page(request.GET.get("page")),
|
||||
}
|
||||
|
||||
return TemplateResponse(request, "user/shelf.html", data)
|
||||
return TemplateResponse(request, "user/shelf/shelf.html", data)
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
# pylint: disable=unused-argument
|
||||
|
|
|
@ -10,7 +10,8 @@ def get_notification_count(request):
|
|||
"""any notifications waiting?"""
|
||||
return JsonResponse(
|
||||
{
|
||||
"count": request.user.notification_set.filter(read=False).count(),
|
||||
"count": request.user.unread_notification_count,
|
||||
"has_mentions": request.user.has_unread_mentions,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ from PIL import Image
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.paginator import Paginator
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
|
@ -17,7 +16,7 @@ from bookwyrm import forms, models
|
|||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from .helpers import get_user_from_username, is_api_request
|
||||
from .helpers import is_blocked, privacy_filter
|
||||
from .helpers import privacy_filter
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -26,14 +25,7 @@ class User(View):
|
|||
|
||||
def get(self, request, username):
|
||||
"""profile page for a user"""
|
||||
try:
|
||||
user = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
# make sure we're not blocked
|
||||
if is_blocked(request.user, user):
|
||||
return HttpResponseNotFound()
|
||||
|
||||
if is_api_request(request):
|
||||
# we have a json request
|
||||
|
@ -94,14 +86,7 @@ class Followers(View):
|
|||
|
||||
def get(self, request, username):
|
||||
"""list of followers"""
|
||||
try:
|
||||
user = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
# make sure we're not blocked
|
||||
if is_blocked(request.user, user):
|
||||
return HttpResponseNotFound()
|
||||
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(user.to_followers_activity(**request.GET))
|
||||
|
@ -110,9 +95,9 @@ class Followers(View):
|
|||
data = {
|
||||
"user": user,
|
||||
"is_self": request.user.id == user.id,
|
||||
"followers": paginated.page(request.GET.get("page", 1)),
|
||||
"follow_list": paginated.page(request.GET.get("page", 1)),
|
||||
}
|
||||
return TemplateResponse(request, "user/followers.html", data)
|
||||
return TemplateResponse(request, "user/relationships/followers.html", data)
|
||||
|
||||
|
||||
class Following(View):
|
||||
|
@ -120,25 +105,18 @@ class Following(View):
|
|||
|
||||
def get(self, request, username):
|
||||
"""list of followers"""
|
||||
try:
|
||||
user = get_user_from_username(request.user, username)
|
||||
except models.User.DoesNotExist:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
# make sure we're not blocked
|
||||
if is_blocked(request.user, user):
|
||||
return HttpResponseNotFound()
|
||||
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(user.to_following_activity(**request.GET))
|
||||
|
||||
paginated = Paginator(user.followers.all(), PAGE_LENGTH)
|
||||
paginated = Paginator(user.following.all(), PAGE_LENGTH)
|
||||
data = {
|
||||
"user": user,
|
||||
"is_self": request.user.id == user.id,
|
||||
"following": paginated.page(request.GET.get("page", 1)),
|
||||
"follow_list": paginated.page(request.GET.get("page", 1)),
|
||||
}
|
||||
return TemplateResponse(request, "user/following.html", data)
|
||||
return TemplateResponse(request, "user/relationships/following.html", data)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
|
|
Loading…
Reference in a new issue