mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-23 01:51:08 +00:00
Merge pull request #1949 from bookwyrm-social/admin-setup
Flow for creating the admin account on a new instance
This commit is contained in:
commit
ebf905e20b
22 changed files with 607 additions and 42 deletions
|
@ -41,7 +41,7 @@ REDIS_BROKER_PASSWORD=redispassword123
|
||||||
|
|
||||||
# Monitoring for celery
|
# Monitoring for celery
|
||||||
FLOWER_PORT=8888
|
FLOWER_PORT=8888
|
||||||
FLOWER_USER=mouse
|
FLOWER_USER=admin
|
||||||
FLOWER_PASSWORD=changeme
|
FLOWER_PASSWORD=changeme
|
||||||
|
|
||||||
# Email config
|
# Email config
|
||||||
|
|
|
@ -54,6 +54,13 @@ class RegisterForm(CustomForm):
|
||||||
help_texts = {f: None for f in fields}
|
help_texts = {f: None for f in fields}
|
||||||
widgets = {"password": PasswordInput()}
|
widgets = {"password": PasswordInput()}
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""Check if the username is taken"""
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
localname = cleaned_data.get("localname").strip()
|
||||||
|
if models.User.objects.filter(localname=localname).first():
|
||||||
|
self.add_error("localname", _("User with this username already exists"))
|
||||||
|
|
||||||
|
|
||||||
class RatingForm(CustomForm):
|
class RatingForm(CustomForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
23
bookwyrm/management/commands/admin_code.py
Normal file
23
bookwyrm/management/commands/admin_code.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
""" Get your admin code to allow install """
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from bookwyrm import models
|
||||||
|
|
||||||
|
|
||||||
|
def get_admin_code():
|
||||||
|
"""get that code"""
|
||||||
|
return models.SiteSettings.objects.get().admin_code
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""command-line options"""
|
||||||
|
|
||||||
|
help = "Gets admin code for configuring BookWyrm"
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"""execute init"""
|
||||||
|
self.stdout.write("*******************************************")
|
||||||
|
self.stdout.write("Use this code to create your admin account:")
|
||||||
|
self.stdout.write(get_admin_code())
|
||||||
|
self.stdout.write("*******************************************")
|
|
@ -10,7 +10,9 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
help = "Generate preview images"
|
help = "Generate preview images"
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
|
"""options for how the command is run"""
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--all",
|
"--all",
|
||||||
"-a",
|
"-a",
|
||||||
|
@ -38,6 +40,7 @@ class Command(BaseCommand):
|
||||||
preview_images.generate_site_preview_image_task.delay()
|
preview_images.generate_site_preview_image_task.delay()
|
||||||
self.stdout.write(" OK 🖼")
|
self.stdout.write(" OK 🖼")
|
||||||
|
|
||||||
|
# pylint: disable=consider-using-f-string
|
||||||
if options["all"]:
|
if options["all"]:
|
||||||
# Users
|
# Users
|
||||||
users = models.User.objects.filter(
|
users = models.User.objects.filter(
|
||||||
|
|
|
@ -120,6 +120,7 @@ def init_settings():
|
||||||
models.SiteSettings.objects.create(
|
models.SiteSettings.objects.create(
|
||||||
support_link="https://www.patreon.com/bookwyrm",
|
support_link="https://www.patreon.com/bookwyrm",
|
||||||
support_title="Patreon",
|
support_title="Patreon",
|
||||||
|
install_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
24
bookwyrm/migrations/0136_auto_20220217_1708.py
Normal file
24
bookwyrm/migrations/0136_auto_20220217_1708.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.2.12 on 2022-02-17 17:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0135_auto_20220217_1624"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="sitesettings",
|
||||||
|
name="admin_code",
|
||||||
|
field=models.CharField(default=uuid.uuid4, max_length=50),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="sitesettings",
|
||||||
|
name="install_mode",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.12 on 2022-02-17 19:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0136_auto_20220217_1708"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="sitesettings",
|
||||||
|
name="allow_registration",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
""" the particulars for this instance of BookWyrm """
|
""" the particulars for this instance of BookWyrm """
|
||||||
import datetime
|
import datetime
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.db import models, IntegrityError
|
from django.db import models, IntegrityError
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
@ -24,6 +25,10 @@ class SiteSettings(models.Model):
|
||||||
instance_description = models.TextField(default="This instance has no description.")
|
instance_description = models.TextField(default="This instance has no description.")
|
||||||
instance_short_description = models.CharField(max_length=255, blank=True, null=True)
|
instance_short_description = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
|
||||||
|
# admin setup options
|
||||||
|
install_mode = models.BooleanField(default=False)
|
||||||
|
admin_code = models.CharField(max_length=50, default=uuid.uuid4)
|
||||||
|
|
||||||
# about page
|
# about page
|
||||||
registration_closed_text = models.TextField(
|
registration_closed_text = models.TextField(
|
||||||
default="We aren't taking new users at this time. You can find an open "
|
default="We aren't taking new users at this time. You can find an open "
|
||||||
|
@ -38,7 +43,7 @@ class SiteSettings(models.Model):
|
||||||
privacy_policy = models.TextField(default="Add a privacy policy here.")
|
privacy_policy = models.TextField(default="Add a privacy policy here.")
|
||||||
|
|
||||||
# registration
|
# registration
|
||||||
allow_registration = models.BooleanField(default=True)
|
allow_registration = models.BooleanField(default=False)
|
||||||
allow_invite_requests = models.BooleanField(default=True)
|
allow_invite_requests = models.BooleanField(default=True)
|
||||||
require_confirm_email = models.BooleanField(default=True)
|
require_confirm_email = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
{% block head_links %}{% endblock %}
|
{% block head_links %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{% block body %}
|
||||||
<nav class="navbar" aria-label="main navigation">
|
<nav class="navbar" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
|
@ -208,11 +209,8 @@
|
||||||
|
|
||||||
<div class="section is-flex-grow-1">
|
<div class="section is-flex-grow-1">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{# almost every view needs to know the user shelves #}
|
|
||||||
{% with request.user.shelf_set.all as user_shelves %}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endwith %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -256,6 +254,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var csrf_token = '{{ csrf_token }}';
|
var csrf_token = '{{ csrf_token }}';
|
||||||
|
|
61
bookwyrm/templates/setup/admin.html
Normal file
61
bookwyrm/templates/setup/admin.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{% extends 'setup/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1 class="title">{% trans "Set up BookWyrm" %}</h1>
|
||||||
|
<div class="subtitle">
|
||||||
|
{% trans "Your account as a user and an admin" %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
|
<div class="block content">
|
||||||
|
<h2 class="title is-4">{% trans "Create your account" %}</h2>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="box has-background-primary-light">
|
||||||
|
<form name="register" method="post" action="{% url 'setup-admin' %}">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_admin_key">
|
||||||
|
{% trans "Admin key:" %}
|
||||||
|
</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="admin_key"
|
||||||
|
class="input"
|
||||||
|
id="id_admin_key"
|
||||||
|
aria-describedby="desc_admin_key"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<p class="help" id="desc_admin_key">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
An admin key was created when you installed BookWyrm.
|
||||||
|
You can get your admin key by running <code>./bw-dev admin_code</code> from the command line on your server.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'snippets/register_form.html' %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
As an admin, you'll be able to configure the instance name and information, and moderate your instance.
|
||||||
|
This means you will have access to private information about your users, and are responsible for responding to reports of bad behavior or spam.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% trans "Once the instance is set up, you can promote other users to moderator or admin roles from the admin panel." %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://docs.joinbookwyrm.com/moderation.html" target="_blank">
|
||||||
|
{% trans "Learn more about moderation" %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
154
bookwyrm/templates/setup/config.html
Normal file
154
bookwyrm/templates/setup/config.html
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
{% extends 'setup/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1 class="title">{% trans "Instance Configuration" %}</h1>
|
||||||
|
<div class="subtitle">
|
||||||
|
{% trans "Make sure everything looks right before proceeding" %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
|
|
||||||
|
<div class="block content">
|
||||||
|
{% if warnings.debug %}
|
||||||
|
<div class="notification is-danger is-flex is-align-items-start">
|
||||||
|
<span class="icon icon-warning is-size-4 pr-3" aria-hidden="true"></span>
|
||||||
|
<span>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You are running BookWyrm in <strong>debug</strong> mode.
|
||||||
|
This should <strong>never</strong> be used in a production environment.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if warnings.invalid_domain %}
|
||||||
|
<div class="notification is-danger is-flex is-align-items-start">
|
||||||
|
<span class="icon icon-warning is-size-4 pr-3" aria-hidden="true"></span>
|
||||||
|
<span>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
Your domain appears to be misconfigured.
|
||||||
|
It should not include protocol or slashes.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if warnings.protocol %}
|
||||||
|
<div class="notification is-danger is-flex is-align-items-start">
|
||||||
|
<span class="icon icon-warning is-size-4 pr-3" aria-hidden="true"></span>
|
||||||
|
<span>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You are running BookWyrm in production mode without https.
|
||||||
|
<strong>USE_HTTPS</strong> should be enabled in production.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-half is-flex is-flex-direction-column">
|
||||||
|
<h2 class="title is-4">{% trans "Settings" %}</h2>
|
||||||
|
<div class="notification is-flex-grow-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Instance domain:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ info.domain }}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Protocol:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{% if info.use_https %}
|
||||||
|
<span class="tag is-success">https</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="tag is-danger">http</span>
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Software version:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ info.version }}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Using S3:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ info.use_s3|yesno }}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-half is-flex is-flex-direction-column">
|
||||||
|
<h2 class="title is-4">{% trans "Display" %}</h2>
|
||||||
|
<div class="notification is-flex-grow-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Default interface language:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ info.language }}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Email sender:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ info.email_sender }}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Enable preview images:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ info.preview_images|yesno }}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||||
|
{% trans "Enable image thumbnails:" %}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{ info.thumbnails|yesno }}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block content">
|
||||||
|
<h2 class="title is-4">{% trans "Does everything look right?" %}</h2>
|
||||||
|
<p class="subtitle help">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
This is your last chance to set your domain and protocol.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-primary" href="{% url 'setup-admin' %}">
|
||||||
|
<span class="icon icon-check" aria-hidden="true"></span>
|
||||||
|
<span>{% trans "Continue" %}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You can change your instance settings in the <code>.env</code> file on your server.
|
||||||
|
{% endblocktrans %}
|
||||||
|
<a href="https://docs.joinbookwyrm.com/installing-in-production.html" target="_blank">
|
||||||
|
{% trans "View installation instructions" %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
37
bookwyrm/templates/setup/layout.html
Normal file
37
bookwyrm/templates/setup/layout.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Instance Setup" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<nav class="navbar" aria-label="main navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand is-flex-grow-1">
|
||||||
|
<span class="navbar-item" href="/">
|
||||||
|
<img class="image logo" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}" alt="{% blocktrans with site_name=site.name %}{{ site_name }} home page{% endblocktrans %}">
|
||||||
|
</span>
|
||||||
|
<div class="navbar-item is-align-items-start pt-5 is-flex-grow-1">
|
||||||
|
{% trans "Installing BookWyrm" %}
|
||||||
|
</div>
|
||||||
|
<div class="navbar-item is-align-items-start pt-5">
|
||||||
|
<a href="https://joinbookwyrm.com/get-involved/#dev-chat" target="_blank">{% trans "Need help?" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="section is-flex-grow-1">
|
||||||
|
<div class="container">
|
||||||
|
<header class="block content">
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
{% block panel %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -3,27 +3,61 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_localname_register">{% trans "Username:" %}</label>
|
<label class="label" for="id_localname_register">{% trans "Username:" %}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input type="text" name="localname" maxlength="150" class="input" required="" id="id_localname_register" value="{% if register_form.localname.value %}{{ register_form.localname.value }}{% endif %}" aria-describedby="desc_localname_register">
|
<input
|
||||||
|
type="text"
|
||||||
|
name="localname"
|
||||||
|
maxlength="150"
|
||||||
|
class="input"
|
||||||
|
required=""
|
||||||
|
id="id_localname_register"
|
||||||
|
value="{% if register_form.localname.value %}{{ register_form.localname.value }}{% endif %}"
|
||||||
|
aria-describedby="desc_localname_register_panel"
|
||||||
|
>
|
||||||
|
<div id="desc_localname_register_panel">
|
||||||
|
<p class="help">
|
||||||
|
{% trans "Choose wisely! Your username cannot be changed." %}
|
||||||
|
</p>
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=register_form.localname.errors id="desc_localname_register" %}
|
{% include 'snippets/form_errors.html' with errors_list=register_form.localname.errors id="desc_localname_register" %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_email_register">{% trans "Email address:" %}</label>
|
<label class="label" for="id_email_register">{% trans "Email address:" %}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input type="email" name="email" maxlength="254" class="input" id="id_email_register" value="{% if register_form.email.value %}{{ register_form.email.value }}{% endif %}" required aria-describedby="desc_email_register">
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
maxlength="254"
|
||||||
|
class="input"
|
||||||
|
id="id_email_register"
|
||||||
|
value="{% if register_form.email.value %}{{ register_form.email.value }}{% endif %}"
|
||||||
|
required
|
||||||
|
aria-describedby="desc_email_register"
|
||||||
|
>
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=register_form.email.errors id="desc_email_register" %}
|
{% include 'snippets/form_errors.html' with errors_list=register_form.email.errors id="desc_email_register" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_password_register">{% trans "Password:" %}</label>
|
<label class="label" for="id_password_register">{% trans "Password:" %}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password_register" aria-describedby="desc_password_register">
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
maxlength="128"
|
||||||
|
class="input"
|
||||||
|
required=""
|
||||||
|
id="id_password_register"
|
||||||
|
aria-describedby="desc_password_register"
|
||||||
|
>
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=register_form.password.errors id="desc_password_register" %}
|
{% include 'snippets/form_errors.html' with errors_list=register_form.password.errors id="desc_password_register" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-primary" type="submit">
|
<button class="button is-primary" type="submit">
|
||||||
|
|
|
@ -37,7 +37,7 @@ class RegisterViews(TestCase):
|
||||||
self.anonymous_user.is_authenticated = False
|
self.anonymous_user.is_authenticated = False
|
||||||
|
|
||||||
self.settings = models.SiteSettings.objects.create(
|
self.settings = models.SiteSettings.objects.create(
|
||||||
id=1, require_confirm_email=False
|
id=1, require_confirm_email=False, allow_registration=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_redirect(self, *_):
|
def test_get_redirect(self, *_):
|
||||||
|
|
79
bookwyrm/tests/views/test_setup.py
Normal file
79
bookwyrm/tests/views/test_setup.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
""" test for app action functionality """
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
|
from bookwyrm import forms, models, views
|
||||||
|
from bookwyrm.tests.validate_html import validate_html
|
||||||
|
|
||||||
|
|
||||||
|
class SetupViews(TestCase):
|
||||||
|
"""activity feed, statuses, dms"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""we need basic test data and mocks"""
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
self.site = models.SiteSettings.objects.create(install_mode=True)
|
||||||
|
|
||||||
|
def test_instance_config_permission_denied(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
self.site.install_mode = False
|
||||||
|
self.site.save()
|
||||||
|
view = views.InstanceConfig.as_view()
|
||||||
|
request = self.factory.get("")
|
||||||
|
with self.assertRaises(PermissionDenied):
|
||||||
|
view(request)
|
||||||
|
|
||||||
|
def test_instance_config(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
view = views.InstanceConfig.as_view()
|
||||||
|
request = self.factory.get("")
|
||||||
|
|
||||||
|
result = view(request)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
validate_html(result.render())
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
def test_create_admin_get(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
view = views.CreateAdmin.as_view()
|
||||||
|
request = self.factory.get("")
|
||||||
|
|
||||||
|
result = view(request)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
validate_html(result.render())
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
def test_create_admin_post(self):
|
||||||
|
"""there are so many views, this just makes sure it LOADS"""
|
||||||
|
self.site.name = "hello"
|
||||||
|
self.site.save()
|
||||||
|
self.assertFalse(self.site.allow_registration)
|
||||||
|
self.assertTrue(self.site.require_confirm_email)
|
||||||
|
self.assertTrue(self.site.install_mode)
|
||||||
|
|
||||||
|
view = views.CreateAdmin.as_view()
|
||||||
|
|
||||||
|
form = forms.RegisterForm()
|
||||||
|
form.data["localname"] = "mouse"
|
||||||
|
form.data["password"] = "mouseword"
|
||||||
|
form.data["email"] = "aaa@bbb.ccc"
|
||||||
|
request = self.factory.post("", form.data)
|
||||||
|
|
||||||
|
with patch("bookwyrm.views.setup.login") as mock:
|
||||||
|
view(request)
|
||||||
|
self.assertTrue(mock.called)
|
||||||
|
|
||||||
|
self.site.refresh_from_db()
|
||||||
|
self.assertFalse(self.site.install_mode)
|
||||||
|
|
||||||
|
user = models.User.objects.get()
|
||||||
|
self.assertTrue(user.is_active)
|
||||||
|
self.assertTrue(user.is_superuser)
|
||||||
|
self.assertTrue(user.is_staff)
|
||||||
|
self.assertTrue(user.shelf_set.exists())
|
|
@ -58,6 +58,9 @@ urlpatterns = [
|
||||||
views.get_unread_status_string,
|
views.get_unread_status_string,
|
||||||
name="stream-updates",
|
name="stream-updates",
|
||||||
),
|
),
|
||||||
|
# instance setup
|
||||||
|
re_path(r"^setup/?$", views.InstanceConfig.as_view(), name="setup"),
|
||||||
|
re_path(r"^setup/admin/?$", views.CreateAdmin.as_view(), name="setup-admin"),
|
||||||
# authentication
|
# authentication
|
||||||
re_path(r"^login/?$", views.Login.as_view(), name="login"),
|
re_path(r"^login/?$", views.Login.as_view(), name="login"),
|
||||||
re_path(r"^login/(?P<confirmed>confirmed)/?$", views.Login.as_view(), name="login"),
|
re_path(r"^login/(?P<confirmed>confirmed)/?$", views.Login.as_view(), name="login"),
|
||||||
|
|
|
@ -113,6 +113,7 @@ from .reading import ReadingStatus
|
||||||
from .report import Report
|
from .report import Report
|
||||||
from .rss_feed import RssFeed
|
from .rss_feed import RssFeed
|
||||||
from .search import Search
|
from .search import Search
|
||||||
|
from .setup import InstanceConfig, CreateAdmin
|
||||||
from .status import CreateStatus, EditStatus, DeleteStatus, update_progress
|
from .status import CreateStatus, EditStatus, DeleteStatus, update_progress
|
||||||
from .status import edit_readthrough
|
from .status import edit_readthrough
|
||||||
from .updates import get_notification_count, get_unread_status_string
|
from .updates import get_notification_count, get_unread_status_string
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
""" non-interactive pages """
|
""" non-interactive pages """
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.views.feed import Feed
|
from bookwyrm.views.feed import Feed
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +16,11 @@ class Home(View):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
feed_view = Feed.as_view()
|
feed_view = Feed.as_view()
|
||||||
return feed_view(request, "home")
|
return feed_view(request, "home")
|
||||||
|
site = models.SiteSettings.objects.get()
|
||||||
|
|
||||||
|
if site.install_mode:
|
||||||
|
return redirect("setup")
|
||||||
|
|
||||||
landing_view = Landing.as_view()
|
landing_view = Landing.as_view()
|
||||||
return landing_view(request)
|
return landing_view(request)
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@ class Register(View):
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""join the server"""
|
"""join the server"""
|
||||||
settings = models.SiteSettings.get()
|
settings = models.SiteSettings.get()
|
||||||
|
# no registration allowed when the site is being installed
|
||||||
|
if settings.install_mode:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
if not settings.allow_registration:
|
if not settings.allow_registration:
|
||||||
invite_code = request.POST.get("invite_code")
|
invite_code = request.POST.get("invite_code")
|
||||||
|
|
||||||
|
@ -38,9 +42,16 @@ class Register(View):
|
||||||
invite = None
|
invite = None
|
||||||
|
|
||||||
form = forms.RegisterForm(request.POST)
|
form = forms.RegisterForm(request.POST)
|
||||||
errors = False
|
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
errors = True
|
data = {
|
||||||
|
"login_form": forms.LoginForm(),
|
||||||
|
"register_form": form,
|
||||||
|
"invite": invite,
|
||||||
|
"valid": invite.valid() if invite else True,
|
||||||
|
}
|
||||||
|
if invite:
|
||||||
|
return TemplateResponse(request, "landing/invite.html", data)
|
||||||
|
return TemplateResponse(request, "landing/login.html", data)
|
||||||
|
|
||||||
localname = form.data["localname"].strip()
|
localname = form.data["localname"].strip()
|
||||||
email = form.data["email"]
|
email = form.data["email"]
|
||||||
|
@ -52,22 +63,6 @@ class Register(View):
|
||||||
# treat this like a successful registration, but don't do anything
|
# treat this like a successful registration, but don't do anything
|
||||||
return redirect("confirm-email")
|
return redirect("confirm-email")
|
||||||
|
|
||||||
# check localname and email uniqueness
|
|
||||||
if models.User.objects.filter(localname=localname).first():
|
|
||||||
form.errors["localname"] = ["User with this username already exists"]
|
|
||||||
errors = True
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
data = {
|
|
||||||
"login_form": forms.LoginForm(),
|
|
||||||
"register_form": form,
|
|
||||||
"invite": invite,
|
|
||||||
"valid": invite.valid() if invite else True,
|
|
||||||
}
|
|
||||||
if invite:
|
|
||||||
return TemplateResponse(request, "landing/invite.html", data)
|
|
||||||
return TemplateResponse(request, "landing/login.html", data)
|
|
||||||
|
|
||||||
username = f"{localname}@{DOMAIN}"
|
username = f"{localname}@{DOMAIN}"
|
||||||
user = models.User.objects.create_user(
|
user = models.User.objects.create_user(
|
||||||
username,
|
username,
|
||||||
|
|
99
bookwyrm/views/setup.py
Normal file
99
bookwyrm/views/setup.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
""" Installation wizard 🧙 """
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.contrib.auth import login
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import transaction
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
from bookwyrm import forms, models
|
||||||
|
from bookwyrm import settings
|
||||||
|
from bookwyrm.utils import regex
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable= no-self-use
|
||||||
|
class InstanceConfig(View):
|
||||||
|
"""make sure the instance looks correct before adding any data"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
"""Check out this cool instance"""
|
||||||
|
# only allow this view when an instance is being configured
|
||||||
|
site = models.SiteSettings.objects.get()
|
||||||
|
if not site.install_mode:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
# check for possible problems with the instance configuration
|
||||||
|
warnings = {}
|
||||||
|
warnings["debug"] = settings.DEBUG
|
||||||
|
warnings["invalid_domain"] = not re.match(rf"^{regex.DOMAIN}$", settings.DOMAIN)
|
||||||
|
warnings["protocol"] = not settings.DEBUG and not settings.USE_HTTPS
|
||||||
|
|
||||||
|
# pylint: disable=line-too-long
|
||||||
|
data = {
|
||||||
|
"warnings": warnings,
|
||||||
|
"info": {
|
||||||
|
"domain": settings.DOMAIN,
|
||||||
|
"version": settings.VERSION,
|
||||||
|
"use_https": settings.USE_HTTPS,
|
||||||
|
"language": settings.LANGUAGE_CODE,
|
||||||
|
"use_s3": settings.USE_S3,
|
||||||
|
"email_sender": f"{settings.EMAIL_SENDER_NAME}@{settings.EMAIL_SENDER_DOMAIN}",
|
||||||
|
"preview_images": settings.ENABLE_PREVIEW_IMAGES,
|
||||||
|
"thumbnails": settings.ENABLE_THUMBNAIL_GENERATION,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return TemplateResponse(request, "setup/config.html", data)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateAdmin(View):
|
||||||
|
"""manage things like the instance name"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
"""Create admin user form"""
|
||||||
|
# only allow this view when an instance is being configured
|
||||||
|
site = models.SiteSettings.objects.get()
|
||||||
|
if not site.install_mode:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
data = {"register_form": forms.RegisterForm()}
|
||||||
|
return TemplateResponse(request, "setup/admin.html", data)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def post(self, request):
|
||||||
|
"""Create that user"""
|
||||||
|
site = models.SiteSettings.objects.get()
|
||||||
|
# you can't create an admin user if you're in config mode
|
||||||
|
if not site.install_mode:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
form = forms.RegisterForm(request.POST)
|
||||||
|
if not form.is_valid():
|
||||||
|
data = {"register_form": form}
|
||||||
|
return TemplateResponse(request, "setup/admin.html", data)
|
||||||
|
|
||||||
|
localname = form.data["localname"].strip()
|
||||||
|
username = f"{localname}@{settings.DOMAIN}"
|
||||||
|
|
||||||
|
user = models.User.objects.create_superuser(
|
||||||
|
username,
|
||||||
|
form.data["email"],
|
||||||
|
form.data["password"],
|
||||||
|
localname=localname,
|
||||||
|
local=True,
|
||||||
|
deactivation_reason=None,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
# Set "admin" role
|
||||||
|
try:
|
||||||
|
user.groups.set(Group.objects.filter(name__in=["admin", "moderator"]))
|
||||||
|
except Group.DoesNotExist:
|
||||||
|
# this should only happen in tests
|
||||||
|
pass
|
||||||
|
|
||||||
|
login(request, user)
|
||||||
|
site.install_mode = False
|
||||||
|
site.save()
|
||||||
|
return redirect("settings-site")
|
38
bw-dev
38
bw-dev
|
@ -21,8 +21,8 @@ function runweb {
|
||||||
docker-compose run --rm web "$@"
|
docker-compose run --rm web "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
function execdb {
|
function rundb {
|
||||||
docker-compose exec db $@
|
docker-compose run --rm db $@
|
||||||
}
|
}
|
||||||
|
|
||||||
function execweb {
|
function execweb {
|
||||||
|
@ -30,12 +30,15 @@ function execweb {
|
||||||
}
|
}
|
||||||
|
|
||||||
function initdb {
|
function initdb {
|
||||||
runweb python manage.py migrate
|
|
||||||
runweb python manage.py initdb "$@"
|
runweb python manage.py initdb "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeitblack {
|
function migrate {
|
||||||
docker-compose run --rm dev-tools black celerywyrm bookwyrm
|
runweb python manage.py migrate "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function admin_code {
|
||||||
|
runweb python manage.py admin_code
|
||||||
}
|
}
|
||||||
|
|
||||||
function awscommand {
|
function awscommand {
|
||||||
|
@ -65,16 +68,17 @@ case "$CMD" in
|
||||||
docker-compose run --rm --service-ports web
|
docker-compose run --rm --service-ports web
|
||||||
;;
|
;;
|
||||||
initdb)
|
initdb)
|
||||||
initdb "$@"
|
initdb "@"
|
||||||
;;
|
;;
|
||||||
resetdb)
|
resetdb)
|
||||||
clean
|
clean
|
||||||
# Start just the DB so no one else is using it
|
# Start just the DB so no one else is using it
|
||||||
docker-compose up --build -d db
|
docker-compose up --build -d db
|
||||||
execdb dropdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
rundb dropdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||||
execdb createdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
rundb createdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||||
# Now start up web so we can run the migrations
|
# Now start up web so we can run the migrations
|
||||||
docker-compose up --build -d web
|
docker-compose up --build -d web
|
||||||
|
migrate
|
||||||
initdb
|
initdb
|
||||||
clean
|
clean
|
||||||
;;
|
;;
|
||||||
|
@ -82,7 +86,7 @@ case "$CMD" in
|
||||||
runweb python manage.py makemigrations "$@"
|
runweb python manage.py makemigrations "$@"
|
||||||
;;
|
;;
|
||||||
migrate)
|
migrate)
|
||||||
runweb python manage.py migrate "$@"
|
migrate "$@"
|
||||||
;;
|
;;
|
||||||
bash)
|
bash)
|
||||||
runweb bash
|
runweb bash
|
||||||
|
@ -91,13 +95,13 @@ case "$CMD" in
|
||||||
runweb python manage.py shell
|
runweb python manage.py shell
|
||||||
;;
|
;;
|
||||||
dbshell)
|
dbshell)
|
||||||
execdb psql -U ${POSTGRES_USER} ${POSTGRES_DB}
|
rundb psql -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||||
;;
|
;;
|
||||||
restart_celery)
|
restart_celery)
|
||||||
docker-compose restart celery_worker
|
docker-compose restart celery_worker
|
||||||
;;
|
;;
|
||||||
pytest)
|
pytest)
|
||||||
runweb pytest --no-cov-on-fail "$@"
|
runweb --no-deps pytest --no-cov-on-fail "$@"
|
||||||
;;
|
;;
|
||||||
collectstatic)
|
collectstatic)
|
||||||
runweb python manage.py collectstatic --no-input
|
runweb python manage.py collectstatic --no-input
|
||||||
|
@ -132,7 +136,7 @@ case "$CMD" in
|
||||||
clean
|
clean
|
||||||
;;
|
;;
|
||||||
black)
|
black)
|
||||||
makeitblack
|
docker-compose run --rm dev-tools black celerywyrm bookwyrm
|
||||||
;;
|
;;
|
||||||
prettier)
|
prettier)
|
||||||
docker-compose run --rm dev-tools npx prettier --write bookwyrm/static/js/*.js
|
docker-compose run --rm dev-tools npx prettier --write bookwyrm/static/js/*.js
|
||||||
|
@ -197,6 +201,15 @@ case "$CMD" in
|
||||||
--endpoint-url ${AWS_S3_ENDPOINT_URL}\
|
--endpoint-url ${AWS_S3_ENDPOINT_URL}\
|
||||||
--cors-configuration file:///bw/$config_file" "$@"
|
--cors-configuration file:///bw/$config_file" "$@"
|
||||||
;;
|
;;
|
||||||
|
admin_code)
|
||||||
|
admin_code
|
||||||
|
;;
|
||||||
|
setup)
|
||||||
|
migrate
|
||||||
|
initdb
|
||||||
|
runweb python manage.py collectstatic --no-input
|
||||||
|
admin_code
|
||||||
|
;;
|
||||||
runweb)
|
runweb)
|
||||||
runweb "$@"
|
runweb "$@"
|
||||||
;;
|
;;
|
||||||
|
@ -225,6 +238,7 @@ case "$CMD" in
|
||||||
echo " stylelint"
|
echo " stylelint"
|
||||||
echo " formatters"
|
echo " formatters"
|
||||||
echo " populate_streams [--stream=<stream name>]"
|
echo " populate_streams [--stream=<stream name>]"
|
||||||
|
echo " populate_lists_streams"
|
||||||
echo " populate_suggestions"
|
echo " populate_suggestions"
|
||||||
echo " generate_thumbnails"
|
echo " generate_thumbnails"
|
||||||
echo " generate_preview_images [--all]"
|
echo " generate_preview_images [--all]"
|
||||||
|
|
|
@ -30,4 +30,6 @@ generate_thumbnails
|
||||||
generate_preview_images
|
generate_preview_images
|
||||||
copy_media_to_s3
|
copy_media_to_s3
|
||||||
set_cors_to_s3
|
set_cors_to_s3
|
||||||
|
setup
|
||||||
|
admin_code
|
||||||
runweb" -o bashdefault -o default bw-dev
|
runweb" -o bashdefault -o default bw-dev
|
||||||
|
|
Loading…
Reference in a new issue