Merge branch 'release/3.5.0'
This commit is contained in:
commit
6a95facf9e
@ -1,15 +1,50 @@
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
|
||||
from .models import User
|
||||
from .forms import AccountCreateForm, AccountUpdateForm
|
||||
from .forms import UserCreateForm, UserUpdateForm
|
||||
|
||||
class UserAdmin(UserAdmin):
|
||||
add_form = AccountCreateForm
|
||||
form = AccountUpdateForm
|
||||
model = User
|
||||
list_display = ['email', 'username',]
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
# The forms to add and change user instances
|
||||
form = UserUpdateForm
|
||||
add_form = UserCreateForm
|
||||
|
||||
# The fields to be used in displaying the User model.
|
||||
# These override the definitions on the base UserAdmin
|
||||
# that reference specific fields on auth.User.
|
||||
fieldsets = (
|
||||
(None, {"fields": ("email", "password")}),
|
||||
("Personal info", {"fields": ("first_name", "last_name")}),
|
||||
(
|
||||
"Permissions",
|
||||
{
|
||||
"fields": (
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"user_permissions",
|
||||
),
|
||||
},
|
||||
),
|
||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"classes": ("wide",),
|
||||
"fields": ("email", "password1", "password2"),
|
||||
},
|
||||
),
|
||||
)
|
||||
list_display = ("email", "first_name", "last_name", "is_staff")
|
||||
list_filter = ("is_staff", "is_superuser", "is_active", "groups")
|
||||
search_fields = ("email", "first_name", "last_name")
|
||||
ordering = ["email"]
|
||||
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
|
||||
|
||||
@ -1,29 +1,74 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
from allauth.account.forms import SignupForm
|
||||
from django.contrib.auth.forms import PasswordChangeForm, UserCreationForm
|
||||
from django.contrib.auth.forms import ReadOnlyPasswordHashField
|
||||
from django.core.exceptions import ValidationError
|
||||
from captcha.fields import CaptchaField
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
class AccountCreateForm(UserCreationForm):
|
||||
class UserCreateForm(forms.ModelForm):
|
||||
"""A form for creating new users. Includes all the required
|
||||
fields, plus a repeated password."""
|
||||
|
||||
required_css_class = "field-required"
|
||||
password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
|
||||
password2 = forms.CharField(
|
||||
label="Password confirmation", widget=forms.PasswordInput
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email')
|
||||
fields = ["email", "first_name", "last_name"]
|
||||
|
||||
def clean_password2(self):
|
||||
# Check that the two password entries match
|
||||
password1 = self.cleaned_data.get("password1")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
if password1 and password2 and password1 != password2:
|
||||
raise ValidationError("Passwords don't match")
|
||||
return password2
|
||||
|
||||
def save(self, commit=True):
|
||||
# Save the provided password in hashed format
|
||||
user = super().save(commit=False)
|
||||
user.set_password(self.cleaned_data["password1"])
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class AccountUpdateForm(UserChangeForm):
|
||||
class UserUpdateForm(forms.ModelForm):
|
||||
"""A form for updating users. Includes all the fields on
|
||||
the user, but replaces the password field with admin's
|
||||
disabled password hash display field.
|
||||
"""
|
||||
|
||||
required_css_class = "field-required"
|
||||
password = ReadOnlyPasswordHashField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = (
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
fields = [
|
||||
"email",
|
||||
"password",
|
||||
"first_name",
|
||||
"last_name",
|
||||
'shipping_street_address_1',
|
||||
'shipping_street_address_2',
|
||||
'shipping_city',
|
||||
'shipping_state',
|
||||
'shipping_postal_code',
|
||||
)
|
||||
"is_active",
|
||||
"is_superuser"
|
||||
]
|
||||
labels = {
|
||||
'shipping_street_address_1': 'Street line 1',
|
||||
'shipping_street_address_2': 'Street line 2',
|
||||
'shipping_city': 'City',
|
||||
'shipping_state': 'State',
|
||||
'shipping_postal_code': 'ZIP code',
|
||||
}
|
||||
|
||||
|
||||
class CustomerUpdateForm(forms.ModelForm):
|
||||
@ -55,14 +100,15 @@ class CustomerShippingAddressUpdateForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class UserSignupForm(SignupForm):
|
||||
first_name = forms.CharField(
|
||||
required=True,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'First name'})
|
||||
)
|
||||
last_name = forms.CharField(
|
||||
required=True,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'Last name'})
|
||||
)
|
||||
class AccountCreateForm(UserCreationForm):
|
||||
required_css_class = "field-required"
|
||||
captcha = CaptchaField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
"email",
|
||||
"first_name",
|
||||
"last_name",
|
||||
]
|
||||
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
# Generated by Django 4.1.6 on 2023-07-31 23:54
|
||||
|
||||
import accounts.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0005_remove_user_addresses_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='user',
|
||||
options={},
|
||||
),
|
||||
migrations.AlterModelManagers(
|
||||
name='user',
|
||||
managers=[
|
||||
('objects', accounts.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='username',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(max_length=255, unique=True, verbose_name='email address'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True, help_text='Designates whether this user should be treated as active.\nUnselect this instead of deleting accounts.', verbose_name='active'),
|
||||
),
|
||||
]
|
||||
@ -1,12 +1,89 @@
|
||||
import stripe
|
||||
from django.apps import apps
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.mail import send_mail
|
||||
from django.utils import timezone as tz
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import (
|
||||
BaseUserManager, AbstractBaseUser, PermissionsMixin
|
||||
)
|
||||
from localflavor.us.us_states import USPS_CHOICES
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
stripe_id = models.CharField(max_length=255, blank=True)
|
||||
class UserManager(BaseUserManager):
|
||||
use_in_migrations = True
|
||||
|
||||
def _create_user(self, email, password, **extra_fields):
|
||||
"""
|
||||
Create and save a user with the given email and password.
|
||||
"""
|
||||
if not email:
|
||||
raise ValueError("The given email must be set")
|
||||
email = self.normalize_email(email)
|
||||
# Lookup the real model class from the global app registry so this
|
||||
# manager method can be used in migrations. This is fine because
|
||||
# managers are by definition working on the real model.
|
||||
GlobalUserModel = apps.get_model(
|
||||
self.model._meta.app_label, self.model._meta.object_name
|
||||
)
|
||||
user = self.model(email=email, **extra_fields)
|
||||
user.password = make_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_user(self, email, password=None, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", False)
|
||||
extra_fields.setdefault("is_superuser", False)
|
||||
return self._create_user(email, password, **extra_fields)
|
||||
|
||||
def create_superuser(self, email, password=None, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", True)
|
||||
extra_fields.setdefault("is_superuser", True)
|
||||
|
||||
if extra_fields.get("is_staff") is not True:
|
||||
raise ValueError("Superuser must have is_staff=True.")
|
||||
if extra_fields.get("is_superuser") is not True:
|
||||
raise ValueError("Superuser must have is_superuser=True.")
|
||||
|
||||
return self._create_user(email, password, **extra_fields)
|
||||
|
||||
def with_perm(
|
||||
self, perm, is_active=True, include_superusers=True, backend=None, obj=None
|
||||
):
|
||||
if backend is None:
|
||||
backends = auth._get_backends(return_tuples=True)
|
||||
if len(backends) == 1:
|
||||
backend, _ = backends[0]
|
||||
else:
|
||||
raise ValueError(
|
||||
"You have multiple authentication backends configured and "
|
||||
"therefore must provide the `backend` argument."
|
||||
)
|
||||
elif not isinstance(backend, str):
|
||||
raise TypeError(
|
||||
"backend must be a dotted import path string (got %r)." % backend
|
||||
)
|
||||
else:
|
||||
backend = auth.load_backend(backend)
|
||||
if hasattr(backend, "with_perm"):
|
||||
return backend.with_perm(
|
||||
perm,
|
||||
is_active=is_active,
|
||||
include_superusers=include_superusers,
|
||||
obj=obj,
|
||||
)
|
||||
return self.none()
|
||||
|
||||
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
email = models.EmailField(
|
||||
verbose_name="email address",
|
||||
max_length=255,
|
||||
unique=True,
|
||||
)
|
||||
first_name = models.CharField("first name", max_length=150, blank=True)
|
||||
last_name = models.CharField("last name", max_length=150, blank=True)
|
||||
|
||||
# Shipping address
|
||||
shipping_street_address_1 = models.CharField(max_length=256, blank=True)
|
||||
@ -19,6 +96,25 @@ class User(AbstractUser):
|
||||
)
|
||||
shipping_postal_code = models.CharField(max_length=20, blank=True)
|
||||
|
||||
is_staff = models.BooleanField(
|
||||
"staff status",
|
||||
default=False,
|
||||
help_text="Designates whether the user can log into this admin site.",
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
"active",
|
||||
default=True,
|
||||
help_text="Designates whether this user should be treated as active.\n"
|
||||
+ "Unselect this instead of deleting accounts."
|
||||
)
|
||||
date_joined = models.DateTimeField("date joined", default=tz.now)
|
||||
stripe_id = models.CharField(max_length=255, blank=True)
|
||||
|
||||
USERNAME_FIELD = "email"
|
||||
REQUIRED_FIELDS = []
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
@property
|
||||
def has_shipping_address(self):
|
||||
if (self.shipping_street_address_1 != ''
|
||||
@ -29,12 +125,35 @@ class User(AbstractUser):
|
||||
return True
|
||||
return False
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
self.email = self.__class__.objects.normalize_email(self.email)
|
||||
|
||||
def get_full_name(self):
|
||||
"""
|
||||
Return the first_name plus the last_name, with a space in between.
|
||||
"""
|
||||
full_name = "%s %s" % (self.first_name, self.last_name)
|
||||
return full_name.strip()
|
||||
|
||||
def get_short_name(self):
|
||||
"""Return the short name for the user."""
|
||||
return self.first_name
|
||||
|
||||
def email_user(self, subject, message, from_email=None, **kwargs):
|
||||
"""Send an email to this user."""
|
||||
send_mail(subject, message, from_email, [self.email], **kwargs)
|
||||
|
||||
def get_or_create_stripe_id(self):
|
||||
if not self.stripe_id:
|
||||
response = stripe.Customer.create(
|
||||
name=self.first_name + ' ' + self.last_name,
|
||||
name=self.get_full_name(),
|
||||
email=self.email
|
||||
)
|
||||
self.stripe_id = response['id']
|
||||
self.stripe_id = response["id"]
|
||||
self.save()
|
||||
return self.stripe_id
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("storefront:customer-detail", kwargs={"pk": self.pk})
|
||||
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<section>
|
||||
<h1>Sign up</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<input type="submit" value="Create account">
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
||||
23
accounts/templates/accounts/account_create_form.html
Normal file
23
accounts/templates/accounts/account_create_form.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<section>
|
||||
<h1>Sign up</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table class="form-table">
|
||||
{{ form.as_table }}
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button type="submit">Create account</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
56
accounts/templates/accounts/account_detail.html
Executable file → Normal file
56
accounts/templates/accounts/account_detail.html
Executable file → Normal file
@ -1,13 +1,49 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block head_title %}{{ object.get_full_name }} | {% endblock head_title %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<section>
|
||||
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
|
||||
<p>{{ user.email }}</p>
|
||||
<p>
|
||||
<a href="{% url 'account-update' user.id %}">Update email/change password</a>
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
<header class="page-header">
|
||||
<h3>{{ object.get_full_name }}</h3>
|
||||
|
||||
{% if request.user.is_superuser or request.user == object%}
|
||||
<a href="{% url 'account-update' object.pk %}">Update account</a>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Email:</th>
|
||||
<td>{{ object.email }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<br><br>
|
||||
|
||||
<section>
|
||||
<h4>Students</h4>
|
||||
<table class="index">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Student</th>
|
||||
<th>Membership</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for student in object.student_set.all %}
|
||||
<tr>
|
||||
<td><a href="{{ student.get_absolute_url }}">{{ student.full_name }}</a></td>
|
||||
<td {% if not student.membership %}class="text-danger"{% endif %}>{{ student.membership }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="2">Could not find any students</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
23
accounts/templates/accounts/account_form.html
Executable file → Normal file
23
accounts/templates/accounts/account_form.html
Executable file → Normal file
@ -1,17 +1,22 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<section>
|
||||
<h1>Update Profile</h1>
|
||||
<header class="page-header">
|
||||
<h3>Update Profile</h3>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p><a href="{% url 'password_change' %}">Change password</a></p>
|
||||
<form method="post" action="{% url 'account-update' user.id %}">
|
||||
<form id="account-form" method="post" action="{% url 'account-update' user.pk %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<input type="submit" value="Save changes" class="btn"> or
|
||||
<a href="{% url 'account-detail' user.id %}">Cancel</a>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
<button form="account-form" type="submit" class="action-button">Save changes</button> or
|
||||
<a href="{% url 'account-detail' user.pk %}">Cancel</a>
|
||||
</p>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
||||
22
accounts/templates/accounts/account_list.html
Executable file → Normal file
22
accounts/templates/accounts/account_list.html
Executable file → Normal file
@ -1,21 +1,31 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<header class="page-header">
|
||||
<h3>Users</h3>
|
||||
<menu>
|
||||
<a href="{% url 'account-create' %}">
|
||||
<button class="btn btn-add">Create new account</button>
|
||||
</a>
|
||||
</menu>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h1>Users</h1>
|
||||
<table>
|
||||
<table class="index">
|
||||
<thead>
|
||||
<th>Username</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in user_list %}
|
||||
<tr>
|
||||
<td>{{ user.username }}</td>
|
||||
<td><a href="{% url 'account-detail' user.id %}">{{user.first_name}} {{user.last_name}}</a></td>
|
||||
<td><a href="{% url 'account-detail' user.id %}">{{ user.get_full_name }}</a></td>
|
||||
<td><a href="mailto:{{ user.email }}">{{ user.email }} ↗</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td>No users yet.</td></tr>
|
||||
<tr>
|
||||
<td colspan="3">No users yet.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
8
accounts/templates/accounts/base_accounts.html
Normal file
8
accounts/templates/accounts/base_accounts.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block head_title %}Accounts |{% endblock %}
|
||||
|
||||
{% block tabs %}
|
||||
{% include 'core/_partials/tabs.html' with active_tab='accounts' %}
|
||||
{% endblock %}
|
||||
|
||||
9
accounts/templates/accounts/logged_out.html
Normal file
9
accounts/templates/accounts/logged_out.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<section>
|
||||
<p>You have been logged out. <a href="{% url 'login' %}">Log in</a></p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
58
accounts/templates/accounts/login.html
Normal file
58
accounts/templates/accounts/login.html
Normal file
@ -0,0 +1,58 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>Log in</h1>
|
||||
{% 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 'login' %}">
|
||||
{% csrf_token %}
|
||||
<table class="form-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>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<small>
|
||||
Forgot your password?
|
||||
<a class="password__reset hover" href="{% url 'password_reset' %}">Reset your password here</a>.
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button type="submit">Log in</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
12
accounts/templates/accounts/password_change_done.html
Normal file
12
accounts/templates/accounts/password_change_done.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<section>
|
||||
<p>
|
||||
Password has been changed.
|
||||
<a href="{% url 'login' %}">Log in</a>
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
23
accounts/templates/accounts/password_change_form.html
Normal file
23
accounts/templates/accounts/password_change_form.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<p>
|
||||
<a href="{% url 'storefront:customer-detail' user.pk %}">← Back</a>
|
||||
</p>
|
||||
<form id="password-form" method="post">
|
||||
{% csrf_token %}
|
||||
<h3>Change password</h3>
|
||||
|
||||
<table class="form-table">
|
||||
{{ form.as_table }}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<button type="submit" form="password-form" class="action-button">Change my password</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
2
templates/account/password_reset_complete.html → accounts/templates/accounts/password_reset_complete.html
Executable file → Normal file
2
templates/account/password_reset_complete.html → accounts/templates/accounts/password_reset_complete.html
Executable file → Normal file
@ -1,7 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<article>
|
||||
<p>Password was reset successfully.</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
19
accounts/templates/accounts/password_reset_confirm.html
Normal file
19
accounts/templates/accounts/password_reset_confirm.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
{% if validlink %}
|
||||
<h1>Reset password</h1>
|
||||
<p>Enter a <em>new</em> password below.</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<p>
|
||||
<button type="submit" class="action-button">Reset your password</button>
|
||||
</p>
|
||||
</form>
|
||||
{% else %}
|
||||
<h1 class="text-danger">Password reset failed</h1>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
7
accounts/templates/accounts/password_reset_done.html
Normal file
7
accounts/templates/accounts/password_reset_done.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<p>An email with password reset instructions has been sent.</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
10
accounts/templates/accounts/password_reset_email.html
Normal file
10
accounts/templates/accounts/password_reset_email.html
Normal file
@ -0,0 +1,10 @@
|
||||
You're receiving this email because you requested a password reset for your user account at {{ site_name }}.
|
||||
|
||||
Please go to the following page and choose a new password:
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
|
||||
Your username, in case you’ve forgotten: {{ user.get_username }}
|
||||
|
||||
Sincerely,
|
||||
Cache Valley Martial Arts & Fitness
|
||||
|
||||
15
accounts/templates/accounts/password_reset_form.html
Normal file
15
accounts/templates/accounts/password_reset_form.html
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>Reset your password</h1>
|
||||
<p>Enter your email address below and we'll send you instructions on how to reset your password.</p>
|
||||
<form method="post" action="{% url 'password_reset' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<p>
|
||||
<button type="submit">Send me instructions</button>
|
||||
</p>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,11 +1,46 @@
|
||||
from django.urls import path, include
|
||||
from django.contrib.auth import views as auth_views
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.AccountListView.as_view(), name='account-list'),
|
||||
path('<int:pk>/', include([
|
||||
path('', views.AccountDetailView.as_view(), name='account-detail'),
|
||||
path('update/', views.AccountUpdateView.as_view(), name='account-update'),
|
||||
path('delete/', views.AccountDeleteView.as_view(), name='account-delete'),
|
||||
])),
|
||||
path("new/", views.AccountCreateView.as_view(), name="signup"),
|
||||
path(
|
||||
"login/",
|
||||
auth_views.LoginView.as_view(template_name="accounts/login.html"),
|
||||
name="login"
|
||||
),
|
||||
path("logout/", auth_views.LogoutView.as_view(template_name="accounts/logged_out.html"),
|
||||
name="logout"
|
||||
),
|
||||
path(
|
||||
"password-change/",
|
||||
auth_views.PasswordChangeView.as_view(template_name="accounts/password_change_form.html"),
|
||||
name="password_change"
|
||||
),
|
||||
path(
|
||||
"password-change/done/",
|
||||
auth_views.PasswordChangeDoneView.as_view(template_name="accounts/password_change_done.html"),
|
||||
name="password_change_done"
|
||||
),
|
||||
path(
|
||||
"password-reset/",
|
||||
auth_views.PasswordResetView.as_view(template_name="accounts/password_reset_form.html"),
|
||||
name="password_reset"
|
||||
),
|
||||
path(
|
||||
"password-reset/done/",
|
||||
auth_views.PasswordResetDoneView.as_view(template_name="accounts/password_reset_done.html"),
|
||||
name="password_reset_done"
|
||||
),
|
||||
path(
|
||||
"reset/<uidb64>/<token>/",
|
||||
auth_views.PasswordResetConfirmView.as_view(template_name="accounts/password_reset_confirm.html"),
|
||||
name="password_reset_confirm"
|
||||
),
|
||||
path(
|
||||
"reset/done/",
|
||||
auth_views.PasswordResetCompleteView.as_view(template_name="accounts/password_reset_complete.html"),
|
||||
name="password_reset_complete"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -1,38 +1,53 @@
|
||||
from django.shortcuts import render, reverse, redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.generic.list import ListView
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
|
||||
from .models import User
|
||||
from .forms import AccountUpdateForm
|
||||
from .forms import UserCreateForm
|
||||
|
||||
|
||||
class AccountListView(LoginRequiredMixin, ListView):
|
||||
class AccountCreateView(CreateView, SuccessMessageMixin):
|
||||
model = User
|
||||
template_name = 'accounts/account_list.html'
|
||||
template_name = "accounts/account_create_form.html"
|
||||
form_class = UserCreateForm
|
||||
success_message = "Account created. You are now logged in."
|
||||
|
||||
class AccountDetailView(LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'accounts/account_detail.html'
|
||||
def get_user(self, uidb64):
|
||||
try:
|
||||
# urlsafe_base64_decode() decodes to bytestring
|
||||
uid = urlsafe_base64_decode(uidb64).decode()
|
||||
user = UserModel._default_manager.get(pk=uid)
|
||||
except (
|
||||
TypeError,
|
||||
ValueError,
|
||||
OverflowError,
|
||||
UserModel.DoesNotExist,
|
||||
ValidationError,
|
||||
):
|
||||
user = None
|
||||
return user
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
return context
|
||||
|
||||
class AccountUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = User
|
||||
form_class = AccountUpdateForm
|
||||
template_name = 'accounts/account_form.html'
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
user = authenticate(
|
||||
self.request,
|
||||
username=form.cleaned_data['email'],
|
||||
password=form.cleaned_data['password1']
|
||||
)
|
||||
if user is not None:
|
||||
login(self.request, user)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return self.form_invalid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
pk = self.kwargs["pk"]
|
||||
return reverse('account-detail', kwargs={'pk': pk})
|
||||
return reverse("storefront:customer-detail", kwargs={"pk": self.object.pk})
|
||||
|
||||
class AccountDeleteView(LoginRequiredMixin, DeleteView):
|
||||
model = User
|
||||
success_url = reverse_lazy('account-list')
|
||||
|
||||
102
poetry.lock
generated
102
poetry.lock
generated
@ -447,17 +447,6 @@ sdist = ["setuptools-rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
|
||||
|
||||
[[package]]
|
||||
name = "defusedxml"
|
||||
version = "0.7.1"
|
||||
description = "XML bomb protection for Python stdlib modules"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
|
||||
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "4.1.6"
|
||||
@ -478,23 +467,6 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||
bcrypt = ["bcrypt"]
|
||||
|
||||
[[package]]
|
||||
name = "django-allauth"
|
||||
version = "0.52.0"
|
||||
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "django-allauth-0.52.0.tar.gz", hash = "sha256:e380661ceafe55734c40102819ae720403027036f28e9f9827f0faeddc24ed5f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Django = ">=2.0"
|
||||
pyjwt = {version = ">=1.7", extras = ["crypto"]}
|
||||
python3-openid = ">=3.0.8"
|
||||
requests = "*"
|
||||
requests-oauthlib = ">=0.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "django-analytical"
|
||||
version = "3.1.0"
|
||||
@ -917,22 +889,6 @@ files = [
|
||||
develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"]
|
||||
tests = ["pytest (>=4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.2.2"
|
||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
|
||||
{file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
rsa = ["cryptography (>=3.0.0)"]
|
||||
signals = ["blinker (>=1.4.0)"]
|
||||
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
|
||||
|
||||
[[package]]
|
||||
name = "outcome"
|
||||
version = "1.2.0"
|
||||
@ -1213,26 +1169,6 @@ files = [
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.6.0"
|
||||
description = "JSON Web Token implementation in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"},
|
||||
{file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""}
|
||||
|
||||
[package.extras]
|
||||
crypto = ["cryptography (>=3.4.0)"]
|
||||
dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
|
||||
docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
|
||||
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "23.0.0"
|
||||
@ -1293,24 +1229,6 @@ soap = ["zeep"]
|
||||
soap-alt = ["suds"]
|
||||
soap-fallback = ["PySimpleSOAP"]
|
||||
|
||||
[[package]]
|
||||
name = "python3-openid"
|
||||
version = "3.2.0"
|
||||
description = "OpenID support for modern servers and consumers."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
|
||||
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
defusedxml = "*"
|
||||
|
||||
[package.extras]
|
||||
mysql = ["mysql-connector-python"]
|
||||
postgresql = ["psycopg2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2022.7.1"
|
||||
@ -1393,24 +1311,6 @@ urllib3 = ">=1.21.1,<1.27"
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "requests-oauthlib"
|
||||
version = "1.3.1"
|
||||
description = "OAuthlib authentication support for Requests."
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
|
||||
{file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
oauthlib = ">=3.0.0"
|
||||
requests = ">=2.0.0"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "rjsmin"
|
||||
version = "1.2.1"
|
||||
@ -1728,4 +1628,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "fc52ce089cd76070e64e3f927f18a8f3a96e0b27e5377a9453d400520250f3f2"
|
||||
content-hash = "172f51231b971fe70bfd8a2b54b8c1778c95430dbca232d1e6f69ed305141650"
|
||||
|
||||
@ -75,9 +75,6 @@ INSTALLED_APPS = [
|
||||
'localflavor',
|
||||
'anymail',
|
||||
'compressor',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'analytical',
|
||||
'captcha',
|
||||
|
||||
@ -196,27 +193,10 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
},
|
||||
]
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = 'accounts.User'
|
||||
|
||||
|
||||
# All-auth
|
||||
# https://django-allauth.readthedocs.io/en/latest/installation.html
|
||||
|
||||
ACCOUNT_FORMS = {'signup': 'accounts.forms.UserSignupForm'}
|
||||
ACCOUNT_ADAPTER = 'accounts.adapter.AccountAdapter'
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
ACCOUNT_USERNAME_REQUIRED = False
|
||||
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
||||
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = True
|
||||
ACCOUNT_SESSION_REMEMBER = True
|
||||
ACCOUNT_UNIQUE_EMAIL = True
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.1/topics/i18n/
|
||||
|
||||
|
||||
@ -6,8 +6,7 @@ from django.conf.urls.static import static
|
||||
urlpatterns = [
|
||||
path('', include(('storefront.urls', 'storefront'), namespace='storefront')),
|
||||
path('dashboard/', include(('dashboard.urls', 'dashboard'), namespace='dashboard')),
|
||||
path('accounts/', include('allauth.urls')),
|
||||
path('accounts/', include(('accounts.urls', 'accounts'), namespace='accounts')),
|
||||
path("accounts/", include("accounts.urls")),
|
||||
path('admin/', admin.site.urls),
|
||||
path('captcha/', include('captcha.urls')),
|
||||
]
|
||||
|
||||
@ -9,7 +9,6 @@ readme = "README.md"
|
||||
python = "^3.11"
|
||||
django = "^4.1.5"
|
||||
celery = {extras = ["redis"], version = "^5.2.7"}
|
||||
django-allauth = "^0.52.0"
|
||||
django-anymail = {extras = ["mailgun"], version = "^9.0"}
|
||||
django-compressor = "^4.1"
|
||||
django-filter = "^22.1"
|
||||
|
||||
@ -22,26 +22,12 @@ closeBtn.addEventListener("click", event => {
|
||||
|
||||
showBtn.addEventListener("click", event => {
|
||||
modal.style.display = "flex";
|
||||
setCookie('newsletter-modal', 'true', oneDay)
|
||||
})
|
||||
|
||||
const scrollFunction = () => {
|
||||
let modalDismissed = getCookie('newsletter-modal')
|
||||
if (modalDismissed != 'true') {
|
||||
if (document.body.scrollTop > 600 || document.documentElement.scrollTop > 600) {
|
||||
modal.style.display = "flex";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onscroll = () => {
|
||||
scrollFunction();
|
||||
};
|
||||
|
||||
// When the user clicks anywhere outside of the modal, close it
|
||||
window.addEventListener("click", event => {
|
||||
if (event.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
setCookie('newsletter-modal', 'true', oneDay)
|
||||
});
|
||||
|
||||
@ -54,7 +54,6 @@ h1, h2, h3, h4, h5 {
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
font-family: 'Vollkorn', serif;
|
||||
font-size: 2.488rem;
|
||||
}
|
||||
|
||||
@ -129,16 +128,17 @@ table a {
|
||||
.form-table {
|
||||
width: unset;
|
||||
border: none;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 1rem;
|
||||
}
|
||||
.form-table th,
|
||||
.form-table td {
|
||||
border: none;
|
||||
}
|
||||
.form-table th {
|
||||
padding: 0 1rem 1rem 0;
|
||||
}
|
||||
.form-table th,
|
||||
.form-table td {
|
||||
padding: 0 0 1rem 0;
|
||||
padding: 0 1rem 1rem 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
||||
@ -473,7 +473,6 @@ section:not(:last-child) {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
font-family: 'Vollkorn', serif;
|
||||
}
|
||||
|
||||
.carousel {
|
||||
@ -499,7 +498,6 @@ section:not(:last-child) {
|
||||
padding: 2rem 1rem;
|
||||
box-sizing: border-box;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
font-family: 'Vollkorn', serif;
|
||||
color: white;
|
||||
|
||||
display: flex;
|
||||
@ -750,7 +748,6 @@ article + article {
|
||||
.product__item h3,
|
||||
.product__info h1 {
|
||||
/*text-decoration: underline;*/
|
||||
font-family: "Vollkorn", serif;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<section>
|
||||
<h2>My Account</h2>
|
||||
<p>
|
||||
<a href="{% url 'account_change_password' %}">Change password</a>
|
||||
<a href="{% url 'password_change' %}">Change password</a>
|
||||
</p>
|
||||
<div class="customer__detail-section">
|
||||
<div>
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "E-mail Addresses" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<p><a href="{% url 'storefront:customer-detail' user.pk %}">← Back</a></p>
|
||||
<section>
|
||||
<h1>{% trans "E-mail Addresses" %}</h1>
|
||||
</section>
|
||||
<section class="user__emails">
|
||||
{% if user.emailaddress_set.all %}
|
||||
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
|
||||
<form action="{% url 'account_email' %}" class="email_list" method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
{% for emailaddress in user.emailaddress_set.all %}
|
||||
<div>
|
||||
<input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/>
|
||||
<label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
|
||||
|
||||
|
||||
{{ emailaddress.email }}
|
||||
{% if emailaddress.verified %}
|
||||
<span class="verified">{% trans "Verified" %}</span>
|
||||
{% else %}
|
||||
<span class="unverified">{% trans "Unverified" %}</span>
|
||||
{% endif %}
|
||||
{% if emailaddress.primary %}<span class="primary">{% trans "Primary" %}</span>{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div>
|
||||
<button type="submit" name="action_primary" >{% trans 'Make Primary' %}</button>
|
||||
<button type="submit" name="action_send" >{% trans 'Re-send Verification' %}</button>
|
||||
<button type="submit" name="action_remove" >{% trans 'Remove' %}</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% else %}
|
||||
<p><strong>{% trans 'Warning:'%}</strong> {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
<section>
|
||||
{% if can_add_email %}
|
||||
<h2>{% trans "Add E-mail Address" %}</h2>
|
||||
|
||||
<form method="post" action="{% url 'account_email' %}" class="add_email">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button name="action_add" type="submit">{% trans "Add E-mail" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
</article>
|
||||
|
||||
|
||||
{% block extra_body %}
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
var message = "{% trans 'Do you really want to remove the selected e-mail address?' %}";
|
||||
var actions = document.getElementsByName('action_remove');
|
||||
if (actions.length) {
|
||||
actions[0].addEventListener("click", function(e) {
|
||||
if (! confirm(message)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,34 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>{% trans "Confirm E-mail Address" %}</h1>
|
||||
|
||||
{% if confirmation %}
|
||||
|
||||
{% user_display confirmation.email_address.user as user_display %}
|
||||
|
||||
<p>{% blocktrans with confirmation.email_address.email as email %}Please confirm that <a href="mailto:{{ email }}">{{ email }}</a> is an e-mail address for user {{ user_display }}.{% endblocktrans %}</p>
|
||||
|
||||
<form method="post" action="{% url 'account_confirm_email' confirmation.key %}">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<button type="submit">{% trans 'Confirm' %}</button>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
||||
{% url 'account_email' as email_url %}
|
||||
|
||||
<p>{% blocktrans %}This e-mail confirmation link expired or is invalid. Please <a href="{{ email_url }}">issue a new e-mail confirmation request</a>.{% endblocktrans %}</p>
|
||||
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,9 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<section>
|
||||
<p>You have been logged out. <a href="{% url 'login' %}">Log in</a></p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,22 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<h1>Log in</h1>
|
||||
{% if form.errors %}
|
||||
<p class="error">Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'account_login' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<p>
|
||||
<small><a href="{% url 'account_reset_password' %}">Forgot your password?</a></small>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Login" class="btn">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</p>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,18 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<h1>Log Out</h1>
|
||||
<p>Are you sure you want to log out?</p>
|
||||
|
||||
<form method="post" action="{% url 'account_logout' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<p>
|
||||
<input type="submit" value="Log out" class="btn">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</p>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,33 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block head_title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<p><a href="{% url 'storefront:customer-detail' user.pk %}">← Back</a></p>
|
||||
<h1>{% trans "Change Password" %}</h1>
|
||||
<a href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
|
||||
</header>
|
||||
|
||||
<form method="POST" action="{% url 'account_change_password' %}" class="password_change">
|
||||
{% csrf_token %}
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
{{ form.as_table }}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<button type="submit">Save changes</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,12 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<section>
|
||||
<p>
|
||||
Password has been changed.
|
||||
<a href="{% url 'login' %}" class="btn">Log in</a>
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,14 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article class="panel">
|
||||
<h1>Change password</h1>
|
||||
<form method="post" action="{% url 'password_change' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<input type="submit" value="Change my password" class="btn"> or
|
||||
<a href="{{request.META.HTTP_REFERER}}">Cancel</a>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,25 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% block head_title %}{% trans "Password Reset" %} | {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>{% trans "Password Reset" %}</h1>
|
||||
{% if user.is_authenticated %}
|
||||
{% include "account/snippets/already_logged_in.html" %}
|
||||
{% endif %}
|
||||
|
||||
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
|
||||
|
||||
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="{% trans 'Reset My Password' %}" />
|
||||
</form>
|
||||
|
||||
<p>{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,24 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if validlink %}
|
||||
<article class="panel">
|
||||
<h1>Reset password</h1>
|
||||
<p>Enter a <em>new</em> password below.</p>
|
||||
<form method="post" action=".">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
<input type="submit" value="Reset your password" class="btn">
|
||||
</form>
|
||||
</article>
|
||||
{% else %}
|
||||
|
||||
<article class="panel">
|
||||
<h1 class="error">Password reset failed</h1>
|
||||
</article>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@ -1,18 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load account %}
|
||||
|
||||
{% block head_title %}{% trans "Password Reset" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>{% trans "Password Reset" %}</h1>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
{% include "account/snippets/already_logged_in.html" %}
|
||||
{% endif %}
|
||||
|
||||
<p>{% blocktrans %}We have sent you an e-mail. If you have not received it please check your spam folder. Otherwise contact us if you do not receive it in a few minutes.{% endblocktrans %}</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,10 +0,0 @@
|
||||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktranslate %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktranslate %}
|
||||
|
||||
{% translate "Please go to the following page and choose a new password:" %}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
{% endblock %}
|
||||
{% translate 'Your username, in case you’ve forgotten:' %} {{ user.get_username }}
|
||||
|
||||
{% endautoescape %}
|
||||
@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block head_title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}</h1>
|
||||
|
||||
{% if token_fail %}
|
||||
{% url 'account_reset_password' as passwd_reset_url %}
|
||||
<p>{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a <a href="{{ passwd_reset_url }}">new password reset</a>.{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
<form method="POST" action="{{ action_url }}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="action" value="{% trans 'change password' %}"/>
|
||||
</form>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,11 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block head_title %}{% trans "Change Password" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>{% trans "Change Password" %}</h1>
|
||||
<p>{% trans 'Your password is now changed.' %}</p>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,17 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>Sign up</h1>
|
||||
|
||||
<form method="post" action="{% url 'account_signup' %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<p><small>By clicking you agree to the <a href="{% url 'storefront:terms-and-conditions' %}">Terms and conditions</a> and <a href="{% url 'storefront:privacy-policy' %}">Privacy Policy</a>.</small></p>
|
||||
<p>
|
||||
<input type="submit" value="Create account" class="btn">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</p>
|
||||
</form>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -1,6 +1,5 @@
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
{% load account %}
|
||||
{% load analytical %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@ -19,7 +18,7 @@
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Vollkorn:wght@900&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;400;700;900&display=swap" rel="stylesheet">
|
||||
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'styles/normalize.css' %}">
|
||||
@ -69,14 +68,14 @@
|
||||
<li><a class="nav__link" href="{% url 'dashboard:home' %}">Dashboard</a></li>
|
||||
{% endif %}
|
||||
<li><a class="nav__link" href="{% url 'storefront:customer-detail' user.pk %}">Orders</a></li>
|
||||
<li><a class="nav__link" href="{% url 'account_logout' %}">Logout</a></li>
|
||||
<li><a class="nav__link" href="{% url 'logout' %}">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<ul class="nav__list nav__account">
|
||||
<li><a class="nav__link" href="{% url 'account_login' %}">Login</a></li>
|
||||
<li><a class="nav__link" href="{% url 'login' %}">Login</a></li>
|
||||
<li>/</li>
|
||||
<li><a class="nav__link" href="{% url 'account_signup' %}">Signup</a></li>
|
||||
<li><a class="nav__link" href="{% url 'signup' %}">Signup</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<a class="site__cart" href="{% url 'storefront:cart-detail' %}">
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;400;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;400;700;900&display=swap" rel="stylesheet">
|
||||
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "styles/dashboard.css" %}">
|
||||
@ -65,11 +65,11 @@
|
||||
<a href="{% url 'storefront:product-list' %}">Storefront</a>
|
||||
{% if user.is_authenticated %}
|
||||
<a href="account">{{ user.get_full_name }}</a>
|
||||
<a href="{% url 'account_logout' %}">Log Out</a>
|
||||
<a href="{% url 'logout' %}">Log Out</a>
|
||||
{% else %}
|
||||
<p>You are not logged in</p>
|
||||
<a href="{% url 'account_login' %}">Log In</a> |
|
||||
<a href="{% url 'account_signup' %}">Sign Up</a>
|
||||
<a href="{% url 'login' %}">Log In</a> |
|
||||
<a href="{% url 'signup' %}">Sign Up</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user