mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
User registration
This commit is contained in:
@@ -10,10 +10,21 @@ _KIBIBYTE = 1024
|
|||||||
_MEBIBYTE = 1024 * _KIBIBYTE
|
_MEBIBYTE = 1024 * _KIBIBYTE
|
||||||
|
|
||||||
|
|
||||||
|
# CB means "create by"
|
||||||
|
CB_ANYBODY = 0
|
||||||
|
CB_MEMBERS = 1
|
||||||
|
CB_ADMINS = 2
|
||||||
|
CB_NOBODY = 3
|
||||||
|
|
||||||
|
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
"BASE_URL": "http://127.0.0.1:9000", # no trailing slash
|
"BASE_URL": "http://127.0.0.1:9000", # no trailing slash
|
||||||
"SITE_TITLE": "Bugsink", # you can customize this as e.g. "My Bugsink" or "Bugsink for My Company"
|
"SITE_TITLE": "Bugsink", # you can customize this as e.g. "My Bugsink" or "Bugsink for My Company"
|
||||||
|
|
||||||
|
# Users, teams, projects
|
||||||
|
"USER_REGISTRATION": CB_ANYBODY, # who can register new users. default: anybody, i.e. users can register themselves
|
||||||
|
|
||||||
|
# System inner workings:
|
||||||
"DIGEST_IMMEDIATELY": True,
|
"DIGEST_IMMEDIATELY": True,
|
||||||
|
|
||||||
# MAX* below mirror the (current) values for the Sentry Relax
|
# MAX* below mirror the (current) values for the Sentry Relax
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ from django.contrib import admin
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
|
||||||
from .views import home, trigger_error, favicon
|
|
||||||
from alerts.views import debug_email
|
from alerts.views import debug_email
|
||||||
from bugsink.app_settings import get_settings
|
from bugsink.app_settings import get_settings
|
||||||
|
from users.views import signup
|
||||||
|
|
||||||
|
from .views import home, trigger_error, favicon
|
||||||
|
|
||||||
|
|
||||||
admin.site.site_header = get_settings().SITE_TITLE
|
admin.site.site_header = get_settings().SITE_TITLE
|
||||||
@@ -15,8 +17,9 @@ admin.site.index_title = "Admin" # everyone calls this the "admin" anyway. Let'
|
|||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', home),
|
path('', home, name='home'),
|
||||||
|
|
||||||
|
path("accounts/signup/", signup, name="signup"),
|
||||||
path("accounts/login/", auth_views.LoginView.as_view(template_name="bugsink/login.html"), name="login"),
|
path("accounts/login/", auth_views.LoginView.as_view(template_name="bugsink/login.html"), name="login"),
|
||||||
path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
|
path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
<div class="m-4">
|
<div class="m-4">
|
||||||
<h1 class="text-4xl mt-4 font-bold">Not Found</h1>
|
<h1 class="text-4xl mt-4 font-bold">Not Found</h1>
|
||||||
|
|
||||||
<div class="pt-2">The requested page could not be found with exception "{{ exception }}".</div>
|
<div class="pt-2">{{ exception }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
60
templates/signup.html
Normal file
60
templates/signup.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{% extends "barest_base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Sign up · {{ site_title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="bg-cyan-100 h-screen overflow-y-scroll flex items-center justify-center"> {# the cyan background #}
|
||||||
|
<div class="bg-white lg:w-5/12 md:6/12 w-10/12"> {# the centered box #}
|
||||||
|
<div class="bg-slate-200 absolute left-1/2 transform -translate-x-1/2 -translate-y-1/2 rounded-full p-4 md:p-8"> {# the logo #}
|
||||||
|
<a href="/"><img src="{% static 'images/bugsink-logo.png' %}" class="h-8 w-8 md:h-16 md:w-16" alt="Bugsink"></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-12 md:pt-24 md:pl-24 md:pr-24 md:pb-16">
|
||||||
|
|
||||||
|
<form method="post" action=".">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="text-lg mb-6 md:mb-8">
|
||||||
|
<input name="username" type="text" class="{% if form.username.errors %}bg-red-100{% else %}bg-slate-200{% endif %} pl-4 py-2 md:py-4 focus:outline-none w-full" {% if form.username.value %}value="{{ form.username.value }}"{% endif %} placeholder="{{ form.username.label }}" />
|
||||||
|
{% if form.username.errors %}
|
||||||
|
{% for error in form.username.errors %}
|
||||||
|
<div class="text-red-500 pt-1 px-2 text-sm">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% elif form.username.help_text %}
|
||||||
|
<div class="text-gray-500 pt-1 px-2 text-sm">{{ form.username.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-lg mb-6 md:mb-8">
|
||||||
|
<input name="password1" type="password" class="{% if form.password1.errors %}bg-red-100{% else %}bg-slate-200{% endif %} pl-4 py-2 md:py-4 focus:outline-none w-full" {% if form.password1.value %}value="{{ form.password1.value }}"{% endif %} placeholder="{{ form.password1.label }}" />
|
||||||
|
{% if form.password1.errors %}
|
||||||
|
{% for error in form.password1.errors %}
|
||||||
|
<div class="text-red-500 pt-1 px-2 text-sm">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% elif form.password1.help_text %}
|
||||||
|
<div class="text-gray-500 pt-1 px-2 text-sm">{{ form.password1.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-lg mb-6 md:mb-8">
|
||||||
|
<input name="password2" type="password" class="{% if form.password2.errors %}bg-red-100{% else %}bg-slate-200{% endif %} pl-4 py-2 md:py-4 focus:outline-none w-full" {% if form.password2.value %}value="{{ form.password2.value }}"{% endif %} placeholder="{{ form.password2.label }}" />
|
||||||
|
{% if form.password2.errors %}
|
||||||
|
{% for error in form.password2.errors %}
|
||||||
|
<div class="text-red-500 pt-1 px-2 text-sm">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% elif form.password2.help_text %}
|
||||||
|
<div class="text-gray-500 pt-1 px-2 text-sm">{{ form.password2.help_text }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="bg-slate-800 font-medium p-2 md:p-4 text-white uppercase w-full">Sign up</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
2
theme/static/css/dist/styles.css
vendored
2
theme/static/css/dist/styles.css
vendored
File diff suppressed because one or more lines are too long
53
users/forms.py
Normal file
53
users/forms.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm
|
||||||
|
from django.core.validators import EmailValidator
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.contrib.auth import password_validation
|
||||||
|
from django.forms import ModelForm
|
||||||
|
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreationForm(BaseUserCreationForm):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['username'].validators = [EmailValidator()]
|
||||||
|
self.fields['username'].label = "Email"
|
||||||
|
|
||||||
|
self.fields['username'].help_text = None # "Email" is descriptive enough
|
||||||
|
|
||||||
|
# the other conditions will be "revealed" when you trip'em up. Arguably that's an UX anti-pattern, but so is
|
||||||
|
# having too many instructions. I'm erring on assuming my users are smart enough to pick a good password
|
||||||
|
# initially, and if they don't, at least they'll have only a single instruction to read. (bad password-pickers
|
||||||
|
# are probably bad readers too)
|
||||||
|
self.fields['password1'].help_text = "At least 8 characters"
|
||||||
|
|
||||||
|
self.fields['password2'].help_text = None # "Confirm password" is descriptive enough
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = UserModel
|
||||||
|
fields = ("username",)
|
||||||
|
|
||||||
|
def _post_clean(self):
|
||||||
|
# copy of django.contrib.auth.forms.UserCreationForm._post_clean; but with password1 instead of password2; I'd
|
||||||
|
# say it's better UX to complain where the original error is made
|
||||||
|
|
||||||
|
ModelForm._post_clean(self) # commented out because we want to skip the direct superclass
|
||||||
|
# Validate the password after self.instance is updated with form data
|
||||||
|
# by super().
|
||||||
|
password = self.cleaned_data.get("password1")
|
||||||
|
if password:
|
||||||
|
try:
|
||||||
|
password_validation.validate_password(password, self.instance)
|
||||||
|
except ValidationError as error:
|
||||||
|
self.add_error("password1", error)
|
||||||
|
|
||||||
|
def save(self, **kwargs):
|
||||||
|
commit = kwargs.pop("commit", True)
|
||||||
|
user = super().save(commit=False)
|
||||||
|
user.email = user.username
|
||||||
|
if commit:
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
30
users/views.py
Normal file
30
users/views.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from django.contrib.auth import login # , authenticate
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.http import Http404
|
||||||
|
|
||||||
|
from bugsink.app_settings import get_settings, CB_ANYBODY
|
||||||
|
|
||||||
|
from .forms import UserCreationForm
|
||||||
|
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
def signup(request):
|
||||||
|
if get_settings().USER_REGISTRATION != CB_ANYBODY:
|
||||||
|
raise Http404("User self-registration is not allowed.")
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = UserCreationForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
username = form.cleaned_data.get('username')
|
||||||
|
user = UserModel.objects.get(username=username)
|
||||||
|
login(request, user)
|
||||||
|
return redirect('home')
|
||||||
|
else:
|
||||||
|
form = UserCreationForm()
|
||||||
|
|
||||||
|
return render(request, "signup.html", {"form": form})
|
||||||
Reference in New Issue
Block a user