mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-23 08:36:32 +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
|
||||
FLOWER_PORT=8888
|
||||
FLOWER_USER=mouse
|
||||
FLOWER_USER=admin
|
||||
FLOWER_PASSWORD=changeme
|
||||
|
||||
# Email config
|
||||
|
|
|
@ -54,6 +54,13 @@ class RegisterForm(CustomForm):
|
|||
help_texts = {f: None for f in fields}
|
||||
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 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"
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
def add_arguments(self, parser):
|
||||
"""options for how the command is run"""
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
"-a",
|
||||
|
@ -38,6 +40,7 @@ class Command(BaseCommand):
|
|||
preview_images.generate_site_preview_image_task.delay()
|
||||
self.stdout.write(" OK 🖼")
|
||||
|
||||
# pylint: disable=consider-using-f-string
|
||||
if options["all"]:
|
||||
# Users
|
||||
users = models.User.objects.filter(
|
||||
|
|
|
@ -120,6 +120,7 @@ def init_settings():
|
|||
models.SiteSettings.objects.create(
|
||||
support_link="https://www.patreon.com/bookwyrm",
|
||||
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 """
|
||||
import datetime
|
||||
from urllib.parse import urljoin
|
||||
import uuid
|
||||
|
||||
from django.db import models, IntegrityError
|
||||
from django.dispatch import receiver
|
||||
|
@ -24,6 +25,10 @@ class SiteSettings(models.Model):
|
|||
instance_description = models.TextField(default="This instance has no description.")
|
||||
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
|
||||
registration_closed_text = models.TextField(
|
||||
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.")
|
||||
|
||||
# registration
|
||||
allow_registration = models.BooleanField(default=True)
|
||||
allow_registration = models.BooleanField(default=False)
|
||||
allow_invite_requests = models.BooleanField(default=True)
|
||||
require_confirm_email = models.BooleanField(default=True)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
{% block head_links %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
<nav class="navbar" aria-label="main navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
|
@ -208,11 +209,8 @@
|
|||
|
||||
<div class="section is-flex-grow-1">
|
||||
<div class="container">
|
||||
{# almost every view needs to know the user shelves #}
|
||||
{% with request.user.shelf_set.all as user_shelves %}
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -256,6 +254,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
||||
<script>
|
||||
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">
|
||||
<label class="label" for="id_localname_register">{% trans "Username:" %}</label>
|
||||
<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">
|
||||
|
||||
{% include 'snippets/form_errors.html' with errors_list=register_form.localname.errors id="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" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="id_email_register">{% trans "Email address:" %}</label>
|
||||
<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" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="id_password_register">{% trans "Password:" %}</label>
|
||||
<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" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="submit">
|
||||
|
|
|
@ -37,7 +37,7 @@ class RegisterViews(TestCase):
|
|||
self.anonymous_user.is_authenticated = False
|
||||
|
||||
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, *_):
|
||||
|
|
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,
|
||||
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
|
||||
re_path(r"^login/?$", 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 .rss_feed import RssFeed
|
||||
from .search import Search
|
||||
from .setup import InstanceConfig, CreateAdmin
|
||||
from .status import CreateStatus, EditStatus, DeleteStatus, update_progress
|
||||
from .status import edit_readthrough
|
||||
from .updates import get_notification_count, get_unread_status_string
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
""" non-interactive pages """
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.views.feed import Feed
|
||||
|
||||
|
||||
|
@ -15,6 +16,11 @@ class Home(View):
|
|||
if request.user.is_authenticated:
|
||||
feed_view = Feed.as_view()
|
||||
return feed_view(request, "home")
|
||||
site = models.SiteSettings.objects.get()
|
||||
|
||||
if site.install_mode:
|
||||
return redirect("setup")
|
||||
|
||||
landing_view = Landing.as_view()
|
||||
return landing_view(request)
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ class Register(View):
|
|||
def post(self, request):
|
||||
"""join the server"""
|
||||
settings = models.SiteSettings.get()
|
||||
# no registration allowed when the site is being installed
|
||||
if settings.install_mode:
|
||||
raise PermissionDenied()
|
||||
|
||||
if not settings.allow_registration:
|
||||
invite_code = request.POST.get("invite_code")
|
||||
|
||||
|
@ -38,9 +42,16 @@ class Register(View):
|
|||
invite = None
|
||||
|
||||
form = forms.RegisterForm(request.POST)
|
||||
errors = False
|
||||
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()
|
||||
email = form.data["email"]
|
||||
|
@ -52,22 +63,6 @@ class Register(View):
|
|||
# treat this like a successful registration, but don't do anything
|
||||
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}"
|
||||
user = models.User.objects.create_user(
|
||||
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 "$@"
|
||||
}
|
||||
|
||||
function execdb {
|
||||
docker-compose exec db $@
|
||||
function rundb {
|
||||
docker-compose run --rm db $@
|
||||
}
|
||||
|
||||
function execweb {
|
||||
|
@ -30,12 +30,15 @@ function execweb {
|
|||
}
|
||||
|
||||
function initdb {
|
||||
runweb python manage.py migrate
|
||||
runweb python manage.py initdb "$@"
|
||||
}
|
||||
|
||||
function makeitblack {
|
||||
docker-compose run --rm dev-tools black celerywyrm bookwyrm
|
||||
function migrate {
|
||||
runweb python manage.py migrate "$@"
|
||||
}
|
||||
|
||||
function admin_code {
|
||||
runweb python manage.py admin_code
|
||||
}
|
||||
|
||||
function awscommand {
|
||||
|
@ -65,16 +68,17 @@ case "$CMD" in
|
|||
docker-compose run --rm --service-ports web
|
||||
;;
|
||||
initdb)
|
||||
initdb "$@"
|
||||
initdb "@"
|
||||
;;
|
||||
resetdb)
|
||||
clean
|
||||
# Start just the DB so no one else is using it
|
||||
docker-compose up --build -d db
|
||||
execdb dropdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||
execdb createdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||
rundb dropdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||
rundb createdb -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||
# Now start up web so we can run the migrations
|
||||
docker-compose up --build -d web
|
||||
migrate
|
||||
initdb
|
||||
clean
|
||||
;;
|
||||
|
@ -82,7 +86,7 @@ case "$CMD" in
|
|||
runweb python manage.py makemigrations "$@"
|
||||
;;
|
||||
migrate)
|
||||
runweb python manage.py migrate "$@"
|
||||
migrate "$@"
|
||||
;;
|
||||
bash)
|
||||
runweb bash
|
||||
|
@ -91,13 +95,13 @@ case "$CMD" in
|
|||
runweb python manage.py shell
|
||||
;;
|
||||
dbshell)
|
||||
execdb psql -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||
rundb psql -U ${POSTGRES_USER} ${POSTGRES_DB}
|
||||
;;
|
||||
restart_celery)
|
||||
docker-compose restart celery_worker
|
||||
;;
|
||||
pytest)
|
||||
runweb pytest --no-cov-on-fail "$@"
|
||||
runweb --no-deps pytest --no-cov-on-fail "$@"
|
||||
;;
|
||||
collectstatic)
|
||||
runweb python manage.py collectstatic --no-input
|
||||
|
@ -132,7 +136,7 @@ case "$CMD" in
|
|||
clean
|
||||
;;
|
||||
black)
|
||||
makeitblack
|
||||
docker-compose run --rm dev-tools black celerywyrm bookwyrm
|
||||
;;
|
||||
prettier)
|
||||
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}\
|
||||
--cors-configuration file:///bw/$config_file" "$@"
|
||||
;;
|
||||
admin_code)
|
||||
admin_code
|
||||
;;
|
||||
setup)
|
||||
migrate
|
||||
initdb
|
||||
runweb python manage.py collectstatic --no-input
|
||||
admin_code
|
||||
;;
|
||||
runweb)
|
||||
runweb "$@"
|
||||
;;
|
||||
|
@ -225,6 +238,7 @@ case "$CMD" in
|
|||
echo " stylelint"
|
||||
echo " formatters"
|
||||
echo " populate_streams [--stream=<stream name>]"
|
||||
echo " populate_lists_streams"
|
||||
echo " populate_suggestions"
|
||||
echo " generate_thumbnails"
|
||||
echo " generate_preview_images [--all]"
|
||||
|
|
|
@ -30,4 +30,6 @@ generate_thumbnails
|
|||
generate_preview_images
|
||||
copy_media_to_s3
|
||||
set_cors_to_s3
|
||||
setup
|
||||
admin_code
|
||||
runweb" -o bashdefault -o default bw-dev
|
||||
|
|
Loading…
Reference in a new issue