mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2025-01-10 17:25:35 +00:00
commit
9555470f2a
23 changed files with 463 additions and 39 deletions
|
@ -16,9 +16,6 @@ Social reading and reviewing, decentralized with ActivityPub
|
|||
## Joining BookWyrm
|
||||
BookWyrm is still a young piece of software, and isn't at the level of stability and feature-richness that you'd find in a production-ready application. But it does what it says on the box! If you'd like to join an instance, you can check out the [instances](https://github.com/mouse-reeve/bookwyrm/blob/main/instances.md) list.
|
||||
|
||||
You can request an invite to https://bookwyrm.social by [email](mailto:mousereeve@riseup.net), [Mastodon direct message](https://friend.camp/@tripofmice), or [Twitter direct message](https://twitter.com/tripofmice).
|
||||
|
||||
|
||||
## Contributing
|
||||
There are many ways you can contribute to this project, regardless of your level of technical expertise.
|
||||
|
||||
|
|
|
@ -1,27 +1,48 @@
|
|||
""" send emails """
|
||||
from django.core.mail import send_mail
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template.loader import get_template
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.tasks import app
|
||||
|
||||
|
||||
def invite_email(invite_request):
|
||||
""" send out an invite code """
|
||||
site = models.SiteSettings.objects.get()
|
||||
data = {
|
||||
"site_name": site.name,
|
||||
"invite_link": invite_request.invite.link,
|
||||
}
|
||||
send_email.delay(invite_request.email, "invite", data)
|
||||
|
||||
|
||||
def password_reset_email(reset_code):
|
||||
""" generate a password reset email """
|
||||
site = models.SiteSettings.get()
|
||||
send_email.delay(
|
||||
reset_code.user.email,
|
||||
"Reset your password on %s" % site.name,
|
||||
"Your password reset link: %s" % reset_code.link,
|
||||
)
|
||||
site = models.SiteSettings.objects.get()
|
||||
data = {
|
||||
"site_name": site.name,
|
||||
"reset_link": reset_code.link,
|
||||
}
|
||||
send_email.delay(reset_code.user.email, "password_reset", data)
|
||||
|
||||
|
||||
@app.task
|
||||
def send_email(recipient, subject, message):
|
||||
def send_email(recipient, message_name, data):
|
||||
""" use a task to send the email """
|
||||
send_mail(
|
||||
subject,
|
||||
message,
|
||||
None, # sender will be the config default
|
||||
[recipient],
|
||||
fail_silently=False,
|
||||
subject = (
|
||||
get_template("email/{}/subject.html".format(message_name)).render(data).strip()
|
||||
)
|
||||
html_content = (
|
||||
get_template("email/{}/html_content.html".format(message_name))
|
||||
.render(data)
|
||||
.strip()
|
||||
)
|
||||
text_content = (
|
||||
get_template("email/{}/text_content.html".format(message_name))
|
||||
.render(data)
|
||||
.strip()
|
||||
)
|
||||
|
||||
email = EmailMultiAlternatives(subject, text_content, None, [recipient])
|
||||
email.attach_alternative(html_content, "text/html")
|
||||
email.send()
|
||||
|
|
|
@ -3,6 +3,7 @@ import datetime
|
|||
from collections import defaultdict
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import ModelForm, PasswordInput, widgets
|
||||
from django.forms.widgets import Textarea
|
||||
from django.utils import timezone
|
||||
|
@ -202,6 +203,19 @@ class ExpiryWidget(widgets.Select):
|
|||
return timezone.now() + interval
|
||||
|
||||
|
||||
class InviteRequestForm(CustomForm):
|
||||
def clean(self):
|
||||
""" make sure the email isn't in use by a registered user """
|
||||
cleaned_data = super().clean()
|
||||
email = cleaned_data.get("email")
|
||||
if email and models.User.objects.filter(email=email).exists():
|
||||
self.add_error("email", _("A user with this email already exists."))
|
||||
|
||||
class Meta:
|
||||
model = models.InviteRequest
|
||||
fields = ["email"]
|
||||
|
||||
|
||||
class CreateInviteForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.SiteInvite
|
||||
|
|
59
bookwyrm/migrations/0056_auto_20210321_0303.py
Normal file
59
bookwyrm/migrations/0056_auto_20210321_0303.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Generated by Django 3.1.6 on 2021-03-21 03:03
|
||||
|
||||
import bookwyrm.models.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0055_auto_20210321_0101"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="sitesettings",
|
||||
name="allow_invite_requests",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="InviteRequest",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_date", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_date", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"remote_id",
|
||||
bookwyrm.models.fields.RemoteIdField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
validators=[bookwyrm.models.fields.validate_remote_id],
|
||||
),
|
||||
),
|
||||
("email", models.EmailField(max_length=255, unique=True)),
|
||||
("invite_sent", models.BooleanField(default=False)),
|
||||
("ignored", models.BooleanField(default=False)),
|
||||
(
|
||||
"invite",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="bookwyrm.siteinvite",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -26,7 +26,7 @@ from .federated_server import FederatedServer
|
|||
|
||||
from .import_job import ImportJob, ImportItem
|
||||
|
||||
from .site import SiteSettings, SiteInvite, PasswordReset
|
||||
from .site import SiteSettings, SiteInvite, PasswordReset, InviteRequest
|
||||
|
||||
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
|
||||
activity_models = {
|
||||
|
|
|
@ -3,10 +3,11 @@ import base64
|
|||
import datetime
|
||||
|
||||
from Crypto import Random
|
||||
from django.db import models
|
||||
from django.db import models, IntegrityError
|
||||
from django.utils import timezone
|
||||
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from .base_model import BookWyrmModel
|
||||
from .user import User
|
||||
|
||||
|
||||
|
@ -24,6 +25,7 @@ class SiteSettings(models.Model):
|
|||
code_of_conduct = models.TextField(default="Add a code of conduct here.")
|
||||
privacy_policy = models.TextField(default="Add a privacy policy here.")
|
||||
allow_registration = models.BooleanField(default=True)
|
||||
allow_invite_requests = models.BooleanField(default=True)
|
||||
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)
|
||||
|
@ -69,6 +71,23 @@ class SiteInvite(models.Model):
|
|||
return "https://{}/invite/{}".format(DOMAIN, self.code)
|
||||
|
||||
|
||||
class InviteRequest(BookWyrmModel):
|
||||
""" prospective users can request an invite """
|
||||
|
||||
email = models.EmailField(max_length=255, unique=True)
|
||||
invite = models.ForeignKey(
|
||||
SiteInvite, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
invite_sent = models.BooleanField(default=False)
|
||||
ignored = models.BooleanField(default=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" don't create a request for a registered email """
|
||||
if User.objects.filter(email=self.email).exists():
|
||||
raise IntegrityError()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
def get_passowrd_reset_expiry():
|
||||
""" give people a limited time to use the link """
|
||||
now = timezone.now()
|
||||
|
|
|
@ -45,9 +45,33 @@
|
|||
<form name="register" method="post" action="/register">
|
||||
{% include 'snippets/register_form.html' %}
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<h2 class="title">{% trans "This instance is closed" %}</h2>
|
||||
<p>{{ site.registration_closed_text | safe}}</p>
|
||||
|
||||
{% if site.allow_invite_requests %}
|
||||
{% if request_received %}
|
||||
<p>
|
||||
{% trans "Thank you! Your request has been received." %}
|
||||
</p>
|
||||
{% else %}
|
||||
<h3>{% trans "Request an Invitation" %}</h3>
|
||||
<form name="invite-request" action="{% url 'invite-request' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="block">
|
||||
<label for="id_request_email" class="label">{% trans "Email address:" %}</label>
|
||||
<input type="email" name="email" maxlength="255" class="input" required="" id="id_request_email">
|
||||
{% for error in request_form.email.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="submit" class="button is-link">{% trans "Submit" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
|
2
bookwyrm/templates/email/invite/html_content.html
Normal file
2
bookwyrm/templates/email/invite/html_content.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
<a href="{{ invite_link }}">{% blocktrans %}Join {{ site_name }}{% endblocktrans %}</a>
|
2
bookwyrm/templates/email/invite/subject.html
Normal file
2
bookwyrm/templates/email/invite/subject.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}You're invited! Join {{ site_name }}{% endblocktrans %}
|
2
bookwyrm/templates/email/invite/text_content.html
Normal file
2
bookwyrm/templates/email/invite/text_content.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Join {{ site_name }}: {{ invite_link }}{% endblocktrans %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Your password reset link is: {{ reset_link }}{% endblocktrans %}
|
2
bookwyrm/templates/email/password_reset/subject.html
Normal file
2
bookwyrm/templates/email/password_reset/subject.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Reset your password on {{ site_name }}{% endblocktrans %}
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
{% blocktrans %}Your password reset link is: {{ reset_link }}{% endblocktrans %}
|
|
@ -15,8 +15,9 @@
|
|||
<h2 class="menu-label">{% trans "Manage Users" %}</h2>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
{% url 'settings-invites' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Invites" %}</a>
|
||||
{% url 'settings-invite-requests' as url %}
|
||||
{% url 'settings-invites' as alt_url %}
|
||||
<a href="{{ url }}"{% if url in request.path or request.path in alt_url %} class="is-active" aria-selected="true"{% endif %}>{% trans "Invites" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% url 'settings-reports' as url %}
|
||||
|
|
88
bookwyrm/templates/settings/manage_invite_requests.html
Normal file
88
bookwyrm/templates/settings/manage_invite_requests.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
{% extends 'settings/admin_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% block header %}{% trans "Invite Requests" %}{% endblock %}
|
||||
{% block panel %}
|
||||
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
{% url 'settings-invite-requests' as url %}
|
||||
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Invite Requests" %}</a>
|
||||
</li>
|
||||
{% url 'settings-invites' as url %}
|
||||
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Invites" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<section class="block">
|
||||
<h2 class="title is-4">
|
||||
{% if ignored %}
|
||||
{% trans "Ignored Invite Requests" %}
|
||||
{% else %}
|
||||
{% trans "Invite Requests" %}
|
||||
{% endif %}
|
||||
</h2>
|
||||
|
||||
<table class="table is-striped">
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "Email" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Action" %}</th>
|
||||
</tr>
|
||||
{% if not requests %}
|
||||
<tr><td colspan="4">{% trans "No requests" %}</td></tr>
|
||||
{% endif %}
|
||||
{% for req in requests %}
|
||||
<tr>
|
||||
<td>{{ req.created_date | naturaltime }}</td>
|
||||
<td>{{ req.email }}</td>
|
||||
<td>
|
||||
{% if req.invite.times_used %}
|
||||
{% trans "Accepted" %}
|
||||
{% elif req.invite %}
|
||||
{% trans "Sent" %}
|
||||
{% else %}
|
||||
{% trans "Requested" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="field is-grouped">
|
||||
<form name="send-invite" method="post" action="{% url 'settings-invite-requests' %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="invite-request" value="{{ req.id }}">
|
||||
{% if not req.invite %}
|
||||
<button type="submit" class="button is-link is-light is-small">{% trans "Send invite" %}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="button is-link is-light is-small">{% trans "Re-send invite" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% if req.invite and not req.invite.times_used %}
|
||||
{# <button class="button is-danger is-light is-small">{% trans "Revoke invite" %}</button> #}
|
||||
{% else %}
|
||||
<form name="ignore-request" method="post" action="{% url 'settings-invite-requests-ignore' %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="invite-request" value="{{ req.id }}">
|
||||
{% if not req.ignored %}
|
||||
<button type="submit" class="button is-danger is-light is-small">{% trans "Ignore" %}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="button is-danger is-light is-small">{% trans "Un-gnore" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% include 'snippets/pagination.html' with page=requests path=request.path %}
|
||||
|
||||
{% if ignored %}
|
||||
<p><a href="{% url 'settings-invite-requests' %}">{% trans "Back to pending requests" %}</a></p>
|
||||
{% else %}
|
||||
<p><a href="{% url 'settings-invite-requests' %}?ignored=True">{% trans "View ignored requests" %}</a></p>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -3,6 +3,20 @@
|
|||
{% block header %}{% trans "Invites" %}{% endblock %}
|
||||
{% load humanize %}
|
||||
{% block panel %}
|
||||
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
{% url 'settings-invite-requests' as url %}
|
||||
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Invite Requests" %}</a>
|
||||
</li>
|
||||
{% url 'settings-invites' as url %}
|
||||
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Invites" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<section class="block">
|
||||
<h2 class="title is-4">{% trans "Generate New Invite" %}</h2>
|
||||
|
||||
|
|
|
@ -79,6 +79,10 @@
|
|||
<label class="label" for="id_allow_registration">{% trans "Allow registration:" %}
|
||||
{{ site_form.allow_registration }}
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="label" for="id_allow_invite_requests">{% trans "Allow invite requests:" %}
|
||||
{{ site_form.allow_invite_requests }}
|
||||
</div>
|
||||
<div class="control">
|
||||
<label class="label" for="id_registration_closed_text">{% trans "Registration closed text:" %}</label>
|
||||
{{ site_form.registration_closed_text }}
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.template.response import TemplateResponse
|
|||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm import views
|
||||
|
||||
|
||||
|
@ -50,3 +50,81 @@ class InviteViews(TestCase):
|
|||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_invite_request(self):
|
||||
""" request to join a server """
|
||||
form = forms.InviteRequestForm()
|
||||
form.data["email"] = "new@user.email"
|
||||
|
||||
view = views.InviteRequest.as_view()
|
||||
request = self.factory.post("", form.data)
|
||||
|
||||
result = view(request)
|
||||
result.render()
|
||||
|
||||
req = models.InviteRequest.objects.get()
|
||||
self.assertEqual(req.email, "new@user.email")
|
||||
|
||||
def test_invite_request_email_taken(self):
|
||||
""" request to join a server with an existing user email """
|
||||
form = forms.InviteRequestForm()
|
||||
form.data["email"] = "mouse@mouse.mouse"
|
||||
|
||||
view = views.InviteRequest.as_view()
|
||||
request = self.factory.post("", form.data)
|
||||
|
||||
result = view(request)
|
||||
result.render()
|
||||
|
||||
# no request created
|
||||
self.assertFalse(models.InviteRequest.objects.exists())
|
||||
|
||||
def test_manage_invite_requests(self):
|
||||
""" there are so many views, this just makes sure it LOADS """
|
||||
view = views.ManageInviteRequests.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
# now with data
|
||||
models.InviteRequest.objects.create(email="fish@example.com")
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_manage_invite_requests_send(self):
|
||||
""" send an invite """
|
||||
req = models.InviteRequest.objects.create(email="fish@example.com")
|
||||
|
||||
view = views.ManageInviteRequests.as_view()
|
||||
request = self.factory.post("", {"invite-request": req.id})
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
with patch("bookwyrm.emailing.send_email.delay") as mock:
|
||||
view(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
req.refresh_from_db()
|
||||
self.assertIsNotNone(req.invite)
|
||||
|
||||
def test_ignore_invite_request(self):
|
||||
""" don't invite that jerk """
|
||||
req = models.InviteRequest.objects.create(email="fish@example.com")
|
||||
|
||||
view = views.ignore_invite_request
|
||||
request = self.factory.post("", {"invite-request": req.id})
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
|
||||
view(request)
|
||||
req.refresh_from_db()
|
||||
self.assertTrue(req.ignored)
|
||||
|
||||
view(request)
|
||||
req.refresh_from_db()
|
||||
self.assertFalse(req.ignored)
|
||||
|
|
|
@ -54,6 +54,19 @@ urlpatterns = [
|
|||
re_path(
|
||||
r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites"
|
||||
),
|
||||
re_path(
|
||||
r"^settings/requests/?$",
|
||||
views.ManageInviteRequests.as_view(),
|
||||
name="settings-invite-requests",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/requests/ignore?$",
|
||||
views.ignore_invite_request,
|
||||
name="settings-invite-requests-ignore",
|
||||
),
|
||||
re_path(
|
||||
r"^invite-request/?$", views.InviteRequest.as_view(), name="invite-request"
|
||||
),
|
||||
re_path(r"^invite/(?P<code>[A-Za-z0-9]+)/?$", views.Invite.as_view()),
|
||||
# moderation
|
||||
re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"),
|
||||
|
|
|
@ -13,7 +13,8 @@ from .goal import Goal, hide_goal
|
|||
from .import_data import Import, ImportStatus
|
||||
from .inbox import Inbox
|
||||
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
||||
from .invite import ManageInvites, Invite
|
||||
from .invite import ManageInvites, Invite, InviteRequest
|
||||
from .invite import ManageInviteRequests, ignore_invite_request
|
||||
from .isbn import Isbn
|
||||
from .landing import About, Home, Discover
|
||||
from .list import Lists, List, Curate, UserLists
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import re
|
||||
from requests import HTTPError
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models import Q
|
||||
from django.db.models import Max, Q
|
||||
|
||||
from bookwyrm import activitypub, models
|
||||
from bookwyrm.connectors import ConnectorException, get_data
|
||||
|
@ -216,3 +216,20 @@ def is_blocked(viewer, user):
|
|||
if viewer.is_authenticated and viewer in user.blocks.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_discover_books():
|
||||
""" list of books for the discover page """
|
||||
return list(
|
||||
set(
|
||||
models.Edition.objects.filter(
|
||||
review__published_date__isnull=False,
|
||||
review__deleted=False,
|
||||
review__user__local=True,
|
||||
review__privacy__in=["public", "unlisted"],
|
||||
)
|
||||
.exclude(cover__exact="")
|
||||
.annotate(Max("review__published_date"))
|
||||
.order_by("-review__published_date__max")[:6]
|
||||
)
|
||||
)
|
||||
|
|
|
@ -6,9 +6,11 @@ from django.shortcuts import get_object_or_404, redirect
|
|||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm import emailing, forms, models
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from . import helpers
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -77,3 +79,74 @@ class Invite(View):
|
|||
return TemplateResponse(request, "invite.html", data)
|
||||
|
||||
# post handling is in views.authentication.Register
|
||||
|
||||
|
||||
class ManageInviteRequests(View):
|
||||
""" grant invites like the benevolent lord you are """
|
||||
|
||||
def get(self, request):
|
||||
""" view a list of requests """
|
||||
ignored = request.GET.get("ignored", False)
|
||||
try:
|
||||
page = int(request.GET.get("page", 1))
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
paginated = Paginator(
|
||||
models.InviteRequest.objects.filter(ignored=ignored).order_by(
|
||||
"-created_date"
|
||||
),
|
||||
PAGE_LENGTH,
|
||||
)
|
||||
|
||||
data = {
|
||||
"ignored": ignored,
|
||||
"requests": paginated.page(page),
|
||||
}
|
||||
return TemplateResponse(request, "settings/manage_invite_requests.html", data)
|
||||
|
||||
def post(self, request):
|
||||
""" send out an invite """
|
||||
invite_request = get_object_or_404(
|
||||
models.InviteRequest, id=request.POST.get("invite-request")
|
||||
)
|
||||
# allows re-sending invites
|
||||
invite_request.invite, _ = models.SiteInvite.objects.get_or_create(
|
||||
use_limit=1,
|
||||
user=request.user,
|
||||
)
|
||||
|
||||
invite_request.save()
|
||||
emailing.invite_email(invite_request)
|
||||
return redirect("settings-invite-requests")
|
||||
|
||||
|
||||
class InviteRequest(View):
|
||||
""" prospective users sign up here """
|
||||
|
||||
def post(self, request):
|
||||
""" create a request """
|
||||
form = forms.InviteRequestForm(request.POST)
|
||||
received = False
|
||||
if form.is_valid():
|
||||
received = True
|
||||
form.save()
|
||||
|
||||
data = {
|
||||
"request_form": form,
|
||||
"request_received": received,
|
||||
"books": helpers.get_discover_books(),
|
||||
}
|
||||
return TemplateResponse(request, "discover/discover.html", data)
|
||||
|
||||
|
||||
@require_POST
|
||||
def ignore_invite_request(request):
|
||||
""" hide an invite request """
|
||||
invite_request = get_object_or_404(
|
||||
models.InviteRequest, id=request.POST.get("invite-request")
|
||||
)
|
||||
|
||||
invite_request.ignored = not invite_request.ignored
|
||||
invite_request.save()
|
||||
return redirect("settings-invite-requests")
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
""" non-interactive pages """
|
||||
from django.db.models import Max
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
from .feed import Feed
|
||||
from . import helpers
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -33,20 +33,9 @@ class Discover(View):
|
|||
|
||||
def get(self, request):
|
||||
""" tiled book activity page """
|
||||
books = (
|
||||
models.Edition.objects.filter(
|
||||
review__published_date__isnull=False,
|
||||
review__deleted=False,
|
||||
review__user__local=True,
|
||||
review__privacy__in=["public", "unlisted"],
|
||||
)
|
||||
.exclude(cover__exact="")
|
||||
.annotate(Max("review__published_date"))
|
||||
.order_by("-review__published_date__max")[:6]
|
||||
)
|
||||
|
||||
data = {
|
||||
"register_form": forms.RegisterForm(),
|
||||
"books": list(set(books)),
|
||||
"request_form": forms.InviteRequestForm(),
|
||||
"books": helpers.get_discover_books(),
|
||||
}
|
||||
return TemplateResponse(request, "discover/discover.html", data)
|
||||
|
|
Loading…
Reference in a new issue