forked from mirrors/bookwyrm
Merge branch 'main' into production
This commit is contained in:
commit
588d2da2c6
29 changed files with 258 additions and 158 deletions
|
@ -4,6 +4,7 @@ from django import forms
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.models.fields import ClearableFileInputWithWarning
|
from bookwyrm.models.fields import ClearableFileInputWithWarning
|
||||||
from .custom_form import CustomForm
|
from .custom_form import CustomForm
|
||||||
|
from .widgets import ArrayWidget, SelectDateWidget, Select
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=missing-class-docstring
|
# pylint: disable=missing-class-docstring
|
||||||
|
@ -14,14 +15,6 @@ class CoverForm(CustomForm):
|
||||||
help_texts = {f: None for f in fields}
|
help_texts = {f: None for f in fields}
|
||||||
|
|
||||||
|
|
||||||
class ArrayWidget(forms.widgets.TextInput):
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
"""get all values for this name"""
|
|
||||||
return [i for i in data.getlist(name) if i]
|
|
||||||
|
|
||||||
|
|
||||||
class EditionForm(CustomForm):
|
class EditionForm(CustomForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Edition
|
model = models.Edition
|
||||||
|
@ -56,16 +49,16 @@ class EditionForm(CustomForm):
|
||||||
"publishers": forms.TextInput(
|
"publishers": forms.TextInput(
|
||||||
attrs={"aria-describedby": "desc_publishers_help desc_publishers"}
|
attrs={"aria-describedby": "desc_publishers_help desc_publishers"}
|
||||||
),
|
),
|
||||||
"first_published_date": forms.SelectDateWidget(
|
"first_published_date": SelectDateWidget(
|
||||||
attrs={"aria-describedby": "desc_first_published_date"}
|
attrs={"aria-describedby": "desc_first_published_date"}
|
||||||
),
|
),
|
||||||
"published_date": forms.SelectDateWidget(
|
"published_date": SelectDateWidget(
|
||||||
attrs={"aria-describedby": "desc_published_date"}
|
attrs={"aria-describedby": "desc_published_date"}
|
||||||
),
|
),
|
||||||
"cover": ClearableFileInputWithWarning(
|
"cover": ClearableFileInputWithWarning(
|
||||||
attrs={"aria-describedby": "desc_cover"}
|
attrs={"aria-describedby": "desc_cover"}
|
||||||
),
|
),
|
||||||
"physical_format": forms.Select(
|
"physical_format": Select(
|
||||||
attrs={"aria-describedby": "desc_physical_format"}
|
attrs={"aria-describedby": "desc_physical_format"}
|
||||||
),
|
),
|
||||||
"physical_format_detail": forms.TextInput(
|
"physical_format_detail": forms.TextInput(
|
||||||
|
|
70
bookwyrm/forms/widgets.py
Normal file
70
bookwyrm/forms/widgets.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
""" using django model forms """
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class ArrayWidget(forms.widgets.TextInput):
|
||||||
|
"""Inputs for postgres array fields"""
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
def value_from_datadict(self, data, files, name):
|
||||||
|
"""get all values for this name"""
|
||||||
|
return [i for i in data.getlist(name) if i]
|
||||||
|
|
||||||
|
|
||||||
|
class Select(forms.Select):
|
||||||
|
"""custom template for select widget"""
|
||||||
|
|
||||||
|
template_name = "widgets/select.html"
|
||||||
|
|
||||||
|
|
||||||
|
class SelectDateWidget(forms.SelectDateWidget):
|
||||||
|
"""
|
||||||
|
A widget that splits date input into two <select> boxes and a numerical year.
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_name = "widgets/addon_multiwidget.html"
|
||||||
|
select_widget = Select
|
||||||
|
|
||||||
|
def get_context(self, name, value, attrs):
|
||||||
|
"""sets individual widgets"""
|
||||||
|
context = super().get_context(name, value, attrs)
|
||||||
|
date_context = {}
|
||||||
|
year_name = self.year_field % name
|
||||||
|
date_context["year"] = forms.NumberInput().get_context(
|
||||||
|
name=year_name,
|
||||||
|
value=context["widget"]["value"]["year"],
|
||||||
|
attrs={
|
||||||
|
**context["widget"]["attrs"],
|
||||||
|
"id": f"id_{year_name}",
|
||||||
|
"class": "input",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
month_choices = list(self.months.items())
|
||||||
|
if not self.is_required:
|
||||||
|
month_choices.insert(0, self.month_none_value)
|
||||||
|
month_name = self.month_field % name
|
||||||
|
date_context["month"] = self.select_widget(
|
||||||
|
attrs, choices=month_choices
|
||||||
|
).get_context(
|
||||||
|
name=month_name,
|
||||||
|
value=context["widget"]["value"]["month"],
|
||||||
|
attrs={**context["widget"]["attrs"], "id": f"id_{month_name}"},
|
||||||
|
)
|
||||||
|
day_choices = [(i, i) for i in range(1, 32)]
|
||||||
|
if not self.is_required:
|
||||||
|
day_choices.insert(0, self.day_none_value)
|
||||||
|
day_name = self.day_field % name
|
||||||
|
date_context["day"] = self.select_widget(
|
||||||
|
attrs,
|
||||||
|
choices=day_choices,
|
||||||
|
).get_context(
|
||||||
|
name=day_name,
|
||||||
|
value=context["widget"]["value"]["day"],
|
||||||
|
attrs={**context["widget"]["attrs"], "id": f"id_{day_name}"},
|
||||||
|
)
|
||||||
|
subwidgets = []
|
||||||
|
for field in self._parse_date_fmt():
|
||||||
|
subwidgets.append(date_context[field]["widget"])
|
||||||
|
context["widget"]["subwidgets"] = subwidgets
|
||||||
|
return context
|
|
@ -129,14 +129,6 @@ button:focus-visible .button-invisible-overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Tooltips
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** States
|
/** States
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -155,8 +155,7 @@
|
||||||
<label class="label" for="id_first_published_date">
|
<label class="label" for="id_first_published_date">
|
||||||
{% trans "First published date:" %}
|
{% trans "First published date:" %}
|
||||||
</label>
|
</label>
|
||||||
<input type="date" name="first_published_date" class="input" id="id_first_published_date"{% if form.first_published_date.value %} value="{{ form.first_published_date.value|date:'Y-m-d' }}"{% endif %} aria-describedby="desc_first_published_date">
|
{{ form.first_published_date }}
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.first_published_date.errors id="desc_first_published_date" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.first_published_date.errors id="desc_first_published_date" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -164,7 +163,7 @@
|
||||||
<label class="label" for="id_published_date">
|
<label class="label" for="id_published_date">
|
||||||
{% trans "Published date:" %}
|
{% trans "Published date:" %}
|
||||||
</label>
|
</label>
|
||||||
<input type="date" name="published_date" class="input" id="id_published_date"{% if form.published_date.value %} value="{{ form.published_date.value|date:'Y-m-d'}}"{% endif %} aria-describedby="desc_published_date">
|
{{ form.published_date }}
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.published_date.errors id="desc_published_date" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.published_date.errors id="desc_published_date" %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -259,9 +258,7 @@
|
||||||
<label class="label" for="id_physical_format">
|
<label class="label" for="id_physical_format">
|
||||||
{% trans "Format:" %}
|
{% trans "Format:" %}
|
||||||
</label>
|
</label>
|
||||||
<div class="select">
|
{{ form.physical_format }}
|
||||||
{{ form.physical_format }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.physical_format.errors id="desc_physical_format" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.physical_format.errors id="desc_physical_format" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% trans "Help" as button_text %}
|
|
||||||
{% include 'snippets/toggle/open_button.html' with text=button_text class="ml-3 is-rounded is-small has-background-body p-0 pb-1" icon="question-circle is-size-6" controls_text=controls_text controls_uid=controls_uid %}
|
|
||||||
|
|
||||||
<aside class="tooltip notification is-hidden transition-y is-pulled-left mb-2" id="{{ controls_text }}{% if controls_uid %}-{{ controls_uid }}{% endif %}">
|
|
||||||
{% trans "Close" as button_text %}
|
|
||||||
{% include 'snippets/toggle/close_button.html' with label=button_text class="delete" nonbutton=True controls_text=controls_text controls_uid=controls_uid %}
|
|
||||||
|
|
||||||
{% block tooltip_content %}{% endblock %}
|
|
||||||
</aside>
|
|
|
@ -29,9 +29,16 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="block">
|
<section class="block">
|
||||||
{% trans "Can't find your code?" as button_text %}
|
<form name="fallback" method="GET" action="{% url 'resend-link' %}" autocomplete="off">
|
||||||
{% include "snippets/toggle/open_button.html" with text=button_text controls_text="resend_form" focus="resend_form_header" %}
|
<button
|
||||||
{% include "confirm_email/resend_form.html" with controls_text="resend_form" %}
|
type="submit"
|
||||||
|
class="button"
|
||||||
|
data-modal-open="resend_form"
|
||||||
|
>
|
||||||
|
{% trans "Can't find your code?" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% include "confirm_email/resend_modal.html" with id="resend_form" %}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
10
bookwyrm/templates/confirm_email/resend.html
Normal file
10
bookwyrm/templates/confirm_email/resend.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends 'landing/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans "Resend confirmation link" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include "confirm_email/resend_modal.html" with active=True static=True id="resend-modal" %}
|
||||||
|
{% endblock %}
|
|
@ -1,20 +0,0 @@
|
||||||
{% extends "components/inline_form.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block header %}
|
|
||||||
{% trans "Resend confirmation link" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<form name="resend" method="post" action="{% url 'resend-link' %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="email">{% trans "Email address:" %}</label>
|
|
||||||
<div class="control">
|
|
||||||
<input type="text" name="email" class="input" required id="email">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-link">{% trans "Resend link" %}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
44
bookwyrm/templates/confirm_email/resend_modal.html
Normal file
44
bookwyrm/templates/confirm_email/resend_modal.html
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{% extends "components/modal.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
{% trans "Resend confirmation link" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-open %}
|
||||||
|
<form name="resend" method="post" action="{% url 'resend-link' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="email">{% trans "Email address:" %}</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
class="input"
|
||||||
|
id="email"
|
||||||
|
aria-described-by="id_email_errors"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
{% if error %}
|
||||||
|
<div id="id_email_errors">
|
||||||
|
<p class="help is-danger">
|
||||||
|
{% trans "No user matching this email address found." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link">{% trans "Resend link" %}</button>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-close %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -5,7 +5,19 @@
|
||||||
<section class="block">
|
<section class="block">
|
||||||
<h2 class="title is-4">{% trans "Your Books" %}</h2>
|
<h2 class="title is-4">{% trans "Your Books" %}</h2>
|
||||||
{% if not suggested_books %}
|
{% if not suggested_books %}
|
||||||
<p>{% trans "There are no books here right now! Try searching for a book to get started" %}</p>
|
|
||||||
|
<div class="content">
|
||||||
|
<p>{% trans "There are no books here right now! Try searching for a book to get started" %}</p>
|
||||||
|
|
||||||
|
<div class="box has-background-link-light">
|
||||||
|
<p>{% trans "Do you have book data from another service like GoodReads?" %}</p>
|
||||||
|
<a href="{% url 'import' %}">
|
||||||
|
<span class="icon icon-list" aria-hidden="true"></span>
|
||||||
|
{% trans "Import your reading history" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with active_book=request.GET.book %}
|
{% with active_book=request.GET.book %}
|
||||||
<div class="tab-group">
|
<div class="tab-group">
|
||||||
|
|
|
@ -14,28 +14,32 @@
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label is-pulled-left" for="source">
|
<label class="label" for="source">
|
||||||
{% trans "Data source:" %}
|
{% trans "Data source:" %}
|
||||||
</label>
|
</label>
|
||||||
{% include 'import/tooltip.html' with controls_text="goodreads-tooltip" %}
|
|
||||||
|
<div class="select">
|
||||||
|
<select name="source" id="source" aria-describedby="desc_source">
|
||||||
|
<option value="Goodreads" {% if current == 'Goodreads' %}selected{% endif %}>
|
||||||
|
Goodreads (CSV)
|
||||||
|
</option>
|
||||||
|
<option value="Storygraph" {% if current == 'Storygraph' %}selected{% endif %}>
|
||||||
|
Storygraph (CSV)
|
||||||
|
</option>
|
||||||
|
<option value="LibraryThing" {% if current == 'LibraryThing' %}selected{% endif %}>
|
||||||
|
LibraryThing (TSV)
|
||||||
|
</option>
|
||||||
|
<option value="OpenLibrary" {% if current == 'OpenLibrary' %}selected{% endif %}>
|
||||||
|
OpenLibrary (CSV)
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="help" id="desc_source">
|
||||||
|
{% trans 'You can download your Goodreads data from the <a href="https://www.goodreads.com/review/import" target="_blank" rel="noopener noreferrer">Import/Export page</a> of your Goodreads account.' %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="select block">
|
|
||||||
<select name="source" id="source">
|
|
||||||
<option value="Goodreads" {% if current == 'Goodreads' %}selected{% endif %}>
|
|
||||||
Goodreads (CSV)
|
|
||||||
</option>
|
|
||||||
<option value="Storygraph" {% if current == 'Storygraph' %}selected{% endif %}>
|
|
||||||
Storygraph (CSV)
|
|
||||||
</option>
|
|
||||||
<option value="LibraryThing" {% if current == 'LibraryThing' %}selected{% endif %}>
|
|
||||||
LibraryThing (TSV)
|
|
||||||
</option>
|
|
||||||
<option value="OpenLibrary" {% if current == 'OpenLibrary' %}selected{% endif %}>
|
|
||||||
OpenLibrary (CSV)
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_csv_file">{% trans "Data file:" %}</label>
|
<label class="label" for="id_csv_file">{% trans "Data file:" %}</label>
|
||||||
{{ import_form.csv_file }}
|
{{ import_form.csv_file }}
|
||||||
|
@ -63,7 +67,7 @@
|
||||||
<div class="content block">
|
<div class="content block">
|
||||||
<h2 class="title">{% trans "Recent Imports" %}</h2>
|
<h2 class="title">{% trans "Recent Imports" %}</h2>
|
||||||
{% if not jobs %}
|
{% if not jobs %}
|
||||||
<p>{% trans "No recent imports" %}</p>
|
<p><em>{% trans "No recent imports" %}</em></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for job in jobs %}
|
{% for job in jobs %}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% extends 'components/tooltip.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block tooltip_content %}
|
|
||||||
|
|
||||||
{% trans 'You can download your Goodreads data from the <a href="https://www.goodreads.com/review/import" target="_blank" rel="noopener noreferrer">Import/Export page</a> of your Goodreads account.' %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
<div class="block table-container">
|
<div class="block table-container">
|
||||||
<table class="table is-striped">
|
<table class="table is-striped is-fullwidth">
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% url 'settings-announcements' as url %}
|
{% url 'settings-announcements' as url %}
|
||||||
|
|
|
@ -154,7 +154,7 @@
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="table is-striped">
|
<table class="table is-striped is-fullwidth">
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<label for="id_string_match">{% trans "String match" %}</label>
|
<label for="id_string_match">{% trans "String match" %}</label>
|
||||||
|
|
|
@ -10,26 +10,26 @@
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
|
||||||
<div class="columns block has-text-centered is-mobile is-multiline">
|
<div class="columns block has-text-centered is-mobile is-multiline">
|
||||||
<div class="column is-3-desktop is-6-mobile">
|
<div class="column is-3-desktop is-6-mobile is-flex">
|
||||||
<div class="notification">
|
<div class="notification is-flex-grow-1">
|
||||||
<h3>{% trans "Total users" %}</h3>
|
<h3>{% trans "Total users" %}</h3>
|
||||||
<p class="title is-5">{{ users|intcomma }}</p>
|
<p class="title is-5">{{ users|intcomma }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-3-desktop is-6-mobile">
|
<div class="column is-3-desktop is-6-mobil is-flexe">
|
||||||
<div class="notification">
|
<div class="notification is-flex-grow-1">
|
||||||
<h3>{% trans "Active this month" %}</h3>
|
<h3>{% trans "Active this month" %}</h3>
|
||||||
<p class="title is-5">{{ active_users|intcomma }}</p>
|
<p class="title is-5">{{ active_users|intcomma }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-3-desktop is-6-mobile">
|
<div class="column is-3-desktop is-6-mobile is-flex">
|
||||||
<div class="notification">
|
<div class="notification is-flex-grow-1">
|
||||||
<h3>{% trans "Statuses" %}</h3>
|
<h3>{% trans "Statuses" %}</h3>
|
||||||
<p class="title is-5">{{ statuses|intcomma }}</p>
|
<p class="title is-5">{{ statuses|intcomma }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-3-desktop is-6-mobile">
|
<div class="column is-3-desktop is-6-mobile is-flex">
|
||||||
<div class="notification">
|
<div class="notification is-flex-grow-1">
|
||||||
<h3>{% trans "Works" %}</h3>
|
<h3>{% trans "Works" %}</h3>
|
||||||
<p class="title is-5">{{ works|intcomma }}</p>
|
<p class="title is-5">{{ works|intcomma }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,8 +38,8 @@
|
||||||
|
|
||||||
<div class="columns block is-multiline">
|
<div class="columns block is-multiline">
|
||||||
{% if reports %}
|
{% if reports %}
|
||||||
<div class="column">
|
<div class="column is-flex">
|
||||||
<a href="{% url 'settings-reports' %}" class="notification is-warning is-block">
|
<a href="{% url 'settings-reports' %}" class="notification is-warning is-block is-flex-grow-1">
|
||||||
{% blocktrans trimmed count counter=reports with display_count=reports|intcomma %}
|
{% blocktrans trimmed count counter=reports with display_count=reports|intcomma %}
|
||||||
{{ display_count }} open report
|
{{ display_count }} open report
|
||||||
{% plural %}
|
{% plural %}
|
||||||
|
@ -50,8 +50,8 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if pending_domains %}
|
{% if pending_domains %}
|
||||||
<div class="column">
|
<div class="column is-flex">
|
||||||
<a href="{% url 'settings-link-domain' %}" class="notification is-primary is-block">
|
<a href="{% url 'settings-link-domain' %}" class="notification is-primary is-block is-flex-grow-1">
|
||||||
{% blocktrans trimmed count counter=pending_domains with display_count=pending_domains|intcomma %}
|
{% blocktrans trimmed count counter=pending_domains with display_count=pending_domains|intcomma %}
|
||||||
{{ display_count }} domain needs review
|
{{ display_count }} domain needs review
|
||||||
{% plural %}
|
{% plural %}
|
||||||
|
@ -62,8 +62,8 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not site.allow_registration and site.allow_invite_requests and invite_requests %}
|
{% if not site.allow_registration and site.allow_invite_requests and invite_requests %}
|
||||||
<div class="column">
|
<div class="column is-flex">
|
||||||
<a href="{% url 'settings-invite-requests' %}" class="notification is-block is-success">
|
<a href="{% url 'settings-invite-requests' %}" class="notification is-block is-success is-flex-grow-1">
|
||||||
{% blocktrans trimmed count counter=invite_requests with display_count=invite_requests|intcomma %}
|
{% blocktrans trimmed count counter=invite_requests with display_count=invite_requests|intcomma %}
|
||||||
{{ display_count }} invite request
|
{{ display_count }} invite request
|
||||||
{% plural %}
|
{% plural %}
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_version %}
|
{% if current_version %}
|
||||||
<div class="column">
|
<div class="column is-flex">
|
||||||
<a href="https://docs.joinbookwyrm.com/updating-your-instance.html" class="notification is-block is-warning" target="_blank">
|
<a href="https://docs.joinbookwyrm.com/updating-your-instance.html" class="notification is-block is-warning is-flex-grow-1" target="_blank">
|
||||||
{% blocktrans trimmed with current=current_version available=available_version %}
|
{% blocktrans trimmed with current=current_version available=available_version %}
|
||||||
An update is available! You're running v{{ current }} and the latest release is {{ available }}.
|
An update is available! You're running v{{ current }} and the latest release is {{ available }}.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table is-striped">
|
<table class="table is-striped is-fullwidth">
|
||||||
<tr>
|
<tr>
|
||||||
{% url 'settings-federation' as url %}
|
{% url 'settings-federation' as url %}
|
||||||
<th>
|
<th>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<input type="text" name="address" maxlength="255" class="input" required="" id="id_address" placeholder="190.0.2.0/24" aria-describedby="desc_address">
|
<input type="text" name="address" maxlength="255" class="input" required="" id="id_address" placeholder="190.0.2.0/24" aria-describedby="desc_address">
|
||||||
|
<p class="help">{% trans "You can block IP ranges using CIDR syntax." %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.address.errors id="desc_address" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.address.errors id="desc_address" %}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{% extends 'components/tooltip.html' %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block tooltip_content %}
|
|
||||||
|
|
||||||
{% trans "You can block IP ranges using CIDR syntax." %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
|
@ -93,7 +93,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
<div class="column">
|
<div class="column is-clipped">
|
||||||
{% block panel %}{% endblock %}
|
{% block panel %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -139,6 +139,13 @@
|
||||||
{% trans "Allow registration" %}
|
{% trans "Allow registration" %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label mb-0" for="id_require_confirm_email">
|
||||||
|
{{ site_form.require_confirm_email }}
|
||||||
|
{% trans "Require users to confirm email address" %}
|
||||||
|
</label>
|
||||||
|
<p class="help" id="desc_require_confirm_email">{% trans "(Recommended if registration is open)" %}</p>
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_allow_invite_requests">
|
<label class="label" for="id_allow_invite_requests">
|
||||||
{{ site_form.allow_invite_requests }}
|
{{ site_form.allow_invite_requests }}
|
||||||
|
@ -157,13 +164,6 @@
|
||||||
{{ site_form.invite_question_text }}
|
{{ site_form.invite_question_text }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
|
||||||
<label class="label mb-0" for="id_require_confirm_email">
|
|
||||||
{{ site_form.require_confirm_email }}
|
|
||||||
{% trans "Require users to confirm email address" %}
|
|
||||||
</label>
|
|
||||||
<p class="help" id="desc_require_confirm_email">{% trans "(Recommended if registration is open)" %}</p>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_registration_closed_text">{% trans "Registration closed text:" %}</label>
|
<label class="label" for="id_registration_closed_text">{% trans "Registration closed text:" %}</label>
|
||||||
{{ site_form.registration_closed_text }}
|
{{ site_form.registration_closed_text }}
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_invite_request_text">{% trans "Invite request text:" %}</label>
|
<label class="label" for="id_invite_request_text">{% trans "Invite request text:" %}</label>
|
||||||
{{ site_form.invite_request_text }}
|
{{ site_form.invite_request_text }}
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=site_form.invite_request_text.errors id="desc_invite_request_text" %}
|
{% include 'snippets/form_errors.html' with errors_list=site_form.invite_request_text.errors id="desc_invite_request_text" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
<section class="block content">
|
<section class="block content">
|
||||||
<h2 class="title is-4">{% trans "Available Themes" %}</h2>
|
<h2 class="title is-4">{% trans "Available Themes" %}</h2>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="table is-striped">
|
<table class="table is-striped is-fullwidth">
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Theme name" %}
|
{% trans "Theme name" %}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-container block">
|
<div class="table-container block">
|
||||||
<table class="table is-striped">
|
<table class="table is-striped is-fullwidth">
|
||||||
<tr>
|
<tr>
|
||||||
{% url 'settings-users' as url %}
|
{% url 'settings-users' as url %}
|
||||||
<th>
|
<th>
|
||||||
|
|
9
bookwyrm/templates/widgets/addon_multiwidget.html
Normal file
9
bookwyrm/templates/widgets/addon_multiwidget.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{% spaceless %}
|
||||||
|
<div class="field has-addons">
|
||||||
|
{% for widget in widget.subwidgets %}
|
||||||
|
<div class="control{% if forloop.last %} is-expanded{% endif %}">
|
||||||
|
{% include widget.template_name %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endspaceless %}
|
10
bookwyrm/templates/widgets/select.html
Normal file
10
bookwyrm/templates/widgets/select.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<div class="select">
|
||||||
|
<select
|
||||||
|
name="{{ widget.name }}"
|
||||||
|
{% include "django/forms/widgets/attrs.html" %}
|
||||||
|
>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
|
||||||
|
<optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
|
||||||
|
{% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
|
||||||
|
</optgroup>{% endif %}{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
|
@ -360,10 +360,17 @@ class RegisterViews(TestCase):
|
||||||
result = view(request)
|
result = view(request)
|
||||||
validate_html(result.render())
|
validate_html(result.render())
|
||||||
|
|
||||||
def test_resend_link(self, *_):
|
def test_resend_link_get(self, *_):
|
||||||
|
"""try again"""
|
||||||
|
request = self.factory.get("")
|
||||||
|
request.user = self.anonymous_user
|
||||||
|
result = views.ResendConfirmEmail.as_view()(request)
|
||||||
|
validate_html(result.render())
|
||||||
|
|
||||||
|
def test_resend_link_post(self, *_):
|
||||||
"""try again"""
|
"""try again"""
|
||||||
request = self.factory.post("", {"email": "mouse@mouse.com"})
|
request = self.factory.post("", {"email": "mouse@mouse.com"})
|
||||||
request.user = self.anonymous_user
|
request.user = self.anonymous_user
|
||||||
with patch("bookwyrm.emailing.send_email.delay") as mock:
|
with patch("bookwyrm.emailing.send_email.delay") as mock:
|
||||||
views.resend_link(request)
|
views.ResendConfirmEmail.as_view()(request)
|
||||||
self.assertEqual(mock.call_count, 1)
|
self.assertEqual(mock.call_count, 1)
|
||||||
|
|
|
@ -71,7 +71,7 @@ urlpatterns = [
|
||||||
views.ConfirmEmailCode.as_view(),
|
views.ConfirmEmailCode.as_view(),
|
||||||
name="confirm-email-code",
|
name="confirm-email-code",
|
||||||
),
|
),
|
||||||
re_path(r"^resend-link/?$", views.resend_link, name="resend-link"),
|
re_path(r"^resend-link/?$", views.ResendConfirmEmail.as_view(), name="resend-link"),
|
||||||
re_path(r"^logout/?$", views.Logout.as_view(), name="logout"),
|
re_path(r"^logout/?$", views.Logout.as_view(), name="logout"),
|
||||||
re_path(
|
re_path(
|
||||||
r"^password-reset/?$",
|
r"^password-reset/?$",
|
||||||
|
|
|
@ -52,7 +52,8 @@ from .books.links import BookFileLinks, AddFileLink, delete_link
|
||||||
from .landing.about import about, privacy, conduct
|
from .landing.about import about, privacy, conduct
|
||||||
from .landing.landing import Home, Landing
|
from .landing.landing import Home, Landing
|
||||||
from .landing.login import Login, Logout
|
from .landing.login import Login, Logout
|
||||||
from .landing.register import Register, ConfirmEmail, ConfirmEmailCode, resend_link
|
from .landing.register import Register
|
||||||
|
from .landing.register import ConfirmEmail, ConfirmEmailCode, ResendConfirmEmail
|
||||||
from .landing.password import PasswordResetRequest, PasswordReset
|
from .landing.password import PasswordResetRequest, PasswordReset
|
||||||
|
|
||||||
# shelves
|
# shelves
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
""" the good stuff! the books! """
|
""" the good stuff! the books! """
|
||||||
from re import sub, findall
|
from re import sub, findall
|
||||||
from dateutil.parser import parse as dateparse
|
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.contrib.postgres.search import SearchRank, SearchVector
|
from django.contrib.postgres.search import SearchRank, SearchVector
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.datastructures import MultiValueDictKeyError
|
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
@ -52,7 +50,6 @@ class EditBook(View):
|
||||||
|
|
||||||
# either of the above cases requires additional confirmation
|
# either of the above cases requires additional confirmation
|
||||||
if data.get("add_author"):
|
if data.get("add_author"):
|
||||||
data = copy_form(data)
|
|
||||||
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
||||||
|
|
||||||
remove_authors = request.POST.getlist("remove_authors")
|
remove_authors = request.POST.getlist("remove_authors")
|
||||||
|
@ -118,7 +115,6 @@ class CreateBook(View):
|
||||||
|
|
||||||
# go to confirm mode
|
# go to confirm mode
|
||||||
if not parent_work_id or data.get("add_author"):
|
if not parent_work_id or data.get("add_author"):
|
||||||
data = copy_form(data)
|
|
||||||
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
@ -139,21 +135,6 @@ class CreateBook(View):
|
||||||
return redirect(f"/book/{book.id}")
|
return redirect(f"/book/{book.id}")
|
||||||
|
|
||||||
|
|
||||||
def copy_form(data):
|
|
||||||
"""helper to re-create the date fields in the form"""
|
|
||||||
formcopy = data["form"].data.copy()
|
|
||||||
try:
|
|
||||||
formcopy["first_published_date"] = dateparse(formcopy["first_published_date"])
|
|
||||||
except (MultiValueDictKeyError, ValueError):
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
formcopy["published_date"] = dateparse(formcopy["published_date"])
|
|
||||||
except (MultiValueDictKeyError, ValueError):
|
|
||||||
pass
|
|
||||||
data["form"].data = formcopy
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def add_authors(request, data):
|
def add_authors(request, data):
|
||||||
"""helper for adding authors"""
|
"""helper for adding authors"""
|
||||||
add_author = [author for author in request.POST.getlist("add_author") if author]
|
add_author = [author for author in request.POST.getlist("add_author") if author]
|
||||||
|
|
|
@ -5,7 +5,6 @@ from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.http import require_POST
|
|
||||||
from django.views.decorators.debug import sensitive_variables, sensitive_post_parameters
|
from django.views.decorators.debug import sensitive_variables, sensitive_post_parameters
|
||||||
|
|
||||||
from bookwyrm import emailing, forms, models
|
from bookwyrm import emailing, forms, models
|
||||||
|
@ -129,12 +128,22 @@ class ConfirmEmail(View):
|
||||||
return ConfirmEmailCode().get(request, code)
|
return ConfirmEmailCode().get(request, code)
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
class ResendConfirmEmail(View):
|
||||||
def resend_link(request):
|
"""you probably didn't get the email because celery is slow but you can try this"""
|
||||||
"""resend confirmation link"""
|
|
||||||
email = request.POST.get("email")
|
def get(self, request, error=False):
|
||||||
user = get_object_or_404(models.User, email=email)
|
"""resend link landing page"""
|
||||||
emailing.email_confirmation_email(user)
|
return TemplateResponse(request, "confirm_email/resend.html", {"error": error})
|
||||||
return TemplateResponse(
|
|
||||||
request, "confirm_email/confirm_email.html", {"valid": True}
|
def post(self, request):
|
||||||
)
|
"""resend confirmation link"""
|
||||||
|
email = request.POST.get("email")
|
||||||
|
try:
|
||||||
|
user = models.User.objects.get(email=email)
|
||||||
|
except models.User.DoesNotExist:
|
||||||
|
return self.get(request, error=True)
|
||||||
|
|
||||||
|
emailing.email_confirmation_email(user)
|
||||||
|
return TemplateResponse(
|
||||||
|
request, "confirm_email/confirm_email.html", {"valid": True}
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue