# and '.env' into a folder.
# - Run 'docker compose pull'.
# - Run 'docker compose up -d'.
-# - Wait until the webserver has completed startup
-# - Run 'docker compose exec webserver createsuperuser' to create a user.
#
# For more extensive installation and update instructions, refer to the
# and '.env' into a folder.
# - Run 'docker compose pull'.
# - Run 'docker compose up -d'.
-# - Wait until the webserver has completed startup
-# - Run 'docker compose exec webserver createsuperuser' to create a user.
#
# For more extensive installation and update instructions, refer to the
# documentation.
# - Upload 'docker-compose.env' by clicking on 'Load variables from .env file'
# - Modify the environment variables as needed
# - Click 'Deploy the stack' and wait for it to be deployed
-# - Open the list of containers, select paperless_webserver_1
-# - Click 'Console' and then 'Connect' to open the command line inside the container
-# - Run 'createsuperuser' to create a user
-# - Exit the console
#
# For more extensive installation and update instructions, refer to the
# documentation.
# and '.env' into a folder.
# - Run 'docker compose pull'.
# - Run 'docker compose up -d'.
-# - Wait until the webserver has completed startup
-# - Run 'docker compose exec webserver createsuperuser' to create a user.
#
# For more extensive installation and update instructions, refer to the
# documentation.
# and '.env' into a folder.
# - Run 'docker compose pull'.
# - Run 'docker compose up -d'.
-# - Wait until the webserver has completed startup
-# - Run 'docker compose exec webserver createsuperuser' to create a user.
#
# For more extensive installation and update instructions, refer to the
# documentation.
# and '.env' into a folder.
# - Run 'docker compose pull'.
# - Run 'docker compose up -d'.
-# - Wait until the webserver has completed startup
-# - Run 'docker compose exec webserver createsuperuser' to create a user.
#
# For more extensive installation and update instructions, refer to the
# documentation.
# and '.env' into a folder.
# - Run 'docker compose pull'.
# - Run 'docker compose up -d'.
-# - Wait until the webserver has completed startup
-# - Run 'docker compose exec webserver createsuperuser' to create a user.
#
# For more extensive installation and update instructions, refer to the
# documentation.
```shell
prune_audit_logs
```
+
+### Create superuser {#create-superuser}
+
+If you need to create a superuser, use the following command:
+
+```shell
+createsuperuser
+```
$ uv run pre-commit install
```
-6. Apply migrations and create a superuser for your development instance:
+6. Apply migrations and create a superuser (also can be done via the web UI) for your development instance:
```bash
# src/
6. Run `docker compose up -d`. This will create and start the necessary containers.
-7. Wait for the containers to complete their startup. You can monitor the logs using Docker, such as:
-
- ```shell-session
- docker logs --follow webserver
- ```
-
-8. To be able to login, you will need a "superuser". To create it,
- execute the following command:
-
- ```shell-session
- docker compose exec webserver createsuperuser
- ```
-
- or using docker exec from within the container:
-
- ```shell-session
- createsuperuser
- ```
-
- This will guide you through the superuser setup.
-
-9. Congratulations! Your Paperless-ngx instance should now be accessible at `http://127.0.0.1:8000`
- (or similar, depending on your configuration). Use the superuser credentials you have
- created in the previous step to login.
+7. Congratulations! Your Paperless-ngx instance should now be accessible at `http://127.0.0.1:8000`
+ (or similar, depending on your configuration). When you first access the web interface, you will be
+ prompted to create a superuser account.
### Build the Docker image yourself {#docker_build}
dependencies for Postgres or Mariadb. You can select those extras with `--extra <EXTRA>`
or all with `--all-extras`
-9. Go to `/opt/paperless/src`, and execute the following commands:
+9. Go to `/opt/paperless/src`, and execute the following command:
```bash
# This creates the database schema.
sudo -Hu paperless python3 manage.py migrate
-
- # This creates your first paperless user
- sudo -Hu paperless python3 manage.py createsuperuser
```
+ When you first access the web interface you will be prompted to create a superuser account.
+
10. Optional: Test that paperless is working by executing
```bash
from django.conf import settings as django_settings
+from django.contrib.auth.models import User
+from documents.models import Document
from paperless.config import GeneralConfig
"domain": getattr(django_settings, "PAPERLESS_URL", request.get_host()),
"APP_TITLE": app_title,
"APP_LOGO": app_logo,
+ "FIRST_INSTALL": User.objects.exclude(
+ username__in=["consumer", "AnonymousUser"],
+ ).count()
+ == 0
+ and Document.global_objects.count() == 0,
}
{% endblock form_top_content %}
{% block form_content %}
+ {% if FIRST_INSTALL %}
+ <script type="text/javascript">
+ // forward to the signup page if no users exist
+ window.location.href = "{{ signup_url }}";
+ </script>
+ {% endif %}
{% if not DISABLE_REGULAR_LOGIN %}
{% translate "Username" as i18n_username %}
{% translate "Password" as i18n_password %}
{% endblock head_title %}
{% block form_top_content %}
- <p>
- {% blocktrans %}Already have an account? <a href="{{ login_url }}">Sign in</a>{% endblocktrans %}
- </p>
+ {% if not FIRST_INSTALL %}
+ <p>
+ {% blocktrans %}Already have an account? <a href="{{ login_url }}">Sign in</a>{% endblocktrans %}
+ </p>
+ {% endif %}
{% endblock form_top_content %}
{% block form_content %}
+ {% if FIRST_INSTALL %}
+ <p>
+ {% blocktrans %}Note: This is the first user account for this installation and will be granted superuser privileges.{% endblocktrans %}
+ </p>
+ {% endif %}
{% translate "Username" as i18n_username %}
{% translate "Email (optional)" as i18n_email %}
{% translate "Password" as i18n_password1 %}
{% endblock form_content %}
{% block after_form_content %}
- {% load allauth socialaccount %}
- {% get_providers as socialaccount_providers %}
- {% if socialaccount_providers %}
- {% if not DISABLE_REGULAR_LOGIN %}
- <p class="mt-3">{% translate "or sign in via" %}</p>
- {% endif %}
- <ul class="m-0 p-0">
- {% for provider in socialaccount_providers %}
- {% if provider.id == "openid" %}
- {% for brand in provider.get_brands %}
- {% provider_login_url provider openid=brand.openid_url process=process as href %}
- <li class="d-grid mt-3"><a class="btn btn-secondary" href="{{ href }}">{{ brand.name }}</a></li>
- {% endfor %}
- {% else %}
- {% provider_login_url provider process=process scope=scope auth_params=auth_params as href %}
- <li class="d-grid mt-3">
- <form class="d-grid" method="POST" action="{{ href }}">
- {% csrf_token %}
- <button type="submit" class="btn btn-secondary">{{ provider.name }}</button>
- </form>
- </li>
- {% endif %}
- {% endfor %}
- </ul>
- {% endif %}
+ {% if not FIRST_INSTALL %}
+ {% load allauth socialaccount %}
+ {% get_providers as socialaccount_providers %}
+ {% if socialaccount_providers %}
+ {% if not DISABLE_REGULAR_LOGIN %}
+ <p class="mt-3">{% translate "or sign in via" %}</p>
+ {% endif %}
+ <ul class="m-0 p-0">
+ {% for provider in socialaccount_providers %}
+ {% if provider.id == "openid" %}
+ {% for brand in provider.get_brands %}
+ {% provider_login_url provider openid=brand.openid_url process=process as href %}
+ <li class="d-grid mt-3"><a class="btn btn-secondary" href="{{ href }}">{{ brand.name }}</a></li>
+ {% endfor %}
+ {% else %}
+ {% provider_login_url provider process=process scope=scope auth_params=auth_params as href %}
+ <li class="d-grid mt-3">
+ <form class="d-grid" method="POST" action="{{ href }}">
+ {% csrf_token %}
+ <button type="submit" class="btn btn-secondary">{{ provider.name }}</button>
+ </form>
+ </li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ {% endif %}
+ {% endif %}
{% endblock after_form_content %}
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-03-11 13:33-0700\n"
+"POT-Creation-Date: 2025-03-26 21:04-0700\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
msgid "Don't have an account yet? <a href=\"%(signup_url)s\">Sign up</a>"
msgstr ""
-#: documents/templates/account/login.html:19
-#: documents/templates/account/signup.html:15
+#: documents/templates/account/login.html:25
+#: documents/templates/account/signup.html:22
#: documents/templates/socialaccount/signup.html:13
msgid "Username"
msgstr ""
-#: documents/templates/account/login.html:20
-#: documents/templates/account/signup.html:17
+#: documents/templates/account/login.html:26
+#: documents/templates/account/signup.html:24
msgid "Password"
msgstr ""
-#: documents/templates/account/login.html:30
+#: documents/templates/account/login.html:36
#: documents/templates/mfa/authenticate.html:23
msgid "Sign in"
msgstr ""
-#: documents/templates/account/login.html:34
+#: documents/templates/account/login.html:40
msgid "Forgot your password?"
msgstr ""
-#: documents/templates/account/login.html:45
-#: documents/templates/account/signup.html:49
+#: documents/templates/account/login.html:51
+#: documents/templates/account/signup.html:57
msgid "or sign in via"
msgstr ""
msgid "Paperless-ngx sign up"
msgstr ""
-#: documents/templates/account/signup.html:10
+#: documents/templates/account/signup.html:11
#, python-format
msgid "Already have an account? <a href=\"%(login_url)s\">Sign in</a>"
msgstr ""
-#: documents/templates/account/signup.html:16
+#: documents/templates/account/signup.html:19
+msgid ""
+"Note: This is the first user account for this installation and will be "
+"granted superuser privileges."
+msgstr ""
+
+#: documents/templates/account/signup.html:23
#: documents/templates/socialaccount/signup.html:14
msgid "Email (optional)"
msgstr ""
-#: documents/templates/account/signup.html:18
+#: documents/templates/account/signup.html:25
msgid "Password (again)"
msgstr ""
-#: documents/templates/account/signup.html:36
+#: documents/templates/account/signup.html:43
#: documents/templates/socialaccount/signup.html:27
msgid "Sign up"
msgstr ""
msgid "paperless application settings"
msgstr ""
-#: paperless/settings.py:723
+#: paperless/settings.py:722
msgid "English (US)"
msgstr ""
-#: paperless/settings.py:724
+#: paperless/settings.py:723
msgid "Arabic"
msgstr ""
-#: paperless/settings.py:725
+#: paperless/settings.py:724
msgid "Afrikaans"
msgstr ""
-#: paperless/settings.py:726
+#: paperless/settings.py:725
msgid "Belarusian"
msgstr ""
-#: paperless/settings.py:727
+#: paperless/settings.py:726
msgid "Bulgarian"
msgstr ""
-#: paperless/settings.py:728
+#: paperless/settings.py:727
msgid "Catalan"
msgstr ""
-#: paperless/settings.py:729
+#: paperless/settings.py:728
msgid "Czech"
msgstr ""
-#: paperless/settings.py:730
+#: paperless/settings.py:729
msgid "Danish"
msgstr ""
-#: paperless/settings.py:731
+#: paperless/settings.py:730
msgid "German"
msgstr ""
-#: paperless/settings.py:732
+#: paperless/settings.py:731
msgid "Greek"
msgstr ""
-#: paperless/settings.py:733
+#: paperless/settings.py:732
msgid "English (GB)"
msgstr ""
-#: paperless/settings.py:734
+#: paperless/settings.py:733
msgid "Spanish"
msgstr ""
-#: paperless/settings.py:735
+#: paperless/settings.py:734
msgid "Finnish"
msgstr ""
-#: paperless/settings.py:736
+#: paperless/settings.py:735
msgid "French"
msgstr ""
-#: paperless/settings.py:737
+#: paperless/settings.py:736
msgid "Hungarian"
msgstr ""
-#: paperless/settings.py:738
+#: paperless/settings.py:737
msgid "Italian"
msgstr ""
-#: paperless/settings.py:739
+#: paperless/settings.py:738
msgid "Japanese"
msgstr ""
-#: paperless/settings.py:740
+#: paperless/settings.py:739
msgid "Korean"
msgstr ""
-#: paperless/settings.py:741
+#: paperless/settings.py:740
msgid "Luxembourgish"
msgstr ""
-#: paperless/settings.py:742
+#: paperless/settings.py:741
msgid "Norwegian"
msgstr ""
-#: paperless/settings.py:743
+#: paperless/settings.py:742
msgid "Dutch"
msgstr ""
-#: paperless/settings.py:744
+#: paperless/settings.py:743
msgid "Polish"
msgstr ""
-#: paperless/settings.py:745
+#: paperless/settings.py:744
msgid "Portuguese (Brazil)"
msgstr ""
-#: paperless/settings.py:746
+#: paperless/settings.py:745
msgid "Portuguese"
msgstr ""
-#: paperless/settings.py:747
+#: paperless/settings.py:746
msgid "Romanian"
msgstr ""
-#: paperless/settings.py:748
+#: paperless/settings.py:747
msgid "Russian"
msgstr ""
-#: paperless/settings.py:749
+#: paperless/settings.py:748
msgid "Slovak"
msgstr ""
-#: paperless/settings.py:750
+#: paperless/settings.py:749
msgid "Slovenian"
msgstr ""
-#: paperless/settings.py:751
+#: paperless/settings.py:750
msgid "Serbian"
msgstr ""
-#: paperless/settings.py:752
+#: paperless/settings.py:751
msgid "Swedish"
msgstr ""
-#: paperless/settings.py:753
+#: paperless/settings.py:752
msgid "Turkish"
msgstr ""
-#: paperless/settings.py:754
+#: paperless/settings.py:753
msgid "Ukrainian"
msgstr ""
-#: paperless/settings.py:755
+#: paperless/settings.py:754
msgid "Chinese Simplified"
msgstr ""
-#: paperless/settings.py:756
+#: paperless/settings.py:755
msgid "Chinese Traditional"
msgstr ""
from django.forms import ValidationError
from django.urls import reverse
+from documents.models import Document
from paperless.signals import handle_social_account_updated
logger = logging.getLogger("paperless.auth")
Check whether the site is open for signups, which can be
disabled via the ACCOUNT_ALLOW_SIGNUPS setting.
"""
+ if (
+ User.objects.exclude(username__in=["consumer", "AnonymousUser"]).count()
+ == 0
+ and Document.global_objects.count() == 0
+ ):
+ # I.e. a fresh install, allow signups
+ return True
allow_signups = super().is_open_for_signup(request)
# Override with setting, otherwise default to super.
return getattr(settings, "ACCOUNT_ALLOW_SIGNUPS", allow_signups)
Save the user instance. Default groups are assigned to the user, if
specified in the settings.
"""
+
+ if (
+ User.objects.exclude(username__in=["consumer", "AnonymousUser"]).count()
+ == 0
+ and Document.global_objects.count() == 0
+ ):
+ # I.e. a fresh install, make the user a superuser
+ logger.debug(f"Creating initial superuser `{user}`")
+ user.is_superuser = True
+ user.is_staff = True
+
user: User = super().save_user(request, user, form, commit)
group_names: list[str] = settings.ACCOUNT_DEFAULT_GROUPS
if len(group_names) > 0:
def test_is_open_for_signup(self):
adapter = get_adapter()
+ # With no accounts, signups should be allowed
+ self.assertTrue(adapter.is_open_for_signup(None))
+
+ User.objects.create_user("testuser")
+
# Test when ACCOUNT_ALLOW_SIGNUPS is True
settings.ACCOUNT_ALLOW_SIGNUPS = True
self.assertTrue(adapter.is_open_for_signup(None))
self.assertTrue(user.groups.filter(name="group1").exists())
self.assertFalse(user.groups.filter(name="group2").exists())
+ def test_fresh_install_save_creates_superuser(self):
+ adapter = get_adapter()
+ form = mock.Mock(
+ cleaned_data={
+ "username": "testuser",
+ "email": "user@paperless-ngx.com",
+ },
+ )
+ user = adapter.save_user(HttpRequest(), User(), form, commit=True)
+ self.assertTrue(user.is_superuser)
+
+ # Next time, it should not create a superuser
+ form = mock.Mock(
+ cleaned_data={
+ "username": "testuser2",
+ "email": "user2@paperless-ngx.com",
+ },
+ )
+ user2 = adapter.save_user(HttpRequest(), User(), form, commit=True)
+ self.assertFalse(user2.is_superuser)
+
class TestCustomSocialAccountAdapter(TestCase):
def test_is_open_for_signup(self):