Invites overhaul
No email tie, added uses and expires, now works by URL.
This commit is contained in:
parent
ff38948b2f
commit
9c376395db
|
@ -25,9 +25,7 @@ def instance_info(request):
|
||||||
},
|
},
|
||||||
"thumbnail": Config.system.site_banner,
|
"thumbnail": Config.system.site_banner,
|
||||||
"languages": ["en"],
|
"languages": ["en"],
|
||||||
"registrations": (
|
"registrations": (Config.system.signup_allowed),
|
||||||
Config.system.signup_allowed and not Config.system.signup_invite_only
|
|
||||||
),
|
|
||||||
"approval_required": False,
|
"approval_required": False,
|
||||||
"invites_enabled": False,
|
"invites_enabled": False,
|
||||||
"configuration": {
|
"configuration": {
|
||||||
|
|
|
@ -211,7 +211,6 @@ class Config(models.Model):
|
||||||
policy_rules: str = ""
|
policy_rules: str = ""
|
||||||
|
|
||||||
signup_allowed: bool = True
|
signup_allowed: bool = True
|
||||||
signup_invite_only: bool = False
|
|
||||||
signup_text: str = ""
|
signup_text: str = ""
|
||||||
content_warning_text: str = "Content Warning"
|
content_warning_text: str = "Content Warning"
|
||||||
|
|
||||||
|
|
|
@ -182,6 +182,7 @@ urlpatterns = [
|
||||||
path("auth/login/", auth.Login.as_view(), name="login"),
|
path("auth/login/", auth.Login.as_view(), name="login"),
|
||||||
path("auth/logout/", auth.Logout.as_view(), name="logout"),
|
path("auth/logout/", auth.Logout.as_view(), name="logout"),
|
||||||
path("auth/signup/", auth.Signup.as_view(), name="signup"),
|
path("auth/signup/", auth.Signup.as_view(), name="signup"),
|
||||||
|
path("auth/signup/<token>/", auth.Signup.as_view(), name="signup"),
|
||||||
path("auth/reset/", auth.TriggerReset.as_view(), name="trigger_reset"),
|
path("auth/reset/", auth.TriggerReset.as_view(), name="trigger_reset"),
|
||||||
path("auth/reset/<token>/", auth.PerformReset.as_view(), name="password_reset"),
|
path("auth/reset/<token>/", auth.PerformReset.as_view(), name="password_reset"),
|
||||||
# Identity selection
|
# Identity selection
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Details</legend>
|
<legend>Details</legend>
|
||||||
{% include "forms/_field.html" with field=form.email %}
|
{% include "forms/_field.html" with field=form.uses %}
|
||||||
|
{% include "forms/_field.html" with field=form.expires_days %}
|
||||||
{% include "forms/_field.html" with field=form.notes %}
|
{% include "forms/_field.html" with field=form.notes %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Details</legend>
|
<legend>Details</legend>
|
||||||
{% include "forms/_field.html" with field=form.code %}
|
{% include "forms/_field.html" with field=form.link %}
|
||||||
{% include "forms/_field.html" with field=form.email %}
|
{% include "forms/_field.html" with field=form.uses %}
|
||||||
|
{% include "forms/_field.html" with field=form.expires_days %}
|
||||||
{% include "forms/_field.html" with field=form.notes %}
|
{% include "forms/_field.html" with field=form.notes %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
|
|
@ -17,12 +17,22 @@
|
||||||
{{ invite.token }}
|
{{ invite.token }}
|
||||||
|
|
||||||
<small>
|
<small>
|
||||||
{% if invite.email %}
|
{% if invite.expires %}
|
||||||
(email {{ invite.email }})
|
Expires in {{ invite.expires|timeuntil }}
|
||||||
|
{% if invite.notes %}|{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if invite.notes %}
|
||||||
|
{{ invite.notes }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
<time>{{ invite.created|timedeltashort }} ago</time>
|
<time>
|
||||||
|
{% if invite.uses %}
|
||||||
|
{{ invite.uses }} use{{ invite.uses|pluralize }} left
|
||||||
|
{% else %}
|
||||||
|
Infinite uses
|
||||||
|
{% endif %}
|
||||||
|
</time>
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p class="option empty">
|
<p class="option empty">
|
||||||
|
|
|
@ -4,27 +4,27 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="." method="POST">
|
<form action="." method="POST">
|
||||||
{% csrf_token %}
|
{% if form %}
|
||||||
<fieldset>
|
{% csrf_token %}
|
||||||
<legend>Create An Account</legend>
|
<fieldset>
|
||||||
{% if signup_text %}{{ signup_text }}{% endif %}
|
<legend>Create An Account</legend>
|
||||||
{% if config.signup_allowed %}
|
{% if signup_text %}{{ signup_text }}{% endif %}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% include "forms/_field.html" %}
|
{% include "forms/_field.html" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
</fieldset>
|
||||||
{% if not config.signup_text %}
|
<div class="buttons">
|
||||||
<p>Not accepting new users at this time</p>
|
<button>Create</button>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<fieldset>
|
||||||
|
<legend>Create An Account</legend>
|
||||||
|
{% if signup_text %}
|
||||||
|
{{ signup_text }}
|
||||||
|
{% else %}
|
||||||
|
<p>We're not accepting new users at this time.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
</fieldset>
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
{% if config.signup_allowed %}
|
|
||||||
<div class="buttons">
|
|
||||||
<button>Create</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
|
from django.utils import timezone
|
||||||
from pytest_django.asserts import assertContains, assertNotContains
|
from pytest_django.asserts import assertContains, assertNotContains
|
||||||
|
|
||||||
from users.models import Invite, User
|
from users.models import Invite, User
|
||||||
|
@ -13,7 +16,9 @@ def test_signup_disabled(client, config_system):
|
||||||
# Signup disabled and no signup text
|
# Signup disabled and no signup text
|
||||||
config_system.signup_allowed = False
|
config_system.signup_allowed = False
|
||||||
response = client.get("/auth/signup/")
|
response = client.get("/auth/signup/")
|
||||||
assertContains(response, "Not accepting new users at this time", status_code=200)
|
assertContains(
|
||||||
|
response, "We're not accepting new users at this time", status_code=200
|
||||||
|
)
|
||||||
assertNotContains(response, "<button>Create</button>")
|
assertNotContains(response, "<button>Create</button>")
|
||||||
|
|
||||||
# Signup disabled with signup text configured
|
# Signup disabled with signup text configured
|
||||||
|
@ -32,7 +37,7 @@ def test_signup_disabled(client, config_system):
|
||||||
config_system.signup_allowed = True
|
config_system.signup_allowed = True
|
||||||
response = client.get("/auth/signup/")
|
response = client.get("/auth/signup/")
|
||||||
assertContains(response, "<button>Create</button>", status_code=200)
|
assertContains(response, "<button>Create</button>", status_code=200)
|
||||||
assertNotContains(response, "Not accepting new users at this time")
|
assertNotContains(response, "We're not accepting new users at this time")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
@ -40,43 +45,45 @@ def test_signup_invite_only(client, config_system):
|
||||||
"""
|
"""
|
||||||
Tests that invite codes work with signup
|
Tests that invite codes work with signup
|
||||||
"""
|
"""
|
||||||
config_system.signup_allowed = True
|
config_system.signup_allowed = False
|
||||||
config_system.signup_invite_only = True
|
|
||||||
|
|
||||||
# Try to sign up without an invite code
|
# Try to sign up without an invite code
|
||||||
response = client.post("/auth/signup/", {"email": "random@example.com"})
|
response = client.post("/auth/signup/", {"email": "random@example.com"})
|
||||||
assertNotContains(response, "Email Sent", status_code=200)
|
assertNotContains(response, "Email Sent", status_code=200)
|
||||||
|
|
||||||
# Make an invite code for any email
|
# Make an invite code for any email with infinite uses
|
||||||
invite_any = Invite.create_random()
|
invite_infinite = Invite.create_random()
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/auth/signup/",
|
f"/auth/signup/{invite_infinite.token}/",
|
||||||
{"email": "random@example.com", "invite_code": invite_any.token},
|
{"email": "random@example.com"},
|
||||||
)
|
|
||||||
assertNotContains(response, "not a valid invite")
|
|
||||||
assertContains(response, "Email Sent", status_code=200)
|
|
||||||
|
|
||||||
# Make sure you can't reuse an invite code
|
|
||||||
response = client.post(
|
|
||||||
"/auth/signup/",
|
|
||||||
{"email": "random2@example.com", "invite_code": invite_any.token},
|
|
||||||
)
|
|
||||||
assertNotContains(response, "Email Sent", status_code=200)
|
|
||||||
|
|
||||||
# Make an invite code for a specific email
|
|
||||||
invite_specific = Invite.create_random(email="special@example.com")
|
|
||||||
response = client.post(
|
|
||||||
"/auth/signup/",
|
|
||||||
{"email": "random3@example.com", "invite_code": invite_specific.token},
|
|
||||||
)
|
|
||||||
assertContains(response, "valid invite code for this email", status_code=200)
|
|
||||||
assertNotContains(response, "Email Sent")
|
|
||||||
response = client.post(
|
|
||||||
"/auth/signup/",
|
|
||||||
{"email": "special@example.com", "invite_code": invite_specific.token},
|
|
||||||
)
|
)
|
||||||
assertContains(response, "Email Sent", status_code=200)
|
assertContains(response, "Email Sent", status_code=200)
|
||||||
|
|
||||||
|
# Ensure it still has infinite uses
|
||||||
|
assert Invite.objects.get(token=invite_infinite.token).uses is None
|
||||||
|
|
||||||
|
# Make an invite code for any email with one use
|
||||||
|
invite_single = Invite.create_random(uses=1)
|
||||||
|
response = client.post(
|
||||||
|
f"/auth/signup/{invite_single.token}/",
|
||||||
|
{"email": "random2@example.com"},
|
||||||
|
)
|
||||||
|
assertContains(response, "Email Sent", status_code=200)
|
||||||
|
|
||||||
|
# Verify it was used up
|
||||||
|
assert Invite.objects.filter(token=invite_single.token).count() == 0
|
||||||
|
|
||||||
|
# Make an invite code that's invalid
|
||||||
|
invite_expired = Invite.create_random(
|
||||||
|
expires=timezone.now() - datetime.timedelta(days=1)
|
||||||
|
)
|
||||||
|
response = client.post(
|
||||||
|
f"/auth/signup/{invite_expired.token}/",
|
||||||
|
{"email": "random3@example.com"},
|
||||||
|
)
|
||||||
|
print(response.content)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_signup_policy(client, config_system):
|
def test_signup_policy(client, config_system):
|
||||||
|
@ -84,7 +91,6 @@ def test_signup_policy(client, config_system):
|
||||||
Tests that you must agree to policies to sign up
|
Tests that you must agree to policies to sign up
|
||||||
"""
|
"""
|
||||||
config_system.signup_allowed = True
|
config_system.signup_allowed = True
|
||||||
config_system.signup_invite_only = False
|
|
||||||
|
|
||||||
# Make sure we can sign up when there are no policies
|
# Make sure we can sign up when there are no policies
|
||||||
response = client.post("/auth/signup/", {"email": "random@example.com"})
|
response = client.post("/auth/signup/", {"email": "random@example.com"})
|
||||||
|
@ -103,7 +109,6 @@ def test_signup_email(client, config_system, stator):
|
||||||
Tests that you can sign up and get an email sent to you
|
Tests that you can sign up and get an email sent to you
|
||||||
"""
|
"""
|
||||||
config_system.signup_allowed = True
|
config_system.signup_allowed = True
|
||||||
config_system.signup_invite_only = False
|
|
||||||
|
|
||||||
# Sign up with a user
|
# Sign up with a user
|
||||||
response = client.post("/auth/signup/", {"email": "random@example.com"})
|
response = client.post("/auth/signup/", {"email": "random@example.com"})
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 4.1.4 on 2022-12-22 06:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0006_identity_actor_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="invite",
|
||||||
|
name="email",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="invite",
|
||||||
|
name="expires",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="invite",
|
||||||
|
name="uses",
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,6 +2,7 @@ import random
|
||||||
|
|
||||||
import urlman
|
import urlman
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
class Invite(models.Model):
|
class Invite(models.Model):
|
||||||
|
@ -12,12 +13,15 @@ class Invite(models.Model):
|
||||||
# Should always be lowercase
|
# Should always be lowercase
|
||||||
token = models.CharField(max_length=500, unique=True)
|
token = models.CharField(max_length=500, unique=True)
|
||||||
|
|
||||||
# Is it limited to a specific email?
|
|
||||||
email = models.EmailField(null=True, blank=True)
|
|
||||||
|
|
||||||
# Admin note about this code
|
# Admin note about this code
|
||||||
note = models.TextField(null=True, blank=True)
|
note = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
# Uses remaining (null means "infinite")
|
||||||
|
uses = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
# Expiry date
|
||||||
|
expires = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
updated = models.DateTimeField(auto_now=True)
|
updated = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
@ -27,11 +31,21 @@ class Invite(models.Model):
|
||||||
admin_view = "{admin}{self.pk}/"
|
admin_view = "{admin}{self.pk}/"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_random(cls, email=None, note=None):
|
def create_random(cls, uses=None, expires=None, note=None):
|
||||||
return cls.objects.create(
|
return cls.objects.create(
|
||||||
token="".join(
|
token="".join(
|
||||||
random.choice("abcdefghkmnpqrstuvwxyz23456789") for i in range(20)
|
random.choice("abcdefghkmnpqrstuvwxyz23456789") for i in range(20)
|
||||||
),
|
),
|
||||||
email=email,
|
uses=uses,
|
||||||
|
expires=expires,
|
||||||
note=note,
|
note=note,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valid(self):
|
||||||
|
if self.uses is not None:
|
||||||
|
if self.uses <= 0:
|
||||||
|
return False
|
||||||
|
if self.expires is not None:
|
||||||
|
return self.expires >= timezone.now()
|
||||||
|
return True
|
||||||
|
|
|
@ -82,8 +82,7 @@ class NodeInfo2(View):
|
||||||
"users": {"total": local_identities},
|
"users": {"total": local_identities},
|
||||||
"localPosts": local_posts,
|
"localPosts": local_posts,
|
||||||
},
|
},
|
||||||
"openRegistrations": Config.system.signup_allowed
|
"openRegistrations": Config.system.signup_allowed,
|
||||||
and not Config.system.signup_invite_only,
|
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.generic import FormView, ListView
|
from django.views.generic import FormView, ListView
|
||||||
|
|
||||||
|
@ -32,19 +36,28 @@ class InviteCreate(FormView):
|
||||||
}
|
}
|
||||||
|
|
||||||
class form_class(forms.Form):
|
class form_class(forms.Form):
|
||||||
email = forms.EmailField(
|
uses = forms.IntegerField(
|
||||||
required=False,
|
required=False,
|
||||||
help_text="Optional email to tie the invite to.\nYou will still need to email the user this code yourself!",
|
help_text="Number of times this can be used. Leave blank for infinite uses.",
|
||||||
|
)
|
||||||
|
expires_days = forms.IntegerField(
|
||||||
|
required=False,
|
||||||
|
help_text="Number of days until this expires. Leave blank to make it last forever.",
|
||||||
)
|
)
|
||||||
notes = forms.CharField(
|
notes = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
widget=forms.Textarea,
|
|
||||||
help_text="Notes for other admins",
|
help_text="Notes for other admins",
|
||||||
)
|
)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
expires_days = form.cleaned_data.get("expires_days")
|
||||||
invite = Invite.create_random(
|
invite = Invite.create_random(
|
||||||
email=form.cleaned_data.get("email") or None,
|
uses=form.cleaned_data.get("uses") or None,
|
||||||
|
expires=(
|
||||||
|
timezone.now() + datetime.timedelta(days=expires_days)
|
||||||
|
if expires_days is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
note=form.cleaned_data.get("notes"),
|
note=form.cleaned_data.get("notes"),
|
||||||
)
|
)
|
||||||
return redirect(invite.urls.admin_view)
|
return redirect(invite.urls.admin_view)
|
||||||
|
@ -59,7 +72,7 @@ class InviteView(FormView):
|
||||||
}
|
}
|
||||||
|
|
||||||
class form_class(InviteCreate.form_class):
|
class form_class(InviteCreate.form_class):
|
||||||
code = forms.CharField(disabled=True, required=False)
|
link = forms.CharField(disabled=True, required=False)
|
||||||
|
|
||||||
def dispatch(self, request, id, *args, **kwargs):
|
def dispatch(self, request, id, *args, **kwargs):
|
||||||
self.invite = get_object_or_404(Invite, id=id)
|
self.invite = get_object_or_404(Invite, id=id)
|
||||||
|
@ -74,13 +87,19 @@ class InviteView(FormView):
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
return {
|
return {
|
||||||
"notes": self.invite.note,
|
"notes": self.invite.note,
|
||||||
"email": self.invite.email,
|
"uses": self.invite.uses,
|
||||||
"code": self.invite.token,
|
"link": f"https://{settings.MAIN_DOMAIN}/auth/signup/{self.invite.token}/",
|
||||||
}
|
}
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
expires_days = form.cleaned_data.get("expires_days")
|
||||||
self.invite.note = form.cleaned_data.get("notes") or ""
|
self.invite.note = form.cleaned_data.get("notes") or ""
|
||||||
self.invite.email = form.cleaned_data.get("email") or None
|
self.invite.uses = form.cleaned_data.get("uses") or None
|
||||||
|
self.invite.expires = (
|
||||||
|
timezone.now() + datetime.timedelta(days=expires_days)
|
||||||
|
if expires_days is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
self.invite.save()
|
self.invite.save()
|
||||||
return redirect(self.invite.urls.admin)
|
return redirect(self.invite.urls.admin)
|
||||||
|
|
||||||
|
|
|
@ -69,11 +69,7 @@ class BasicSettings(AdminSettingsPage):
|
||||||
},
|
},
|
||||||
"signup_allowed": {
|
"signup_allowed": {
|
||||||
"title": "Signups Allowed",
|
"title": "Signups Allowed",
|
||||||
"help_text": "If signups are allowed at all",
|
"help_text": "If uninvited signups are allowed.\nInvite codes will always work.",
|
||||||
},
|
|
||||||
"signup_invite_only": {
|
|
||||||
"title": "Invite-Only",
|
|
||||||
"help_text": "If signups require an invite code",
|
|
||||||
},
|
},
|
||||||
"signup_text": {
|
"signup_text": {
|
||||||
"title": "Signup Page Text",
|
"title": "Signup Page Text",
|
||||||
|
@ -103,7 +99,10 @@ class BasicSettings(AdminSettingsPage):
|
||||||
"site_banner",
|
"site_banner",
|
||||||
"highlight_color",
|
"highlight_color",
|
||||||
],
|
],
|
||||||
"Signups": ["signup_allowed", "signup_invite_only", "signup_text"],
|
"Signups": [
|
||||||
|
"signup_allowed",
|
||||||
|
"signup_text",
|
||||||
|
],
|
||||||
"Posts": [
|
"Posts": [
|
||||||
"post_length",
|
"post_length",
|
||||||
"post_minimum_interval",
|
"post_minimum_interval",
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.conf import settings
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.contrib.auth.password_validation import validate_password
|
from django.contrib.auth.password_validation import validate_password
|
||||||
from django.contrib.auth.views import LoginView, LogoutView
|
from django.contrib.auth.views import LoginView, LogoutView
|
||||||
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
@ -39,11 +40,6 @@ class Signup(FormView):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# Add the invite field if it's enabled
|
|
||||||
if Config.system.signup_invite_only:
|
|
||||||
self.fields["invite_code"] = forms.CharField(
|
|
||||||
help_text="Your invite code from one of our admins"
|
|
||||||
)
|
|
||||||
# Add the policies if they're defined
|
# Add the policies if they're defined
|
||||||
policies = []
|
policies = []
|
||||||
if Config.system.policy_rules:
|
if Config.system.policy_rules:
|
||||||
|
@ -82,30 +78,33 @@ class Signup(FormView):
|
||||||
raise forms.ValidationError("This email already has an account")
|
raise forms.ValidationError("This email already has an account")
|
||||||
return email
|
return email
|
||||||
|
|
||||||
def clean_invite_code(self):
|
def dispatch(self, request, token=None, *args, **kwargs):
|
||||||
invite_code = self.cleaned_data["invite_code"].lower().strip()
|
if token:
|
||||||
invite = Invite.objects.filter(token=invite_code).first()
|
self.invite = get_object_or_404(Invite, token=token)
|
||||||
if not invite:
|
if not self.invite.valid:
|
||||||
raise forms.ValidationError("That is not a valid invite code")
|
raise Http404()
|
||||||
if invite.email and invite.email != self.cleaned_data.get("email"):
|
else:
|
||||||
raise forms.ValidationError(
|
self.invite = None
|
||||||
"That is not a valid invite code for this email address"
|
return super().dispatch(request, *args, **kwargs)
|
||||||
)
|
|
||||||
return invite_code
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
if not Config.system.signup_allowed:
|
|
||||||
raise forms.ValidationError("Not accepting new users at this time")
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
# Don't allow anything if there's no invite and no signup allowed
|
||||||
|
if not Config.system.signup_allowed and not self.invite:
|
||||||
|
return self.render_to_response(self.get_context_data())
|
||||||
|
# Make the new user
|
||||||
user = User.objects.create(email=form.cleaned_data["email"])
|
user = User.objects.create(email=form.cleaned_data["email"])
|
||||||
# Auto-promote the user to admin if that setting is set
|
# Auto-promote the user to admin if that setting is set
|
||||||
if settings.AUTO_ADMIN_EMAIL and user.email == settings.AUTO_ADMIN_EMAIL:
|
if settings.AUTO_ADMIN_EMAIL and user.email == settings.AUTO_ADMIN_EMAIL:
|
||||||
user.admin = True
|
user.admin = True
|
||||||
user.save()
|
user.save()
|
||||||
PasswordReset.create_for_user(user)
|
PasswordReset.create_for_user(user)
|
||||||
if "invite_code" in form.cleaned_data:
|
# Drop invite uses down if it has them
|
||||||
Invite.objects.filter(token=form.cleaned_data["invite_code"]).delete()
|
if self.invite and self.invite.uses is not None:
|
||||||
|
self.invite.uses -= 1
|
||||||
|
if self.invite.uses <= 0:
|
||||||
|
self.invite.delete()
|
||||||
|
else:
|
||||||
|
self.invite.save()
|
||||||
return render(
|
return render(
|
||||||
self.request,
|
self.request,
|
||||||
"auth/signup_success.html",
|
"auth/signup_success.html",
|
||||||
|
@ -114,6 +113,8 @@ class Signup(FormView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
if not Config.system.signup_allowed and not self.invite:
|
||||||
|
del context["form"]
|
||||||
if Config.system.signup_text:
|
if Config.system.signup_text:
|
||||||
context["signup_text"] = mark_safe(
|
context["signup_text"] = mark_safe(
|
||||||
markdown_it.MarkdownIt().render(Config.system.signup_text)
|
markdown_it.MarkdownIt().render(Config.system.signup_text)
|
||||||
|
|
Loading…
Reference in New Issue