Merge branch 'release/3.4.0'

This commit is contained in:
Nathan Chapman 2023-07-16 12:43:38 -06:00
commit adf275331c
34 changed files with 1517 additions and 1470 deletions

View File

@ -2,7 +2,7 @@ from django.contrib import admin
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from .models import Address, User from .models import User
from .forms import AccountCreateForm, AccountUpdateForm from .forms import AccountCreateForm, AccountUpdateForm
class UserAdmin(UserAdmin): class UserAdmin(UserAdmin):
@ -12,5 +12,4 @@ class UserAdmin(UserAdmin):
list_display = ['email', 'username',] list_display = ['email', 'username',]
admin.site.register(Address)
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)

View File

@ -9,3 +9,4 @@ class AccountsConfig(AppConfig):
from .signals import ( from .signals import (
user_saved user_saved
) )

View File

@ -2,21 +2,7 @@ from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from allauth.account.forms import SignupForm from allauth.account.forms import SignupForm
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from .models import Address, User from .models import User
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = (
'first_name',
'last_name',
'street_address_1',
'street_address_2',
'city',
'state',
'postal_code',
)
class AccountCreateForm(UserCreationForm): class AccountCreateForm(UserCreationForm):
@ -32,24 +18,41 @@ class AccountUpdateForm(UserChangeForm):
'first_name', 'first_name',
'last_name', 'last_name',
'email', 'email',
'default_shipping_address', 'shipping_street_address_1',
'addresses', 'shipping_street_address_2',
'shipping_city',
'shipping_state',
'shipping_postal_code',
) )
class CustomerUpdateForm(forms.ModelForm): class CustomerUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs): class Meta:
super().__init__(*args, **kwargs) model = User
self.fields['default_shipping_address'].queryset = kwargs['instance'].addresses fields = (
'email',
)
class CustomerShippingAddressUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ( fields = (
'first_name', 'first_name',
'last_name', 'last_name',
'email', 'shipping_street_address_1',
'default_shipping_address', 'shipping_street_address_2',
'shipping_city',
'shipping_state',
'shipping_postal_code',
) )
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 UserSignupForm(SignupForm): class UserSignupForm(SignupForm):
@ -62,3 +65,4 @@ class UserSignupForm(SignupForm):
widget=forms.TextInput(attrs={'placeholder': 'Last name'}) widget=forms.TextInput(attrs={'placeholder': 'Last name'})
) )
captcha = CaptchaField() captcha = CaptchaField()

View File

@ -0,0 +1,38 @@
# Generated by Django 4.1.6 on 2023-07-15 02:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_address_accounts_address_all_key'),
]
operations = [
migrations.AddField(
model_name='user',
name='shipping_city',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='user',
name='shipping_postal_code',
field=models.CharField(blank=True, max_length=20),
),
migrations.AddField(
model_name='user',
name='shipping_state',
field=models.CharField(blank=True, choices=[('AL', 'Alabama'), ('AK', 'Alaska'), ('AS', 'American Samoa'), ('AZ', 'Arizona'), ('AR', 'Arkansas'), ('AA', 'Armed Forces Americas'), ('AE', 'Armed Forces Europe'), ('AP', 'Armed Forces Pacific'), ('CA', 'California'), ('CO', 'Colorado'), ('CT', 'Connecticut'), ('DE', 'Delaware'), ('DC', 'District of Columbia'), ('FM', 'Federated States of Micronesia'), ('FL', 'Florida'), ('GA', 'Georgia'), ('GU', 'Guam'), ('HI', 'Hawaii'), ('ID', 'Idaho'), ('IL', 'Illinois'), ('IN', 'Indiana'), ('IA', 'Iowa'), ('KS', 'Kansas'), ('KY', 'Kentucky'), ('LA', 'Louisiana'), ('ME', 'Maine'), ('MH', 'Marshall Islands'), ('MD', 'Maryland'), ('MA', 'Massachusetts'), ('MI', 'Michigan'), ('MN', 'Minnesota'), ('MS', 'Mississippi'), ('MO', 'Missouri'), ('MT', 'Montana'), ('NE', 'Nebraska'), ('NV', 'Nevada'), ('NH', 'New Hampshire'), ('NJ', 'New Jersey'), ('NM', 'New Mexico'), ('NY', 'New York'), ('NC', 'North Carolina'), ('ND', 'North Dakota'), ('MP', 'Northern Mariana Islands'), ('OH', 'Ohio'), ('OK', 'Oklahoma'), ('OR', 'Oregon'), ('PW', 'Palau'), ('PA', 'Pennsylvania'), ('PR', 'Puerto Rico'), ('RI', 'Rhode Island'), ('SC', 'South Carolina'), ('SD', 'South Dakota'), ('TN', 'Tennessee'), ('TX', 'Texas'), ('UT', 'Utah'), ('VT', 'Vermont'), ('VI', 'Virgin Islands'), ('VA', 'Virginia'), ('WA', 'Washington'), ('WV', 'West Virginia'), ('WI', 'Wisconsin'), ('WY', 'Wyoming')], max_length=2),
),
migrations.AddField(
model_name='user',
name='shipping_street_address_1',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='user',
name='shipping_street_address_2',
field=models.CharField(blank=True, max_length=256),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 4.1.6 on 2023-07-15 02:56
from django.db import migrations
def copy_default_address_to_user(apps, schema_editor):
User = apps.get_model("accounts", "User")
for user in User.objects.all():
if user.default_shipping_address:
user.shipping_street_address_1 = user.default_shipping_address.street_address_1
user.shipping_street_address_2 = user.default_shipping_address.street_address_2
user.shipping_city = user.default_shipping_address.city
user.shipping_state = user.default_shipping_address.state
user.shipping_postal_code = user.default_shipping_address.postal_code
user.save()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_user_shipping_city_user_shipping_postal_code_and_more'),
]
operations = [
migrations.RunPython(copy_default_address_to_user)
]

View File

@ -0,0 +1,29 @@
# Generated by Django 4.1.6 on 2023-07-15 03:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0008_remove_order_billing_address_and_more'),
('accounts', '0004_transfer_address_data_to_user_model'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='addresses',
),
migrations.RemoveField(
model_name='user',
name='default_billing_address',
),
migrations.RemoveField(
model_name='user',
name='default_shipping_address',
),
migrations.DeleteModel(
name='Address',
),
]

View File

@ -5,76 +5,29 @@ from django.contrib.auth.models import AbstractUser
from localflavor.us.us_states import USPS_CHOICES from localflavor.us.us_states import USPS_CHOICES
class Address(models.Model): class User(AbstractUser):
first_name = models.CharField(max_length=256, blank=True) stripe_id = models.CharField(max_length=255, blank=True)
last_name = models.CharField(max_length=256, blank=True)
street_address_1 = models.CharField(max_length=256, blank=True) # Shipping address
street_address_2 = models.CharField(max_length=256, blank=True) shipping_street_address_1 = models.CharField(max_length=256, blank=True)
city = models.CharField(max_length=256, blank=True) shipping_street_address_2 = models.CharField(max_length=256, blank=True)
state = models.CharField( shipping_city = models.CharField(max_length=256, blank=True)
shipping_state = models.CharField(
max_length=2, max_length=2,
choices=USPS_CHOICES, choices=USPS_CHOICES,
blank=True blank=True
) )
postal_code = models.CharField(max_length=20, blank=True) shipping_postal_code = models.CharField(max_length=20, blank=True)
def as_stripe_dict(self): @property
return { def has_shipping_address(self):
'name': f'{self.first_name} {self.last_name}', if (self.shipping_street_address_1 != ''
'address': { and self.shipping_street_address_2 != ''
'line1': self.street_address_1, and self.shipping_city != ''
'line2': self.street_address_2, and self.shipping_state != ''
'city': self.city, and self.shipping_postal_code != ''):
'state': self.state, return True
'postal_code': self.postal_code return False
}
}
def __str__(self):
return f"""
{self.first_name} {self.last_name}
{self.street_address_1}
{self.street_address_2}
{self.city}, {self.state}, {self.postal_code}
"""
def __iter__(self):
yield ('address_line_1', self.street_address_1),
yield ('address_line_2', self.street_address_2),
yield ('admin_area_2', self.city),
yield ('admin_area_1', self.state),
yield ('postal_code', self.postal_code),
yield ('country_code', 'US')
class Meta:
constraints = [
models.UniqueConstraint(
name='accounts_address_all_key',
fields=[
'first_name',
'last_name',
'street_address_1',
'street_address_2',
'city',
'state',
'postal_code'
],
violation_error_message='Duplicate: Address already exists.'
)
]
class User(AbstractUser):
addresses = models.ManyToManyField(
Address, blank=True, related_name="user_addresses"
)
default_shipping_address = models.ForeignKey(
Address, related_name="+", null=True, blank=True, on_delete=models.SET_NULL
)
default_billing_address = models.ForeignKey(
Address, related_name="+", null=True, blank=True, on_delete=models.SET_NULL
)
stripe_id = models.CharField(max_length=255, blank=True)
def get_or_create_stripe_id(self): def get_or_create_stripe_id(self):
if not self.stripe_id: if not self.stripe_id:

View File

@ -5,7 +5,7 @@ from django.dispatch import receiver
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from .models import Address, User from .models import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
stripe.api_key = settings.STRIPE_API_KEY stripe.api_key = settings.STRIPE_API_KEY
@ -16,3 +16,4 @@ def user_saved(sender, instance, created, **kwargs):
logger.info('User was saved') logger.info('User was saved')
if created or not instance.stripe_id: if created or not instance.stripe_id:
instance.get_or_create_stripe_id() instance.get_or_create_stripe_id()

View File

@ -1,26 +1,9 @@
from allauth.account.models import EmailAddress from .models import User
from .models import Address, User
from .tasks import send_account_created_email
def get_or_create_customer(request, shipping_address): def get_or_create_customer(request, shipping_address):
address, a_created = Address.objects.get_or_create(
first_name=shipping_address['first_name'],
last_name=shipping_address['last_name'],
street_address_1=shipping_address['street_address_1'],
street_address_2=shipping_address['street_address_2'],
city=shipping_address['city'],
state=shipping_address['state'],
postal_code=shipping_address['postal_code']
)
if request.user.is_authenticated: if request.user.is_authenticated:
user = request.user user = request.user
user.addresses.add(address)
if not user.default_shipping_address:
user.default_shipping_address = address
user.save()
else: else:
user, u_created = User.objects.get_or_create( user, u_created = User.objects.get_or_create(
email=shipping_address['email'].lower(), email=shipping_address['email'].lower(),
@ -29,27 +12,17 @@ def get_or_create_customer(request, shipping_address):
'is_staff': False, 'is_staff': False,
'is_active': True, 'is_active': True,
'is_superuser': False, 'is_superuser': False,
'first_name': address.first_name, 'first_name': shipping_address['first_name'],
'last_name': address.last_name, 'last_name': shipping_address['last_name'],
'default_shipping_address': address, 'shipping_street_address_1': shipping_address['street_address_1'],
'shipping_street_address_2': shipping_address['street_address_2'],
'shipping_city': shipping_address['city'],
'shipping_state': shipping_address['state'],
'shipping_postal_code': shipping_address['postal_code']
} }
) )
if u_created: if u_created:
password = User.objects.make_random_password() user.make_random_password()
user.set_password(password)
user.addresses.add(address)
user.save() user.save()
EmailAddress.objects.create( return user
user=user, email=user.email, primary=True, verified=False
)
u = {
'full_name': user.get_full_name(),
'email': user.email,
'password': password
}
send_account_created_email.delay(u)
return user, address

View File

@ -0,0 +1,63 @@
# Generated by Django 4.1.6 on 2023-07-15 02:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0003_rename_max_order_per_customer_productvariant_order_limit'),
]
operations = [
migrations.AddField(
model_name='order',
name='shipping_city',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='order',
name='shipping_postal_code',
field=models.CharField(blank=True, max_length=20),
),
migrations.AddField(
model_name='order',
name='shipping_state',
field=models.CharField(blank=True, choices=[('AL', 'Alabama'), ('AK', 'Alaska'), ('AS', 'American Samoa'), ('AZ', 'Arizona'), ('AR', 'Arkansas'), ('AA', 'Armed Forces Americas'), ('AE', 'Armed Forces Europe'), ('AP', 'Armed Forces Pacific'), ('CA', 'California'), ('CO', 'Colorado'), ('CT', 'Connecticut'), ('DE', 'Delaware'), ('DC', 'District of Columbia'), ('FM', 'Federated States of Micronesia'), ('FL', 'Florida'), ('GA', 'Georgia'), ('GU', 'Guam'), ('HI', 'Hawaii'), ('ID', 'Idaho'), ('IL', 'Illinois'), ('IN', 'Indiana'), ('IA', 'Iowa'), ('KS', 'Kansas'), ('KY', 'Kentucky'), ('LA', 'Louisiana'), ('ME', 'Maine'), ('MH', 'Marshall Islands'), ('MD', 'Maryland'), ('MA', 'Massachusetts'), ('MI', 'Michigan'), ('MN', 'Minnesota'), ('MS', 'Mississippi'), ('MO', 'Missouri'), ('MT', 'Montana'), ('NE', 'Nebraska'), ('NV', 'Nevada'), ('NH', 'New Hampshire'), ('NJ', 'New Jersey'), ('NM', 'New Mexico'), ('NY', 'New York'), ('NC', 'North Carolina'), ('ND', 'North Dakota'), ('MP', 'Northern Mariana Islands'), ('OH', 'Ohio'), ('OK', 'Oklahoma'), ('OR', 'Oregon'), ('PW', 'Palau'), ('PA', 'Pennsylvania'), ('PR', 'Puerto Rico'), ('RI', 'Rhode Island'), ('SC', 'South Carolina'), ('SD', 'South Dakota'), ('TN', 'Tennessee'), ('TX', 'Texas'), ('UT', 'Utah'), ('VT', 'Vermont'), ('VI', 'Virgin Islands'), ('VA', 'Virginia'), ('WA', 'Washington'), ('WV', 'West Virginia'), ('WI', 'Wisconsin'), ('WY', 'Wyoming')], max_length=2),
),
migrations.AddField(
model_name='order',
name='shipping_street_address_1',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='order',
name='shipping_street_address_2',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='subscription',
name='shipping_city',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='subscription',
name='shipping_postal_code',
field=models.CharField(blank=True, max_length=20),
),
migrations.AddField(
model_name='subscription',
name='shipping_state',
field=models.CharField(blank=True, choices=[('AL', 'Alabama'), ('AK', 'Alaska'), ('AS', 'American Samoa'), ('AZ', 'Arizona'), ('AR', 'Arkansas'), ('AA', 'Armed Forces Americas'), ('AE', 'Armed Forces Europe'), ('AP', 'Armed Forces Pacific'), ('CA', 'California'), ('CO', 'Colorado'), ('CT', 'Connecticut'), ('DE', 'Delaware'), ('DC', 'District of Columbia'), ('FM', 'Federated States of Micronesia'), ('FL', 'Florida'), ('GA', 'Georgia'), ('GU', 'Guam'), ('HI', 'Hawaii'), ('ID', 'Idaho'), ('IL', 'Illinois'), ('IN', 'Indiana'), ('IA', 'Iowa'), ('KS', 'Kansas'), ('KY', 'Kentucky'), ('LA', 'Louisiana'), ('ME', 'Maine'), ('MH', 'Marshall Islands'), ('MD', 'Maryland'), ('MA', 'Massachusetts'), ('MI', 'Michigan'), ('MN', 'Minnesota'), ('MS', 'Mississippi'), ('MO', 'Missouri'), ('MT', 'Montana'), ('NE', 'Nebraska'), ('NV', 'Nevada'), ('NH', 'New Hampshire'), ('NJ', 'New Jersey'), ('NM', 'New Mexico'), ('NY', 'New York'), ('NC', 'North Carolina'), ('ND', 'North Dakota'), ('MP', 'Northern Mariana Islands'), ('OH', 'Ohio'), ('OK', 'Oklahoma'), ('OR', 'Oregon'), ('PW', 'Palau'), ('PA', 'Pennsylvania'), ('PR', 'Puerto Rico'), ('RI', 'Rhode Island'), ('SC', 'South Carolina'), ('SD', 'South Dakota'), ('TN', 'Tennessee'), ('TX', 'Texas'), ('UT', 'Utah'), ('VT', 'Vermont'), ('VI', 'Virgin Islands'), ('VA', 'Virginia'), ('WA', 'Washington'), ('WV', 'West Virginia'), ('WI', 'Wisconsin'), ('WY', 'Wyoming')], max_length=2),
),
migrations.AddField(
model_name='subscription',
name='shipping_street_address_1',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='subscription',
name='shipping_street_address_2',
field=models.CharField(blank=True, max_length=256),
),
]

View File

@ -0,0 +1,41 @@
# Generated by Django 4.1.6 on 2023-07-15 02:59
from django.db import migrations
def copy_address_to_order(apps, schema_editor):
Order = apps.get_model("core", "Order")
for order in Order.objects.all():
if order.shipping_address:
order.shipping_street_address_1 = order.shipping_address.street_address_1
order.shipping_street_address_2 = order.shipping_address.street_address_2
order.shipping_city = order.shipping_address.city
order.shipping_state = order.shipping_address.state
order.shipping_postal_code = order.shipping_address.postal_code
order.save()
def copy_address_to_subscription(apps, schema_editor):
Subscription = apps.get_model("core", "Subscription")
for subscription in Subscription.objects.all():
if subscription.shipping_address:
subscription.shipping_street_address_1 = subscription.shipping_address.street_address_1
subscription.shipping_street_address_2 = subscription.shipping_address.street_address_2
subscription.shipping_city = subscription.shipping_address.city
subscription.shipping_state = subscription.shipping_address.state
subscription.shipping_postal_code = subscription.shipping_address.postal_code
subscription.save()
class Migration(migrations.Migration):
dependencies = [
('core', '0004_order_shipping_city_order_shipping_postal_code_and_more'),
]
operations = [
migrations.RunPython(copy_address_to_order),
migrations.RunPython(copy_address_to_subscription)
]

View File

@ -0,0 +1,33 @@
# Generated by Django 4.1.6 on 2023-07-15 03:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_transfer_address_data_to_order_and_subscription_models'),
]
operations = [
migrations.AddField(
model_name='order',
name='shipping_first_name',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='order',
name='shipping_last_name',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='subscription',
name='shipping_first_name',
field=models.CharField(blank=True, max_length=256),
),
migrations.AddField(
model_name='subscription',
name='shipping_last_name',
field=models.CharField(blank=True, max_length=256),
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 4.1.6 on 2023-07-15 03:20
from django.db import migrations
def copy_address_to_order(apps, schema_editor):
Order = apps.get_model("core", "Order")
for order in Order.objects.all():
if order.shipping_address:
order.shipping_first_name = order.shipping_address.first_name
order.shipping_last_name = order.shipping_address.last_name
order.save()
def copy_address_to_subscription(apps, schema_editor):
Subscription = apps.get_model("core", "Subscription")
for subscription in Subscription.objects.all():
if subscription.shipping_address:
subscription.shipping_first_name = subscription.shipping_address.first_name
subscription.shipping_last_name = subscription.shipping_address.last_name
subscription.save()
class Migration(migrations.Migration):
dependencies = [
('core', '0006_order_shipping_first_name_order_shipping_last_name_and_more'),
]
operations = [
migrations.RunPython(copy_address_to_order),
migrations.RunPython(copy_address_to_subscription)
]

View File

@ -0,0 +1,25 @@
# Generated by Django 4.1.6 on 2023-07-15 03:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0007_transfer_address_first_and_last_names_to_order_and_subscriptions'),
]
operations = [
migrations.RemoveField(
model_name='order',
name='billing_address',
),
migrations.RemoveField(
model_name='order',
name='shipping_address',
),
migrations.RemoveField(
model_name='subscription',
name='shipping_address',
),
]

View File

@ -16,8 +16,9 @@ from django.contrib.postgres.fields import ArrayField, HStoreField
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from django_measurement.models import MeasurementField from django_measurement.models import MeasurementField
from localflavor.us.us_states import USPS_CHOICES
from accounts.models import User, Address from accounts.models import User
from . import ( from . import (
DiscountValueType, DiscountValueType,
@ -337,20 +338,18 @@ class Order(models.Model):
default=OrderStatus.UNFULFILLED, default=OrderStatus.UNFULFILLED,
choices=OrderStatus.CHOICES choices=OrderStatus.CHOICES
) )
billing_address = models.ForeignKey( # Shipping address
Address, shipping_first_name = models.CharField(max_length=256, blank=True)
related_name="+", shipping_last_name = models.CharField(max_length=256, blank=True)
editable=False, shipping_street_address_1 = models.CharField(max_length=256, blank=True)
null=True, shipping_street_address_2 = models.CharField(max_length=256, blank=True)
on_delete=models.SET_NULL shipping_city = models.CharField(max_length=256, blank=True)
) shipping_state = models.CharField(
shipping_address = models.ForeignKey( max_length=2,
Address, choices=USPS_CHOICES,
related_name="+", blank=True
editable=False,
null=True,
on_delete=models.SET_NULL
) )
shipping_postal_code = models.CharField(max_length=20, blank=True)
coupon = models.ForeignKey( coupon = models.ForeignKey(
Coupon, Coupon,
related_name='orders', related_name='orders',
@ -544,13 +543,18 @@ class Subscription(models.Model):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True null=True
) )
shipping_address = models.ForeignKey( # Shipping address
Address, shipping_first_name = models.CharField(max_length=256, blank=True)
related_name='+', shipping_last_name = models.CharField(max_length=256, blank=True)
editable=False, shipping_street_address_1 = models.CharField(max_length=256, blank=True)
null=True, shipping_street_address_2 = models.CharField(max_length=256, blank=True)
on_delete=models.SET_NULL shipping_city = models.CharField(max_length=256, blank=True)
shipping_state = models.CharField(
max_length=2,
choices=USPS_CHOICES,
blank=True
) )
shipping_postal_code = models.CharField(max_length=20, blank=True)
items = ArrayField( items = ArrayField(
models.JSONField(blank=True, null=True), models.JSONField(blank=True, null=True),
default=list default=list

View File

@ -53,13 +53,13 @@ def transaction_created(sender, instance, created, **kwargs):
'shipping_total': str(instance.order.shipping_total), 'shipping_total': str(instance.order.shipping_total),
'total_amount': str(instance.order.total_amount), 'total_amount': str(instance.order.total_amount),
'shipping_address': { 'shipping_address': {
'first_name': instance.order.shipping_address.first_name, 'first_name': instance.order.shipping_first_name,
'last_name': instance.order.shipping_address.last_name, 'last_name': instance.order.shipping_last_name,
'street_address_1': instance.order.shipping_address.street_address_1, 'street_address_1': instance.order.shipping_street_address_1,
'street_address_2': instance.order.shipping_address.street_address_2, 'street_address_2': instance.order.shipping_street_address_2,
'city': instance.order.shipping_address.city, 'city': instance.order.shipping_city,
'state': instance.order.shipping_address.state, 'state': instance.order.shipping_state,
'postal_code': instance.order.shipping_address.postal_code 'postal_code': instance.order.shipping_postal_code
}, },
'line_items': list( 'line_items': list(
format_order_lines(instance.order.lines.all()) format_order_lines(instance.order.lines.all())

View File

@ -19,21 +19,14 @@
<h4>Details</h4> <h4>Details</h4>
</header> </header>
<dl class="panel-datalist"> <dl class="panel-datalist">
<dt>Primary email address</dt> <dt>Email address</dt>
<dd> <dd>
<a href="mailto:{{ customer.email }}">{{ customer.email }} &nearr;</a> <a href="mailto:{{ customer.email }}">{{ customer.email }} &nearr;</a>
</dd> </dd>
<dt>Default shipping address</dt> <dt>Shipping address</dt>
<dd> <dd>
{% include 'dashboard/partials/_address.html' with address=customer.default_shipping_address %} {% include 'dashboard/partials/_address.html' with full_name=customer.get_full_name address=customer %}
</dd>
<dt>All addresses</dt>
<dd>
{% for address in customer.addresses.all %}
{% include 'dashboard/partials/_address.html' with address=address %}
{% endfor %}
</dd> </dd>
</dl> </dl>
</section> </section>

View File

@ -133,7 +133,7 @@
<div class="panel-section panel-shipping"> <div class="panel-section panel-shipping">
<div> <div>
<strong>Shipping address</strong> <strong>Shipping address</strong>
{% include 'dashboard/partials/_address.html' with address=order.shipping_address %} {% include 'dashboard/partials/_address.html' with full_name=order.customer.get_full_name address=order %}
</div> </div>
<table> <table>
<thead> <thead>

View File

@ -17,7 +17,7 @@
</header> </header>
<div class="panel-section"> <div class="panel-section">
<strong>Shipping address</strong> <strong>Shipping address</strong>
{% include 'dashboard/partials/_address.html' with address=order.shipping_address %} {% include 'dashboard/partials/_address.html' with full_name=order.customer.get_full_name address=order %}
</div> </div>
<form method="POST"> <form method="POST">
{% csrf_token %} {% csrf_token %}

View File

@ -1,9 +1,8 @@
<address> <address>
{{address.first_name}} {{ full_name }}<br>
{{address.last_name}}<br> {{ address.shipping_street_address_1 }}<br>
{{address.street_address_1}}<br> {% if address.shipping_street_address_2 %}
{% if address.street_address_2 %} {{ address.shipping_street_address_2 }}<br>
{{address.street_address_2}}<br>
{% endif %} {% endif %}
{{address.city}}, {{address.state}}, {{address.postal_code}} {{ address.shipping_city }}, {{ address.shipping_state }}, {{ address.shipping_postal_code }}
</address> </address>

View File

@ -31,7 +31,6 @@ from django.db.models.functions import Coalesce
from accounts.models import User from accounts.models import User
from accounts.utils import get_or_create_customer from accounts.utils import get_or_create_customer
from accounts.forms import AddressForm
from core.models import ( from core.models import (
ProductCategory, ProductCategory,
Product, Product,
@ -259,8 +258,6 @@ class OrderDetailView(LoginRequiredMixin, DetailView):
self.kwargs.get(self.pk_url_kwarg) self.kwargs.get(self.pk_url_kwarg)
).select_related( ).select_related(
'customer', 'customer',
'billing_address',
'shipping_address'
).prefetch_related( ).prefetch_related(
'lines__variant__product__productphoto_set' 'lines__variant__product__productphoto_set'
) )
@ -717,9 +714,11 @@ class CustomerUpdateView(
'first_name', 'first_name',
'last_name', 'last_name',
'email', 'email',
'is_staff', 'shipping_street_address_1',
'addresses', 'shipping_street_address_2',
'default_shipping_address' 'shipping_city',
'shipping_state',
'shipping_postal_code',
) )
def get_success_url(self): def get_success_url(self):

View File

@ -25,7 +25,7 @@ services:
- redis - redis
web: web:
build: . build: .
command: sh -c "python manage.py migrate && python manage.py collectstatic --no-input && gunicorn --bind :8000 --reload ptcoffee.wsgi:application" command: gunicorn --bind :8000 --reload ptcoffee.wsgi:application
volumes: volumes:
- .:/app - .:/app
- ./static/:/var/www/static - ./static/:/var/www/static

View File

@ -19,6 +19,10 @@ server {
listen [::]:443 ssl http2; listen [::]:443 ssl http2;
server_name ptcoffee.com www.ptcoffee.com; server_name ptcoffee.com www.ptcoffee.com;
if ($http_host !~* ^(ptcoffee.com|www.ptcoffee.com)$ ) {
return 444;
}
# SSL # SSL
ssl_certificate /etc/letsencrypt/live/ptcoffee.com/fullchain.pem; ssl_certificate /etc/letsencrypt/live/ptcoffee.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ptcoffee.com/privkey.pem; ssl_certificate_key /etc/letsencrypt/live/ptcoffee.com/privkey.pem;

2109
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -72,10 +72,7 @@ INSTALLED_APPS = [
# 3rd Party # 3rd Party
'django_filters', 'django_filters',
'storages',
'localflavor', 'localflavor',
'django_celery_beat',
'django_celery_results',
'anymail', 'anymail',
'compressor', 'compressor',
'allauth', 'allauth',

View File

@ -11,12 +11,9 @@ django = "^4.1.5"
celery = {extras = ["redis"], version = "^5.2.7"} celery = {extras = ["redis"], version = "^5.2.7"}
django-allauth = "^0.52.0" django-allauth = "^0.52.0"
django-anymail = {extras = ["mailgun"], version = "^9.0"} django-anymail = {extras = ["mailgun"], version = "^9.0"}
django-celery-beat = "^2.4.0"
django-celery-results = "^2.4.0"
django-compressor = "^4.1" django-compressor = "^4.1"
django-filter = "^22.1" django-filter = "^22.1"
django-measurement = "^3.2.4" django-measurement = "^3.2.4"
django-storages = "^1.13.2"
django-templated-email = "^3.0.1" django-templated-email = "^3.0.1"
paypal-checkout-serversdk = "^1.0.1" paypal-checkout-serversdk = "^1.0.1"
pillow = "^9.4.0" pillow = "^9.4.0"

View File

@ -126,6 +126,21 @@ table a {
white-space: normal; white-space: normal;
} }
.form-table {
width: unset;
border: none;
}
.form-table th,
.form-table td {
border: none;
}
.form-table th {
padding: 0 1rem 1rem 0;
}
.form-table td {
padding: 0 0 1rem 0;
}
/* ========================================================================== /* ==========================================================================
Forms Forms

View File

@ -255,7 +255,7 @@ class Cart:
if container is None: if container is None:
container = self.get_shipping_container() container = self.get_shipping_container()
if not self.total_weight > Weight(lb=0): if self.total_weight <= Weight(lb=0):
return Decimal('0.00') return Decimal('0.00')
if len(self) > 0 and self.session.get('shipping_address'): if len(self) > 0 and self.session.get('shipping_address'):
@ -286,11 +286,11 @@ class Cart:
) )
if usps_rate_request['service'] == ShippingContainer.PRIORITY: if usps_rate_request['service'] == ShippingContainer.PRIORITY:
shipping_cost = Decimal(postage['Rate']) shipping_price = Decimal(postage['Rate'])
elif usps_rate_request['service'] == ShippingContainer.PRIORITY_COMMERCIAL: elif usps_rate_request['service'] == ShippingContainer.PRIORITY_COMMERCIAL:
shipping_cost = Decimal(postage['CommercialRate']) shipping_price = Decimal(postage['CommercialRate'])
return shipping_cost return shipping_price
else: else:
raise ShippingAddressError( raise ShippingAddressError(
'Could not retrieve shipping address.' 'Could not retrieve shipping address.'

View File

@ -5,61 +5,32 @@
{% block content %} {% block content %}
<article> <article>
<header class="article__header--with-action">
<h1>{{customer.get_full_name}}</h1>
<a href="{% url 'storefront:customer-update' customer.pk %}" class="btn">Edit profile</a>
</header>
<section> <section>
<h4>Info</h4> <h2>My Account</h2>
<p>
<a href="{% url 'account_change_password' %}">Change password</a>
</p>
<div class="customer__detail-section"> <div class="customer__detail-section">
<div> <div>
<strong>Email address</strong><br> <strong>Email address</strong> <a href="{% url 'storefront:customer-update' customer.pk %}">Change</a><br>
{{customer.email}}<br> {{customer.email}}
<a href="{% url 'account_email' %}">Manage</a>
</div> </div>
<div> <div>
<strong>Default shipping address</strong> <strong>Shipping address</strong> <a href="{% url 'storefront:customer-shipping-address-update' customer.pk %}">Change</a>
{% with shipping_address=customer.default_shipping_address %} <address>
<address> {{ customer.get_full_name }}<br>
{{shipping_address.first_name}} {{customer.shipping_street_address_1}}<br>
{{shipping_address.last_name}}<br> {% if shipping_street_address_2 %}
{{shipping_address.street_address_1}}<br> {{customer.shipping_street_address_2}}<br>
{% if shipping_address.street_address_2 %} {% endif %}
{{shipping_address.street_address_2}}<br> {{customer.shipping_city}}, {{customer.shipping_state}}, {{customer.shipping_postal_code}}
{% endif %} </address>
{{shipping_address.city}}, {{shipping_address.state}}, {{shipping_address.postal_code}}
</address>
{% endwith %}
</div> </div>
</div> </div>
</section> </section>
<section>
<h4>Your addresses</h4>
<p>
<a href="{% url 'storefront:customer-address-create' user.pk %}" class="btn">+ New address</a>
</p>
<div>
{% for address in customer.addresses.all %}
<p>
<address>
{{address.first_name}}
{{address.last_name}}<br>
{{address.street_address_1}}<br>
{% if address.street_address_2 %}
{{address.street_address_2}}<br>
{% endif %}
{{address.city}}, {{address.state}}, {{address.postal_code}}
</address>
<a href="{% url 'storefront:address-update' customer.pk address.pk %}">Edit</a>
</p>
{% empty %}
<p>No other addresses.</p>
{% endfor %}
</div>
</section>
{% if customer.subscriptions.count > 0 %} {% if customer.subscriptions.count > 0 %}
<section> <section>
<h3>Your subscriptions</h3> <h3>My subscriptions</h3>
<table> <table>
<thead> <thead>
<tr> <tr>
@ -81,7 +52,7 @@
</section> </section>
{% endif %} {% endif %}
<section> <section>
<h3>Your orders</h3> <h3>My orders</h3>
<table> <table>
<thead> <thead>
<tr> <tr>

View File

@ -5,17 +5,22 @@
<header> <header>
<p><a href="{% url 'storefront:customer-detail' customer.pk %}">&larr; Back</a></p> <p><a href="{% url 'storefront:customer-detail' customer.pk %}">&larr; Back</a></p>
<h1>Update your profile</h1> <h1>Update your profile</h1>
<p>
<a href="{% url 'account_change_password' %}">Change password</a>
</p>
</header> </header>
<section> <section>
<form method="POST" action="{% url 'storefront:customer-update' customer.pk %}"> <form method="POST" action="{% url 'storefront:customer-update' customer.pk %}">
{% csrf_token %} {% csrf_token %}
{{form.as_p}} <table class="form-table">
<p> {{form.as_table}}
<input type="submit" value="Save changes"> <tfoot>
</p> <tr>
<td>
</td>
<td>
<button type="submit">Save changes</button>
</td>
</tr>
</tfoot>
</table>
</form> </form>
</section> </section>
</article> </article>

View File

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block content %}
<article>
<header>
<p><a href="{% url 'storefront:customer-detail' customer.pk %}">&larr; Back</a></p>
<h1>Update your shipping address</h1>
</header>
<section>
<form method="POST">
{% csrf_token %}
<table class="form-table">
{{form.as_table}}
<tfoot>
<tr>
<td>
</td>
<td>
<button type="submit">Save changes</button>
</td>
</tr>
</tfoot>
</table>
</form>
</section>
</article>
{% endblock %}

View File

@ -89,26 +89,16 @@ urlpatterns = [
views.CustomerUpdateView.as_view(), views.CustomerUpdateView.as_view(),
name='customer-update', name='customer-update',
), ),
# path( path(
# 'delete/', 'shipping-address/update/',
# views.CustomerDeleteView.as_view(), views.CustomerShippingAddressUpdateView.as_view(),
# name='customer-delete' name='customer-shipping-address-update',
# ), ),
path( path(
'orders/<int:order_pk>/', 'orders/<int:order_pk>/',
views.OrderDetailView.as_view(), views.OrderDetailView.as_view(),
name='order-detail', name='order-detail',
), ),
path(
'addresses/new/',
views.CustomerAddressCreateView.as_view(),
name='customer-address-create',
),
path(
'addresses/<int:address_pk>/update/',
views.CustomerAddressUpdateView.as_view(),
name='address-update',
)
])), ])),
path( path(

View File

@ -34,11 +34,9 @@ from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
from moneyed import Money, USD from moneyed import Money, USD
from accounts.models import User, Address from accounts.models import User
from accounts.utils import get_or_create_customer from accounts.utils import get_or_create_customer
from accounts.forms import ( from accounts.forms import CustomerUpdateForm, CustomerShippingAddressUpdateForm
AddressForm as AccountAddressForm, CustomerUpdateForm
)
from core.models import ( from core.models import (
ProductCategory, Product, ProductVariant, ProductOption, ProductCategory, Product, ProductVariant, ProductOption,
Order, Transaction, OrderLine, Coupon, ShippingRate, Order, Transaction, OrderLine, Coupon, ShippingRate,
@ -252,16 +250,15 @@ class CheckoutAddressView(FormView):
def get_initial(self): def get_initial(self):
user = self.request.user user = self.request.user
initial = None initial = None
if user.is_authenticated and user.default_shipping_address: if user.is_authenticated:
address = user.default_shipping_address
initial = { initial = {
'full_name': address.first_name + ' ' + address.last_name, 'full_name': user.first_name + ' ' + user.last_name,
'email': user.email, 'email': user.email,
'street_address_1': address.street_address_1, 'street_address_1': user.shipping_street_address_1,
'street_address_2': address.street_address_2, 'street_address_2': user.shipping_street_address_2,
'city': address.city, 'city': user.shipping_city,
'state': address.state, 'state': user.shipping_state,
'postal_code': address.postal_code 'postal_code': user.shipping_postal_code
} }
elif self.request.session.get('shipping_address'): elif self.request.session.get('shipping_address'):
address = self.request.session.get('shipping_address') address = self.request.session.get('shipping_address')
@ -413,7 +410,14 @@ class OrderCreateView(CreateView):
shipping_container = cart.get_shipping_container() shipping_container = cart.get_shipping_container()
form.instance.shipping_total = cart.get_shipping_price(shipping_container) form.instance.shipping_total = cart.get_shipping_price(shipping_container)
shipping_address = self.request.session.get('shipping_address') shipping_address = self.request.session.get('shipping_address')
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, shipping_address) form.instance.shipping_first_name = shipping_address['first_name']
form.instance.shipping_last_name = shipping_address['last_name']
form.instance.shipping_street_address_1 = shipping_address['street_address_1']
form.instance.shipping_street_address_2 = shipping_address['street_address_2']
form.instance.shipping_city = shipping_address['city']
form.instance.shipping_state = shipping_address['state']
form.instance.shipping_postal_code = shipping_address['postal_code']
form.instance.customer = get_or_create_customer(self.request, shipping_address)
form.instance.status = OrderStatus.DRAFT form.instance.status = OrderStatus.DRAFT
self.object = form.save() self.object = form.save()
bulk_list = cart.build_bulk_list(self.object) bulk_list = cart.build_bulk_list(self.object)
@ -443,7 +447,14 @@ class FreeOrderCreateView(CreateView):
shipping_container = cart.get_shipping_container() shipping_container = cart.get_shipping_container()
form.instance.shipping_total = cart.get_shipping_price(shipping_container) form.instance.shipping_total = cart.get_shipping_price(shipping_container)
shipping_address = self.request.session.get('shipping_address') shipping_address = self.request.session.get('shipping_address')
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, shipping_address) form.instance.shipping_first_name = shipping_address['first_name']
form.instance.shipping_last_name = shipping_address['last_name']
form.instance.shipping_street_address_1 = shipping_address['street_address_1']
form.instance.shipping_street_address_2 = shipping_address['street_address_2']
form.instance.shipping_city = shipping_address['city']
form.instance.shipping_state = shipping_address['state']
form.instance.shipping_postal_code = shipping_address['postal_code']
form.instance.customer = get_or_create_customer(self.request, shipping_address)
form.instance.status = OrderStatus.UNFULFILLED form.instance.status = OrderStatus.UNFULFILLED
self.object = form.save() self.object = form.save()
bulk_list = cart.build_bulk_list(self.object) bulk_list = cart.build_bulk_list(self.object)
@ -546,6 +557,24 @@ class CustomerUpdateView(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
) )
class CustomerShippingAddressUpdateView(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
model = User
template_name = 'storefront/customer_shipping_address_form.html'
context_object_name = 'customer'
form_class = CustomerShippingAddressUpdateForm
permission_denied_message = 'Not authorized.'
raise_exception = True
def test_func(self):
return self.request.user.pk == self.get_object().pk
def get_success_url(self):
return reverse(
'storefront:customer-detail', kwargs={'pk': self.object.pk}
)
class OrderDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView): class OrderDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView):
model = Order model = Order
template_name = 'storefront/order_detail.html' template_name = 'storefront/order_detail.html'
@ -562,58 +591,6 @@ class OrderDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView):
return context return context
class CustomerAddressCreateView(
UserPassesTestMixin, LoginRequiredMixin, CreateView
):
model = Address
template_name = 'storefront/address_create_form.html'
form_class = AccountAddressForm
permission_denied_message = 'Not authorized.'
raise_exception = True
def test_func(self):
return self.request.user.pk == self.kwargs['pk']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['customer'] = User.objects.get(pk=self.kwargs['pk'])
return context
def form_valid(self, form):
customer = User.objects.get(pk=self.kwargs['pk'])
self.object = form.save()
customer.addresses.add(self.object)
return super().form_valid(form)
def get_success_url(self):
return reverse(
'storefront:customer-detail', kwargs={'pk': self.kwargs['pk']}
)
class CustomerAddressUpdateView(
UserPassesTestMixin, LoginRequiredMixin, UpdateView
):
model = Address
pk_url_kwarg = 'address_pk'
template_name = 'storefront/address_form.html'
form_class = AccountAddressForm
permission_denied_message = 'Not authorized.'
raise_exception = True
def test_func(self):
return self.request.user.pk == self.kwargs['pk']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['customer'] = User.objects.get(pk=self.kwargs['pk'])
return context
def get_success_url(self):
return reverse('storefront:customer-detail', kwargs={'pk': self.kwargs['pk']})
class AboutView(TemplateView): class AboutView(TemplateView):
template_name = 'storefront/about.html' template_name = 'storefront/about.html'
@ -754,7 +731,7 @@ class SubscriptionCreateView(SuccessMessageMixin, CreateView):
).get('recurring') ).get('recurring')
shipping_cost = get_shipping_cost( shipping_cost = get_shipping_cost(
self.object.total_weight, self.object.total_weight,
self.object.shipping_address.postal_code self.object.shipping_postal_code
) * 100 ) * 100
line_items.append({ line_items.append({
'price_data': { 'price_data': {
@ -789,7 +766,16 @@ class SubscriptionCreateView(SuccessMessageMixin, CreateView):
def form_valid(self, form): def form_valid(self, form):
shipping_address = self.request.session.get('shipping_address') shipping_address = self.request.session.get('shipping_address')
subscription = self.request.session.get('subscription') subscription = self.request.session.get('subscription')
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, shipping_address) form.instance.shipping_first_name = shipping_address['first_name']
form.instance.shipping_last_name = shipping_address['last_name']
form.instance.shipping_street_address_1 = shipping_address['street_address_1']
form.instance.shipping_street_address_2 = shipping_address['street_address_2']
form.instance.shipping_city = shipping_address['city']
form.instance.shipping_state = shipping_address['state']
form.instance.shipping_postal_code = shipping_address['postal_code']
form.instance.customer = get_or_create_customer(
self.request, shipping_address
)
weight, unit = subscription['metadata']['total_weight'].split(':') weight, unit = subscription['metadata']['total_weight'].split(':')
form.instance.total_weight = guess( form.instance.total_weight = guess(
float(weight), unit, measures=[Weight] float(weight), unit, measures=[Weight]
@ -855,3 +841,4 @@ def stripe_webhook(request):
subscription.create_order(event.data.object) subscription.create_order(event.data.object)
return JsonResponse({'status': 'success'}) return JsonResponse({'status': 'success'})

View File

@ -14,8 +14,20 @@
<form method="POST" action="{% url 'account_change_password' %}" class="password_change"> <form method="POST" action="{% url 'account_change_password' %}" class="password_change">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} <table class="form-table">
<input type="submit" value="{% trans "Save changes" %}"> <tbody>
{{ form.as_table }}
</tbody>
<tfoot>
<tr>
<td>
</td>
<td>
<button type="submit">Save changes</button>
</td>
</tr>
</tfoot>
</table>
</form> </form>
</article> </article>
{% endblock %} {% endblock %}