From 988401422933d6dba6d02f5f5942b28ace2e116b Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Mon, 31 Jul 2023 19:57:14 -0600 Subject: [PATCH] Remove django-allauth and replace with basic user creation/login with just email --- accounts/admin.py | 51 +++++-- accounts/forms.py | 84 +++++++++--- ...er_options_alter_user_managers_and_more.py | 38 ++++++ accounts/models.py | 129 +++++++++++++++++- .../templates/accounts/account_create.html | 14 -- .../accounts/account_create_form.html | 23 ++++ .../templates/accounts/account_detail.html | 56 ++++++-- accounts/templates/accounts/account_form.html | 29 ++-- accounts/templates/accounts/account_list.html | 44 +++--- .../templates/accounts/base_accounts.html | 8 ++ accounts/templates/accounts/logged_out.html | 9 ++ accounts/templates/accounts/login.html | 58 ++++++++ .../accounts/password_change_done.html | 12 ++ .../accounts/password_change_form.html | 23 ++++ .../accounts}/password_reset_complete.html | 4 +- .../accounts/password_reset_confirm.html | 19 +++ .../accounts/password_reset_done.html | 7 + .../accounts/password_reset_email.html | 10 ++ .../accounts/password_reset_form.html | 15 ++ accounts/urls.py | 47 ++++++- accounts/views.py | 53 ++++--- poetry.lock | 102 +------------- ptcoffee/settings.py | 20 --- ptcoffee/urls.py | 3 +- pyproject.toml | 1 - static/scripts/index.js | 14 -- static/styles/main.css | 13 +- .../templates/storefront/customer_detail.html | 2 +- templates/account/email.html | 76 ----------- templates/account/email_confirm.html | 34 ----- templates/account/logged_out.html | 9 -- templates/account/login.html | 22 --- templates/account/logout.html | 18 --- templates/account/password_change.html | 33 ----- templates/account/password_change_done.html | 12 -- templates/account/password_change_form.html | 14 -- templates/account/password_reset.html | 25 ---- templates/account/password_reset_confirm.html | 24 ---- templates/account/password_reset_done.html | 18 --- templates/account/password_reset_email.html | 10 -- .../account/password_reset_from_key.html | 21 --- .../account/password_reset_from_key_done.html | 11 -- templates/account/signup.html | 17 --- templates/base.html | 9 +- templates/dashboard.html | 8 +- 45 files changed, 637 insertions(+), 612 deletions(-) create mode 100644 accounts/migrations/0006_alter_user_options_alter_user_managers_and_more.py delete mode 100755 accounts/templates/accounts/account_create.html create mode 100644 accounts/templates/accounts/account_create_form.html mode change 100755 => 100644 accounts/templates/accounts/account_detail.html mode change 100755 => 100644 accounts/templates/accounts/account_form.html mode change 100755 => 100644 accounts/templates/accounts/account_list.html create mode 100644 accounts/templates/accounts/base_accounts.html create mode 100644 accounts/templates/accounts/logged_out.html create mode 100644 accounts/templates/accounts/login.html create mode 100644 accounts/templates/accounts/password_change_done.html create mode 100644 accounts/templates/accounts/password_change_form.html rename {templates/account => accounts/templates/accounts}/password_reset_complete.html (51%) mode change 100755 => 100644 create mode 100644 accounts/templates/accounts/password_reset_confirm.html create mode 100644 accounts/templates/accounts/password_reset_done.html create mode 100644 accounts/templates/accounts/password_reset_email.html create mode 100644 accounts/templates/accounts/password_reset_form.html delete mode 100644 templates/account/email.html delete mode 100644 templates/account/email_confirm.html delete mode 100755 templates/account/logged_out.html delete mode 100755 templates/account/login.html delete mode 100644 templates/account/logout.html delete mode 100644 templates/account/password_change.html delete mode 100755 templates/account/password_change_done.html delete mode 100755 templates/account/password_change_form.html delete mode 100755 templates/account/password_reset.html delete mode 100755 templates/account/password_reset_confirm.html delete mode 100755 templates/account/password_reset_done.html delete mode 100755 templates/account/password_reset_email.html delete mode 100644 templates/account/password_reset_from_key.html delete mode 100644 templates/account/password_reset_from_key_done.html delete mode 100644 templates/account/signup.html diff --git a/accounts/admin.py b/accounts/admin.py index 12b4437..1dd8fb6 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -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) + diff --git a/accounts/forms.py b/accounts/forms.py index a61a599..8af1fb3 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -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", + ] + diff --git a/accounts/migrations/0006_alter_user_options_alter_user_managers_and_more.py b/accounts/migrations/0006_alter_user_options_alter_user_managers_and_more.py new file mode 100644 index 0000000..3eb777e --- /dev/null +++ b/accounts/migrations/0006_alter_user_options_alter_user_managers_and_more.py @@ -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'), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 8716633..e5ef316 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -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}) + diff --git a/accounts/templates/accounts/account_create.html b/accounts/templates/accounts/account_create.html deleted file mode 100755 index 2ca1e7e..0000000 --- a/accounts/templates/accounts/account_create.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
-

Sign up

- -
- {% csrf_token %} - {{ form.as_p }} - - -
-
-{% endblock %} diff --git a/accounts/templates/accounts/account_create_form.html b/accounts/templates/accounts/account_create_form.html new file mode 100644 index 0000000..9ef764e --- /dev/null +++ b/accounts/templates/accounts/account_create_form.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Sign up

+ +
+ {% csrf_token %} + + {{ form.as_table }} + + + + + +
+ +
+
+
+
+{% endblock %} diff --git a/accounts/templates/accounts/account_detail.html b/accounts/templates/accounts/account_detail.html old mode 100755 new mode 100644 index d0eda10..5ab803d --- a/accounts/templates/accounts/account_detail.html +++ b/accounts/templates/accounts/account_detail.html @@ -1,13 +1,49 @@ -{% extends 'base.html' %} +{% extends "base.html" %} + +{% block head_title %}{{ object.get_full_name }} | {% endblock head_title %} {% block content %} -
-
-

{{ user.first_name }} {{ user.last_name }}

-

{{ user.email }}

-

- Update email/change password -

-
-
+ + +
+ + + + + + + +
Email:{{ object.email }}
+
+

+ +
+

Students

+ + + + + + + + + {% for student in object.student_set.all %} + + + + + {% empty %} + + + + {% endfor %} + +
StudentMembership
{{ student.full_name }}{{ student.membership }}
Could not find any students
+
{% endblock %} diff --git a/accounts/templates/accounts/account_form.html b/accounts/templates/accounts/account_form.html old mode 100755 new mode 100644 index 4caa0c3..7e60576 --- a/accounts/templates/accounts/account_form.html +++ b/accounts/templates/accounts/account_form.html @@ -1,17 +1,22 @@ {% extends 'base.html' %} {% block content %} -
-
-

Update Profile

-

Change password

-
- {% csrf_token %} - {{ form.as_p }} + - or - Cancel -
-
-
+
+

Change password

+
+ {% csrf_token %} + {{ form.as_p }} +
+
+ + {% endblock %} diff --git a/accounts/templates/accounts/account_list.html b/accounts/templates/accounts/account_list.html old mode 100755 new mode 100644 index a28560a..f3bfea9 --- a/accounts/templates/accounts/account_list.html +++ b/accounts/templates/accounts/account_list.html @@ -1,23 +1,33 @@ {% extends 'base.html' %} {% block content %} + +
-

Users

- - - - - - - {% for user in user_list %} - - - - - {% empty %} - - {% endfor %} - -
UsernameName
{{ user.username }}{{user.first_name}} {{user.last_name}}
No users yet.
+ + + + + + + {% for user in user_list %} + + + + + {% empty %} + + + + {% endfor %} + +
NameEmail
{{ user.get_full_name }}{{ user.email }} ↗
No users yet.
{% endblock %} diff --git a/accounts/templates/accounts/base_accounts.html b/accounts/templates/accounts/base_accounts.html new file mode 100644 index 0000000..d6e2404 --- /dev/null +++ b/accounts/templates/accounts/base_accounts.html @@ -0,0 +1,8 @@ +{% extends 'base.html' %} + +{% block head_title %}Accounts |{% endblock %} + +{% block tabs %} + {% include 'core/_partials/tabs.html' with active_tab='accounts' %} +{% endblock %} + diff --git a/accounts/templates/accounts/logged_out.html b/accounts/templates/accounts/logged_out.html new file mode 100644 index 0000000..729b00a --- /dev/null +++ b/accounts/templates/accounts/logged_out.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

You have been logged out. Log in

+
+
+{% endblock %} diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html new file mode 100644 index 0000000..9f548b8 --- /dev/null +++ b/accounts/templates/accounts/login.html @@ -0,0 +1,58 @@ +{% extends 'base.html' %} + + +{% block content %} +
+

Log in

+ {% if form.errors %} +

Your username and password didn't match. Please try again.

+ {% endif %} + + {% if next %} + {% if user.is_authenticated %} +

Your account doesn't have access to this page. To proceed, please login with an account that has access.

+ {% else %} +

Please login to see this page.

+ {% endif %} + {% endif %} + +
+ {% csrf_token %} + + + + + + + + + + + + + + + + + +
+ {{ form.username.label_tag }} + + {{ form.username }} +
+ {{ form.password.label_tag }} + + {{ form.password }} +
+ + Forgot your password? + Reset your password here. + +
+ +
+ +
+
+{% endblock %} + diff --git a/accounts/templates/accounts/password_change_done.html b/accounts/templates/accounts/password_change_done.html new file mode 100644 index 0000000..08bf686 --- /dev/null +++ b/accounts/templates/accounts/password_change_done.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

+ Password has been changed. + Log in +

+
+
+{% endblock %} diff --git a/accounts/templates/accounts/password_change_form.html b/accounts/templates/accounts/password_change_form.html new file mode 100644 index 0000000..638ad5b --- /dev/null +++ b/accounts/templates/accounts/password_change_form.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} +
+

+ ← Back +

+
+ {% csrf_token %} +

Change password

+ + + {{ form.as_table }} + + + + +
+ +
+
+
+{% endblock %} diff --git a/templates/account/password_reset_complete.html b/accounts/templates/accounts/password_reset_complete.html old mode 100755 new mode 100644 similarity index 51% rename from templates/account/password_reset_complete.html rename to accounts/templates/accounts/password_reset_complete.html index ad0c905..442fc57 --- a/templates/account/password_reset_complete.html +++ b/accounts/templates/accounts/password_reset_complete.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block content %} -
-

Password was reset successfully.

+
+

Password was reset successfully.

{% endblock %} diff --git a/accounts/templates/accounts/password_reset_confirm.html b/accounts/templates/accounts/password_reset_confirm.html new file mode 100644 index 0000000..ad0578b --- /dev/null +++ b/accounts/templates/accounts/password_reset_confirm.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block content %} +
+ {% if validlink %} +

Reset password

+

Enter a new password below.

+
+ {% csrf_token %} + {{ form.as_p }} +

+ +

+
+ {% else %} +

Password reset failed

+ {% endif %} +
+{% endblock %} diff --git a/accounts/templates/accounts/password_reset_done.html b/accounts/templates/accounts/password_reset_done.html new file mode 100644 index 0000000..02c4ae6 --- /dev/null +++ b/accounts/templates/accounts/password_reset_done.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} + +{% block content %} +
+

An email with password reset instructions has been sent.

+
+{% endblock %} diff --git a/accounts/templates/accounts/password_reset_email.html b/accounts/templates/accounts/password_reset_email.html new file mode 100644 index 0000000..c971197 --- /dev/null +++ b/accounts/templates/accounts/password_reset_email.html @@ -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 + diff --git a/accounts/templates/accounts/password_reset_form.html b/accounts/templates/accounts/password_reset_form.html new file mode 100644 index 0000000..b542bea --- /dev/null +++ b/accounts/templates/accounts/password_reset_form.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Reset your password

+

Enter your email address below and we'll send you instructions on how to reset your password.

+
+ {% csrf_token %} + {{ form.as_p }} +

+ +

+
+
+{% endblock %} diff --git a/accounts/urls.py b/accounts/urls.py index 4f89f4f..ce04ed9 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -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('/', 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///", + 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" + ), ] + diff --git a/accounts/views.py b/accounts/views.py index c02cd1d..050c547 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -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') diff --git a/poetry.lock b/poetry.lock index f15aa37..66915e8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -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" diff --git a/ptcoffee/settings.py b/ptcoffee/settings.py index b47b55d..9a2ebdd 100644 --- a/ptcoffee/settings.py +++ b/ptcoffee/settings.py @@ -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/ diff --git a/ptcoffee/urls.py b/ptcoffee/urls.py index 6c5d88c..f78a8ef 100644 --- a/ptcoffee/urls.py +++ b/ptcoffee/urls.py @@ -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')), ] diff --git a/pyproject.toml b/pyproject.toml index e296956..40eb126 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/static/scripts/index.js b/static/scripts/index.js index 2b47fc8..b477440 100644 --- a/static/scripts/index.js +++ b/static/scripts/index.js @@ -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) }); diff --git a/static/styles/main.css b/static/styles/main.css index 7406904..33eb689 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -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; } diff --git a/storefront/templates/storefront/customer_detail.html b/storefront/templates/storefront/customer_detail.html index c31dfa4..873bb6b 100644 --- a/storefront/templates/storefront/customer_detail.html +++ b/storefront/templates/storefront/customer_detail.html @@ -8,7 +8,7 @@

My Account

- Change password + Change password

diff --git a/templates/account/email.html b/templates/account/email.html deleted file mode 100644 index 9884d05..0000000 --- a/templates/account/email.html +++ /dev/null @@ -1,76 +0,0 @@ -{% extends "base.html" %} - -{% load i18n %} - -{% block head_title %}{% trans "E-mail Addresses" %}{% endblock %} - -{% block content %} -
-

← Back

-
-

{% trans "E-mail Addresses" %}

-
-
- {% if user.emailaddress_set.all %} -

{% trans 'The following e-mail addresses are associated with your account:' %}

- - {% else %} -

{% trans 'Warning:'%} {% 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." %}

- {% endif %} -
-
- {% if can_add_email %} -

{% trans "Add E-mail Address" %}

- -
- {% csrf_token %} - {{ form.as_p }} - -
- {% endif %} -
- {% endblock %} -
- - -{% block extra_body %} - -{% endblock %} diff --git a/templates/account/email_confirm.html b/templates/account/email_confirm.html deleted file mode 100644 index f485334..0000000 --- a/templates/account/email_confirm.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends 'base.html' %} - -{% load i18n %} -{% load account %} - -{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} - - -{% block content %} -
-

{% trans "Confirm E-mail Address" %}

- - {% if confirmation %} - - {% user_display confirmation.email_address.user as user_display %} - -

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

- -
- {% csrf_token %} -

- -

-
- - {% else %} - - {% url 'account_email' as email_url %} - -

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

- - {% endif %} -
-{% endblock %} diff --git a/templates/account/logged_out.html b/templates/account/logged_out.html deleted file mode 100755 index 0b464dc..0000000 --- a/templates/account/logged_out.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
-
-

You have been logged out. Log in

-
-
-{% endblock %} diff --git a/templates/account/login.html b/templates/account/login.html deleted file mode 100755 index d9657b9..0000000 --- a/templates/account/login.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
-

Log in

- {% if form.errors %} -

Your username and password didn't match. Please try again.

- {% endif %} - -
- {% csrf_token %} - {{ form.as_p }} -

- Forgot your password? -

-

- - -

-
-
-{% endblock %} diff --git a/templates/account/logout.html b/templates/account/logout.html deleted file mode 100644 index 73c4101..0000000 --- a/templates/account/logout.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
-

Log Out

-

Are you sure you want to log out?

- -
- {% csrf_token %} - {{ form.as_p }} - -

- - -

-
-
-{% endblock %} diff --git a/templates/account/password_change.html b/templates/account/password_change.html deleted file mode 100644 index 0a23b3a..0000000 --- a/templates/account/password_change.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "base.html" %} - -{% load i18n %} - -{% block head_title %}{% trans "Change Password" %}{% endblock %} - -{% block content %} - -{% endblock %} diff --git a/templates/account/password_change_done.html b/templates/account/password_change_done.html deleted file mode 100755 index 6624e22..0000000 --- a/templates/account/password_change_done.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
-
-

- Password has been changed. - Log in -

-
-
-{% endblock %} diff --git a/templates/account/password_change_form.html b/templates/account/password_change_form.html deleted file mode 100755 index 76723fb..0000000 --- a/templates/account/password_change_form.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
-

Change password

-
- {% csrf_token %} - {{ form.as_p }} - - or - Cancel -
-
-{% endblock %} diff --git a/templates/account/password_reset.html b/templates/account/password_reset.html deleted file mode 100755 index ea3eae5..0000000 --- a/templates/account/password_reset.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends 'base.html' %} - -{% load i18n %} -{% load account %} - -{% block head_title %}{% trans "Password Reset" %} | {% endblock %} - -{% block content %} -
-

{% trans "Password Reset" %}

- {% if user.is_authenticated %} - {% include "account/snippets/already_logged_in.html" %} - {% endif %} - -

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

- -
- {% csrf_token %} - {{ form.as_p }} - -
- -

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

-
-{% endblock %} diff --git a/templates/account/password_reset_confirm.html b/templates/account/password_reset_confirm.html deleted file mode 100755 index 588c15d..0000000 --- a/templates/account/password_reset_confirm.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} - -{% if validlink %} -
-

Reset password

-

Enter a new password below.

-
- {% csrf_token %} - {{ form.as_p }} - - -
-
-{% else %} - -
-

Password reset failed

-
- -{% endif %} - -{% endblock %} diff --git a/templates/account/password_reset_done.html b/templates/account/password_reset_done.html deleted file mode 100755 index eb804c5..0000000 --- a/templates/account/password_reset_done.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% load i18n %} -{% load account %} - -{% block head_title %}{% trans "Password Reset" %}{% endblock %} - -{% block content %} -
-

{% trans "Password Reset" %}

- - {% if user.is_authenticated %} - {% include "account/snippets/already_logged_in.html" %} - {% endif %} - -

{% 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 %}

-
-{% endblock %} diff --git a/templates/account/password_reset_email.html b/templates/account/password_reset_email.html deleted file mode 100755 index 8c37094..0000000 --- a/templates/account/password_reset_email.html +++ /dev/null @@ -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 %} diff --git a/templates/account/password_reset_from_key.html b/templates/account/password_reset_from_key.html deleted file mode 100644 index 4439659..0000000 --- a/templates/account/password_reset_from_key.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "base.html" %} - -{% load i18n %} -{% block head_title %}{% trans "Change Password" %}{% endblock %} - -{% block content %} -
-

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

- - {% if token_fail %} - {% url 'account_reset_password' as passwd_reset_url %} -

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

- {% else %} -
- {% csrf_token %} - {{ form.as_p }} - -
- {% endif %} -
-{% endblock %} diff --git a/templates/account/password_reset_from_key_done.html b/templates/account/password_reset_from_key_done.html deleted file mode 100644 index 5622a6f..0000000 --- a/templates/account/password_reset_from_key_done.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} - -{% load i18n %} -{% block head_title %}{% trans "Change Password" %}{% endblock %} - -{% block content %} -
-

{% trans "Change Password" %}

-

{% trans 'Your password is now changed.' %}

-
-{% endblock %} diff --git a/templates/account/signup.html b/templates/account/signup.html deleted file mode 100644 index 3f92e08..0000000 --- a/templates/account/signup.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} - -{% endblock %} diff --git a/templates/base.html b/templates/base.html index af3e40a..dfd0def 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,6 +1,5 @@ {% load static %} {% load compress %} -{% load account %} {% load analytical %} @@ -19,7 +18,7 @@ - + {% compress css %} @@ -69,14 +68,14 @@
  • Dashboard
  • {% endif %}
  • Orders
  • -
  • Logout
  • +
  • Logout
  • {% else %} {% endif %} diff --git a/templates/dashboard.html b/templates/dashboard.html index 8aa6d94..0ed2c7f 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -15,7 +15,7 @@ - + {% compress css %} @@ -65,11 +65,11 @@ Storefront {% if user.is_authenticated %} {{ user.get_full_name }} - Log Out + Log Out {% else %}

    You are not logged in

    - Log In | - Sign Up + Log In | + Sign Up {% endif %}