diff --git a/cart/cart.py b/cart/cart.py
index fde5a26..b52499a 100644
--- a/cart/cart.py
+++ b/cart/cart.py
@@ -3,6 +3,7 @@
from django.conf import settings
from shop.models import Product
+from coupons.models import Coupon
from common.constants import (KEY_PRICE, KEY_QUANTITY, KEY_PRODUCT, KEY_TOTAL_PRICE)
@@ -15,11 +16,29 @@ def __init__(self, request):
"""
self.session = request.session
+ self.coupon_id = self.session.get('coupon_id')
cart = self.session.get(settings.SESSION_CART_ID)
if not cart:
cart = self.session[settings.SESSION_CART_ID] = {}
self.cart = cart
+ @property
+ def coupon(self):
+ if self.coupon_id:
+ try:
+ return Coupon.objects.get(id=self.coupon_id)
+ except Coupon.DoesNotExist:
+ pass
+ return None
+
+ def get_discount(self):
+ if self.coupon:
+ return (self.coupon.discount / Decimal(100)) * self.get_total_price()
+ return Decimal(0)
+
+ def get_price_after_coupon(self):
+ return self.get_total_price() - self.get_discount()
+
def add(self, product, quantity=1, override_quantity=False):
"""
Add Item in cart or update its quantity
diff --git a/cart/forms.py b/cart/forms.py
index e7dfc32..3547b66 100644
--- a/cart/forms.py
+++ b/cart/forms.py
@@ -1,9 +1,11 @@
from django import forms
+from django.utils.translation import gettext_lazy as _
PRODUCT_QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 21)]
class CartAddProductForm(forms.Form):
- quantity = forms.TypedChoiceField(choices=PRODUCT_QUANTITY_CHOICES, coerce=int)
+ quantity = forms.TypedChoiceField(choices=PRODUCT_QUANTITY_CHOICES, coerce=int,
+ label=_('Quantity'))
override = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
diff --git a/cart/templates/cart/cart_details.html b/cart/templates/cart/cart_details.html
index 9acb99f..ee873d7 100644
--- a/cart/templates/cart/cart_details.html
+++ b/cart/templates/cart/cart_details.html
@@ -1,19 +1,20 @@
{% extends 'shop/base.html' %}
{% load static %}
+{% load i18n %}
-{% block title %} Your shopping Cart {% endblock %}
+{% block title %} {% trans 'Your shopping Cart' %} {% endblock %}
{% block content %}
-
Your shopping cart
+{% trans 'Your shopping cart' %}
- Image
- Product
- Quantity
- Remove
- Unit Price
- Price
+ {% trans 'Image' %}
+ {% trans 'Product' %}
+ {% trans 'Quantity' %}
+ {% trans 'Remove' %}
+ {% trans 'Unit Price' %}
+ {% trans 'Price' %}
@@ -36,24 +37,50 @@ Your shopping cart
{{item.price}}
{{item.total_price}}
+ {% if cart.coupon %}
+
+ {% trans 'Subtotal' %}
+
+ ${{cart.get_total_price|floatformat:2}}
+
+
+
+ "{{cart.coupon.code}}" coupon
+ ({{cart.coupon.discount}} off)
+
+
+
+ - ${{cart.get_discount|floatformat:2}}
+
+
+ {% endif %}
{% endwith %}
{% endfor %}
- Total
+ {% trans 'Total' %}
- ${{cart.get_total_price}}
+ ${{cart.get_price_after_coupon|floatformat:2}}
+
+
{% trans 'Apply a Coupon' %}
+
+
+
- Continue Shopping
- Checkout
+ {% trans 'Continue Shopping' %}
+ {% trans 'Checkout' %}
{% endblock %}
\ No newline at end of file
diff --git a/cart/views.py b/cart/views.py
index 54d5020..0b2f311 100644
--- a/cart/views.py
+++ b/cart/views.py
@@ -4,6 +4,7 @@
from .cart import Cart
from .forms import CartAddProductForm
from shop.models import Product
+from coupons.forms import CouponApplyForm
from common.constants import KEY_QUANTITY
@@ -30,8 +31,10 @@ def cart_remove(request, product_id):
def cart_details(request):
cart = Cart(request)
+ coupon_apply_form = CouponApplyForm()
for item in cart:
item['product_update_form'] = CartAddProductForm(initial={
KEY_QUANTITY: item.get(KEY_QUANTITY), 'override': True
})
- return render(request, 'cart/cart_details.html', {'cart': cart})
+ return render(request, 'cart/cart_details.html', {'cart': cart,
+ 'coupon_apply_form': coupon_apply_form})
diff --git a/coupons/__init__.py b/coupons/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/coupons/admin.py b/coupons/admin.py
new file mode 100644
index 0000000..3717dfb
--- /dev/null
+++ b/coupons/admin.py
@@ -0,0 +1,10 @@
+from django.contrib import admin
+
+from .models import Coupon
+
+
+@admin.register(Coupon)
+class CouponAdmin(admin.ModelAdmin):
+ list_display = ['code', 'valid_from', 'valid_to', 'discount', 'active', 'total_coupons', 'availed_coupons']
+ list_filter = ['active', 'valid_to', 'valid_from']
+ search_fields = ['code']
\ No newline at end of file
diff --git a/coupons/apps.py b/coupons/apps.py
new file mode 100644
index 0000000..1190536
--- /dev/null
+++ b/coupons/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class CouponsConfig(AppConfig):
+ name = 'coupons'
diff --git a/coupons/forms.py b/coupons/forms.py
new file mode 100644
index 0000000..9c4421f
--- /dev/null
+++ b/coupons/forms.py
@@ -0,0 +1,6 @@
+from django import forms
+from django.utils.translation import gettext_lazy as _
+
+
+class CouponApplyForm(forms.Form):
+ code = forms.CharField(label=_('Code'))
diff --git a/coupons/migrations/0001_initial.py b/coupons/migrations/0001_initial.py
new file mode 100644
index 0000000..7dd1596
--- /dev/null
+++ b/coupons/migrations/0001_initial.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.1.3 on 2021-12-31 18:47
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Coupon',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('code', models.CharField(db_index=True, max_length=32, unique=True)),
+ ('valid_from', models.DateTimeField()),
+ ('valid_to', models.DateTimeField()),
+ ('discount', models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
+ ('active', models.BooleanField()),
+ ('total_coupons', models.PositiveIntegerField(default=3)),
+ ('availed_coupons', models.PositiveIntegerField(default=0)),
+ ],
+ ),
+ ]
diff --git a/coupons/migrations/__init__.py b/coupons/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/coupons/models.py b/coupons/models.py
new file mode 100644
index 0000000..be63841
--- /dev/null
+++ b/coupons/models.py
@@ -0,0 +1,15 @@
+from django.db import models
+from django.core.validators import MinValueValidator, MaxValueValidator
+
+
+class Coupon(models.Model):
+ code = models.CharField(max_length=32, unique=True, db_index=True)
+ valid_from = models.DateTimeField()
+ valid_to = models.DateTimeField()
+ discount = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)])
+ active = models.BooleanField()
+ total_coupons = models.PositiveIntegerField(default=3)
+ availed_coupons = models.PositiveIntegerField(default=0)
+
+ def __str__(self):
+ return f"Coupon code:{self.code}"
diff --git a/coupons/tests.py b/coupons/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/coupons/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/coupons/urls.py b/coupons/urls.py
new file mode 100644
index 0000000..eca8d9b
--- /dev/null
+++ b/coupons/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+
+from .views import coupon_apply
+
+app_name = 'coupons'
+
+
+urlpatterns = [
+ path('apply/', coupon_apply, name='apply')
+]
diff --git a/coupons/views.py b/coupons/views.py
new file mode 100644
index 0000000..de6da20
--- /dev/null
+++ b/coupons/views.py
@@ -0,0 +1,21 @@
+from django.utils import timezone
+from django.shortcuts import render, redirect
+from django.views.decorators.http import require_POST
+
+from .models import Coupon
+from .forms import CouponApplyForm
+
+
+@require_POST
+def coupon_apply(request):
+ now = timezone.now()
+ form = CouponApplyForm(request.POST)
+ if form.is_valid():
+ code = form.cleaned_data['code']
+ try:
+ coupon = Coupon.objects.get(code=code, valid_from__lte=now, valid_to__gte=now, active=True)
+ request.session['coupon_id'] = coupon.id
+ except Coupon.DoesNotExist:
+ request.session['coupon_id'] = None
+
+ return redirect('cart:cart_details')
diff --git a/eShop/settings.py b/eShop/settings.py
index 197abb6..2f321cf 100644
--- a/eShop/settings.py
+++ b/eShop/settings.py
@@ -12,6 +12,7 @@
import os
import braintree
+from django.utils.translation import gettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -39,11 +40,13 @@
'cart.apps.CartConfig',
'orders.apps.OrdersConfig',
'payment.apps.PaymentConfig',
+ 'coupons.apps.CouponsConfig'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -106,7 +109,12 @@
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
-LANGUAGE_CODE = 'en-us'
+LANGUAGE_CODE = 'en'
+LANGUAGES = (
+ ('en', _('English')),
+ ('es', _('Spanish')),
+ ('ur', _('Urdu')),
+)
TIME_ZONE = 'UTC'
@@ -119,6 +127,9 @@
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
+LOCALE_PATHS = (
+ os.path.join(BASE_DIR, 'locale/'),
+)
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
@@ -133,6 +144,10 @@
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+REDIS_HOST = 'localhost'
+REDIS_PORT = '6379'
+REDIS_DB = 1
+
try:
from .local_settings import * # pylint: disable=all
except: # pylint: disable=bare-except
diff --git a/eShop/urls.py b/eShop/urls.py
index 315030c..42577f9 100644
--- a/eShop/urls.py
+++ b/eShop/urls.py
@@ -17,13 +17,15 @@
from django.conf import settings
from django.urls import path, include
from django.conf.urls.static import static
+from django.conf.urls.i18n import i18n_patterns
-urlpatterns = [
+urlpatterns = i18n_patterns(
path('admin/', admin.site.urls),
path('payment/', include('payment.urls', namespace='payment')),
path('cart/', include('cart.urls', namespace='cart')),
path('orders/', include('orders.urls', namespace='orders')),
+ path('coupons/', include('coupons.urls', namespace='coupons')),
path('', include('shop.urls', namespace='shop')),
-]
+)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 0000000..aca0f3a
--- /dev/null
+++ b/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,124 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-01-03 22:29+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: cart/forms.py:9 cart/templates/cart/cart_details.html:14
+msgid "Quantity"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:5
+msgid "Your shopping Cart"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:8
+msgid "Your shopping cart"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:12
+msgid "Image"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:13
+msgid "Product"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:15
+msgid "Remove"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:16
+msgid "Unit Price"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:17
+msgid "Price"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:40
+msgid "Remove Item"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:49
+msgid "Subtotal"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:67
+msgid "Total"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:74
+msgid "Apply a Coupon"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:78
+msgid "Apply"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:83
+msgid "Continue Shopping"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:84
+msgid "Checkout"
+msgstr ""
+
+#: coupons/forms.py:6
+msgid "Code"
+msgstr ""
+
+#: eShop/settings.py:114
+msgid "English"
+msgstr ""
+
+#: eShop/settings.py:115
+msgid "Spanish"
+msgstr ""
+
+#: eShop/settings.py:116
+msgid "Urdu"
+msgstr ""
+
+#: shop/templates/shop/base.html:7 shop/templates/shop/base.html:12
+msgid "eShop by Zee"
+msgstr ""
+
+#: shop/templates/shop/base.html:18
+msgid "Your Cart"
+msgstr ""
+
+#: shop/templates/shop/base.html:20
+#, python-format
+msgid ""
+"\n"
+" %(items)s item, $%(total)s\n"
+" "
+msgid_plural ""
+"\n"
+" %(items)s items, $%(total)s\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: shop/templates/shop/base.html:27
+msgid "Your cart is empty."
+msgstr ""
+
+#: shop/templates/shop/product/details.html:19
+msgid "Add to Cart"
+msgstr ""
diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000..bd62c28
--- /dev/null
+++ b/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,124 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-01-03 22:29+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: cart/forms.py:9 cart/templates/cart/cart_details.html:14
+msgid "Quantity"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:5
+msgid "Your shopping Cart"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:8
+msgid "Your shopping cart"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:12
+msgid "Image"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:13
+msgid "Product"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:15
+msgid "Remove"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:16
+msgid "Unit Price"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:17
+msgid "Price"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:40
+msgid "Remove Item"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:49
+msgid "Subtotal"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:67
+msgid "Total"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:74
+msgid "Apply a Coupon"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:78
+msgid "Apply"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:83
+msgid "Continue Shopping"
+msgstr ""
+
+#: cart/templates/cart/cart_details.html:84
+msgid "Checkout"
+msgstr ""
+
+#: coupons/forms.py:6
+msgid "Code"
+msgstr ""
+
+#: eShop/settings.py:114
+msgid "English"
+msgstr "Inglés"
+
+#: eShop/settings.py:115
+msgid "Spanish"
+msgstr "Español"
+
+#: eShop/settings.py:116
+msgid "Urdu"
+msgstr "Urdau"
+
+#: shop/templates/shop/base.html:7 shop/templates/shop/base.html:12
+msgid "eShop by Zee"
+msgstr ""
+
+#: shop/templates/shop/base.html:18
+msgid "Your Cart"
+msgstr ""
+
+#: shop/templates/shop/base.html:20
+#, python-format
+msgid ""
+"\n"
+" %(items)s item, $%(total)s\n"
+" "
+msgid_plural ""
+"\n"
+" %(items)s items, $%(total)s\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: shop/templates/shop/base.html:27
+msgid "Your cart is empty."
+msgstr ""
+
+#: shop/templates/shop/product/details.html:19
+msgid "Add to Cart"
+msgstr ""
diff --git a/locale/ur/LC_MESSAGES/django.po b/locale/ur/LC_MESSAGES/django.po
new file mode 100644
index 0000000..7520d9d
--- /dev/null
+++ b/locale/ur/LC_MESSAGES/django.po
@@ -0,0 +1,128 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-01-03 22:29+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: cart/forms.py:9 cart/templates/cart/cart_details.html:14
+msgid "Quantity"
+msgstr "مقدار"
+
+#: cart/templates/cart/cart_details.html:5
+#, fuzzy
+#| msgid "Your Cart"
+msgid "Your shopping Cart"
+msgstr "آپکی ٹوکری"
+
+#: cart/templates/cart/cart_details.html:8
+msgid "Your shopping cart"
+msgstr "آپکی ٹوکری"
+
+#: cart/templates/cart/cart_details.html:12
+msgid "Image"
+msgstr "تصویر"
+
+#: cart/templates/cart/cart_details.html:13
+msgid "Product"
+msgstr "چیز"
+
+#: cart/templates/cart/cart_details.html:15
+msgid "Remove"
+msgstr "ہٹائیں"
+
+#: cart/templates/cart/cart_details.html:16
+msgid "Unit Price"
+msgstr "ایک چیزکی قیمت"
+
+#: cart/templates/cart/cart_details.html:17
+msgid "Price"
+msgstr "قیمت"
+
+#: cart/templates/cart/cart_details.html:40
+#, fuzzy
+#| msgid "Remove"
+msgid "Remove Item"
+msgstr "ہٹائیں"
+
+#: cart/templates/cart/cart_details.html:49
+msgid "Subtotal"
+msgstr "آدھا میزان"
+
+#: cart/templates/cart/cart_details.html:67
+msgid "Total"
+msgstr "میزان"
+
+#: cart/templates/cart/cart_details.html:74
+msgid "Apply a Coupon"
+msgstr "کوپن لگائیں"
+
+#: cart/templates/cart/cart_details.html:78
+msgid "Apply"
+msgstr "لگائیے"
+
+#: cart/templates/cart/cart_details.html:83
+msgid "Continue Shopping"
+msgstr "شاپنگ کرتے رہیں"
+
+#: cart/templates/cart/cart_details.html:84
+msgid "Checkout"
+msgstr "پوراکریں"
+
+#: coupons/forms.py:6
+msgid "Code"
+msgstr "کوڈ"
+
+#: eShop/settings.py:114
+msgid "English"
+msgstr "انگریزی"
+
+#: eShop/settings.py:115
+msgid "Spanish"
+msgstr "اسپینش"
+
+#: eShop/settings.py:116
+msgid "Urdu"
+msgstr "اردو"
+
+#: shop/templates/shop/base.html:7 shop/templates/shop/base.html:12
+msgid "eShop by Zee"
+msgstr "آئن لائن دکان"
+
+#: shop/templates/shop/base.html:18
+msgid "Your Cart"
+msgstr "آپکی ٹوکری"
+
+#: shop/templates/shop/base.html:20
+#, python-format
+msgid ""
+"\n"
+" %(items)s چیز, $%(total)s\n"
+" "
+msgid_plural ""
+"\n"
+" %(items)s چیزیں, $%(total)s\n"
+" "
+msgstr[0] ""
+msgstr[1] ""
+
+#: shop/templates/shop/base.html:27
+msgid "Your cart is empty."
+msgstr "آپکی ٹوکری خالی ہے"
+
+#: shop/templates/shop/product/details.html:19
+msgid "Add to Cart"
+msgstr "ٹوکری میں ڈالیں"
diff --git a/orders/locale/en/LC_MESSAGES/django.po b/orders/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 0000000..6012aff
--- /dev/null
+++ b/orders/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,96 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-01-03 22:29+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: orders/models.py:11
+msgid "first name"
+msgstr ""
+
+#: orders/models.py:12
+msgid "last name"
+msgstr ""
+
+#: orders/models.py:13
+msgid "email"
+msgstr ""
+
+#: orders/models.py:14
+msgid "address"
+msgstr ""
+
+#: orders/models.py:15
+msgid "city"
+msgstr ""
+
+#: orders/models.py:16
+msgid "created"
+msgstr ""
+
+#: orders/models.py:17
+msgid "updated"
+msgstr ""
+
+#: orders/models.py:18
+msgid "paid"
+msgstr ""
+
+#: orders/models.py:19
+msgid "braintree id"
+msgstr ""
+
+#: orders/models.py:22
+msgid "Discount"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:4
+#: orders/templates/orders/order/order_create.html:7
+msgid "Checkout"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:10
+msgid "Your Order"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:20
+#, python-format
+msgid ""
+"\n"
+" \"%(code)s\" %(discount)s %%off\n"
+" "
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:27
+msgid "Total"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:31
+msgid "Place Order"
+msgstr ""
+
+#: orders/templates/orders/order/order_created.html:3
+msgid "Thank you"
+msgstr ""
+
+#: orders/templates/orders/order/order_created.html:6
+msgid "Thank you!"
+msgstr ""
+
+#: orders/templates/orders/order/order_created.html:7
+msgid "Your order has been successfully completed. Your order number is"
+msgstr ""
diff --git a/orders/locale/es/LC_MESSAGES/django.po b/orders/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000..95599d5
--- /dev/null
+++ b/orders/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,96 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-01-03 22:29+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: orders/models.py:11
+msgid "first name"
+msgstr "nombre"
+
+#: orders/models.py:12
+msgid "last name"
+msgstr "apellidos"
+
+#: orders/models.py:13
+msgid "email"
+msgstr "email"
+
+#: orders/models.py:14
+msgid "address"
+msgstr "dirección"
+
+#: orders/models.py:15
+msgid "city"
+msgstr "ciudad"
+
+#: orders/models.py:16
+msgid "created"
+msgstr "created"
+
+#: orders/models.py:17
+msgid "updated"
+msgstr "updated"
+
+#: orders/models.py:18
+msgid "paid"
+msgstr "paid"
+
+#: orders/models.py:19
+msgid "braintree id"
+msgstr ""
+
+#: orders/models.py:22
+msgid "Discount"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:4
+#: orders/templates/orders/order/order_create.html:7
+msgid "Checkout"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:10
+msgid "Your Order"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:20
+#, python-format
+msgid ""
+"\n"
+" \"%(code)s\" %(discount)s %%off\n"
+" "
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:27
+msgid "Total"
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:31
+msgid "Place Order"
+msgstr ""
+
+#: orders/templates/orders/order/order_created.html:3
+msgid "Thank you"
+msgstr ""
+
+#: orders/templates/orders/order/order_created.html:6
+msgid "Thank you!"
+msgstr ""
+
+#: orders/templates/orders/order/order_created.html:7
+msgid "Your order has been successfully completed. Your order number is"
+msgstr ""
diff --git a/orders/locale/ur/LC_MESSAGES/django.po b/orders/locale/ur/LC_MESSAGES/django.po
new file mode 100644
index 0000000..108d1fd
--- /dev/null
+++ b/orders/locale/ur/LC_MESSAGES/django.po
@@ -0,0 +1,96 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-01-03 22:29+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: orders/models.py:11
+msgid "first name"
+msgstr "نام کا پہلا حصہ"
+
+#: orders/models.py:12
+msgid "last name"
+msgstr "آخری نام"
+
+#: orders/models.py:13
+msgid "email"
+msgstr "ای میل"
+
+#: orders/models.py:14
+msgid "address"
+msgstr "پتہ"
+
+#: orders/models.py:15
+msgid "city"
+msgstr "شہر"
+
+#: orders/models.py:16
+msgid "created"
+msgstr "آرڈرکی تاریخ"
+
+#: orders/models.py:17
+msgid "updated"
+msgstr "آرڈرمیں ترمیم"
+
+#: orders/models.py:18
+msgid "paid"
+msgstr "مکمل"
+
+#: orders/models.py:19
+msgid "braintree id"
+msgstr "آئی ڈی"
+
+#: orders/models.py:22
+msgid "Discount"
+msgstr "رعایت"
+
+#: orders/templates/orders/order/order_create.html:4
+#: orders/templates/orders/order/order_create.html:7
+msgid "Checkout"
+msgstr "پیسے نکالیں"
+
+#: orders/templates/orders/order/order_create.html:10
+msgid "Your Order"
+msgstr "آپکاآرڈر"
+
+#: orders/templates/orders/order/order_create.html:20
+#, python-format
+msgid ""
+"\n"
+" \"%(code)s\" %(discount)s %%off\n"
+" "
+msgstr ""
+
+#: orders/templates/orders/order/order_create.html:27
+msgid "Total"
+msgstr "میزان"
+
+#: orders/templates/orders/order/order_create.html:31
+msgid "Place Order"
+msgstr "آرڈدیں"
+
+#: orders/templates/orders/order/order_created.html:3
+msgid "Thank you"
+msgstr "شکریہ"
+
+#: orders/templates/orders/order/order_created.html:6
+msgid "Thank you!"
+msgstr "شکریہ!"
+
+#: orders/templates/orders/order/order_created.html:7
+msgid "Your order has been successfully completed. Your order number is"
+msgstr "آپکاآرڈررکھ دیاگیا ہے"
diff --git a/orders/migrations/0003_auto_20220103_0503.py b/orders/migrations/0003_auto_20220103_0503.py
new file mode 100644
index 0000000..ee13258
--- /dev/null
+++ b/orders/migrations/0003_auto_20220103_0503.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.1.3 on 2022-01-03 05:03
+
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('coupons', '0001_initial'),
+ ('orders', '0002_order_braintree_id'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='order',
+ name='coupon',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='coupons.coupon'),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='discount',
+ field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)]),
+ ),
+ ]
diff --git a/orders/models.py b/orders/models.py
index ede3b41..3075f7f 100644
--- a/orders/models.py
+++ b/orders/models.py
@@ -1,18 +1,25 @@
+from decimal import Decimal
from django.db import models
+from django.utils.translation import gettext_lazy as _
+from django.core.validators import MinValueValidator, MaxValueValidator
+from coupons.models import Coupon
from shop.models import Product
class Order(models.Model):
- first_name = models.CharField(max_length=32)
- last_name = models.CharField(max_length=32)
- email = models.EmailField()
- address = models.CharField(max_length=64)
- city = models.CharField(max_length=16)
- created = models.DateTimeField(auto_now_add=True)
- updated = models.DateTimeField(auto_now=True)
- paid = models.BooleanField(default=False)
- braintree_id = models.CharField(max_length=156, blank=True)
+ first_name = models.CharField(_('first name'), max_length=32)
+ last_name = models.CharField(_('last name'), max_length=32)
+ email = models.EmailField(_('email'))
+ address = models.CharField(_('address'), max_length=64)
+ city = models.CharField(_('city'), max_length=16)
+ created = models.DateTimeField(_('created'), auto_now_add=True)
+ updated = models.DateTimeField(_('updated'), auto_now=True)
+ paid = models.BooleanField(_('paid'), default=False)
+ braintree_id = models.CharField(_('braintree id'), max_length=156, blank=True)
+ coupon = models.ForeignKey(Coupon, related_name='orders', null=True, blank=True,
+ on_delete=models.SET_NULL)
+ discount = models.IntegerField(_('Discount'), default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
class Meta:
ordering = ('-created',)
@@ -20,9 +27,13 @@ class Meta:
def __str__(self):
return f'Order ID: {self.id} by {self.first_name}'
- def get_total_cost(self):
+ def get_total_without_coupon(self):
return sum([item.get_cost() for item in self.items.all()])
+ def get_total_cost(self):
+ total_cost = self.get_total_without_coupon()
+ return total_cost - total_cost * (self.discount/Decimal(100))
+
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
diff --git a/orders/templates/admin/orders/order/detail.html b/orders/templates/admin/orders/order/detail.html
index 03388c3..254f72a 100644
--- a/orders/templates/admin/orders/order/detail.html
+++ b/orders/templates/admin/orders/order/detail.html
@@ -69,8 +69,14 @@ Items bought
{% endfor %}
Total
- ${{order.get_total_cost}}
+ ${{order.get_total_without_coupon}}
+ {% if order.coupon %}
+
+ Coupon ("{{order.coupon.discount}}" off)
+ ${{order.get_total_cost}} >
+
+ {% endif %}
diff --git a/orders/templates/orders/order/order_create.html b/orders/templates/orders/order/order_create.html
index f901d21..699b150 100644
--- a/orders/templates/orders/order/order_create.html
+++ b/orders/templates/orders/order/order_create.html
@@ -1,25 +1,34 @@
{% extends 'shop/base.html' %}
+{% load i18n %}
-{% block title %} Checkout {% endblock %}
+{% block title %}{% trans 'Checkout' %} {% endblock %}
{% block content %}
-Checkout
+{% trans 'Checkout' %}
-
Your Order
+
{% trans 'Your Order' %}
{%for item in cart %}
{{item.quantity}}x {{item.product.name}}
- ${{item.total_price}}
+ ${{item.total_price|floatformat:2}}
{% endfor %}
+ {% if cart.coupon %}
+
+ {% blocktrans with code=cart.coupon.code discount=cart.coupon.code %}
+ "{{code}}" {{discount}} %off
+ {% endblocktrans %}
+ - ${{cart.get_discount|floatformat:2}}
+
+ {%endif%}
-
Total: ${{cart.get_total_price}}
+
{% trans 'Total' %}: ${{cart.get_price_after_coupon|floatformat:2}}
{% endblock %}
diff --git a/orders/templates/orders/order/order_created.html b/orders/templates/orders/order/order_created.html
index e7698a4..5670b89 100644
--- a/orders/templates/orders/order/order_created.html
+++ b/orders/templates/orders/order/order_created.html
@@ -1,8 +1,8 @@
{% extends 'shop/base.html' %}
-
-{% block title %}Thank you {% endblock %}
+{% load i18n %}
+{% block title %}{% trans 'Thank you' %} {% endblock %}
{% block content %}
-Thank you!
-Your order has been successfully completed. Your order number is {{order.id}} .
+{% trans 'Thank you!' %}
+{% trans 'Your order has been successfully completed. Your order number is' %} {{order.id}} .
{% endblock %}
\ No newline at end of file
diff --git a/orders/views.py b/orders/views.py
index aeae4f4..dfc3e41 100644
--- a/orders/views.py
+++ b/orders/views.py
@@ -19,7 +19,11 @@ def order_create(request):
if request.method == 'POST':
form = OrderCreateForm(request.POST)
if form.is_valid():
- order = form.save()
+ order = form.save(commit=False)
+ if cart.coupon:
+ order.coupon = cart.coupon
+ order.discount = cart.coupon.discount
+ order.save()
log_info("Order saved!!!")
for item in cart:
OrderItem.objects.create(order=order, product=item['product'],
diff --git a/shop/recommender.py b/shop/recommender.py
new file mode 100644
index 0000000..7aeddf4
--- /dev/null
+++ b/shop/recommender.py
@@ -0,0 +1,59 @@
+import redis
+from django.conf import settings
+from .models import Product
+
+
+# connect to redis
+r = redis.Redis(host=settings.REDIS_HOST,
+ port=settings.REDIS_PORT,
+ db=settings.REDIS_DB)
+
+
+class Recommender(object):
+
+ def get_product_key(self, id):
+ return f'product:{id}:purchased_with'
+
+ def products_bought(self, products):
+ product_ids = [p.id for p in products]
+ for product_id in product_ids:
+ for with_id in product_ids:
+ # get the other products bought with each product
+ if product_id != with_id:
+ # increment score for product purchased together
+ r.zincrby(self.get_product_key(product_id),
+ 1,
+ with_id)
+
+ def suggest_products_for(self, products, max_results=6):
+ product_ids = [p.id for p in products]
+ if len(products) == 1:
+ # only 1 product
+ suggestions = r.zrange(
+ self.get_product_key(product_ids[0]),
+ 0, -1, desc=True)[:max_results]
+ else:
+ # generate a temporary key
+ flat_ids = ''.join([str(id) for id in product_ids])
+ tmp_key = f'tmp_{flat_ids}'
+ # multiple products, combine scores of all products
+ # store the resulting sorted set in a temporary key
+ keys = [self.get_product_key(id) for id in product_ids]
+ r.zunionstore(tmp_key, keys)
+ # remove ids for the products the recommendation is for
+ r.zrem(tmp_key, *product_ids)
+ # get the product ids by their score, descendant sort
+ suggestions = r.zrange(tmp_key, 0, -1,
+ desc=True)[:max_results]
+ # remove the temporary key
+ r.delete(tmp_key)
+ suggested_products_ids = [int(id) for id in suggestions]
+
+ # get suggested products and sort by order of appearance
+ suggested_products = list(Product.objects.filter(id__in=suggested_products_ids))
+ suggested_products.sort(key=lambda x: suggested_products_ids.index(x.id))
+ return suggested_products
+
+ def clear_purchases(self):
+ for id in Product.objects.values_list('id', flat=True):
+ r.delete(self.get_product_key(id))
diff --git a/shop/templates/shop/base.html b/shop/templates/shop/base.html
index c044e75..18d74d9 100644
--- a/shop/templates/shop/base.html
+++ b/shop/templates/shop/base.html
@@ -1,26 +1,45 @@
{% load static %}
+{% load i18n %}
- {% block title %} eShop by Zee {% endblock %}
+ {% block title %}{% trans 'eShop by Zee' %}{% endblock %}