diff --git a/project/settings/base.py b/project/settings/base.py
index 158923b..513b05d 100644
--- a/project/settings/base.py
+++ b/project/settings/base.py
@@ -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'
diff --git a/project/templates/base.html b/project/templates/base.html
index 21f240c..6e3343c 100644
--- a/project/templates/base.html
+++ b/project/templates/base.html
@@ -13,11 +13,11 @@
diff --git a/project/templates/index.html b/project/templates/index.html
new file mode 100644
index 0000000..94d9808
--- /dev/null
+++ b/project/templates/index.html
@@ -0,0 +1 @@
+{% extends "base.html" %}
diff --git a/project/urls.py b/project/urls.py
index 0e3265b..41148c9 100644
--- a/project/urls.py
+++ b/project/urls.py
@@ -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),
]
diff --git a/project/views.py b/project/views.py
new file mode 100644
index 0000000..b5bf757
--- /dev/null
+++ b/project/views.py
@@ -0,0 +1,5 @@
+from django.shortcuts import render_to_response
+
+
+def index(request):
+ return render_to_response("index.html")
diff --git a/users/forms.py b/users/forms.py
new file mode 100644
index 0000000..bcbeaca
--- /dev/null
+++ b/users/forms.py
@@ -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",)
diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py
index 16d03e3..b9ab905 100644
--- a/users/migrations/0001_initial.py
+++ b/users/migrations/0001_initial.py
@@ -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',
+ },
),
]
diff --git a/users/models.py b/users/models.py
index 275e626..5c96291 100644
--- a/users/models.py
+++ b/users/models.py
@@ -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")
diff --git a/users/templates/users/logged_out.html b/users/templates/users/logged_out.html
new file mode 100644
index 0000000..9466a31
--- /dev/null
+++ b/users/templates/users/logged_out.html
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+{% trans "Thanks for spending some quality time with the Web site today." %}
+
+{% trans 'Log in again' %}
+
+{% endblock %}
diff --git a/users/templates/users/login.html b/users/templates/users/login.html
new file mode 100644
index 0000000..7f034b4
--- /dev/null
+++ b/users/templates/users/login.html
@@ -0,0 +1,39 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+{% if form.errors %}
+Your username and password didn't match. Please try again.
+{% endif %}
+
+{% if next %}
+ {% if user.is_authenticated %}
+ Your account doesn't have access to this page. To proceed,
+ please login with an account that has access.
+ {% else %}
+ Please login to see this page.
+ {% endif %}
+{% endif %}
+
+
+
+{# Assumes you setup the password_reset view in your URLconf #}
+Lost password?
+
+{% endblock %}
diff --git a/users/templates/users/password_change_done.html b/users/templates/users/password_change_done.html
new file mode 100644
index 0000000..d167eed
--- /dev/null
+++ b/users/templates/users/password_change_done.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}{{ title }}{% endblock %}
+{% block content_title %}{{ title }}
{% endblock %}
+{% block content %}
+{% trans 'Your password was changed.' %}
+{% endblock %}
diff --git a/users/templates/users/password_change_form.html b/users/templates/users/password_change_form.html
new file mode 100644
index 0000000..bc002b3
--- /dev/null
+++ b/users/templates/users/password_change_form.html
@@ -0,0 +1,52 @@
+{% extends "base.html" %}
+{% load i18n static %}
+
+{% block title %}{{ title }}{% endblock %}
+{% block content_title %}{{ title }}
{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/users/templates/users/password_reset_complete.html b/users/templates/users/password_reset_complete.html
new file mode 100644
index 0000000..f633333
--- /dev/null
+++ b/users/templates/users/password_reset_complete.html
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}{{ title }}{% endblock %}
+{% block content_title %}{{ title }}
{% endblock %}
+
+{% block content %}
+
+{% trans "Your password has been set. You may go ahead and log in now." %}
+
+{% trans 'Log in' %}
+
+{% endblock %}
diff --git a/users/templates/users/password_reset_confirm.html b/users/templates/users/password_reset_confirm.html
new file mode 100644
index 0000000..990be75
--- /dev/null
+++ b/users/templates/users/password_reset_confirm.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+{% load i18n static %}
+
+{% block title %}{{ title }}{% endblock %}
+{% block content_title %}{{ title }}
{% endblock %}
+{% block content %}
+
+{% if validlink %}
+
+{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}
+
+
+
+{% else %}
+
+{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}
+
+{% endif %}
+
+{% endblock %}
diff --git a/users/templates/users/password_reset_done.html b/users/templates/users/password_reset_done.html
new file mode 100644
index 0000000..c097748
--- /dev/null
+++ b/users/templates/users/password_reset_done.html
@@ -0,0 +1,12 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block title %}{{ title }}{% endblock %}
+{% block content_title %}{{ title }}
{% endblock %}
+{% block content %}
+
+{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}
+
+{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}
+
+{% endblock %}
diff --git a/users/templates/users/password_reset_email.html b/users/templates/users/password_reset_email.html
new file mode 100644
index 0000000..f1afa47
--- /dev/null
+++ b/users/templates/users/password_reset_email.html
@@ -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 %}
diff --git a/users/templates/users/password_reset_form.html b/users/templates/users/password_reset_form.html
new file mode 100644
index 0000000..6f55d5c
--- /dev/null
+++ b/users/templates/users/password_reset_form.html
@@ -0,0 +1,19 @@
+{% extends "base.html" %}
+{% load i18n static %}
+
+{% block title %}{{ title }}{% endblock %}
+{% block content_title %}{{ title }}
{% endblock %}
+{% block content %}
+
+{% trans "Forgotten password?" %}
+
+{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}
+
+
+
+{% endblock %}
diff --git a/users/templates/users/signup.html b/users/templates/users/signup.html
new file mode 100644
index 0000000..b47dd9b
--- /dev/null
+++ b/users/templates/users/signup.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+{% trans "Sign up" %}
+
+ {% if form.errors %}
+ {% endif %}
+
+
+
+ {% trans "Already have an account? Log in..." %}
+
+{% endblock %}
diff --git a/users/templates/users/signup_confirm.html b/users/templates/users/signup_confirm.html
new file mode 100644
index 0000000..ab0eb7d
--- /dev/null
+++ b/users/templates/users/signup_confirm.html
@@ -0,0 +1,10 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+
+{% trans "Confirm your email" %}
+
+{% trans "You've got mail - click the link or copy paste it to this browser session and you'll be logged in." %}
+
+{% endblock %}
diff --git a/users/urls.py b/users/urls.py
new file mode 100644
index 0000000..5651d02
--- /dev/null
+++ b/users/urls.py
@@ -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///', 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'),
+]
diff --git a/users/views.py b/users/views.py
index 91ea44a..631f57e 100644
--- a/users/views.py
+++ b/users/views.py
@@ -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