forked from mirrors/bookwyrm
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.auth.models import Group, Permission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
@ -7,12 +8,14 @@ from bookwyrm.settings import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
def init_groups():
|
def init_groups():
|
||||||
|
"""permission levels"""
|
||||||
groups = ["admin", "moderator", "editor"]
|
groups = ["admin", "moderator", "editor"]
|
||||||
for group in groups:
|
for group in groups:
|
||||||
Group.objects.create(name=group)
|
Group.objects.create(name=group)
|
||||||
|
|
||||||
|
|
||||||
def init_permissions():
|
def init_permissions():
|
||||||
|
"""permission types"""
|
||||||
permissions = [
|
permissions = [
|
||||||
{
|
{
|
||||||
"codename": "edit_instance_settings",
|
"codename": "edit_instance_settings",
|
||||||
|
@ -69,6 +72,7 @@ def init_permissions():
|
||||||
|
|
||||||
|
|
||||||
def init_connectors():
|
def init_connectors():
|
||||||
|
"""access book data sources"""
|
||||||
Connector.objects.create(
|
Connector.objects.create(
|
||||||
identifier=DOMAIN,
|
identifier=DOMAIN,
|
||||||
name="Local",
|
name="Local",
|
||||||
|
@ -130,7 +134,11 @@ def init_federated_servers():
|
||||||
|
|
||||||
|
|
||||||
def init_settings():
|
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):
|
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"
|
max_length=150, default="Social Reading and Reviewing"
|
||||||
)
|
)
|
||||||
instance_description = models.TextField(default="This instance has no description.")
|
instance_description = models.TextField(default="This instance has no description.")
|
||||||
|
|
||||||
|
# about page
|
||||||
registration_closed_text = models.TextField(
|
registration_closed_text = models.TextField(
|
||||||
default="Contact an administrator to get an invite"
|
default="Contact an administrator to get an invite"
|
||||||
)
|
)
|
||||||
code_of_conduct = models.TextField(default="Add a code of conduct here.")
|
code_of_conduct = models.TextField(default="Add a code of conduct here.")
|
||||||
privacy_policy = models.TextField(default="Add a privacy policy here.")
|
privacy_policy = models.TextField(default="Add a privacy policy here.")
|
||||||
|
|
||||||
|
# registration
|
||||||
allow_registration = models.BooleanField(default=True)
|
allow_registration = models.BooleanField(default=True)
|
||||||
allow_invite_requests = models.BooleanField(default=True)
|
allow_invite_requests = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
# images
|
||||||
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
|
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||||
logo_small = 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)
|
favicon = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||||
|
|
||||||
|
# footer
|
||||||
support_link = models.CharField(max_length=255, null=True, blank=True)
|
support_link = models.CharField(max_length=255, null=True, blank=True)
|
||||||
support_title = models.CharField(max_length=100, 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)
|
admin_email = models.EmailField(max_length=255, null=True, blank=True)
|
||||||
|
footer_item = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls):
|
def get(cls):
|
||||||
|
|
|
@ -150,6 +150,19 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
"""for consistent naming"""
|
"""for consistent naming"""
|
||||||
return not self.is_active
|
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
|
activity_serializer = activitypub.Person
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -97,10 +97,12 @@ let BookWyrm = new class {
|
||||||
updateCountElement(counter, data) {
|
updateCountElement(counter, data) {
|
||||||
const currentCount = counter.innerText;
|
const currentCount = counter.innerText;
|
||||||
const count = data.count;
|
const count = data.count;
|
||||||
|
const hasMentions = data.has_mentions;
|
||||||
|
|
||||||
if (count != currentCount) {
|
if (count != currentCount) {
|
||||||
this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-hidden', count < 1);
|
this.addRemoveClass(counter.closest('[data-poll-wrapper]'), 'is-hidden', count < 1);
|
||||||
counter.innerText = count;
|
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 %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
|
|
@ -135,8 +135,11 @@
|
||||||
<span class="is-sr-only">{% trans "Notifications" %}</span>
|
<span class="is-sr-only">{% trans "Notifications" %}</span>
|
||||||
</span>
|
</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
|
||||||
<span data-poll="notifications">{{ request.user | notification_count }}</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>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -190,7 +193,7 @@
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column is-one-fifth">
|
||||||
<p>
|
<p>
|
||||||
<a href="/about">{% trans "About this server" %}</a>
|
<a href="/about">{% trans "About this server" %}</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -199,16 +202,26 @@
|
||||||
<a href="mailto:{{ site.admin_email }}">{% trans "Contact site admin" %}</a>
|
<a href="mailto:{{ site.admin_email }}">{% trans "Contact site admin" %}</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
<a href="https://docs.joinbookwyrm.com/">{% trans "Documentation" %}</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% if site.support_link %}
|
<div class="column content is-two-fifth">
|
||||||
|
{% if site.support_link %}
|
||||||
|
<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">
|
<div class="column">
|
||||||
<span class="icon icon-heart"></span>
|
<p>{{ site.footer_item|safe }}</p>
|
||||||
{% 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 %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
@ -44,8 +44,15 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column ml-3">
|
<div class="column ml-3">
|
||||||
<span>{% include 'snippets/book_titleby.html' %}</span>
|
<p>
|
||||||
{% include 'snippets/stars.html' with rating=item.book|rating:request.user %}
|
{% 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' %}
|
{% include 'snippets/shelve_button/shelve_button.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -37,16 +37,16 @@
|
||||||
|
|
||||||
<section class="block" id="images">
|
<section class="block" id="images">
|
||||||
<h2 class="title is-4">{% trans "Images" %}</h2>
|
<h2 class="title is-4">{% trans "Images" %}</h2>
|
||||||
<div class="field is-grouped">
|
<div class="columns">
|
||||||
<div class="control">
|
<div class="column">
|
||||||
<label class="label" for="id_logo">{% trans "Logo:" %}</label>
|
<label class="label" for="id_logo">{% trans "Logo:" %}</label>
|
||||||
{{ site_form.logo }}
|
{{ site_form.logo }}
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="column">
|
||||||
<label class="label" for="id_logo_small">{% trans "Logo small:" %}</label>
|
<label class="label" for="id_logo_small">{% trans "Logo small:" %}</label>
|
||||||
{{ site_form.logo_small }}
|
{{ site_form.logo_small }}
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="column">
|
||||||
<label class="label" for="id_favicon">{% trans "Favicon:" %}</label>
|
<label class="label" for="id_favicon">{% trans "Favicon:" %}</label>
|
||||||
{{ site_form.favicon }}
|
{{ site_form.favicon }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,6 +69,10 @@
|
||||||
<label class="label" for="id_admin_email">{% trans "Admin email:" %}</label>
|
<label class="label" for="id_admin_email">{% trans "Admin email:" %}</label>
|
||||||
{{ site_form.admin_email }}
|
{{ site_form.admin_email }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<label class="label" for="id_footer_item">{% trans "Additional info:" %}</label>
|
||||||
|
{{ site_form.footer_item }}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr aria-hidden="true">
|
<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 %}
|
{% block content %}
|
||||||
<header class="block">
|
<header class="block">
|
||||||
{% block header %}{% endblock %}
|
{% block header %}
|
||||||
|
<h1 class="title">
|
||||||
|
{% trans "User Profile" %}
|
||||||
|
</h1>
|
||||||
|
{% endblock %}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{# user bio #}
|
{# user bio #}
|
||||||
|
@ -41,8 +45,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
{% with user|username as username %}
|
{% 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">
|
<nav class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
{% url 'user-feed' user|username as url %}
|
{% url 'user-feed' user|username as url %}
|
||||||
|
@ -70,8 +75,8 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel %}{% endblock %}
|
{% block panel %}{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'user/user_layout.html' %}
|
{% extends 'user/layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
|
|
||||||
{% block panel %}
|
{% 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">
|
<form name="create-list" method="post" action="{% url 'lists' %}" class="box is-hidden" id="create-list">
|
||||||
<header class="columns">
|
<header class="columns">
|
||||||
<h3 class="title column">{% trans "Create list" %}</h3>
|
<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 bookwyrm_tags %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% include 'user/books_header.html' %}
|
{% include 'user/shelf/books_header.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<header class="columns">
|
<header class="columns">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
{% include 'user/books_header.html' %}
|
{% include 'user/shelf/books_header.html' %}
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block tabs %}
|
||||||
<div class="block columns">
|
<div class="block columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
|
@ -39,9 +39,11 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
<div class="block">
|
<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>
|
||||||
|
|
||||||
<div class="block columns is-mobile">
|
<div class="block columns is-mobile">
|
||||||
|
@ -62,7 +64,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<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>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'user/user_layout.html' %}
|
{% extends 'user/layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
{% load bookwyrm_tags %}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% if is_self %}
|
{% if is_self %}
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<a href="/preferences/profile">
|
<a href="{% url 'prefs-profile' %}">
|
||||||
<span class="icon icon-pencil" title="Edit profile">
|
<span class="icon icon-pencil" title="Edit profile">
|
||||||
<span class="is-sr-only">{% trans "Edit profile" %}</span>
|
<span class="is-sr-only">{% trans "Edit profile" %}</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
{% if user.bookwyrm_user %}
|
{% if user.bookwyrm_user %}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 class="title">
|
<h2 class="title">
|
||||||
{% include 'user/books_header.html' %}
|
{% include 'user/shelf/books_header.html' %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
{% for shelf in shelves %}
|
{% for shelf in shelves %}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
{% load bookwyrm_tags %}
|
||||||
|
|
||||||
<div class="media block">
|
<div class="media block">
|
||||||
<div class="media-left">
|
<div class="media-left">
|
||||||
|
@ -12,8 +13,19 @@
|
||||||
<p><a href="{{ user.remote_id }}">{{ user.username }}</a></p>
|
<p><a href="{{ user.remote_id }}">{{ user.username }}</a></p>
|
||||||
<p>{% blocktrans with date=user.created_date|naturaltime %}Joined {{ date }}{% endblocktrans %}</p>
|
<p>{% blocktrans with date=user.created_date|naturaltime %}Joined {{ date }}{% endblocktrans %}</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ user.local_path }}/followers">{% blocktrans count counter=user.followers.count %}{{ counter }} follower{% plural %}{{ counter }} followers{% endblocktrans %}</a>,
|
{% if is_self %}
|
||||||
<a href="{{ user.local_path }}/following">{% blocktrans with counter=user.following.count %}{{ counter }} following{% endblocktrans %}</a>
|
|
||||||
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -235,3 +235,12 @@ def get_lang():
|
||||||
"""get current language, strip to the first two letters"""
|
"""get current language, strip to the first two letters"""
|
||||||
language = utils.translation.get_language()
|
language = utils.translation.get_language()
|
||||||
return language[0 : language.find("-")]
|
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
|
from unittest.mock import patch
|
||||||
import pathlib
|
import pathlib
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.http import Http404
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
import responses
|
import responses
|
||||||
|
@ -67,7 +68,7 @@ class ViewsHelpers(TestCase):
|
||||||
views.helpers.get_user_from_username(self.local_user, "mouse@local.com"),
|
views.helpers.get_user_from_username(self.local_user, "mouse@local.com"),
|
||||||
self.local_user,
|
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")
|
views.helpers.get_user_from_username(self.local_user, "mojfse@example.com")
|
||||||
|
|
||||||
def test_is_api_request(self, _):
|
def test_is_api_request(self, _):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from PIL import Image
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
|
from django.http.response import Http404
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
@ -76,8 +77,8 @@ class UserViews(TestCase):
|
||||||
self.rat.blocks.add(self.local_user)
|
self.rat.blocks.add(self.local_user)
|
||||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, "rat")
|
with self.assertRaises(Http404):
|
||||||
self.assertEqual(result.status_code, 404)
|
view(request, "rat")
|
||||||
|
|
||||||
def test_followers_page(self):
|
def test_followers_page(self):
|
||||||
"""there are so many views, this just makes sure it LOADS"""
|
"""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)
|
self.rat.blocks.add(self.local_user)
|
||||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, "rat")
|
with self.assertRaises(Http404):
|
||||||
self.assertEqual(result.status_code, 404)
|
view(request, "rat")
|
||||||
|
|
||||||
def test_following_page(self):
|
def test_following_page(self):
|
||||||
"""there are so many views, this just makes sure it LOADS"""
|
"""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)
|
self.rat.blocks.add(self.local_user)
|
||||||
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
with patch("bookwyrm.views.user.is_api_request") as is_api:
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, "rat")
|
with self.assertRaises(Http404):
|
||||||
self.assertEqual(result.status_code, 404)
|
view(request, "rat")
|
||||||
|
|
||||||
def test_edit_user_page(self):
|
def test_edit_user_page(self):
|
||||||
"""there are so many views, this just makes sure it LOADS"""
|
"""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.name, "New Name")
|
||||||
self.assertEqual(self.local_user.email, "wow@email.com")
|
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):
|
def test_edit_user_avatar(self):
|
||||||
"""use a form to update a user"""
|
"""use a form to update a user"""
|
||||||
view = views.EditUser.as_view()
|
view = views.EditUser.as_view()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Q
|
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.template.response import TemplateResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
@ -62,7 +62,7 @@ class DirectMessage(View):
|
||||||
if username:
|
if username:
|
||||||
try:
|
try:
|
||||||
user = get_user_from_username(request.user, username)
|
user = get_user_from_username(request.user, username)
|
||||||
except models.User.DoesNotExist:
|
except Http404:
|
||||||
pass
|
pass
|
||||||
if user:
|
if user:
|
||||||
queryset = queryset.filter(Q(user=user) | Q(mention_users=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(
|
status = models.Status.objects.select_subclasses().get(
|
||||||
id=status_id, deleted=False
|
id=status_id, deleted=False
|
||||||
)
|
)
|
||||||
except (ValueError, models.Status.DoesNotExist, models.User.DoesNotExist):
|
except (ValueError, models.Status.DoesNotExist):
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
# the url should have the poster's username in it
|
# the url should have the poster's username in it
|
||||||
|
|
|
@ -14,10 +14,7 @@ from .helpers import get_user_from_username
|
||||||
def follow(request):
|
def follow(request):
|
||||||
"""follow another user, here or abroad"""
|
"""follow another user, here or abroad"""
|
||||||
username = request.POST["user"]
|
username = request.POST["user"]
|
||||||
try:
|
to_follow = get_user_from_username(request.user, username)
|
||||||
to_follow = get_user_from_username(request.user, username)
|
|
||||||
except models.User.DoesNotExist:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
models.UserFollowRequest.objects.create(
|
models.UserFollowRequest.objects.create(
|
||||||
|
@ -35,10 +32,7 @@ def follow(request):
|
||||||
def unfollow(request):
|
def unfollow(request):
|
||||||
"""unfollow a user"""
|
"""unfollow a user"""
|
||||||
username = request.POST["user"]
|
username = request.POST["user"]
|
||||||
try:
|
to_unfollow = get_user_from_username(request.user, username)
|
||||||
to_unfollow = get_user_from_username(request.user, username)
|
|
||||||
except models.User.DoesNotExist:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
models.UserFollows.objects.get(
|
models.UserFollows.objects.get(
|
||||||
|
@ -63,10 +57,7 @@ def unfollow(request):
|
||||||
def accept_follow_request(request):
|
def accept_follow_request(request):
|
||||||
"""a user accepts a follow request"""
|
"""a user accepts a follow request"""
|
||||||
username = request.POST["user"]
|
username = request.POST["user"]
|
||||||
try:
|
requester = get_user_from_username(request.user, username)
|
||||||
requester = get_user_from_username(request.user, username)
|
|
||||||
except models.User.DoesNotExist:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
follow_request = models.UserFollowRequest.objects.get(
|
follow_request = models.UserFollowRequest.objects.get(
|
||||||
|
@ -85,10 +76,7 @@ def accept_follow_request(request):
|
||||||
def delete_follow_request(request):
|
def delete_follow_request(request):
|
||||||
"""a user rejects a follow request"""
|
"""a user rejects a follow request"""
|
||||||
username = request.POST["user"]
|
username = request.POST["user"]
|
||||||
try:
|
requester = get_user_from_username(request.user, username)
|
||||||
requester = get_user_from_username(request.user, username)
|
|
||||||
except models.User.DoesNotExist:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
follow_request = models.UserFollowRequest.objects.get(
|
follow_request = models.UserFollowRequest.objects.get(
|
||||||
|
|
|
@ -3,6 +3,7 @@ import re
|
||||||
from requests import HTTPError
|
from requests import HTTPError
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db.models import Count, Max, Q
|
from django.db.models import Count, Max, Q
|
||||||
|
from django.http import Http404
|
||||||
|
|
||||||
from bookwyrm import activitypub, models
|
from bookwyrm import activitypub, models
|
||||||
from bookwyrm.connectors import ConnectorException, get_data
|
from bookwyrm.connectors import ConnectorException, get_data
|
||||||
|
@ -12,11 +13,17 @@ from bookwyrm.utils import regex
|
||||||
|
|
||||||
def get_user_from_username(viewer, username):
|
def get_user_from_username(viewer, username):
|
||||||
"""helper function to resolve a localname or a username to a user"""
|
"""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:
|
try:
|
||||||
return models.User.viewer_aware_objects(viewer).get(localname=username)
|
return models.User.viewer_aware_objects(viewer).get(localname=username)
|
||||||
except models.User.DoesNotExist:
|
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)
|
return models.User.viewer_aware_objects(viewer).get(username=username)
|
||||||
|
except models.User.DoesNotExist:
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
|
|
||||||
def is_api_request(request):
|
def is_api_request(request):
|
||||||
|
|
|
@ -25,10 +25,7 @@ class Shelf(View):
|
||||||
|
|
||||||
def get(self, request, username, shelf_identifier=None):
|
def get(self, request, username, shelf_identifier=None):
|
||||||
"""display a shelf"""
|
"""display a shelf"""
|
||||||
try:
|
user = get_user_from_username(request.user, username)
|
||||||
user = get_user_from_username(request.user, username)
|
|
||||||
except models.User.DoesNotExist:
|
|
||||||
return HttpResponseNotFound()
|
|
||||||
|
|
||||||
shelves = privacy_filter(request.user, user.shelf_set)
|
shelves = privacy_filter(request.user, user.shelf_set)
|
||||||
|
|
||||||
|
@ -68,7 +65,7 @@ class Shelf(View):
|
||||||
"books": paginated.get_page(request.GET.get("page")),
|
"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")
|
@method_decorator(login_required, name="dispatch")
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|
|
@ -10,7 +10,8 @@ def get_notification_count(request):
|
||||||
"""any notifications waiting?"""
|
"""any notifications waiting?"""
|
||||||
return JsonResponse(
|
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.contrib.auth.decorators import login_required
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.http import HttpResponseNotFound
|
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -17,7 +16,7 @@ from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
from .helpers import get_user_from_username, is_api_request
|
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
|
# pylint: disable= no-self-use
|
||||||
|
@ -26,14 +25,7 @@ class User(View):
|
||||||
|
|
||||||
def get(self, request, username):
|
def get(self, request, username):
|
||||||
"""profile page for a user"""
|
"""profile page for a user"""
|
||||||
try:
|
user = get_user_from_username(request.user, username)
|
||||||
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):
|
if is_api_request(request):
|
||||||
# we have a json request
|
# we have a json request
|
||||||
|
@ -94,14 +86,7 @@ class Followers(View):
|
||||||
|
|
||||||
def get(self, request, username):
|
def get(self, request, username):
|
||||||
"""list of followers"""
|
"""list of followers"""
|
||||||
try:
|
user = get_user_from_username(request.user, username)
|
||||||
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):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(user.to_followers_activity(**request.GET))
|
return ActivitypubResponse(user.to_followers_activity(**request.GET))
|
||||||
|
@ -110,9 +95,9 @@ class Followers(View):
|
||||||
data = {
|
data = {
|
||||||
"user": user,
|
"user": user,
|
||||||
"is_self": request.user.id == user.id,
|
"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):
|
class Following(View):
|
||||||
|
@ -120,25 +105,18 @@ class Following(View):
|
||||||
|
|
||||||
def get(self, request, username):
|
def get(self, request, username):
|
||||||
"""list of followers"""
|
"""list of followers"""
|
||||||
try:
|
user = get_user_from_username(request.user, username)
|
||||||
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):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(user.to_following_activity(**request.GET))
|
return ActivitypubResponse(user.to_following_activity(**request.GET))
|
||||||
|
|
||||||
paginated = Paginator(user.followers.all(), PAGE_LENGTH)
|
paginated = Paginator(user.following.all(), PAGE_LENGTH)
|
||||||
data = {
|
data = {
|
||||||
"user": user,
|
"user": user,
|
||||||
"is_self": request.user.id == user.id,
|
"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")
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
|
Loading…
Reference in a new issue