Basic user management skeleton #8
|
@ -14,7 +14,6 @@ import os
|
|||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
||||
|
||||
|
@ -55,7 +54,7 @@ ROOT_URLCONF = 'project.urls'
|
|||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, "project", "templates")],
|
||||
'DIRS': [os.path.join("project", "templates")],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
@ -106,6 +105,8 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
},
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
||||
|
@ -126,6 +127,8 @@ USE_TZ = True
|
|||
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
</h1>
|
||||
<ul>
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="{% url 'account_email' %}">Change e-mail</a></li>
|
||||
<li><a href="{% url 'account_logout' %}">Sign out</a></li>
|
||||
<li><a href="{% url 'users:email' %}">Change e-mail</a></li>
|
||||
<li><a href="{% url 'users:logout' %}">Sign out</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'account_login' %}">Sign in</a></li>
|
||||
<li><a href="{% url 'account_signup' %}">Sign up</a></li>
|
||||
<li><a href="{% url 'users:login' %}">Sign in</a></li>
|
||||
<li><a href="{% url 'users:signup' %}">Sign up</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</header>
|
||||
|
|
1
project/templates/index.html
Normal file
1
project/templates/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
{% extends "base.html" %}
|
|
@ -1,7 +1,12 @@
|
|||
"""URLs for the membersystem"""
|
||||
from django.contrib import admin
|
||||
from django.urls import include
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index),
|
||||
path("users/", include("users.urls")),
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
|
5
project/views.py
Normal file
5
project/views.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.shortcuts import render_to_response
|
||||
|
||||
|
||||
def index(request):
|
||||
return render_to_response("index.html")
|
|
@ -1,3 +1,2 @@
|
|||
Django==2.0.6
|
||||
django-money==0.14
|
||||
django-extensions==2.0.7
|
||||
Django>=2.2,<2.3
|
||||
django-money==0.15
|
||||
|
|
19
users/forms.py
Normal file
19
users/forms.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
def get_confirm_code(email):
|
||||
return default_token_generator(email)[:7]
|
||||
|
||||
|
||||
class SignupForm(UserCreationForm):
|
||||
|
||||
username = forms.EmailField(label=_("Email"))
|
||||
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ("username",)
|
|
@ -1,6 +1,6 @@
|
|||
# Generated by Django 2.0.6 on 2018-06-23 19:45
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
# Generated by Django 2.2.4 on 2019-08-31 18:44
|
||||
import uuid
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
@ -10,15 +10,27 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('auth', '0011_update_proxy_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Profile',
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('nick', models.CharField(blank=True, max_length=60, null=True)),
|
||||
('email', models.EmailField(help_text='Your email address will be used for password resets and notification about your event/submissions.', max_length=254, unique=True, verbose_name='E-Mail')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('is_staff', models.BooleanField(default=False)),
|
||||
('is_superuser', models.BooleanField(default=False)),
|
||||
('token_uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'User',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,5 +1,60 @@
|
|||
import uuid
|
||||
|
||||
from django.contrib.auth.base_user import BaseUserManager
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.contrib.auth.models import PermissionsMixin
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class Profile(models.Model):
|
||||
user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
|
||||
class UserManager(BaseUserManager):
|
||||
"""The user manager class."""
|
||||
|
||||
def create_user(self, password: str = None, **kwargs):
|
||||
user = self.model(**kwargs)
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
def create_superuser(self, password: str, **kwargs):
|
||||
user = self.create_user(password=password, **kwargs)
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save(update_fields=['is_staff', 'is_superuser'])
|
||||
return user
|
||||
|
||||
|
||||
class User(PermissionsMixin, AbstractBaseUser):
|
||||
|
||||
EMAIL_FIELD = 'email'
|
||||
USERNAME_FIELD = 'email'
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
nick = models.CharField(max_length=60, null=True, blank=True)
|
||||
email = models.EmailField(
|
||||
unique=True,
|
||||
verbose_name=_('E-Mail'),
|
||||
help_text=_(
|
||||
'Your email address will be used for password resets and notification about your event/submissions.'
|
||||
),
|
||||
)
|
||||
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
# For the Django admin...
|
||||
is_staff = models.BooleanField(default=False)
|
||||
is_superuser = models.BooleanField(default=False)
|
||||
|
||||
# Used for confirmations and password reminders to NOT disclose email in URL
|
||||
token_uuid = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Use a useful string representation."""
|
||||
return self.get_display_name()
|
||||
|
||||
def get_display_name(self) -> str:
|
||||
return self.nick if self.nick else str(_('Unnamed user'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("User")
|
||||
|
|
10
users/templates/users/logged_out.html
Normal file
10
users/templates/users/logged_out.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
|
||||
|
||||
<p><a href="{% url 'admin:index' %}">{% trans 'Log in again' %}</a></p>
|
||||
|
||||
{% endblock %}
|
39
users/templates/users/login.html
Normal file
39
users/templates/users/login.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
{% if user.is_authenticated %}
|
||||
<p>Your account doesn't have access to this page. To proceed,
|
||||
please login with an account that has access.</p>
|
||||
{% else %}
|
||||
<p>Please login to see this page.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'users:login' %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ form.username.label_tag }}</td>
|
||||
<td>{{ form.username }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.password.label_tag }}</td>
|
||||
<td>{{ form.password }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="login">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</form>
|
||||
|
||||
{# Assumes you setup the password_reset view in your URLconf #}
|
||||
<p><a href="{% url 'users:password_reset' %}">Lost password?</a></p>
|
||||
|
||||
{% endblock %}
|
8
users/templates/users/password_change_done.html
Normal file
8
users/templates/users/password_change_done.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
<p>{% trans 'Your password was changed.' %}</p>
|
||||
{% endblock %}
|
52
users/templates/users/password_change_form.html
Normal file
52
users/templates/users/password_change_form.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
|
||||
{% block content %}<div id="content-main">
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
<div>
|
||||
{% if form.errors %}
|
||||
<p class="errornote">
|
||||
{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<fieldset class="module aligned wide">
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.old_password.errors }}
|
||||
{{ form.old_password.label_tag }} {{ form.old_password }}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password1.errors }}
|
||||
{{ form.new_password1.label_tag }} {{ form.new_password1 }}
|
||||
{% if form.new_password1.help_text %}
|
||||
<div class="help">{{ form.new_password1.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
{{ form.new_password2.errors }}
|
||||
{{ form.new_password2.label_tag }} {{ form.new_password2 }}
|
||||
{% if form.new_password2.help_text %}
|
||||
<div class="help">{{ form.new_password2.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div class="submit-row">
|
||||
<input type="submit" value="{% trans 'Change my password' %}" class="default">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form></div>
|
||||
|
||||
{% endblock %}
|
13
users/templates/users/password_reset_complete.html
Normal file
13
users/templates/users/password_reset_complete.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
|
||||
|
||||
<p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>
|
||||
|
||||
{% endblock %}
|
34
users/templates/users/password_reset_confirm.html
Normal file
34
users/templates/users/password_reset_confirm.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
{% if validlink %}
|
||||
|
||||
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
<fieldset class="module aligned">
|
||||
<div class="form-row field-password1">
|
||||
{{ form.new_password1.errors }}
|
||||
<label for="id_new_password1">{% trans 'New password:' %}</label>
|
||||
{{ form.new_password1 }}
|
||||
</div>
|
||||
<div class="form-row field-password2">
|
||||
{{ form.new_password2.errors }}
|
||||
<label for="id_new_password2">{% trans 'Confirm password:' %}</label>
|
||||
{{ form.new_password2 }}
|
||||
</div>
|
||||
<input type="submit" value="{% trans 'Change my password' %}">
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
12
users/templates/users/password_reset_done.html
Normal file
12
users/templates/users/password_reset_done.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p>
|
||||
|
||||
<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
|
||||
|
||||
{% endblock %}
|
14
users/templates/users/password_reset_email.html
Normal file
14
users/templates/users/password_reset_email.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ domain }}{% url 'users:password_reset_confirm' uidb64=uid token=token %}
|
||||
{% endblock %}
|
||||
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||
|
||||
{% endautoescape %}
|
19
users/templates/users/password_reset_form.html
Normal file
19
users/templates/users/password_reset_form.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content_title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Forgotten password?" %}</h1>
|
||||
|
||||
<p>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</p>
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form.email.errors }}
|
||||
<label for="id_email">{% trans 'Email address:' %}</label>
|
||||
{{ form.email }}
|
||||
<input type="submit" value="{% trans 'Reset my password' %}">
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
21
users/templates/users/signup.html
Normal file
21
users/templates/users/signup.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Sign up" %}</h1>
|
||||
|
||||
{% if form.errors %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'users:signup' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<p><button type="submit">{% trans "Confirm email..." %}</button></p>
|
||||
|
||||
</form>
|
||||
|
||||
<p><a href="{% url 'users:login' %}">{% trans "Already have an account? Log in..." %}</a></p>
|
||||
|
||||
{% endblock %}
|
10
users/templates/users/signup_confirm.html
Normal file
10
users/templates/users/signup_confirm.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans "Confirm your email" %}</h1>
|
||||
|
||||
<p>{% trans "You've got mail - click the link or copy paste it to this browser session and you'll be logged in." %}</p>
|
||||
|
||||
{% endblock %}
|
22
users/urls.py
Normal file
22
users/urls.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'users'
|
||||
|
||||
urlpatterns = [
|
||||
path('signup/', views.SignupView.as_view(), name='signup'),
|
||||
path('signup/confirm/', views.SignupConfirmView.as_view(), name='signup_confirm'),
|
||||
|
||||
path('login/', auth_views.LoginView.as_view(template_name="users/login.html"), name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(template_name="users/logged_out.html"), name='logout'),
|
||||
|
||||
path('password_change/', views.PasswordChangeView.as_view(template_name="users/password_change_form.html"), name='password_change'),
|
||||
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name="users/password_change_done.html"), name='password_change_done'),
|
||||
|
||||
path('password_reset/', views.PasswordResetView.as_view(template_name="users/password_reset_form.html"), name='password_reset'),
|
||||
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name="users/password_reset_done.html"), name='password_reset_done'),
|
||||
path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(template_name="users/password_reset_confirm.html"), name='password_reset_confirm'),
|
||||
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name="users/password_reset_complete.html"), name='password_reset_complete'),
|
||||
]
|
|
@ -1,3 +1,59 @@
|
|||
from django.shortcuts import render
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.shortcuts import redirect
|
||||
from django.urls.base import reverse_lazy
|
||||
from django.views.generic.base import RedirectView
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
# Create your views here.
|
||||
from . import forms
|
||||
# from . import email
|
||||
|
||||
|
||||
class PasswordResetView(auth_views.PasswordResetView):
|
||||
email_template_name = 'users/password_reset_email.html'
|
||||
success_url = reverse_lazy('users:password_reset_done')
|
||||
|
||||
|
||||
class PasswordResetConfirmView(auth_views.PasswordResetConfirmView):
|
||||
success_url = reverse_lazy('users:password_reset_complete')
|
||||
|
||||
|
||||
class PasswordChangeView(auth_views.PasswordChangeView):
|
||||
success_url = reverse_lazy('users:password_change_done')
|
||||
|
||||
|
||||
class SignupView(FormView):
|
||||
|
||||
template_name = "users/signup.html"
|
||||
form_class = forms.SignupForm
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
user = form.save(commit=False)
|
||||
user.is_active = False
|
||||
user.set_password(form.cleaned_data['password1'])
|
||||
user.save()
|
||||
|
||||
# mail = email.UserConfirm(user=user)
|
||||
# mail.send_with_feedback(success_msg=_("An email was sent with a confirmation link"))
|
||||
|
||||
self.request.session["user_confirm_pending_id"] = user.id
|
||||
|
||||
return redirect("users:signup_confirm")
|
||||
|
||||
|
||||
class SignupConfirmView(TemplateView):
|
||||
|
||||
template_name = "users/signup_confirm.html"
|
||||
|
||||
|
||||
class SignupConfirmRedirectView(RedirectView):
|
||||
|
||||
def get_redirect_url(self):
|
||||
|
||||
uuid = self.kwargs['uuid']
|
||||
|
||||
if self.kwargs["token"] == forms.get_confirm_code(uuid):
|
||||
redirect("users:confirmed") # TODO
|
||||
|
||||
redirect("users:confirm_nope") # TODO
|
||||
|
|
Loading…
Reference in a new issue