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 import admin
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
|
||||||
|
|
||||||
from .models import User
|
from .models import User
|
||||||
from .forms import AccountCreateForm, AccountUpdateForm
|
from .forms import UserCreateForm, UserUpdateForm
|
||||||
|
|
||||||
class UserAdmin(UserAdmin):
|
|
||||||
add_form = AccountCreateForm
|
class UserAdmin(BaseUserAdmin):
|
||||||
form = AccountUpdateForm
|
# The forms to add and change user instances
|
||||||
model = User
|
form = UserUpdateForm
|
||||||
list_display = ['email', 'username',]
|
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)
|
admin.site.register(User, UserAdmin)
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +1,74 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
from django.contrib.auth.forms import PasswordChangeForm, UserCreationForm
|
||||||
from allauth.account.forms import SignupForm
|
from django.contrib.auth.forms import ReadOnlyPasswordHashField
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from captcha.fields import CaptchaField
|
from captcha.fields import CaptchaField
|
||||||
|
|
||||||
from .models import User
|
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:
|
class Meta:
|
||||||
model = User
|
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:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = (
|
fields = [
|
||||||
'first_name',
|
"email",
|
||||||
'last_name',
|
"password",
|
||||||
'email',
|
"first_name",
|
||||||
|
"last_name",
|
||||||
'shipping_street_address_1',
|
'shipping_street_address_1',
|
||||||
'shipping_street_address_2',
|
'shipping_street_address_2',
|
||||||
'shipping_city',
|
'shipping_city',
|
||||||
'shipping_state',
|
'shipping_state',
|
||||||
'shipping_postal_code',
|
'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):
|
class CustomerUpdateForm(forms.ModelForm):
|
||||||
@ -55,14 +100,15 @@ class CustomerShippingAddressUpdateForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserSignupForm(SignupForm):
|
class AccountCreateForm(UserCreationForm):
|
||||||
first_name = forms.CharField(
|
required_css_class = "field-required"
|
||||||
required=True,
|
|
||||||
widget=forms.TextInput(attrs={'placeholder': 'First name'})
|
|
||||||
)
|
|
||||||
last_name = forms.CharField(
|
|
||||||
required=True,
|
|
||||||
widget=forms.TextInput(attrs={'placeholder': 'Last name'})
|
|
||||||
)
|
|
||||||
captcha = CaptchaField()
|
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
|
import stripe
|
||||||
|
from django.apps import apps
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
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
|
from localflavor.us.us_states import USPS_CHOICES
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class UserManager(BaseUserManager):
|
||||||
stripe_id = models.CharField(max_length=255, blank=True)
|
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 address
|
||||||
shipping_street_address_1 = models.CharField(max_length=256, blank=True)
|
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)
|
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
|
@property
|
||||||
def has_shipping_address(self):
|
def has_shipping_address(self):
|
||||||
if (self.shipping_street_address_1 != ''
|
if (self.shipping_street_address_1 != ''
|
||||||
@ -29,12 +125,35 @@ class User(AbstractUser):
|
|||||||
return True
|
return True
|
||||||
return False
|
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):
|
def get_or_create_stripe_id(self):
|
||||||
if not self.stripe_id:
|
if not self.stripe_id:
|
||||||
response = stripe.Customer.create(
|
response = stripe.Customer.create(
|
||||||
name=self.first_name + ' ' + self.last_name,
|
name=self.get_full_name(),
|
||||||
email=self.email
|
email=self.email
|
||||||
)
|
)
|
||||||
self.stripe_id = response['id']
|
self.stripe_id = response["id"]
|
||||||
self.save()
|
self.save()
|
||||||
return self.stripe_id
|
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 %}
|
{% block content %}
|
||||||
<article class="panel">
|
<header class="page-header">
|
||||||
<section>
|
<h3>{{ object.get_full_name }}</h3>
|
||||||
<h1>{{ user.first_name }} {{ user.last_name }}</h1>
|
|
||||||
<p>{{ user.email }}</p>
|
{% if request.user.is_superuser or request.user == object%}
|
||||||
<p>
|
<a href="{% url 'account-update' object.pk %}">Update account</a>
|
||||||
<a href="{% url 'account-update' user.id %}">Update email/change password</a>
|
{% endif %}
|
||||||
</p>
|
</header>
|
||||||
</section>
|
|
||||||
</article>
|
<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 %}
|
{% 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' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<header class="page-header">
|
||||||
<section>
|
<h3>Update Profile</h3>
|
||||||
<h1>Update Profile</h1>
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
<p><a href="{% url 'password_change' %}">Change password</a></p>
|
<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 %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
|
|
||||||
<input type="submit" value="Save changes" class="btn"> or
|
|
||||||
<a href="{% url 'account-detail' user.id %}">Cancel</a>
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</article>
|
|
||||||
|
<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 %}
|
{% 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' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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>
|
<section>
|
||||||
<h1>Users</h1>
|
<table class="index">
|
||||||
<table>
|
|
||||||
<thead>
|
<thead>
|
||||||
<th>Username</th>
|
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in user_list %}
|
{% for user in user_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user.username }}</td>
|
<td><a href="{% url 'account-detail' user.id %}">{{ user.get_full_name }}</a></td>
|
||||||
<td><a href="{% url 'account-detail' user.id %}">{{user.first_name}} {{user.last_name}}</a></td>
|
<td><a href="mailto:{{ user.email }}">{{ user.email }} ↗</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr><td>No users yet.</td></tr>
|
<tr>
|
||||||
|
<td colspan="3">No users yet.</td>
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article class="panel">
|
<article>
|
||||||
<p>Password was reset successfully.</p>
|
<p>Password was reset successfully.</p>
|
||||||
</article>
|
</article>
|
||||||
{% endblock %}
|
{% 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.urls import path, include
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.AccountListView.as_view(), name='account-list'),
|
path("new/", views.AccountCreateView.as_view(), name="signup"),
|
||||||
path('<int:pk>/', include([
|
path(
|
||||||
path('', views.AccountDetailView.as_view(), name='account-detail'),
|
"login/",
|
||||||
path('update/', views.AccountUpdateView.as_view(), name='account-update'),
|
auth_views.LoginView.as_view(template_name="accounts/login.html"),
|
||||||
path('delete/', views.AccountDeleteView.as_view(), name='account-delete'),
|
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.shortcuts import render, reverse, redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
|
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
|
||||||
from django.views.generic.detail import DetailView
|
from django.views.generic.detail import DetailView
|
||||||
from django.views.generic.list import ListView
|
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.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.forms import PasswordChangeForm
|
from django.contrib.auth.forms import PasswordChangeForm
|
||||||
|
|
||||||
from .models import User
|
from .models import User
|
||||||
from .forms import AccountUpdateForm
|
from .forms import UserCreateForm
|
||||||
|
|
||||||
|
|
||||||
class AccountListView(LoginRequiredMixin, ListView):
|
class AccountCreateView(CreateView, SuccessMessageMixin):
|
||||||
model = User
|
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):
|
def get_user(self, uidb64):
|
||||||
model = User
|
try:
|
||||||
template_name = 'accounts/account_detail.html'
|
# 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):
|
def form_valid(self, form):
|
||||||
context = super().get_context_data(**kwargs)
|
self.object = form.save()
|
||||||
return context
|
user = authenticate(
|
||||||
|
self.request,
|
||||||
class AccountUpdateView(LoginRequiredMixin, UpdateView):
|
username=form.cleaned_data['email'],
|
||||||
model = User
|
password=form.cleaned_data['password1']
|
||||||
form_class = AccountUpdateForm
|
)
|
||||||
template_name = 'accounts/account_form.html'
|
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):
|
def get_success_url(self):
|
||||||
pk = self.kwargs["pk"]
|
return reverse("storefront:customer-detail", kwargs={"pk": self.object.pk})
|
||||||
return reverse('account-detail', kwargs={'pk': 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)"]
|
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"]
|
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]]
|
[[package]]
|
||||||
name = "django"
|
name = "django"
|
||||||
version = "4.1.6"
|
version = "4.1.6"
|
||||||
@ -478,23 +467,6 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
|||||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||||
bcrypt = ["bcrypt"]
|
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]]
|
[[package]]
|
||||||
name = "django-analytical"
|
name = "django-analytical"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
@ -917,22 +889,6 @@ files = [
|
|||||||
develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"]
|
develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"]
|
||||||
tests = ["pytest (>=4.6)"]
|
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]]
|
[[package]]
|
||||||
name = "outcome"
|
name = "outcome"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -1213,26 +1169,6 @@ files = [
|
|||||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
{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]]
|
[[package]]
|
||||||
name = "pyopenssl"
|
name = "pyopenssl"
|
||||||
version = "23.0.0"
|
version = "23.0.0"
|
||||||
@ -1293,24 +1229,6 @@ soap = ["zeep"]
|
|||||||
soap-alt = ["suds"]
|
soap-alt = ["suds"]
|
||||||
soap-fallback = ["PySimpleSOAP"]
|
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]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2022.7.1"
|
version = "2022.7.1"
|
||||||
@ -1393,24 +1311,6 @@ urllib3 = ">=1.21.1,<1.27"
|
|||||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
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]]
|
[[package]]
|
||||||
name = "rjsmin"
|
name = "rjsmin"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@ -1728,4 +1628,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "fc52ce089cd76070e64e3f927f18a8f3a96e0b27e5377a9453d400520250f3f2"
|
content-hash = "172f51231b971fe70bfd8a2b54b8c1778c95430dbca232d1e6f69ed305141650"
|
||||||
|
|||||||
@ -75,9 +75,6 @@ INSTALLED_APPS = [
|
|||||||
'localflavor',
|
'localflavor',
|
||||||
'anymail',
|
'anymail',
|
||||||
'compressor',
|
'compressor',
|
||||||
'allauth',
|
|
||||||
'allauth.account',
|
|
||||||
'allauth.socialaccount',
|
|
||||||
'analytical',
|
'analytical',
|
||||||
'captcha',
|
'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'
|
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
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.1/topics/i18n/
|
# https://docs.djangoproject.com/en/4.1/topics/i18n/
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,7 @@ from django.conf.urls.static import static
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(('storefront.urls', 'storefront'), namespace='storefront')),
|
path('', include(('storefront.urls', 'storefront'), namespace='storefront')),
|
||||||
path('dashboard/', include(('dashboard.urls', 'dashboard'), namespace='dashboard')),
|
path('dashboard/', include(('dashboard.urls', 'dashboard'), namespace='dashboard')),
|
||||||
path('accounts/', include('allauth.urls')),
|
path("accounts/", include("accounts.urls")),
|
||||||
path('accounts/', include(('accounts.urls', 'accounts'), namespace='accounts')),
|
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('captcha/', include('captcha.urls')),
|
path('captcha/', include('captcha.urls')),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -9,7 +9,6 @@ readme = "README.md"
|
|||||||
python = "^3.11"
|
python = "^3.11"
|
||||||
django = "^4.1.5"
|
django = "^4.1.5"
|
||||||
celery = {extras = ["redis"], version = "^5.2.7"}
|
celery = {extras = ["redis"], version = "^5.2.7"}
|
||||||
django-allauth = "^0.52.0"
|
|
||||||
django-anymail = {extras = ["mailgun"], version = "^9.0"}
|
django-anymail = {extras = ["mailgun"], version = "^9.0"}
|
||||||
django-compressor = "^4.1"
|
django-compressor = "^4.1"
|
||||||
django-filter = "^22.1"
|
django-filter = "^22.1"
|
||||||
|
|||||||
@ -22,26 +22,12 @@ closeBtn.addEventListener("click", event => {
|
|||||||
|
|
||||||
showBtn.addEventListener("click", event => {
|
showBtn.addEventListener("click", event => {
|
||||||
modal.style.display = "flex";
|
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
|
// When the user clicks anywhere outside of the modal, close it
|
||||||
window.addEventListener("click", event => {
|
window.addEventListener("click", event => {
|
||||||
if (event.target == modal) {
|
if (event.target == modal) {
|
||||||
modal.style.display = "none";
|
modal.style.display = "none";
|
||||||
}
|
}
|
||||||
setCookie('newsletter-modal', 'true', oneDay)
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,7 +54,6 @@ h1, h2, h3, h4, h5 {
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
font-family: 'Vollkorn', serif;
|
|
||||||
font-size: 2.488rem;
|
font-size: 2.488rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,16 +128,17 @@ table a {
|
|||||||
.form-table {
|
.form-table {
|
||||||
width: unset;
|
width: unset;
|
||||||
border: none;
|
border: none;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0 1rem;
|
||||||
}
|
}
|
||||||
.form-table th,
|
.form-table th,
|
||||||
.form-table td {
|
.form-table td {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.form-table th {
|
.form-table th,
|
||||||
padding: 0 1rem 1rem 0;
|
|
||||||
}
|
|
||||||
.form-table td {
|
.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;
|
text-align: center;
|
||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
text-shadow: 1px 1px 2px black;
|
text-shadow: 1px 1px 2px black;
|
||||||
font-family: 'Vollkorn', serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel {
|
.carousel {
|
||||||
@ -499,7 +498,6 @@ section:not(:last-child) {
|
|||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-shadow: 1px 1px 2px black;
|
text-shadow: 1px 1px 2px black;
|
||||||
font-family: 'Vollkorn', serif;
|
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -750,7 +748,6 @@ article + article {
|
|||||||
.product__item h3,
|
.product__item h3,
|
||||||
.product__info h1 {
|
.product__info h1 {
|
||||||
/*text-decoration: underline;*/
|
/*text-decoration: underline;*/
|
||||||
font-family: "Vollkorn", serif;
|
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<h2>My Account</h2>
|
<h2>My Account</h2>
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url 'account_change_password' %}">Change password</a>
|
<a href="{% url 'password_change' %}">Change password</a>
|
||||||
</p>
|
</p>
|
||||||
<div class="customer__detail-section">
|
<div class="customer__detail-section">
|
||||||
<div>
|
<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 static %}
|
||||||
{% load compress %}
|
{% load compress %}
|
||||||
{% load account %}
|
|
||||||
{% load analytical %}
|
{% load analytical %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@ -19,7 +18,7 @@
|
|||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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 %}
|
{% compress css %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'styles/normalize.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>
|
<li><a class="nav__link" href="{% url 'dashboard:home' %}">Dashboard</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a class="nav__link" href="{% url 'storefront:customer-detail' user.pk %}">Orders</a></li>
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<ul class="nav__list nav__account">
|
<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>/</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>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="site__cart" href="{% url 'storefront:cart-detail' %}">
|
<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.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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 %}
|
{% compress css %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "styles/dashboard.css" %}">
|
<link rel="stylesheet" type="text/css" href="{% static "styles/dashboard.css" %}">
|
||||||
@ -65,11 +65,11 @@
|
|||||||
<a href="{% url 'storefront:product-list' %}">Storefront</a>
|
<a href="{% url 'storefront:product-list' %}">Storefront</a>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="account">{{ user.get_full_name }}</a>
|
<a href="account">{{ user.get_full_name }}</a>
|
||||||
<a href="{% url 'account_logout' %}">Log Out</a>
|
<a href="{% url 'logout' %}">Log Out</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>You are not logged in</p>
|
<p>You are not logged in</p>
|
||||||
<a href="{% url 'account_login' %}">Log In</a> |
|
<a href="{% url 'login' %}">Log In</a> |
|
||||||
<a href="{% url 'account_signup' %}">Sign Up</a>
|
<a href="{% url 'signup' %}">Sign Up</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user