Merge branch 'release/0.5.3'
This commit is contained in:
commit
8ebc58b6b5
112
Pipfile.lock
generated
112
Pipfile.lock
generated
@ -152,28 +152,28 @@
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3",
|
||||
"sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31",
|
||||
"sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac",
|
||||
"sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf",
|
||||
"sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316",
|
||||
"sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca",
|
||||
"sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638",
|
||||
"sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94",
|
||||
"sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12",
|
||||
"sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173",
|
||||
"sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b",
|
||||
"sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a",
|
||||
"sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f",
|
||||
"sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2",
|
||||
"sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9",
|
||||
"sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46",
|
||||
"sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903",
|
||||
"sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3",
|
||||
"sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1",
|
||||
"sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"
|
||||
"sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b",
|
||||
"sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51",
|
||||
"sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7",
|
||||
"sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d",
|
||||
"sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6",
|
||||
"sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29",
|
||||
"sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9",
|
||||
"sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf",
|
||||
"sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815",
|
||||
"sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf",
|
||||
"sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85",
|
||||
"sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77",
|
||||
"sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86",
|
||||
"sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb",
|
||||
"sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e",
|
||||
"sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0",
|
||||
"sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3",
|
||||
"sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84",
|
||||
"sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2",
|
||||
"sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"
|
||||
],
|
||||
"version": "==36.0.1"
|
||||
"version": "==36.0.2"
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
@ -497,10 +497,10 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c",
|
||||
"sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"
|
||||
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
|
||||
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
|
||||
],
|
||||
"version": "==2021.3"
|
||||
"version": "==2022.1"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
@ -641,19 +641,19 @@
|
||||
},
|
||||
"sympy": {
|
||||
"hashes": [
|
||||
"sha256:2009368e862cd29f1b568dc6572786371a2faa1cd8eb4d313e11a90195d6ee36",
|
||||
"sha256:6cf85a5cfe8fff69553e745b05128de6fc8de8f291965c63871c79701dc6efc9"
|
||||
"sha256:5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b",
|
||||
"sha256:df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.10"
|
||||
"version": "==1.10.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
|
||||
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
|
||||
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
|
||||
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'",
|
||||
"version": "==1.26.8"
|
||||
"version": "==1.26.9"
|
||||
},
|
||||
"vine": {
|
||||
"hashes": [
|
||||
@ -830,28 +830,28 @@
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3",
|
||||
"sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31",
|
||||
"sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac",
|
||||
"sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf",
|
||||
"sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316",
|
||||
"sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca",
|
||||
"sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638",
|
||||
"sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94",
|
||||
"sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12",
|
||||
"sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173",
|
||||
"sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b",
|
||||
"sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a",
|
||||
"sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f",
|
||||
"sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2",
|
||||
"sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9",
|
||||
"sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46",
|
||||
"sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903",
|
||||
"sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3",
|
||||
"sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1",
|
||||
"sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"
|
||||
"sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b",
|
||||
"sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51",
|
||||
"sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7",
|
||||
"sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d",
|
||||
"sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6",
|
||||
"sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29",
|
||||
"sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9",
|
||||
"sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf",
|
||||
"sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815",
|
||||
"sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf",
|
||||
"sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85",
|
||||
"sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77",
|
||||
"sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86",
|
||||
"sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb",
|
||||
"sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e",
|
||||
"sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0",
|
||||
"sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3",
|
||||
"sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84",
|
||||
"sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2",
|
||||
"sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"
|
||||
],
|
||||
"version": "==36.0.1"
|
||||
"version": "==36.0.2"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
@ -951,7 +951,7 @@
|
||||
"sha256:670a52d3115d0e879e1ac838a4eb999af32f858163e3a704fe4839de2a676070",
|
||||
"sha256:fb2d48e4eab0dfb786a472cd514aaadc71e3445b203bc300bad93daa75d77c1a"
|
||||
],
|
||||
"markers": "python_full_version >= '3.7.0'",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.20.0"
|
||||
},
|
||||
"trio-websocket": {
|
||||
@ -964,18 +964,18 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
|
||||
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
|
||||
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
|
||||
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'",
|
||||
"version": "==1.26.8"
|
||||
"version": "==1.26.9"
|
||||
},
|
||||
"wsproto": {
|
||||
"hashes": [
|
||||
"sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b",
|
||||
"sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"
|
||||
],
|
||||
"markers": "python_full_version >= '3.7.0'",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ class OrderStatus:
|
||||
DRAFT = "draft" # fully editable, not finalized order created by staff users
|
||||
UNFULFILLED = "unfulfilled" # order with no items marked as fulfilled
|
||||
PARTIALLY_FULFILLED = (
|
||||
"partially fulfilled" # order with some items marked as fulfilled
|
||||
"partially_fulfilled" # order with some items marked as fulfilled
|
||||
)
|
||||
FULFILLED = "fulfilled" # order with all items marked as fulfilled
|
||||
|
||||
|
||||
@ -6,4 +6,4 @@ class CoreConfig(AppConfig):
|
||||
name = 'core'
|
||||
|
||||
def ready(self):
|
||||
from .signals import order_created, transaction_created
|
||||
from .signals import order_created, transaction_created, order_line_post_save
|
||||
|
||||
@ -58,6 +58,9 @@ class Product(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dashboard:product-detail', kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
class ProductPhoto(models.Model):
|
||||
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
||||
@ -122,8 +125,15 @@ class OrderManager(models.Manager):
|
||||
def with_fulfillment(self):
|
||||
return self.annotate(
|
||||
total_quantity_fulfilled=models.Sum('lines__quantity_fulfilled'),
|
||||
total_quantity_ordered=models.Sum('lines__quantity')
|
||||
)
|
||||
|
||||
def with_fulfillment_and_filter(self, query=None):
|
||||
return self.annotate(
|
||||
total_quantity_fulfilled=models.Sum('lines__quantity_fulfilled'),
|
||||
total_quantity_ordered=models.Sum('lines__quantity')
|
||||
).filter(pk=query)
|
||||
|
||||
|
||||
class Order(models.Model):
|
||||
customer = models.ForeignKey(
|
||||
@ -182,6 +192,7 @@ class Order(models.Model):
|
||||
return reverse('dashboard:order-detail', kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
status = models.CharField(
|
||||
max_length=32,
|
||||
|
||||
@ -3,9 +3,10 @@ from io import BytesIO
|
||||
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.db import models
|
||||
|
||||
from . import TransactionStatus
|
||||
from .models import Order, Transaction
|
||||
from . import OrderStatus, TransactionStatus
|
||||
from .models import Order, OrderLine, Transaction
|
||||
from .tasks import send_order_confirmation_email
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -32,3 +33,31 @@ def transaction_created(sender, instance, created, **kwargs):
|
||||
send_order_confirmation_email.delay(order)
|
||||
instance.confirmation_email_sent = True
|
||||
instance.save()
|
||||
|
||||
def get_order_status(total_quantity_fulfilled, total_quantity_ordered):
|
||||
if total_quantity_fulfilled >= total_quantity_ordered:
|
||||
return OrderStatus.FULFILLED
|
||||
elif total_quantity_fulfilled > 0:
|
||||
return OrderStatus.PARTIALLY_FULFILLED
|
||||
else:
|
||||
return OrderStatus.UNFULFILLED
|
||||
|
||||
@receiver(post_save, sender=OrderLine, dispatch_uid="order_line_post_save")
|
||||
def order_line_post_save(sender, instance, created, **kwargs):
|
||||
if not created:
|
||||
order = Order.objects.with_fulfillment().filter(
|
||||
pk=instance.order.pk
|
||||
)[0]
|
||||
|
||||
order.status = get_order_status(order.total_quantity_fulfilled, order.total_quantity_ordered)
|
||||
order.save()
|
||||
|
||||
# order.update(
|
||||
# status=models.Case(
|
||||
# models.When(models.lookups.GreaterThan(models.F('total_quantity_fulfilled'), models.F('total_quantity_ordered')),
|
||||
# then=models.Value(OrderStatus.FULFILLED)),
|
||||
# models.When(models.lookups.GreaterThan(models.F('total_quantity_fulfilled'), 0),
|
||||
# then=models.Value(OrderStatus.PARTIALLY_FULFILLED)),
|
||||
# default=models.Value(OrderStatus.UNFULFILLED)
|
||||
# )
|
||||
# )
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
from measurement.measures import Weight
|
||||
|
||||
class WeightUnits:
|
||||
KILOGRAM = "kg"
|
||||
POUND = "lb"
|
||||
# KILOGRAM = "kg"
|
||||
# POUND = "lb"
|
||||
OUNCE = "oz"
|
||||
GRAM = "g"
|
||||
# GRAM = "g"
|
||||
|
||||
CHOICES = [
|
||||
(KILOGRAM, "kg"),
|
||||
(POUND, "lb"),
|
||||
# (KILOGRAM, "kg"),
|
||||
# (POUND, "lb"),
|
||||
(OUNCE, "oz"),
|
||||
(GRAM, "g"),
|
||||
# (GRAM, "g"),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -5,7 +5,16 @@
|
||||
<article>
|
||||
<header class="object__header">
|
||||
<h1><img src="{% static "images/box.png" %}" alt=""> Order #{{order.pk}}</h1>
|
||||
<span class="order__status order__status--{{order.status}}">{{order.get_status_display}}</span>
|
||||
<div class="object__menu">
|
||||
<div class="dropdown">
|
||||
<span class="dropdown__menu">Options ↓</span>
|
||||
<div class="dropdown__child">
|
||||
<a href="">Cancel order</a>
|
||||
<a href="">Return order</a>
|
||||
</div>
|
||||
</div>
|
||||
<span class="order__status order__status--{{order.status}}">{{order.get_status_display}} ({{order.total_quantity_fulfilled}} / {{order.total_quantity_ordered}})</span>
|
||||
</div>
|
||||
</header>
|
||||
<section class="object__list">
|
||||
<div class="object__item object__item--header">
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
<article>
|
||||
<header class="object__header">
|
||||
<h1><img src="{% static "images/cubes.png" %}" alt=""> Product</h1>
|
||||
<a href="edit" class="action-button">Edit</a>
|
||||
<a href="{% url 'dashboard:product-update' product.pk %}" class="action-button">Edit</a>
|
||||
</header>
|
||||
<section class="product__detail">
|
||||
<section class="product__detail object__panel">
|
||||
<figure class="product__figure">
|
||||
<img class="" src="{{product.productphoto_set.first.image.url}}" alt="{{product.productphoto_set.first.image}}">
|
||||
</figure>
|
||||
|
||||
18
src/dashboard/templates/dashboard/product_update_form.html
Normal file
18
src/dashboard/templates/dashboard/product_update_form.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% extends "dashboard.html" %}
|
||||
|
||||
{% block content %}
|
||||
<article class="product">
|
||||
<header class="object__header">
|
||||
<h1>Update Product</h1>
|
||||
</header>
|
||||
<section class="object__panel">
|
||||
<form class="panel__item" method="POST" action="{% url 'dashboard:product-update' product.pk %}">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
<p class="form__submit">
|
||||
<input class="action-button" type="submit" value="Save changes"> or <a href="{% url 'dashboard:product-detail' product.pk %}">cancel</a>
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
@ -16,7 +16,7 @@ urlpatterns = [
|
||||
path('products/new/', views.ProductCreateView.as_view(), name='product-create'),
|
||||
path('<int:pk>/', include([
|
||||
path('', views.ProductDetailView.as_view(), name='product-detail'),
|
||||
# path('update/', views.ProductUpdateView.as_view(), name='product-update'),
|
||||
path('update/', views.ProductUpdateView.as_view(), name='product-update'),
|
||||
# path('delete/', views.ProductDeleteView.as_view(), name='product-delete'),
|
||||
])),
|
||||
|
||||
|
||||
@ -75,8 +75,8 @@ class OrderDetailView(DetailView):
|
||||
template_name = 'dashboard/order_detail.html'
|
||||
|
||||
def get_object(self):
|
||||
queryset = Order.objects.filter(
|
||||
pk=self.kwargs.get(self.pk_url_kwarg)
|
||||
queryset = Order.objects.with_fulfillment_and_filter(
|
||||
self.kwargs.get(self.pk_url_kwarg)
|
||||
).select_related(
|
||||
'customer',
|
||||
'billing_address',
|
||||
@ -106,21 +106,6 @@ class OrderFulfillView(UpdateView):
|
||||
return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk})
|
||||
|
||||
|
||||
def order_fulfill(request, pk):
|
||||
order = Order.objects.get(pk=pk)
|
||||
OrderLineFormset = inlineformset_factory(Order, OrderLine, form=OrderLineFulfillForm, extra=0, can_delete=False)
|
||||
if request.method == "POST":
|
||||
formset = OrderLineFormset(request.POST, request.FILES, instance=order)
|
||||
if formset.is_valid():
|
||||
formset.save()
|
||||
# Do something. Should generally end with a redirect. For example:
|
||||
return HttpResponseRedirect(order.get_absolute_url())
|
||||
|
||||
else:
|
||||
formset = OrderLineFormset(instance=order)
|
||||
return render(request, 'dashboard/order_fulfill.html', {'formset': formset, 'order': order})
|
||||
|
||||
|
||||
class ProductListView(ListView):
|
||||
model = Product
|
||||
template_name = 'dashboard/product_list.html'
|
||||
@ -137,6 +122,11 @@ class ProductDetailView(DetailView):
|
||||
model = Product
|
||||
template_name = 'dashboard/product_detail.html'
|
||||
|
||||
class ProductUpdateView(UpdateView):
|
||||
model = Product
|
||||
template_name = 'dashboard/product_update_form.html'
|
||||
fields = '__all__'
|
||||
|
||||
class ProductCreateView(CreateView):
|
||||
model = Product
|
||||
template_name = "dashboard/product_create_form.html"
|
||||
|
||||
@ -3,7 +3,7 @@ import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
DEBUG = os.environ.get('DEBUG', True)
|
||||
DEBUG = os.environ.get('DEBUG', 'True') == 'True'
|
||||
|
||||
DATABASE_CONFIG = {
|
||||
'ENGINE' : 'django.db.backends.postgresql',
|
||||
@ -29,7 +29,7 @@ ANYMAIL_CONFIG = {
|
||||
SERVER_EMAIL = os.environ.get('SERVER_EMAIL', '')
|
||||
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', '')
|
||||
|
||||
SECURE_HSTS_SECONDS = os.environ.get('SECURE_HSTS_SECONDS', False)
|
||||
SECURE_SSL_REDIRECT = os.environ.get('SECURE_SSL_REDIRECT', False)
|
||||
SESSION_COOKIE_SECURE = os.environ.get('SESSION_COOKIE_SECURE', False)
|
||||
CSRF_COOKIE_SECURE = os.environ.get('CSRF_COOKIE_SECURE', False)
|
||||
SECURE_HSTS_SECONDS = os.environ.get('SECURE_HSTS_SECONDS', 3600)
|
||||
SECURE_SSL_REDIRECT = os.environ.get('SECURE_SSL_REDIRECT', 'True') == 'True'
|
||||
SESSION_COOKIE_SECURE = os.environ.get('SESSION_COOKIE_SECURE', 'True') == 'True'
|
||||
CSRF_COOKIE_SECURE = os.environ.get('CSRF_COOKIE_SECURE', 'True') == 'True'
|
||||
|
||||
@ -146,9 +146,12 @@ USE_TZ = True
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = BASE_DIR / 'public'
|
||||
if DEBUG:
|
||||
STATIC_ROOT = BASE_DIR / 'public'
|
||||
else:
|
||||
STATIC_ROOT = '/var/www/ptcoffee-dev/static/'
|
||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||
MEDIA_URL = '/images/'
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
STATICFILES_FINDERS = (
|
||||
|
||||
@ -13,5 +13,7 @@ urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('__debug__/', include('debug_toolbar.urls')),
|
||||
]
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
import { getCookie, setCookie } from "../lib/cookie.js"
|
||||
import { Controller } from "../stimulus.js"
|
||||
|
||||
export default class extends Controller {
|
||||
static get targets() {
|
||||
return [ "cartList" ]
|
||||
}
|
||||
|
||||
connect() {
|
||||
console.log(getCookie('cart'))
|
||||
this.cart = this.getOrSetCart()
|
||||
this.element.addEventListener('addToCart', this.addItem.bind(this))
|
||||
}
|
||||
|
||||
getOrSetCart() {
|
||||
let cart = JSON.parse(getCookie('cart'))
|
||||
if (cart === null) {
|
||||
console.log('created cart cookie')
|
||||
setCookie('cart', '{}')
|
||||
cart = {}
|
||||
} else {
|
||||
Object.keys(cart).forEach((item_id) => {
|
||||
fetch(`/${item_id}/`)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
this.cartListTarget.innerHTML += html;
|
||||
});
|
||||
})
|
||||
}
|
||||
return cart
|
||||
}
|
||||
|
||||
decodeCartItems(cart) {
|
||||
|
||||
}
|
||||
|
||||
addItem(event) {
|
||||
if (this.cart[event.detail.item] === undefined) {
|
||||
this.cart[event.detail.item] = {'quantity': 1}
|
||||
} else {
|
||||
this.cart[event.detail.item]['quantity'] += 1
|
||||
}
|
||||
setCookie('cart', JSON.stringify(this.cart))
|
||||
console.log(this.cart)
|
||||
|
||||
fetch(`/${event.detail.item}/`)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
this.cartListTarget.innerHTML += html;
|
||||
});
|
||||
}
|
||||
|
||||
removeItem() {
|
||||
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import { getCookie, setCookie } from "../lib/cookie.js"
|
||||
import { Controller } from "../stimulus.js"
|
||||
|
||||
export default class extends Controller {
|
||||
static get targets() {
|
||||
return [ "qty" ]
|
||||
}
|
||||
|
||||
connect() {
|
||||
console.log(this.qtyTarget)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
// import getCookie from "../get_cookie.js"
|
||||
import { Controller } from "../stimulus.js"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { url: String }
|
||||
|
||||
connect() {
|
||||
this.event = new CustomEvent('addToCart', {
|
||||
detail: {
|
||||
item: this.urlValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
start() {
|
||||
|
||||
}
|
||||
|
||||
addToCart(event) {
|
||||
event.preventDefault()
|
||||
window.dispatchEvent(this.event)
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import { Application } from "./stimulus.js"
|
||||
|
||||
import CartController from "./controllers/cart_controller.js"
|
||||
import CartitemController from "./controllers/cartitem_controller.js"
|
||||
import ProductController from "./controllers/product_controller.js"
|
||||
|
||||
const application = Application.start()
|
||||
application.register("cart", CartController)
|
||||
application.register("cartitem", CartitemController)
|
||||
application.register("product", ProductController)
|
||||
@ -60,99 +60,3 @@ paypal.Buttons({
|
||||
});
|
||||
}
|
||||
}).render('#paypal-button-container');
|
||||
|
||||
|
||||
|
||||
// RETURNED DATA
|
||||
// {
|
||||
// "id": "1WM83684A69628456",
|
||||
// "status": "COMPLETED",
|
||||
// "purchase_units": [
|
||||
// {
|
||||
// "reference_id": "default",
|
||||
// "shipping": {
|
||||
// "name": {
|
||||
// "full_name": "John Doe"
|
||||
// },
|
||||
// "address": {
|
||||
// "address_line_1": "1 Main St",
|
||||
// "admin_area_2": "San Jose",
|
||||
// "admin_area_1": "CA",
|
||||
// "postal_code": "95131",
|
||||
// "country_code": "US"
|
||||
// }
|
||||
// },
|
||||
// "payments": {
|
||||
// "captures": [
|
||||
// {
|
||||
// "id": "9AU23265T5630860D",
|
||||
// "status": "COMPLETED",
|
||||
// "amount": {
|
||||
// "currency_code": "USD",
|
||||
// "value": "13.40"
|
||||
// },
|
||||
// "final_capture": true,
|
||||
// "seller_protection": {
|
||||
// "status": "ELIGIBLE",
|
||||
// "dispute_categories": [
|
||||
// "ITEM_NOT_RECEIVED",
|
||||
// "UNAUTHORIZED_TRANSACTION"
|
||||
// ]
|
||||
// },
|
||||
// "seller_receivable_breakdown": {
|
||||
// "gross_amount": {
|
||||
// "currency_code": "USD",
|
||||
// "value": "13.40"
|
||||
// },
|
||||
// "paypal_fee": {
|
||||
// "currency_code": "USD",
|
||||
// "value": "0.96"
|
||||
// },
|
||||
// "net_amount": {
|
||||
// "currency_code": "USD",
|
||||
// "value": "12.44"
|
||||
// }
|
||||
// },
|
||||
// "links": [
|
||||
// {
|
||||
// "href": "https://api.sandbox.paypal.com/v2/payments/captures/9AU23265T5630860D",
|
||||
// "rel": "self",
|
||||
// "method": "GET"
|
||||
// },
|
||||
// {
|
||||
// "href": "https://api.sandbox.paypal.com/v2/payments/captures/9AU23265T5630860D/refund",
|
||||
// "rel": "refund",
|
||||
// "method": "POST"
|
||||
// },
|
||||
// {
|
||||
// "href": "https://api.sandbox.paypal.com/v2/checkout/orders/1WM83684A69628456",
|
||||
// "rel": "up",
|
||||
// "method": "GET"
|
||||
// }
|
||||
// ],
|
||||
// "create_time": "2022-02-28T15:33:07Z",
|
||||
// "update_time": "2022-02-28T15:33:07Z"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
// "payer": {
|
||||
// "name": {
|
||||
// "given_name": "John",
|
||||
// "surname": "Doe"
|
||||
// },
|
||||
// "email_address": "sb-rlst914027742@personal.example.com",
|
||||
// "payer_id": "G9RGHQ72CGKF6",
|
||||
// "address": {
|
||||
// "country_code": "US"
|
||||
// }
|
||||
// },
|
||||
// "links": [
|
||||
// {
|
||||
// "href": "https://api.sandbox.paypal.com/v2/checkout/orders/1WM83684A69628456",
|
||||
// "rel": "self",
|
||||
// "method": "GET"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,9 @@
|
||||
--yellow-color: #f8a911;
|
||||
--yellow-alt-color: #f6c463;
|
||||
--green-color: #13ce65;
|
||||
--red-color: #ff4d44;
|
||||
|
||||
--default-border: 2px solid var(--gray-color);
|
||||
}
|
||||
|
||||
html {
|
||||
@ -62,13 +65,60 @@ small {
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
input[type=text],
|
||||
input[type=email],
|
||||
input[type=number],
|
||||
input[type=password],
|
||||
select[multiple=multiple],
|
||||
textarea {
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
color: var(--fg-color);
|
||||
border: var(--default-border);
|
||||
padding: 0.5rem;
|
||||
outline: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
border-color: var(--yellow-color);
|
||||
}
|
||||
|
||||
select {
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
font-size: 1rem;
|
||||
border: var(--default-border);
|
||||
padding: 0.5em;
|
||||
outline: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
input[type=number] + select {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=email],
|
||||
input[type=password],
|
||||
select[multiple=multiple],
|
||||
textarea {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
max-width: 4rem;
|
||||
max-width: 6rem;
|
||||
}
|
||||
|
||||
input[type=checkbox],
|
||||
input[type=radio] {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
@ -93,6 +143,23 @@ button:hover {
|
||||
background-color: var(--yellow-alt-color);
|
||||
}
|
||||
|
||||
.action-button--warning {
|
||||
background-color: var(--red-color);
|
||||
}
|
||||
|
||||
.action-link {
|
||||
font-family: inherit;
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
color: var(--yellow-color);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-link--warning {
|
||||
color: var(--red-color);
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@ -197,7 +264,7 @@ main article {
|
||||
|
||||
.object__header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@ -246,6 +313,7 @@ main article {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.product__image {
|
||||
@ -254,16 +322,90 @@ main article {
|
||||
}
|
||||
|
||||
|
||||
.object__menu {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.order__fulfill {
|
||||
grid-column: 8;
|
||||
}
|
||||
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown__menu {
|
||||
background-color: var(--bg-alt-color);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
margin-right: 1rem;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown__child {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
background-color: var(--bg-color);
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0 3rem var(--gray-color);
|
||||
}
|
||||
|
||||
.dropdown__child a {
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
display: block;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
/*border-radius: 0.5rem;*/
|
||||
}
|
||||
.dropdown__child a:hover {
|
||||
background-color: var(--bg-alt-color);
|
||||
}
|
||||
|
||||
.dropdown__child a:first-child {
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
.dropdown__child a:last-child {
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown__child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown__icon {
|
||||
background-color: var(--bg-alt-color);
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
fill: currentColor;
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
display: inline-block;
|
||||
line-height: 1.75;
|
||||
transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
}
|
||||
|
||||
.dropdown__icon:hover {
|
||||
background-color: var(--yellow-alt-color);
|
||||
}
|
||||
|
||||
.order__status {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.order__status--display {
|
||||
@ -276,13 +418,13 @@ main article {
|
||||
background-color: var(--gray-color);
|
||||
}
|
||||
.order__status--unfulfilled {
|
||||
background-color: var(--yellow-alt-color);
|
||||
background-color: var(--red-color);
|
||||
}
|
||||
.order__status--partially_returned {
|
||||
background-color: var(--gray-color);
|
||||
background-color: var(--yellow-alt-color);
|
||||
}
|
||||
.order__status--partially_fulfilled {
|
||||
background-color: var(--gray-color);
|
||||
background-color: var(--yellow-alt-color);
|
||||
}
|
||||
.order__status--returned {
|
||||
background-color: var(--gray-color);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
:root {
|
||||
--fg-color: #333;
|
||||
--bg-color: #f9f9f9;
|
||||
--bg-color: #f6ebd3;
|
||||
--gray-color: #9d9d9d;
|
||||
--yellow-color: #f8a911;
|
||||
--yellow-alt-color: #ffce6f;
|
||||
@ -275,7 +275,7 @@ nav {
|
||||
.cart__length {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
font-family: 'Eczar';
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.cart__item {
|
||||
|
||||
@ -14,19 +14,17 @@ class Cart:
|
||||
cart = self.session[settings.CART_SESSION_ID] = {}
|
||||
self.cart = cart
|
||||
|
||||
def add(self, product, quantity=1, roast='', other='', update_quantity=False):
|
||||
def add(self, product, quantity=1, roast='', update_quantity=False):
|
||||
product_id = str(product.id)
|
||||
if product_id not in self.cart:
|
||||
self.cart[product_id] = {
|
||||
'quantity': 0,
|
||||
'roast': roast,
|
||||
'other': other,
|
||||
'price': str(product.price)
|
||||
}
|
||||
elif product_id in self.cart:
|
||||
self.cart[product_id].update({
|
||||
'roast': roast,
|
||||
'other': other,
|
||||
})
|
||||
if update_quantity:
|
||||
self.cart[product_id]['quantity'] = quantity
|
||||
@ -88,7 +86,7 @@ class Cart:
|
||||
bulk_list = [OrderLine(
|
||||
order=order,
|
||||
product=item['product'],
|
||||
customer_note=f'{item["roast"]} {item["other"]}',
|
||||
customer_note=item['roast'],
|
||||
unit_price=item['price'],
|
||||
quantity=item['quantity'],
|
||||
tax_rate=2,
|
||||
|
||||
@ -30,7 +30,6 @@ class AddToCartForm(forms.Form):
|
||||
]
|
||||
quantity = forms.IntegerField(min_value=1, initial=1)
|
||||
roast = forms.ChoiceField(choices=ROAST_CHOICES)
|
||||
other = forms.CharField(required=False)
|
||||
update = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
|
||||
|
||||
|
||||
|
||||
@ -2,9 +2,8 @@
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h2>Shopping Cart</h2>
|
||||
<section>
|
||||
<p>{{user}}</p>
|
||||
|
||||
{% for item in cart %}
|
||||
<div class="cart__item">
|
||||
{% with product=item.product %}
|
||||
@ -13,16 +12,14 @@
|
||||
</figure>
|
||||
<div class="item__details">
|
||||
<p>{{product.name}}<br> ${{item.price}}</p>
|
||||
<p><strong>Grind options</strong>: {{item.customer_note}}</p>
|
||||
<p></p>
|
||||
<p>
|
||||
<a href="{% url 'storefront:cart-remove' product.pk %}">Remove from cart</a>
|
||||
</p>
|
||||
<form class="product__form" action="{% url 'storefront:cart-add' product.pk %}" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ item.update_quantity_form }}
|
||||
<input type="submit" value="Update">
|
||||
</form>
|
||||
<p>
|
||||
<a href="{% url 'storefront:cart-remove' product.pk %}">Remove from cart</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
@ -14,7 +14,9 @@
|
||||
<form method="post" action="{% url 'storefront:cart-add' product.pk %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<p>
|
||||
<input type="submit" value="Add to cart" class="action-button">
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
@ -37,7 +37,6 @@ class CartView(TemplateView):
|
||||
initial={
|
||||
'quantity': item['quantity'],
|
||||
'roast': item['roast'],
|
||||
'other': item['other'],
|
||||
'update': True
|
||||
}
|
||||
)
|
||||
@ -58,7 +57,6 @@ class CartAddProductView(SingleObjectMixin, FormView):
|
||||
cart.add(
|
||||
product=self.get_object(),
|
||||
roast=form.cleaned_data['roast'],
|
||||
other=form.cleaned_data['other'],
|
||||
quantity=form.cleaned_data['quantity'],
|
||||
update_quantity=form.cleaned_data['update']
|
||||
)
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
Hi <a href="{% url 'storefront:customer-detail' user.pk %}">{{user.first_name}} {{user.last_name}}</a>!
|
||||
</p>
|
||||
{% if user.is_staff %}
|
||||
<p><a href="{% url 'dashboard:home' %}">DASHBOARD</a></p>
|
||||
<p><a href="{% url 'dashboard:home' %}">Dashboard</a></p>
|
||||
{% endif %}
|
||||
<p><a href="{% url 'account_logout' %}">Log Out</a></p>
|
||||
{% else %}
|
||||
@ -45,7 +45,7 @@
|
||||
<a href="">Wholesale</a>
|
||||
<a href="">Subscribe</a>
|
||||
<a href="">Cafe</a>
|
||||
<a href="">Enjoy</a>
|
||||
<a href="">Fair Trade</a>
|
||||
<a href="{% url 'storefront:about' %}">About</a>
|
||||
<a href="">Contact</a>
|
||||
<a class="site__cart" href="{% url 'storefront:cart-detail' %}">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user