Merge branch 'main' into partially-read-shelf

This commit is contained in:
Thomas Versteeg 2022-02-17 16:34:10 +00:00 committed by GitHub
commit d67dac4519
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 426 additions and 1793 deletions

View file

@ -89,3 +89,19 @@ PREVIEW_TEXT_COLOR=#363636
PREVIEW_IMG_WIDTH=1200
PREVIEW_IMG_HEIGHT=630
PREVIEW_DEFAULT_COVER_COLOR=#002549
# Below are example keys if you want to enable automatically
# sending telemetry to an OTLP-compatible service. Many of
# the main monitoring apps have OLTP collectors, including
# NewRelic, DataDog, and Honeycomb.io - consult their
# documentation for setup instructions, and what exactly to
# put below!
#
# Service name is an arbitrary tag that is attached to any
# data sent, used to distinguish different sources. Useful
# for sending prod and dev metrics to the same place and
# keeping them separate, for instance!
OTEL_EXPORTER_OTLP_ENDPOINT= # API endpoint for your provider
OTEL_EXPORTER_OTLP_HEADERS= # Any headers required, usually authentication info
OTEL_SERVICE_NAME= # Service name to identify your app

View file

@ -1,5 +1,5 @@
# @url https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: Lint Frontend
name: Lint Frontend (run `./bw-dev stylelint` to fix css errors)
on:
push:
@ -8,7 +8,7 @@ on:
- '.github/workflows/**'
- 'static/**'
- '.eslintrc'
- '.stylelintrc'
- '.stylelintrc.js'
pull_request:
branches: [ main, ci, frontend ]
@ -22,17 +22,16 @@ jobs:
- uses: actions/checkout@v2
- name: Install modules
run: yarn
run: npm install stylelint stylelint-config-recommended stylelint-config-standard stylelint-order eslint
# See .stylelintignore for files that are not linted.
- name: Run stylelint
run: >
yarn stylelint bookwyrm/static/**/*.css \
--report-needless-disables \
--report-invalid-scope-disables
npx stylelint bookwyrm/static/css/*.css \
--config dev-tools/.stylelintrc.js
# See .eslintignore for files that are not linted.
- name: Run ESLint
run: >
yarn eslint bookwyrm/static \
npx eslint bookwyrm/static \
--ext .js,.jsx,.ts,.tsx

View file

@ -17,8 +17,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install modules
run: npm install .
run: npm install prettier
# See .stylelintignore for files that are not linted.
- name: Run Prettier
run: npx prettier --check bookwyrm/static/js/*.js

4
.gitignore vendored
View file

@ -24,7 +24,9 @@
.idea
#Node tools
/node_modules/
node_modules/
package-lock.json
yarn.lock
#nginx
nginx/default.conf

View file

@ -32,7 +32,15 @@ class BookwyrmConfig(AppConfig):
name = "bookwyrm"
verbose_name = "BookWyrm"
# pylint: disable=no-self-use
def ready(self):
"""set up OTLP and preview image files, if desired"""
if settings.OTEL_EXPORTER_OTLP_ENDPOINT:
# pylint: disable=import-outside-toplevel
from bookwyrm.telemetry import open_telemetry
open_telemetry.instrumentDjango()
if settings.ENABLE_PREVIEW_IMAGES and settings.FONTS:
# Download any fonts that we don't have yet
logger.debug("Downloading fonts..")

View file

@ -7,6 +7,7 @@ from bookwyrm import settings
r = redis.Redis(
host=settings.REDIS_ACTIVITY_HOST,
port=settings.REDIS_ACTIVITY_PORT,
password=settings.REDIS_ACTIVITY_PASSWORD,
db=settings.REDIS_ACTIVITY_DB_INDEX,
)

View file

@ -0,0 +1,29 @@
# Generated by Django 3.2.11 on 2022-02-11 18:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0133_alter_listitem_notes"),
]
operations = [
migrations.AddField(
model_name="announcement",
name="display_type",
field=models.CharField(
choices=[
("white-ter", "None"),
("primary-light", "Primary"),
("success-light", "Success"),
("link-light", "Link"),
("warning-light", "Warning"),
("danger-light", "Danger"),
],
default="white-ter",
max_length=20,
),
),
]

View file

@ -2,10 +2,21 @@
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .base_model import BookWyrmModel
DisplayTypes = [
("white-ter", _("None")),
("primary-light", _("Primary")),
("success-light", _("Success")),
("link-light", _("Link")),
("warning-light", _("Warning")),
("danger-light", _("Danger")),
]
class Announcement(BookWyrmModel):
"""The admin has something to say"""
@ -16,6 +27,13 @@ class Announcement(BookWyrmModel):
start_date = models.DateTimeField(blank=True, null=True)
end_date = models.DateTimeField(blank=True, null=True)
active = models.BooleanField(default=True)
display_type = models.CharField(
max_length=20,
blank=False,
null=False,
choices=DisplayTypes,
default="white-ter",
)
@classmethod
def active_announcements(cls):

View file

@ -67,7 +67,7 @@ SECRET_KEY = env("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool("DEBUG", True)
USE_HTTPS = env.bool("USE_HTTPS", False)
USE_HTTPS = env.bool("USE_HTTPS", not DEBUG)
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", ["*"])
@ -333,3 +333,7 @@ else:
MEDIA_URL = "/images/"
MEDIA_FULL_URL = f"{PROTOCOL}://{DOMAIN}{MEDIA_URL}"
STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}"
OTEL_EXPORTER_OTLP_ENDPOINT = env("OTEL_EXPORTER_OTLP_ENDPOINT", None)
OTEL_EXPORTER_OTLP_HEADERS = env("OTEL_EXPORTER_OTLP_HEADERS", None)
OTEL_SERVICE_NAME = env("OTEL_SERVICE_NAME", None)

View file

@ -319,7 +319,7 @@ details.details-panel summary {
position: relative;
}
details.details-panel summary .details-close {
details summary .details-close {
position: absolute;
right: 0;
top: 0;
@ -327,7 +327,7 @@ details.details-panel summary .details-close {
transition: transform 0.2s ease;
}
details[open].details-panel summary .details-close {
details[open] summary .details-close {
transform: rotate(0deg);
}
@ -496,7 +496,7 @@ details[open].details-panel summary .details-close {
max-height: 100%;
/* Useful when stretching under-sized images. */
image-rendering: optimizeQuality;
image-rendering: optimizequality;
image-rendering: smooth;
}

View file

@ -0,0 +1,22 @@
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
def instrumentDjango():
from opentelemetry.instrumentation.django import DjangoInstrumentor
DjangoInstrumentor().instrument()
def instrumentCelery():
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from celery.signals import worker_process_init
@worker_process_init.connect(weak=False)
def init_celery_tracing(*args, **kwargs):
CeleryInstrumentor().instrument()

View file

@ -6,24 +6,24 @@
{% block content %}
<header class="block content">
<header class="block">
<h1 class="title">
{% blocktrans with title=book|book_title %}
Links for "<em>{{ title }}</em>"
{% endblocktrans %}
</h1>
</header>
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
<ul>
<li><a href="{% url 'book' book.id %}">{{ book|book_title }}</a></li>
<li class="is-active">
<a href="#" aria-current="page">
{% trans "Edit links" %}
</a>
</li>
</ul>
</nav>
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
<ul>
<li><a href="{% url 'book' book.id %}">{{ book|book_title }}</a></li>
<li class="is-active">
<a href="#" aria-current="page">
{% trans "Edit links" %}
</a>
</li>
</ul>
</nav>
</header>
<section class="block content">
<div class="table-container">

View file

@ -52,7 +52,7 @@
<div class="columns is-mobile">
<div class="column is-narrow is-cover">
<a href="{{ item.book.local_path }}" aria-hidden="true">
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-tablet is-align-items-flex-start' size='medium' %}
{% include 'snippets/book_cover.html' with cover_class='is-w-auto is-h-m-mobile is-h-m-tablet is-align-items-flex-start' size='medium' %}
</a>
</div>
@ -80,7 +80,7 @@
<div class="content">
<header>
{% url 'user-feed' item.user|username as user_path %}
{% blocktrans trimmed with username=user.display_name %}
{% blocktrans trimmed with username=item.user.display_name %}
<a href="{{ user_path }}">{{ username }}</a> says:
{% endblocktrans %}
</header>

View file

@ -1,17 +1,20 @@
{% extends 'settings/layout.html' %}
{% load i18n %}{% load humanize %}
{% load i18n %}
{% load humanize %}
{% block title %}{% trans "Announcement" %} - {{ announcement.preview }}{% endblock %}
{% block header %}
{% trans "Announcement" %}
<a href="{% url 'settings-announcements' %}" class="has-text-weight-normal help">{% trans "Back to list" %}</a>
{% endblock %}
{% block edit-button %}
{% trans "Edit Announcement" as button_text %}
<div class="field has-addons">
<div class="control">
{% include 'snippets/toggle/open_button.html' with controls_text="edit_announcement" icon_with_text="pencil" text=button_text focus="edit_announcement_header" %}
<a class="button" href="{% url 'settings-announcements-edit' announcement.id %}">
<span class="icon icon-pencil m-0-mobile" aria-hidden="true"></span>
<span class="is-sr-only-mobile">{% trans "Edit" %}</span>
</a>
</div>
<form class="control" action="{% url 'settings-announcements-delete' announcement.id %}" method="post">
{% csrf_token %}
@ -23,12 +26,20 @@
</div>
{% endblock %}
{% block breadcrumbs %}
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
<ul>
<li><a href="{% url 'settings-announcements' %}">{% trans "Announcements" %}</a></li>
<li class="is-active">
<a href="#" aria-current="page">
{{ announcement.preview|truncatechars:30 }}
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block panel %}
<form name="edit-announcement" method="post" action="{% url 'settings-announcements' announcement.id %}" class="block">
{% include 'settings/announcements/announcement_form.html' with controls_text="edit_announcement" %}
</form>
<div class="block content">
<dl>
<dt class="is-pulled-left mr-5 has-text-weight-bold">{% trans "Visible:" %}</dt>

View file

@ -1,80 +0,0 @@
{% extends 'components/inline_form.html' %}
{% load i18n %}
{% block header %}
{% if announcement %}
{% trans "Edit Announcement" %}
{% else %}
{% trans "Create Announcement" %}
{% endif %}
{% endblock %}
{% block form %}
{% csrf_token %}
<input type="hidden" name="user" value="{{ request.user.id }}">
<p>
<label class="label" for="id_preview">
{% trans "Preview:" %}
</label>
{{ form.preview }}
{% include 'snippets/form_errors.html' with errors_list=form.preview.errors id="desc_preview" %}
</p>
<p>
<label class="label" for="id_content">
{% trans "Content:" %}
</label>
{{ form.content }}
{% include 'snippets/form_errors.html' with errors_list=form.content.errors id="desc_content" %}
</p>
<p>
<label class="label" for="id_event_date">
{% trans "Event date:" %}
</label>
<input type="date" name="event_date" value="{{ form.event_date.value|date:'Y-m-d' }}" class="input" id="id_event_date">
{% include 'snippets/form_errors.html' with errors_list=form.event_date.errors id="desc_event_date" %}
</p>
<hr aria-hidden="true">
<div class="columns">
<div class="column">
<p>
<label class="label" for="id_start_date">
{% trans "Start date:" %}
</label>
<input type="date" name="start_date" class="input" value="{{ form.start_date.value|date:'Y-m-d' }}" id="id_start_date">
{% include 'snippets/form_errors.html' with errors_list=form.start_date.errors id="desc_start_date" %}
</p>
</div>
<div class="column">
<p>
<label class="label" for="id_end_date">
{% trans "End date:" %}
</label>
<input type="date" name="end_date" class="input" id="id_end_date" value="{{ form.end_date.value|date:'Y-m-d' }}">
{% include 'snippets/form_errors.html' with errors_list=form.end_date.errors id="desc_end_date" %}
</p>
</div>
<div class="column is-narrow">
<p>
<label class="label" for="id_active">
{% trans "Active:" %}
</label>
{{ form.active }}
{% include 'snippets/form_errors.html' with errors_list=form.active.errors id="desc_active" %}
</p>
</div>
</div>
<div class="field has-addons">
<div class="control">
<button type="submit" class="button is-primary">
{% trans "Save" %}
</button>
</div>
</div>
{% endblock %}

View file

@ -5,16 +5,15 @@
{% block header %}{% trans "Announcements" %}{% endblock %}
{% block edit-button %}
{% trans "Create Announcement" as button_text %}
{% include 'snippets/toggle/open_button.html' with controls_text="create_announcement" icon_with_text="plus" text=button_text focus="create_announcement_header" %}
<a href="{% url 'settings-announcements-edit' %}">
{% trans "Create Announcement" as text %}
<span class="icon icon-plus" title="{{ text }}" aria-hidden="true"></span>
<span class="is-sr-only-mobile">{{ text }}</span>
</a>
{% endblock %}
{% block panel %}
<form name="create-announcement" method="post" action="{% url 'settings-announcements' %}" class="block">
{% include 'settings/announcements/announcement_form.html' with controls_text="create_announcement" %}
</form>
<div class="block">
<div class="block table-container">
<table class="table is-striped">
<tr>
<th>
@ -38,6 +37,9 @@
{% trans "Status" as text %}
{% include 'snippets/table-sort-header.html' with field="active" sort=sort text=text %}
</th>
<th>
{% trans "Actions" %}
</th>
</tr>
{% for announcement in announcements %}
<tr>
@ -46,6 +48,15 @@
<td>{{ announcement.start_date|naturaltime|default:'' }}</td>
<td>{{ announcement.end_date|naturaltime|default:'' }}</td>
<td>{% if announcement.active %}{% trans "active" %}{% else %}{% trans "inactive" %}{% endif %}</td>
<td>
<form class="control" action="{% url 'settings-announcements-delete' announcement.id %}" method="post">
{% csrf_token %}
<button type="submit" class="button is-danger is-light is-small">
<span class="icon icon-x m-0-mobile" aria-hidden="true"></span>
<span class="is-sr-only-mobile">{% trans "Delete" %}</span>
</button>
</form>
</td>
</tr>
{% endfor %}
{% if not announcements %}

View file

@ -0,0 +1,125 @@
{% extends 'settings/layout.html' %}
{% load i18n %}
{% block header %}
{% if announcement %}
{% trans "Edit Announcement" %}
{% else %}
{% trans "Create Announcement" %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
<ul>
<li><a href="{% url 'settings-announcements' %}">{% trans "Announcements" %}</a></li>
{% if announcement %}
<li>
<a href="{% url 'settings-announcements' announcement.id %}">
{{ announcement.preview|truncatechars:30 }}
</a>
</li>
{% endif %}
<li class="is-active">
<a href="#" aria-current="page">
Edit
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block panel %}
<form
name="edit-announcement"
method="POST"
{% if announcement.id %}
action="{% url 'settings-announcements-edit' announcement.id %}"
{% else %}
action="{% url 'settings-announcements-edit' %}"
{% endif %}
class="block"
>
{% csrf_token %}
<input type="hidden" name="user" value="{{ request.user.id }}">
<h2 class="title is-4">{% trans "Announcement content" %}</h2>
<div class="box">
<p class="field">
<label class="label" for="id_preview">
{% trans "Summary:" %}
</label>
{{ form.preview }}
{% include 'snippets/form_errors.html' with errors_list=form.preview.errors id="desc_preview" %}
</p>
<p class="field">
<label class="label" for="id_content">
{% trans "Details:" %}
</label>
{{ form.content }}
{% include 'snippets/form_errors.html' with errors_list=form.content.errors id="desc_content" %}
</p>
<p class="field">
<label class="label" for="id_event_date">
{% trans "Event date:" %}
</label>
<input type="date" name="event_date" value="{{ form.event_date.value|date:'Y-m-d' }}" class="input" id="id_event_date">
{% include 'snippets/form_errors.html' with errors_list=form.event_date.errors id="desc_event_date" %}
</p>
</div>
<h2 class="title is-4">{% trans "Display settings" %}</h2>
<div class="box">
<div class="columns">
<div class="column">
<p>
<label class="label" for="id_start_date">
{% trans "Start date:" %}
</label>
<input type="date" name="start_date" class="input" value="{{ form.start_date.value|date:'Y-m-d' }}" id="id_start_date">
{% include 'snippets/form_errors.html' with errors_list=form.start_date.errors id="desc_start_date" %}
</p>
</div>
<div class="column">
<p>
<label class="label" for="id_end_date">
{% trans "End date:" %}
</label>
<input type="date" name="end_date" class="input" id="id_end_date" value="{{ form.end_date.value|date:'Y-m-d' }}">
{% include 'snippets/form_errors.html' with errors_list=form.end_date.errors id="desc_end_date" %}
</p>
</div>
<div class="column is-narrow">
<label class="label" for="id_active">
{% trans "Color:" %}
</label>
<div class="select">
{{ form.display_type }}
</div>
{% include 'snippets/form_errors.html' with errors_list=form.active.errors id="desc_display_type" %}
</div>
</div>
<p class="field">
<label class="label" for="id_active">
{% trans "Active:" %}
{{ form.active }}
</label>
{% include 'snippets/form_errors.html' with errors_list=form.active.errors id="desc_active" %}
</p>
</div>
<div class="field has-addons">
<div class="control">
<button type="submit" class="button is-primary">
{% trans "Save" %}
</button>
</div>
</div>
</form>
{% endblock %}

View file

@ -9,6 +9,7 @@
<div class="columns is-mobile">
<div class="column">
<h1 class="title">{% block header %}{% endblock %}</h1>
{% block breadcrumbs %}{% endblock %}
</div>
<div class="column is-narrow">
{% block edit-button %}{% endblock %}
@ -16,6 +17,7 @@
</div>
</header>
<div class="block columns">
<nav class="menu column is-one-quarter">
<h2 class="menu-label">

View file

@ -1,32 +1,29 @@
{% load humanize %}{% load i18n %}{% load utilities %}
{% with announcement.id|uuid as uuid %}
<aside
class="notification mb-1 p-3{% if not admin_mode %} is-hidden{% endif %} transition-y"
class="notification mb-1 p-3{% if not admin_mode %} is-hidden{% endif %} transition-y has-background-{{ announcement.display_type }}"
{% if not admin_mode %}data-hide="hide_announcement_{{ announcement.id }}"{% endif %}
>
<div class="columns mb-0 is-mobile">
<div class="column pb-0">
<details>
<summary>
{% if announcement.event_date %}
<strong>{{ announcement.event_date|naturalday|title }}:</strong>
{% endif %}
{{ announcement.preview }}
</div>
{{ announcement.preview|safe }}
{% if announcement.content %}
<span class="details-close mt-4 mr-4 icon icon-x is-small" aria-hidden></span>
{% endif %}
</summary>
{% if announcement.content %}
<div class="column is-narrow pb-0">
{% trans "Open" as button_text %}
{% include 'snippets/toggle/open_button.html' with text=button_text controls_text="announcement" class="is-small" controls_uid=uuid icon_with_text="arrow-down" %}
{% trans "Close" as button_text %}
{% include 'snippets/toggle/close_button.html' with text=button_text controls_text="announcement" class="is-small" controls_uid=uuid icon_with_text="arrow-up" %}
<div class="mb-2 mt-2" id="announcement_{{ uuid }}">
<div class="box is-shadowless mb-0">
{{ announcement.content|safe }}
</div>
</div>
{% endif %}
</div>
{% if announcement.content %}
<div class="mb-2 mt-2 {% if not pressed %}is-hidden{% endif %}" id="announcement_{{ uuid }}">
<div class="box is-shadowless mb-0">
{{ announcement.content|safe }}
</div>
</div>
{% endif %}
</details>
<div class="is-flex mt-0 help">
<p>{% blocktrans with user_path=announcement.user.local_path username=announcement.user.display_name %}Posted by <a href="{{ user_path }}">{{ username }}</a>{% endblocktrans %}</p>
{% if not admin_mode %}

View file

@ -74,11 +74,12 @@ class AnnouncementViews(TestCase):
def test_create_announcement(self):
"""create a new announcement"""
view = views.Announcements.as_view()
view = views.EditAnnouncement.as_view()
form = forms.AnnouncementForm()
form.data["preview"] = "hi hi"
form.data["start_date"] = "2021-05-20"
form.data["user"] = self.local_user.id
form.data["display_type"] = "warning-light"
request = self.factory.post("", form.data)
request.user = self.local_user
@ -97,11 +98,12 @@ class AnnouncementViews(TestCase):
announcement = models.Announcement.objects.create(
preview="hi", user=self.local_user
)
view = views.Announcement.as_view()
view = views.EditAnnouncement.as_view()
form = forms.AnnouncementForm(instance=announcement)
form.data["preview"] = "hi hi"
form.data["start_date"] = "2021-05-20"
form.data["user"] = self.local_user.id
form.data["display_type"] = "warning-light"
request = self.factory.post("", form.data)
request.user = self.local_user

View file

@ -93,6 +93,16 @@ urlpatterns = [
views.Announcement.as_view(),
name="settings-announcements",
),
re_path(
r"^settings/announcements/create/?$",
views.EditAnnouncement.as_view(),
name="settings-announcements-edit",
),
re_path(
r"^settings/announcements/(?P<announcement_id>\d+)/edit/?$",
views.EditAnnouncement.as_view(),
name="settings-announcements-edit",
),
re_path(
r"^settings/announcements/(?P<announcement_id>\d+)/delete/?$",
views.delete_announcement,

View file

@ -1,6 +1,7 @@
""" make sure all our nice views are available """
# site admin
from .admin.announcements import Announcements, Announcement, delete_announcement
from .admin.announcements import Announcements, Announcement
from .admin.announcements import EditAnnouncement, delete_announcement
from .admin.dashboard import Dashboard
from .admin.federation import Federation, FederatedServer
from .admin.federation import AddFederatedServer, ImportServerBlocklist

View file

@ -45,23 +45,6 @@ class Announcements(View):
request, "settings/announcements/announcements.html", data
)
def post(self, request):
"""edit the site settings"""
form = forms.AnnouncementForm(request.POST)
if form.is_valid():
form.save()
# reset the create form
form = forms.AnnouncementForm()
data = {
"announcements": Paginator(
models.Announcement.objects.order_by("-created_date"), PAGE_LENGTH
).get_page(request.GET.get("page")),
"form": form,
}
return TemplateResponse(
request, "settings/announcements/announcements.html", data
)
@method_decorator(login_required, name="dispatch")
@method_decorator(
@ -76,27 +59,52 @@ class Announcement(View):
announcement = get_object_or_404(models.Announcement, id=announcement_id)
data = {
"announcement": announcement,
"form": forms.AnnouncementForm(instance=announcement),
}
return TemplateResponse(
request, "settings/announcements/announcement.html", data
)
def post(self, request, announcement_id):
"""edit announcement"""
announcement = get_object_or_404(models.Announcement, id=announcement_id)
form = forms.AnnouncementForm(request.POST, instance=announcement)
if form.is_valid():
announcement = form.save()
form = forms.AnnouncementForm(instance=announcement)
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.edit_instance_settings", raise_exception=True),
name="dispatch",
)
class EditAnnouncement(View):
"""Create of edit an announcement"""
def get(self, request, announcement_id=None):
"""announcement forms"""
announcement = None
if announcement_id:
announcement = get_object_or_404(models.Announcement, id=announcement_id)
data = {
"announcement": announcement,
"form": form,
"form": forms.AnnouncementForm(instance=announcement),
}
return TemplateResponse(
request, "settings/announcements/announcement.html", data
request, "settings/announcements/edit_announcement.html", data
)
def post(self, request, announcement_id=None):
"""edit announcement"""
announcement = None
if announcement_id:
announcement = get_object_or_404(models.Announcement, id=announcement_id)
form = forms.AnnouncementForm(request.POST, instance=announcement)
if not form.is_valid():
data = {
"announcement": announcement,
"form": form,
}
return TemplateResponse(
request, "settings/announcements/edit_announcement.html", data
)
announcement = form.save()
return redirect("settings-announcements", announcement.id)
@login_required
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True)

18
bw-dev
View file

@ -35,7 +35,7 @@ function initdb {
}
function makeitblack {
runweb black celerywyrm bookwyrm
docker-compose run --rm dev-tools black celerywyrm bookwyrm
}
function awscommand {
@ -97,7 +97,7 @@ case "$CMD" in
docker-compose restart celery_worker
;;
pytest)
execweb pytest --no-cov-on-fail "$@"
runweb pytest --no-cov-on-fail "$@"
;;
collectstatic)
runweb python manage.py collectstatic --no-input
@ -135,7 +135,17 @@ case "$CMD" in
makeitblack
;;
prettier)
npx prettier --write bookwyrm/static/js/*.js
docker-compose run --rm dev-tools npx prettier --write bookwyrm/static/js/*.js
;;
stylelint)
docker-compose run --rm dev-tools npx stylelint \
bookwyrm/static/css/*.css --fix \
--config dev-tools/.stylelintrc.js
;;
formatters)
docker-compose run --rm dev-tools black celerywyrm bookwyrm && \
npx prettier --write bookwyrm/static/js/*.js && \
npx stylelint bookwyrm/static/css/*.css --fix --config dev-tools/.stylelintrc.js
;;
update)
git pull
@ -212,6 +222,8 @@ case "$CMD" in
echo " clean"
echo " black"
echo " prettier"
echo " stylelint"
echo " formatters"
echo " populate_streams [--stream=<stream name>]"
echo " populate_suggestions"
echo " generate_thumbnails"

13
celerywyrm/apps.py Normal file
View file

@ -0,0 +1,13 @@
from django.apps import AppConfig
from celerywyrm import settings
class CelerywyrmConfig(AppConfig):
name = "celerywyrm"
verbose_name = "BookWyrm Celery"
def ready(self):
if settings.OTEL_EXPORTER_OTLP_ENDPOINT:
from bookwyrm.telemetry import open_telemetry
open_telemetry.instrumentCelery()

View file

@ -1,19 +0,0 @@
#!/usr/bin/env bash
source .env;
if [ "$CERTBOT_INIT" = "true" ]
then
certonly \
--webroot \
--webroot-path=/var/www/certbot \
--email ${EMAIL} \
--agree-tos \
--no-eff-email \
-d ${DOMAIN} \
-d www.${DOMAIN}
else
renew \
--webroot \
--webroot-path \
/var/www/certbot
fi

View file

@ -21,6 +21,8 @@ build
clean
black
prettier
stylelint
formatters
populate_streams
populate_lists_streams
populate_suggestions

14
dev-tools/Dockerfile Normal file
View file

@ -0,0 +1,14 @@
FROM python:3.9
ENV PYTHONUNBUFFERED 1
RUN mkdir /app
WORKDIR /app
COPY package.json requirements.txt .stylelintrc.js .stylelintignore /app/
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y curl
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash -
RUN apt-get install -y nodejs
RUN npm install .

View file

@ -3,15 +3,16 @@
"watch:static": "yarn watch \"./bw-dev collectstatic\" bookwyrm/static/**"
},
"devDependencies": {
"eslint": "^7.23.0",
"eslint": "^8.9.0",
"prettier": "2.5.1",
"stylelint": "^14.2.0",
"stylelint-config-standard": "^24.0.0",
"stylelint-order": "^5.0.0",
"stylelint": "^14.5.0",
"watch": "^0.13.0"
},
"dependencies": {
"merge": "2.1.1",
"postcss": "8.2.13"
"postcss": "^8.4.6",
"stylelint-config-recommended": "^7.0.0",
"stylelint-config-standard": "^25.0.0",
"stylelint-order": "^5.0.0"
}
}

View file

@ -0,0 +1 @@
black==21.4b2

View file

@ -39,25 +39,23 @@ services:
redis_activity:
image: redis
command: redis-server --requirepass ${REDIS_ACTIVITY_PASSWORD} --appendonly yes --port ${REDIS_ACTIVITY_PORT}
env_file: .env
networks:
- main
restart: on-failure
volumes:
- ./redis.conf:/etc/redis/redis.conf
- redis_activity_data:/data
redis_broker:
image: redis
command: redis-server --requirepass ${REDIS_BROKER_PASSWORD} --appendonly yes --port ${REDIS_BROKER_PORT}
env_file: .env
ports:
- 6379
networks:
- main
restart: on-failure
redis_broker:
image: redis
command: redis-server --requirepass ${REDIS_BROKER_PASSWORD} --appendonly yes --port ${REDIS_BROKER_PORT}
volumes:
- ./redis.conf:/etc/redis/redis.conf
- redis_broker_data:/data
env_file: .env
networks:
- main
restart: on-failure
celery_worker:
env_file: .env
build: .
@ -86,6 +84,11 @@ services:
- db
- redis_broker
restart: on-failure
dev-tools:
build: dev-tools
env_file: .env
volumes:
- .:/app
volumes:
pgdata:
static_volume:

View file

@ -1,6 +1,6 @@
celery==5.2.2
colorthief==0.2.1
Django==3.2.11
Django==3.2.12
django-imagekit==4.1.0
django-model-utils==4.0.0
environs==9.3.4
@ -18,9 +18,13 @@ pytz>=2021.1
boto3==1.17.88
django-storages==1.11.1
django-redis==5.2.0
opentelemetry-api==1.8.0
opentelemetry-sdk==1.8.0
opentelemetry-exporter-otlp-proto-grpc==1.8.0
opentelemetry-instrumentation-django==0.27b0
opentelemetry-instrumentation-celery==0.27b0
# Dev
black==21.4b2
pytest-django==4.1.0
pytest==6.1.2
pytest-cov==2.10.1

1583
yarn.lock

File diff suppressed because it is too large Load diff