mirror of
https://github.com/jointakahe/takahe.git
synced 2024-11-22 07:10:59 +00:00
Deployment re-jiggling
This commit is contained in:
parent
81de10b70c
commit
8019311490
14 changed files with 162 additions and 84 deletions
3
Makefile
3
Makefile
|
@ -2,3 +2,6 @@
|
||||||
|
|
||||||
image:
|
image:
|
||||||
docker build -t takahe -f docker/Dockerfile .
|
docker build -t takahe -f docker/Dockerfile .
|
||||||
|
|
||||||
|
docs:
|
||||||
|
cd docs/ && make html
|
||||||
|
|
|
@ -168,6 +168,8 @@ class Config(models.Model):
|
||||||
identity_max_per_user: int = 5
|
identity_max_per_user: int = 5
|
||||||
identity_max_age: int = 24 * 60 * 60
|
identity_max_age: int = 24 * 60 * 60
|
||||||
|
|
||||||
|
restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"
|
||||||
|
|
||||||
class UserOptions(pydantic.BaseModel):
|
class UserOptions(pydantic.BaseModel):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,29 +1,19 @@
|
||||||
# Build stage
|
FROM python:3.11.0-slim-buster
|
||||||
|
|
||||||
FROM python:3.11.0-buster as builder
|
RUN apt-get update && apt-get -y install libpq-dev python3-dev build-essential
|
||||||
|
|
||||||
RUN mkdir -p /takahe
|
|
||||||
RUN python -m venv /takahe/.venv
|
|
||||||
RUN apt-get update && apt-get -y install libpq-dev python3-dev
|
|
||||||
|
|
||||||
WORKDIR /takahe
|
|
||||||
|
|
||||||
COPY requirements.txt requirements.txt
|
COPY requirements.txt requirements.txt
|
||||||
|
|
||||||
RUN . /takahe/.venv/bin/activate \
|
RUN pip3 install --upgrade pip \
|
||||||
&& pip install --upgrade pip \
|
&& pip3 install --upgrade -r requirements.txt
|
||||||
&& pip install --upgrade -r requirements.txt
|
|
||||||
|
|
||||||
# Final image stage
|
|
||||||
|
|
||||||
FROM python:3.11.0-slim-buster
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y libpq5
|
|
||||||
|
|
||||||
COPY --from=builder /takahe /takahe
|
|
||||||
COPY . /takahe
|
COPY . /takahe
|
||||||
|
|
||||||
WORKDIR /takahe
|
WORKDIR /takahe
|
||||||
|
|
||||||
|
# We use development here to skip settings checks
|
||||||
|
RUN DJANGO_SETTINGS_MODULE=takahe.settings.development python3 manage.py collectstatic
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
CMD ["/takahe/docker/start.sh"]
|
CMD ["sh", "/takahe/docker/start.sh"]
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
. /takahe/.venv/bin/activate
|
python3 manage.py migrate
|
||||||
|
|
||||||
python manage.py migrate
|
exec gunicorn takahe.wsgi:application -w 8 -b 0.0.0.0:8000
|
||||||
|
|
||||||
exec gunicorn takahe.asgi:application -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
|
|
||||||
|
|
|
@ -29,6 +29,9 @@ be provided from the first boot.
|
||||||
* ``PGHOST``, ``PGPORT``, ``PGUSER``, ``PGDATABASE``, and ``PGPASSWORD`` are the
|
* ``PGHOST``, ``PGPORT``, ``PGUSER``, ``PGDATABASE``, and ``PGPASSWORD`` are the
|
||||||
standard PostgreSQL environment variables for configuring your database.
|
standard PostgreSQL environment variables for configuring your database.
|
||||||
|
|
||||||
|
* ``TAKAHE_SECRET_KEY`` must be a fixed, random value (it's used for internal
|
||||||
|
cryptography). Don't change this unless you want to invalidate all sessions.
|
||||||
|
|
||||||
* ``TAKAHE_MEDIA_BACKEND`` must be one of ``local``, ``s3`` or ``gcs``.
|
* ``TAKAHE_MEDIA_BACKEND`` must be one of ``local``, ``s3`` or ``gcs``.
|
||||||
|
|
||||||
* If it is set to ``local``, you must also provide ``TAKAHE_MEDIA_ROOT``,
|
* If it is set to ``local``, you must also provide ``TAKAHE_MEDIA_ROOT``,
|
||||||
|
@ -36,7 +39,8 @@ be provided from the first boot.
|
||||||
fully-qualified URL prefix that serves that directory.
|
fully-qualified URL prefix that serves that directory.
|
||||||
|
|
||||||
* If it is set to ``gcs``, you must also provide ``TAKAHE_MEDIA_BUCKET``,
|
* If it is set to ``gcs``, you must also provide ``TAKAHE_MEDIA_BUCKET``,
|
||||||
the name of the bucket to store files in.
|
the name of the bucket to store files in. The bucket must be publically
|
||||||
|
readable and have "uniform access control" enabled.
|
||||||
|
|
||||||
* If it is set to ``s3``, you must also provide ``TAKAHE_MEDIA_BUCKET``,
|
* If it is set to ``s3``, you must also provide ``TAKAHE_MEDIA_BUCKET``,
|
||||||
the name of the bucket to store files in.
|
the name of the bucket to store files in.
|
||||||
|
@ -60,6 +64,36 @@ be provided from the first boot.
|
||||||
be automatically promoted to administrator when it signs up. You only need
|
be automatically promoted to administrator when it signs up. You only need
|
||||||
this for initial setup, and can unset it after that if you like.
|
this for initial setup, and can unset it after that if you like.
|
||||||
|
|
||||||
|
* ``TAKAHE_STATOR_TOKEN`` should be a random string that you are using to
|
||||||
|
protect the stator (task runner) endpoint. You'll use this value later.
|
||||||
|
|
||||||
|
* If your installation is behind a HTTPS endpoint that is proxying it, set
|
||||||
|
``TAKAHE_SECURE_HEADER`` to the header name used to signify that HTTPS is
|
||||||
|
being used (usually ``X-Forwarded-Proto``)
|
||||||
|
|
||||||
|
* If you want to receive emails about internal site errors, set
|
||||||
|
``TAKAHE_ERROR_EMAILS`` to a comma-separated list of email addresses that
|
||||||
|
should get them.
|
||||||
|
|
||||||
|
|
||||||
|
Setting Up Task Runners
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Takahe is designed to not require a continuously-running background worker;
|
||||||
|
instead, you can trigger the "Stator Runner" (our internal task system) either
|
||||||
|
via a periodic admin command or via a periodic hit to a URL (which is useful
|
||||||
|
if you are on "serverless" hosting that does not allow background tasks).
|
||||||
|
|
||||||
|
To use the URL method, configure something to hit
|
||||||
|
``/.stator/runner/?token=ABCDEF`` every 60 seconds. You can do this less often
|
||||||
|
if you don't mind delays in content and profiles being fetched, or more often
|
||||||
|
if you are under increased load. The value of the token should be the same
|
||||||
|
as what you set for ``TAKAHE_STATOR_TOKEN``.
|
||||||
|
|
||||||
|
Alternatively, you can set up ``python manage.py runstator`` to run in the
|
||||||
|
Docker image with the same time interval. We still recommend setting
|
||||||
|
``TAKAHE_STATOR_TOKEN`` in this case so nobody else can trigger it from a URL.
|
||||||
|
|
||||||
|
|
||||||
Making An Admin Account
|
Making An Admin Account
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -74,3 +108,13 @@ admin account.
|
||||||
If your email settings have a problem and you don't get the email, don't worry;
|
If your email settings have a problem and you don't get the email, don't worry;
|
||||||
fix them and then follow the "reset my password" flow on the login screen, and
|
fix them and then follow the "reset my password" flow on the login screen, and
|
||||||
you'll get another password reset email that you can use.
|
you'll get another password reset email that you can use.
|
||||||
|
|
||||||
|
|
||||||
|
Adding A Domain
|
||||||
|
---------------
|
||||||
|
|
||||||
|
When you login you'll be greeted with the "make an identity" screen, but you
|
||||||
|
won't be able to as you will have no domains yet.
|
||||||
|
|
||||||
|
You should navigate directly to ``/admin/domains/`` and make one, and then
|
||||||
|
you will be able to create an identity.
|
||||||
|
|
|
@ -12,3 +12,4 @@ bleach~=5.0.1
|
||||||
pydantic~=1.10.2
|
pydantic~=1.10.2
|
||||||
django-htmx~=1.13.0
|
django-htmx~=1.13.0
|
||||||
django-storages[google,boto3]~=1.13.1
|
django-storages[google,boto3]~=1.13.1
|
||||||
|
whitenoise~=6.2.0
|
||||||
|
|
|
@ -646,6 +646,10 @@ h1.identity small {
|
||||||
margin: -10px 0 0 0;
|
margin: -10px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.system-note {
|
.system-note {
|
||||||
background: var(--color-bg-menu);
|
background: var(--color-bg-menu);
|
||||||
color: var(--color-text-dull);
|
color: var(--color-text-dull);
|
||||||
|
|
|
@ -19,9 +19,9 @@ class StatorRunner:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
models: List[Type[StatorModel]],
|
models: List[Type[StatorModel]],
|
||||||
concurrency: int = 30,
|
concurrency: int = 50,
|
||||||
concurrency_per_model: int = 5,
|
concurrency_per_model: int = 10,
|
||||||
run_period: int = 30,
|
run_period: int = 60,
|
||||||
wait_period: int = 30,
|
wait_period: int = 30,
|
||||||
):
|
):
|
||||||
self.models = models
|
self.models = models
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from django.http import HttpResponse
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponse, HttpResponseForbidden
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
|
from stator.models import StatorModel
|
||||||
from stator.runner import StatorRunner
|
from stator.runner import StatorRunner
|
||||||
from users.models import Follow
|
|
||||||
|
|
||||||
|
|
||||||
class RequestRunner(View):
|
class RequestRunner(View):
|
||||||
|
@ -12,6 +13,11 @@ class RequestRunner(View):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def get(self, request):
|
async def get(self, request):
|
||||||
runner = StatorRunner([Follow])
|
# Check the token, if supplied
|
||||||
|
if settings.STATOR_TOKEN:
|
||||||
|
if request.GET.get("token") != settings.STATOR_TOKEN:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
# Run on all models
|
||||||
|
runner = StatorRunner(StatorModel.subclasses)
|
||||||
handled = await runner.run()
|
handled = await runner.run()
|
||||||
return HttpResponse(f"Handled {handled}")
|
return HttpResponse(f"Handled {handled}")
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
@ -23,6 +22,7 @@ INSTALLED_APPS = [
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"whitenoise.middleware.WhiteNoiseMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
@ -109,49 +109,10 @@ STATICFILES_DIRS = [
|
||||||
BASE_DIR / "static",
|
BASE_DIR / "static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / "static-collected"
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
### User-configurable options, pulled from the environment ###
|
AUTO_ADMIN_EMAIL: Optional[str] = None
|
||||||
|
|
||||||
MAIN_DOMAIN = os.environ["TAKAHE_MAIN_DOMAIN"]
|
STATOR_TOKEN: Optional[str] = None
|
||||||
if "/" in MAIN_DOMAIN:
|
|
||||||
print("TAKAHE_MAIN_DOMAIN should be just the domain name - no https:// or path")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if os.environ.get("TAKAHE_EMAIL_CONSOLE_ONLY"):
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
|
||||||
EMAIL_FROM = "test@example.com"
|
|
||||||
else:
|
|
||||||
EMAIL_FROM = os.environ["TAKAHE_EMAIL_FROM"]
|
|
||||||
if "TAKAHE_EMAIL_SENDGRID_KEY" in os.environ:
|
|
||||||
EMAIL_HOST = "smtp.sendgrid.net"
|
|
||||||
EMAIL_PORT = 587
|
|
||||||
EMAIL_HOST_USER: Optional[str] = "apikey"
|
|
||||||
EMAIL_HOST_PASSWORD: Optional[str] = os.environ["TAKAHE_EMAIL_SENDGRID_KEY"]
|
|
||||||
EMAIL_USE_TLS = True
|
|
||||||
else:
|
|
||||||
EMAIL_HOST = os.environ["TAKAHE_EMAIL_HOST"]
|
|
||||||
EMAIL_PORT = int(os.environ["TAKAHE_EMAIL_PORT"])
|
|
||||||
EMAIL_HOST_USER = os.environ.get("TAKAHE_EMAIL_USER")
|
|
||||||
EMAIL_HOST_PASSWORD = os.environ.get("TAKAHE_EMAIL_PASSWORD")
|
|
||||||
EMAIL_USE_SSL = EMAIL_PORT == 465
|
|
||||||
EMAIL_USE_TLS = EMAIL_PORT == 587
|
|
||||||
|
|
||||||
AUTO_ADMIN_EMAIL = os.environ.get("TAKAHE_AUTO_ADMIN_EMAIL")
|
|
||||||
|
|
||||||
# Set up media storage
|
|
||||||
MEDIA_BACKEND = os.environ.get("TAKAHE_MEDIA_BACKEND", None)
|
|
||||||
if MEDIA_BACKEND == "local":
|
|
||||||
# Note that this MUST be a fully qualified URL in production
|
|
||||||
MEDIA_URL = os.environ.get("TAKAHE_MEDIA_URL", "/media/")
|
|
||||||
MEDIA_ROOT = os.environ.get("TAKAHE_MEDIA_ROOT", BASE_DIR / "media")
|
|
||||||
elif MEDIA_BACKEND == "gcs":
|
|
||||||
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
|
|
||||||
GS_BUCKET_NAME = os.environ["TAKAHE_MEDIA_BUCKET"]
|
|
||||||
elif MEDIA_BACKEND == "s3":
|
|
||||||
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
|
||||||
AWS_STORAGE_BUCKET_NAME = os.environ["TAKAHE_MEDIA_BUCKET"]
|
|
||||||
else:
|
|
||||||
print("Unknown TAKAHE_MEDIA_BACKEND value")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
|
@ -18,3 +18,9 @@ CSRF_TRUSTED_ORIGINS = [
|
||||||
]
|
]
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
SERVER_EMAIL = "test@example.com"
|
||||||
|
|
||||||
|
MAIN_DOMAIN = os.environ.get("TAKAHE_MAIN_DOMAIN", "https://example.com")
|
||||||
|
|
||||||
|
MEDIA_URL = os.environ.get("TAKAHE_MEDIA_URL", "/media/")
|
||||||
|
MEDIA_ROOT = os.environ.get("TAKAHE_MEDIA_ROOT", BASE_DIR / "media")
|
||||||
|
|
|
@ -1,16 +1,79 @@
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from .base import * # noqa
|
from .base import * # noqa
|
||||||
|
|
||||||
# Load secret key from environment
|
# Ensure debug features are off
|
||||||
|
DEBUG = bool(os.environ.get("TAKAHE__SECURITY_HAZARD__DEBUG", False))
|
||||||
|
|
||||||
|
# TODO: Allow better setting of allowed_hosts, if we need to
|
||||||
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
|
### User-configurable options, pulled from the environment ###
|
||||||
|
|
||||||
|
# Secret key
|
||||||
try:
|
try:
|
||||||
SECRET_KEY = os.environ["TAKAHE_SECRET_KEY"]
|
SECRET_KEY = os.environ["TAKAHE_SECRET_KEY"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print("You must specify the TAKAHE_SECRET_KEY environment variable!")
|
print("You must specify the TAKAHE_SECRET_KEY environment variable!")
|
||||||
os._exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Ensure debug features are off
|
# SSL proxy header
|
||||||
DEBUG = False
|
if "TAKAHE_SECURE_HEADER" in os.environ:
|
||||||
|
SECURE_PROXY_SSL_HEADER = (
|
||||||
|
"HTTP_" + os.environ["TAKAHE_SECURE_HEADER"].replace("-", "_").upper(),
|
||||||
|
"https",
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: Allow better setting of allowed_hosts, if we need to
|
# Fallback domain for links
|
||||||
ALLOWED_HOSTS = ["*"]
|
MAIN_DOMAIN = os.environ["TAKAHE_MAIN_DOMAIN"]
|
||||||
|
if "/" in MAIN_DOMAIN:
|
||||||
|
print("TAKAHE_MAIN_DOMAIN should be just the domain name - no https:// or path")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Email config
|
||||||
|
if os.environ.get("TAKAHE_EMAIL_CONSOLE_ONLY"):
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
SERVER_EMAIL = "test@example.com"
|
||||||
|
else:
|
||||||
|
SERVER_EMAIL = os.environ["TAKAHE_EMAIL_FROM"]
|
||||||
|
if "TAKAHE_EMAIL_SENDGRID_KEY" in os.environ:
|
||||||
|
EMAIL_HOST = "smtp.sendgrid.net"
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_HOST_USER: Optional[str] = "apikey"
|
||||||
|
EMAIL_HOST_PASSWORD: Optional[str] = os.environ["TAKAHE_EMAIL_SENDGRID_KEY"]
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
else:
|
||||||
|
EMAIL_HOST = os.environ["TAKAHE_EMAIL_HOST"]
|
||||||
|
EMAIL_PORT = int(os.environ["TAKAHE_EMAIL_PORT"])
|
||||||
|
EMAIL_HOST_USER = os.environ.get("TAKAHE_EMAIL_USER")
|
||||||
|
EMAIL_HOST_PASSWORD = os.environ.get("TAKAHE_EMAIL_PASSWORD")
|
||||||
|
EMAIL_USE_SSL = EMAIL_PORT == 465
|
||||||
|
EMAIL_USE_TLS = EMAIL_PORT == 587
|
||||||
|
|
||||||
|
AUTO_ADMIN_EMAIL = os.environ.get("TAKAHE_AUTO_ADMIN_EMAIL")
|
||||||
|
|
||||||
|
# Media storage
|
||||||
|
MEDIA_BACKEND = os.environ.get("TAKAHE_MEDIA_BACKEND", None)
|
||||||
|
if MEDIA_BACKEND == "local":
|
||||||
|
# Note that this MUST be a fully qualified URL in production
|
||||||
|
MEDIA_URL = os.environ.get("TAKAHE_MEDIA_URL", "/media/")
|
||||||
|
MEDIA_ROOT = os.environ.get("TAKAHE_MEDIA_ROOT", BASE_DIR / "media")
|
||||||
|
elif MEDIA_BACKEND == "gcs":
|
||||||
|
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
|
||||||
|
GS_BUCKET_NAME = os.environ["TAKAHE_MEDIA_BUCKET"]
|
||||||
|
GS_QUERYSTRING_AUTH = False
|
||||||
|
elif MEDIA_BACKEND == "s3":
|
||||||
|
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
|
||||||
|
AWS_STORAGE_BUCKET_NAME = os.environ["TAKAHE_MEDIA_BUCKET"]
|
||||||
|
else:
|
||||||
|
print("Unknown TAKAHE_MEDIA_BACKEND value")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Stator secret token
|
||||||
|
STATOR_TOKEN = os.environ.get("TAKAHE_STATOR_TOKEN")
|
||||||
|
|
||||||
|
# Error email recipients
|
||||||
|
if "TAKAHE_ERROR_EMAILS" in os.environ:
|
||||||
|
ADMINS = [("Admin", e) for e in os.environ["TAKAHE_ERROR_EMAILS"].split(",")]
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{% if identity.summary %}
|
{% if identity.summary %}
|
||||||
<div class="summary">
|
<div class="bio">
|
||||||
{{ identity.safe_summary }}
|
{{ identity.safe_summary }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -59,6 +59,6 @@
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
{% include "activities/_post.html" %}
|
{% include "activities/_post.html" %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
No posts yet.
|
<span class="empty">No posts yet.</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -34,7 +34,7 @@ class PasswordResetStates(StateGraph):
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
from_email=settings.EMAIL_FROM,
|
from_email=settings.SERVER_EMAIL,
|
||||||
recipient_list=[reset.user.email],
|
recipient_list=[reset.user.email],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -48,7 +48,7 @@ class PasswordResetStates(StateGraph):
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
from_email=settings.EMAIL_FROM,
|
from_email=settings.SERVER_EMAIL,
|
||||||
recipient_list=[reset.user.email],
|
recipient_list=[reset.user.email],
|
||||||
)
|
)
|
||||||
return cls.sent
|
return cls.sent
|
||||||
|
|
Loading…
Reference in a new issue