diff --git a/accounts/__init__.py b/accounts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/accounts/admin.py b/accounts/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/accounts/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/accounts/apps.py b/accounts/apps.py
new file mode 100644
index 0000000..9b3fc5a
--- /dev/null
+++ b/accounts/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+ name = 'accounts'
diff --git a/accounts/forms.py b/accounts/forms.py
new file mode 100644
index 0000000..41201c6
--- /dev/null
+++ b/accounts/forms.py
@@ -0,0 +1,14 @@
+from django import forms
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+
+class GuestForm(forms.Form):
+ email = forms.EmailField()
+
+
+# class LoginForm(forms.Form):
+# username = forms.CharField()
+# password = forms.CharField(widget=forms.PasswordInput)
+
+ # delete guest session if logged in
diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py
new file mode 100644
index 0000000..aa2ca68
--- /dev/null
+++ b/accounts/migrations/0001_initial.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-02 08:23
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='GuestEmail',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('email', models.EmailField(max_length=254)),
+ ('active', models.BooleanField(default=True)),
+ ('time', models.DateTimeField(auto_now_add=True)),
+ ('update', models.DateTimeField(auto_now_add=True)),
+ ],
+ ),
+ ]
diff --git a/accounts/migrations/__init__.py b/accounts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/accounts/models.py b/accounts/models.py
new file mode 100644
index 0000000..505ddbe
--- /dev/null
+++ b/accounts/models.py
@@ -0,0 +1,11 @@
+from django.db import models
+
+
+class GuestEmail(models.Model):
+ email = models.EmailField()
+ active = models.BooleanField(default=True)
+ time = models.DateTimeField(auto_now_add=True)
+ update = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.email
diff --git a/accounts/tests.py b/accounts/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/accounts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/accounts/urls.py b/accounts/urls.py
new file mode 100644
index 0000000..d6af201
--- /dev/null
+++ b/accounts/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls import url
+from .views import guest_login_page
+
+
+urlpatterns = [
+ url("^guest_login", guest_login_page, name="guest_login"),
+]
\ No newline at end of file
diff --git a/accounts/views.py b/accounts/views.py
new file mode 100644
index 0000000..1ecdfd2
--- /dev/null
+++ b/accounts/views.py
@@ -0,0 +1,25 @@
+from django.shortcuts import render
+from .forms import GuestForm
+from .models import GuestEmail
+from django.shortcuts import redirect
+
+
+def guest_login_page(request):
+ form = GuestForm(request.POST or None)
+ # context = {"form": form}
+ # TODO: next stuff
+ if form.is_valid():
+ print('form is valid')
+ email = form.cleaned_data.get("email")
+ new_guest_email = GuestEmail.objects.create(email=email)
+ request.session['guest_email_id'] = new_guest_email.id
+
+ return redirect("carts:checkout")
+
+def login_page(resuest):
+ pass
+
+
+def logout_page(resuest):
+ pass
+
diff --git a/addresses/__init__.py b/addresses/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/addresses/admin.py b/addresses/admin.py
new file mode 100644
index 0000000..98cf5a2
--- /dev/null
+++ b/addresses/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from .models import Address
+
+admin.site.register(Address)
diff --git a/addresses/apps.py b/addresses/apps.py
new file mode 100644
index 0000000..7582c6e
--- /dev/null
+++ b/addresses/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AddressesConfig(AppConfig):
+ name = 'addresses'
diff --git a/addresses/forms.py b/addresses/forms.py
new file mode 100644
index 0000000..6c59885
--- /dev/null
+++ b/addresses/forms.py
@@ -0,0 +1,8 @@
+from django import forms
+
+from .models import Address
+
+class AddressForm(forms.ModelForm):
+ class Meta:
+ model = Address
+ exclude = ['billing_profile']
\ No newline at end of file
diff --git a/addresses/migrations/0001_initial.py b/addresses/migrations/0001_initial.py
new file mode 100644
index 0000000..5de7d78
--- /dev/null
+++ b/addresses/migrations/0001_initial.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-02 13:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('billing', '0002_auto_20180801_1339'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Address',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('address_type', models.CharField(choices=[('billing', 'Billing'), ('shipping', 'Shipping')], max_length=120)),
+ ('adress_line_1', models.CharField(max_length=120)),
+ ('adress_line_2', models.CharField(blank=True, max_length=120, null=True)),
+ ('city', models.CharField(max_length=120)),
+ ('country', models.CharField(max_length=120)),
+ ('code', models.CharField(max_length=120)),
+ ('billing_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='billing.BillingProfile')),
+ ],
+ ),
+ ]
diff --git a/addresses/migrations/__init__.py b/addresses/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/addresses/models.py b/addresses/models.py
new file mode 100644
index 0000000..81f908e
--- /dev/null
+++ b/addresses/models.py
@@ -0,0 +1,27 @@
+from django.db import models
+from billing.models import BillingProfile
+
+ADDRESS_TYPE = (
+ ('billing', 'Billing'),
+ ('shipping', 'Shipping')
+)
+class Address(models.Model):
+ billing_profile = models.ForeignKey(BillingProfile)
+ address_type = models.CharField(max_length=120, choices=ADDRESS_TYPE)
+ adress_line_1 = models.CharField(max_length=120)
+ adress_line_2 = models.CharField(max_length=120, null=True, blank=True)
+ city = models.CharField(max_length=120)
+ country = models.CharField(max_length=120)
+ code = models.CharField(max_length=120)
+
+ def __str__(self):
+ return str(self.billing_profile)
+
+ def get_address(self):
+ return "{line1}\n{line2}\n{city}\n{country}\n".format(
+ line1=self.adress_line_1,
+ line2=self.adress_line_2 or "",
+ city=self.city,
+ country=self.country
+ )
+
diff --git a/addresses/tests.py b/addresses/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/addresses/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/addresses/urls.py b/addresses/urls.py
new file mode 100644
index 0000000..ca9c65b
--- /dev/null
+++ b/addresses/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls import url
+from .views import (
+ checkout_address_create,
+ checkout_address_use
+)
+
+urlpatterns = [
+ url(r"^create/$", checkout_address_create, name="checkout"),
+ url(r"^reuse/$", checkout_address_use, name="reuse"),
+ url(r"^reuse/$", checkout_address_use, name="reuse"),
+]
\ No newline at end of file
diff --git a/addresses/views.py b/addresses/views.py
new file mode 100644
index 0000000..f078d8a
--- /dev/null
+++ b/addresses/views.py
@@ -0,0 +1,46 @@
+from django.shortcuts import redirect
+from .forms import AddressForm
+from billing.models import BillingProfile
+from .models import Address
+
+
+def checkout_address_create(request):
+ form = AddressForm(request.POST or None)
+ if form.is_valid():
+ inst = form.save(commit=False)
+ billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request)
+ if billing_profile is not None:
+ inst.billing_profile = billing_profile
+ inst.address_type = "shipping"
+ inst.save()
+ request.session["address_id"] = inst.id
+ else:
+ print("error")
+ return redirect("carts:checkout")
+
+def checkout_address_use(request):
+ if request.user.is_authenticated():
+ context = {}
+ if request.method == "POST":
+ print(request.POST)
+ address = request.POST.get("address", None)
+ billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request)
+ if address is not None:
+ qs = Address.objects.filter(billing_profile=billing_profile, id=address)
+ if qs.exists():
+ request.session["address_id"] = address
+ return redirect("carts:checkout")
+
+"""
+def guest_login_page(request):
+ form = GuestForm(request.POST or None)
+ # context = {"form": form}
+ # TODO: next stuff
+ if form.is_valid():
+ print('form is valid')
+ email = form.cleaned_data.get("email")
+ new_guest_email = GuestEmail.objects.create(email=email)
+ request.session['guest_email_id'] = new_guest_email.id
+
+ return redirect("carts:checkout")
+"""
\ No newline at end of file
diff --git a/billing/__init__.py b/billing/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/billing/admin.py b/billing/admin.py
new file mode 100644
index 0000000..a72c3b1
--- /dev/null
+++ b/billing/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from .models import BillingProfile
+
+admin.site.register(BillingProfile)
+# Register your models here.
diff --git a/billing/apps.py b/billing/apps.py
new file mode 100644
index 0000000..21d70f8
--- /dev/null
+++ b/billing/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class BillingConfig(AppConfig):
+ name = 'billing'
diff --git a/billing/migrations/0001_initial.py b/billing/migrations/0001_initial.py
new file mode 100644
index 0000000..e5a8c4f
--- /dev/null
+++ b/billing/migrations/0001_initial.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-01 13:32
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='BillingProfile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('email', models.EmailField(max_length=254)),
+ ('active', models.BooleanField(default=True)),
+ ('time', models.DateTimeField(auto_now_add=True)),
+ ('update', models.DateTimeField(auto_now_add=True)),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/billing/migrations/0002_auto_20180801_1339.py b/billing/migrations/0002_auto_20180801_1339.py
new file mode 100644
index 0000000..ac52d06
--- /dev/null
+++ b/billing/migrations/0002_auto_20180801_1339.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-01 13:39
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('billing', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='billingprofile',
+ name='user',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, unique=True),
+ ),
+ ]
diff --git a/billing/migrations/__init__.py b/billing/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/billing/models.py b/billing/models.py
new file mode 100644
index 0000000..84a4227
--- /dev/null
+++ b/billing/models.py
@@ -0,0 +1,50 @@
+from django.db import models
+from django.contrib.auth import get_user_model
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from accounts.models import GuestEmail
+
+User = get_user_model()
+
+
+class BillingProfileManager(models.Manager):
+ def new_or_get(self, request):
+ created = False
+ obj = None
+ guest_email_id = request.session.get("guest_email_id")
+ if request.user.is_authenticated():
+ 'logged in user checkout'
+ obj, created = self.model.objects.get_or_create(
+ user=request.user,
+ email=request.user.email
+ )
+ elif guest_email_id is not None:
+ 'guest user checkout'
+ print(guest_email_id)
+ guest_email_obj = GuestEmail.objects.get(id=guest_email_id)
+ obj, created = self.model.objects.get_or_create(
+ email=guest_email_obj.email
+ )
+ else:
+ pass
+ return obj, created
+
+
+class BillingProfile(models.Model):
+ user = models.ForeignKey(User, unique=True, null=True, blank=True)
+ email = models.EmailField()
+ active = models.BooleanField(default=True)
+ time = models.DateTimeField(auto_now_add=True)
+ update = models.DateTimeField(auto_now_add=True)
+
+ objects = BillingProfileManager()
+
+ def __str__(self):
+ return self.email
+
+
+@receiver(post_save, sender=User)
+def user_create(sender, instance, created, *args, **kwargs):
+ if created:
+ BillingProfile.objects.get_or_create(user=instance)
+
diff --git a/billing/tests.py b/billing/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/billing/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/billing/views.py b/billing/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/billing/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/carts/__init__.py b/carts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/carts/admin.py b/carts/admin.py
new file mode 100644
index 0000000..5933914
--- /dev/null
+++ b/carts/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from .models import Cart
+
+admin.site.register(Cart)
diff --git a/carts/apps.py b/carts/apps.py
new file mode 100644
index 0000000..42ca295
--- /dev/null
+++ b/carts/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class CartsConfig(AppConfig):
+ name = 'carts'
diff --git a/carts/migrations/0001_initial.py b/carts/migrations/0001_initial.py
new file mode 100644
index 0000000..57e9ea5
--- /dev/null
+++ b/carts/migrations/0001_initial.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-31 12:53
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('products', '0006_product_time'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Cart',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('total', models.DecimalField(decimal_places=2, default=0.0, max_digits=100)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('updated', models.DateTimeField(auto_now=True)),
+ ('products', models.ManyToManyField(blank=True, null=True, to='products.Product')),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/carts/migrations/0002_cart_subtotal.py b/carts/migrations/0002_cart_subtotal.py
new file mode 100644
index 0000000..23588ec
--- /dev/null
+++ b/carts/migrations/0002_cart_subtotal.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-31 15:02
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('carts', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cart',
+ name='subtotal',
+ field=models.DecimalField(decimal_places=2, default=0.0, max_digits=100),
+ ),
+ ]
diff --git a/carts/migrations/__init__.py b/carts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/carts/models.py b/carts/models.py
new file mode 100644
index 0000000..85e86d2
--- /dev/null
+++ b/carts/models.py
@@ -0,0 +1,68 @@
+from django.db import models
+from django.conf import settings
+from products.models import Product
+from django.contrib.auth import get_user_model
+from django.db.models.signals import m2m_changed, pre_save
+from django.dispatch import receiver
+
+
+User = get_user_model()
+
+class CartManager(models.Manager):
+ def new_or_get(self, request):
+ cart_id = request.session.get("cart_id", None)
+ qs = self.get_queryset().filter(id=cart_id)
+ if qs.count() == 1:
+
+ new_obj = False
+ cart_obj = qs.first()
+ if request.user.is_authenticated() and cart_obj.user is None:
+ cart_obj.user = request.user
+ cart_obj.save()
+ else:
+ cart_obj = Cart.objects.new(user=request.user)
+ new_obj = True
+ request.session["cart_id"] = cart_obj.id
+ return cart_obj, new_obj
+
+ def new(self, user=None, products=None):
+ if user is not None:
+ if user.is_authenticated():
+ return self.model.objects.create(user=user)
+ return self.model.objects.create(user=None)
+
+
+class Cart(models.Model):
+ user = models.ForeignKey(User, null=True, blank=True)
+ products = models.ManyToManyField(Product, null=True, blank=True)
+ total = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
+ subtotal = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
+ created = models.DateTimeField(auto_now_add=True)
+ updated = models.DateTimeField(auto_now=True)
+
+ objects = CartManager()
+
+ def __str__(self):
+ return str(self.id)
+
+
+@receiver(m2m_changed, sender=Cart.products.through)
+def cart_change(sender, instance, action, **kwargs):
+ if action in ("post_add", "post_remove", "post_clear"):
+
+ print('change')
+ prs = instance.products.all()
+ t = 0
+ for i in prs:
+ t += i.price
+ if instance.subtotal != t:
+
+ instance.subtotal = t
+ instance.save()
+ # cart_obj.save()
+
+@receiver(pre_save, sender=Cart)
+def cart_save(sender, instance, **kwargs):
+ instance.total = float(instance.subtotal) * float(0.95) # 5% discount
+
+
diff --git a/carts/templates/carts/checkout-done.html b/carts/templates/carts/checkout-done.html
new file mode 100644
index 0000000..3566a32
--- /dev/null
+++ b/carts/templates/carts/checkout-done.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+
Tank you for your order!
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/carts/templates/carts/checkout.html b/carts/templates/carts/checkout.html
new file mode 100644
index 0000000..eb0b068
--- /dev/null
+++ b/carts/templates/carts/checkout.html
@@ -0,0 +1,61 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+{{ object }} -- {{ object.cart }}
+
+{% if not billing_profile %}
+
+
+
+ Continue as guest
+
+
+
+{% else %}
+
+ {% if not object.address %}
+
+ {% else %}
+
+ Finalize checkout
+ Shipping Address: {{ object.address.get_address}}
+ Cart items: {% for item in object.cart.products.all %}{{ item }}{% if not forloop.last %}, {% endif %}{% endfor %}
+
+ Cart total: {{ object.cart.total }}
+ Shipping Total: {{ object.shipping_total }}
+ Order Total: {{ object.total }}
+
+{% endif %}
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/carts/templates/carts/form.html b/carts/templates/carts/form.html
new file mode 100644
index 0000000..00bbb9c
--- /dev/null
+++ b/carts/templates/carts/form.html
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/carts/templates/carts/home.html b/carts/templates/carts/home.html
new file mode 100644
index 0000000..8222b1f
--- /dev/null
+++ b/carts/templates/carts/home.html
@@ -0,0 +1,35 @@
+{% extends "base.html" %}
+
+{% block content %}
+Cart
+
+
+
+ # |
+ Product Name |
+ Price |
+
+
+
+ {% for product in cart.products.all %}
+
+ {{ forloop.counter }} |
+ {{ product.title }}{% include "carts/form.html" with object=product in_cart=True %}
+ |
+ {{ product.price }} |
+
+ {% endfor %}
+
+ |
+ Subtotal {{ cart.subtotal }} |
+
+
+ |
+ Total {{ cart.total }} |
+
+
+ |
+ Checkout |
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/carts/tests.py b/carts/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/carts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/carts/urls.py b/carts/urls.py
new file mode 100644
index 0000000..6a6548c
--- /dev/null
+++ b/carts/urls.py
@@ -0,0 +1,10 @@
+from .views import cart_home, cart_update, checkout_home, checkout_done
+from django.conf.urls import url
+
+
+urlpatterns = [
+ url("^$", cart_home, name="home"),
+ url("^checkout/$", checkout_home, name="checkout"),
+ url("^update/$", cart_update, name="update"),
+ url("^checkout/success/$", checkout_done, name="success")
+]
diff --git a/carts/views.py b/carts/views.py
new file mode 100644
index 0000000..3322934
--- /dev/null
+++ b/carts/views.py
@@ -0,0 +1,71 @@
+from django.shortcuts import render, redirect, get_object_or_404
+
+from products.models import Product
+from .models import Cart
+from orders.models import Order
+from billing.models import BillingProfile
+from accounts.forms import GuestForm
+from accounts.models import GuestEmail
+from addresses.forms import AddressForm
+from addresses.models import Address
+
+
+def cart_home(request):
+ cart_obj, new_obj = Cart.objects.new_or_get(request)
+ return render(request, 'carts/home.html', {"cart": cart_obj})
+
+
+def cart_update(request):
+ product_id = request.POST.get("product_id")
+ product_obj = get_object_or_404(Product, id=product_id)
+ cart_obj, new_obj = Cart.objects.new_or_get(request)
+ if product_obj in cart_obj.products.all():
+ cart_obj.products.remove(product_obj)
+ else:
+ cart_obj.products.add(product_obj)
+ request.session['cart_items'] = cart_obj.products.count()
+ return redirect("carts:home")
+
+
+def checkout_home(request):
+ cart_obj, cart_created = Cart.objects.new_or_get(request)
+ order_obj = None
+ if cart_created or cart_obj.products.count() == 0:
+ return redirect("carts:home")
+ guest_form = GuestForm()
+ address_form = AddressForm()
+ address_id = request.session.get("address_id", None)
+ billing_profile, billing_profile_created = BillingProfile.objects.new_or_get(request)
+ address_qs = None
+ if billing_profile is not None:
+ if request.user.is_authenticated():
+
+ address_qs = Address.objects.filter(billing_profile=billing_profile)
+ order_obj, order_obj_created = Order.objects.new_or_get(billing_profile, cart_obj)
+ if address_id:
+ order_obj.address = Address.objects.get(id=address_id)
+ request.session['cart_items'] = 0
+ del request.session["address_id"]
+ order_obj.save()
+
+ if request.method == 'POST':
+ 'check that order is done'
+ is_done = order_obj.check_done()
+ if is_done:
+ order_obj.mark_paid()
+ del request.session['cart_id']
+ return redirect('carts:success')
+
+
+ context = {
+ "object": order_obj,
+ "billing_profile": billing_profile,
+ "guest_form" : guest_form,
+ "address_form": address_form,
+ "address_qs": address_qs
+ }
+ return render(request, "carts/checkout.html", context)
+
+
+def checkout_done(request):
+ return render(request, "carts/checkout-done.html")
\ No newline at end of file
diff --git a/db.sqlite3 b/db.sqlite3
new file mode 100644
index 0000000..88220fb
Binary files /dev/null and b/db.sqlite3 differ
diff --git a/ecommerce/__init__.py b/ecommerce/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ecommerce/settings.py b/ecommerce/settings.py
new file mode 100644
index 0000000..7a98adc
--- /dev/null
+++ b/ecommerce/settings.py
@@ -0,0 +1,131 @@
+"""
+Django settings for ecommerce project.
+
+Generated by 'django-admin startproject' using Django 1.11.4.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.11/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'o0!mhcklo#cz1zrlmr)nfr^8!@$kz56*vv##!hb0(^j$k3iy9='
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'products',
+ 'carts',
+ 'orders',
+ 'billing',
+ 'accounts',
+ 'addresses'
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'ecommerce.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [os.path.join(PROJECT_ROOT, 'templates')],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'ecommerce.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.11/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.11/howto/static-files/
+
+STATIC_URL = '/static/'
+
+
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")
\ No newline at end of file
diff --git a/ecommerce/templates/base.html b/ecommerce/templates/base.html
new file mode 100644
index 0000000..a12182a
--- /dev/null
+++ b/ecommerce/templates/base.html
@@ -0,0 +1,26 @@
+{% load static %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% include 'base/nav.html' with brand_name='Brand name'%}
+
+ {% block content %}{% endblock %}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ecommerce/templates/base/nav.html b/ecommerce/templates/base/nav.html
new file mode 100644
index 0000000..3eb44b3
--- /dev/null
+++ b/ecommerce/templates/base/nav.html
@@ -0,0 +1,50 @@
+{% url 'products:list' as prod_list %}
+{% url 'carts:home' as cart_url %}
+
+
\ No newline at end of file
diff --git a/ecommerce/templates/bootstrap/example.html b/ecommerce/templates/bootstrap/example.html
new file mode 100644
index 0000000..8aacf07
--- /dev/null
+++ b/ecommerce/templates/bootstrap/example.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+ Hello, world!
+
+
+ Hello, world!
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ecommerce/urls.py b/ecommerce/urls.py
new file mode 100644
index 0000000..c35c5bc
--- /dev/null
+++ b/ecommerce/urls.py
@@ -0,0 +1,34 @@
+"""ecommerce URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/1.11/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.conf.urls import url, include
+ 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import include, url
+from django.conf.urls.static import static
+from django.contrib import admin
+from django.conf import settings
+from django.views.generic import TemplateView
+
+
+urlpatterns = [
+ url(r'^admin/', admin.site.urls),
+ url(r'^bootstrap/?$', TemplateView.as_view(template_name='bootstrap/example.html')),
+ url(r'^products/', include('products.urls', namespace="products")),
+ url(r'^cart/', include('carts.urls', namespace="carts")),
+ url(r'^accounts/', include('accounts.urls', namespace="accounts")),
+ url(r'^address/', include('addresses.urls', namespace="addresses")),
+]
+
+if settings.DEBUG:
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
\ No newline at end of file
diff --git a/ecommerce/utils.py b/ecommerce/utils.py
new file mode 100644
index 0000000..b262ed4
--- /dev/null
+++ b/ecommerce/utils.py
@@ -0,0 +1,43 @@
+import string
+import random
+from django.utils.text import slugify
+
+
+def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
+ return ''.join(random.choice(chars) for _ in range(size))
+
+
+def unique_order_id_generator(inst):
+ """
+ Generate unique order_id.
+ Model should have an order_id field to use this function.
+ """
+ print('i am here')
+ order_new_id = random_string_generator()
+
+ Klass = inst.__class__
+ qs_exists = Klass.objects.filter(order_id =order_new_id).exists()
+ if qs_exists:
+ return unique_slug_generator(inst)
+ return order_new_id
+
+
+def unique_slug_generator(inst, new_slug=None):
+ """
+ Generate unique slug from title or based on provided slug.
+ Model should have a slug field and a title (char) field to use this function.
+ """
+ if new_slug is not None:
+ slug = new_slug
+ else:
+ slug = slugify(inst.title)
+
+ Klass = inst.__class__
+ qs_exists = Klass.objects.filter(slug=slug).exists()
+ if qs_exists:
+ new_slug = "{slug}-{randstr}".format(
+ slug=slug,
+ randstr=random_string_generator(size=4)
+ )
+ return unique_slug_generator(inst, new_slug=new_slug)
+ return slug
\ No newline at end of file
diff --git a/ecommerce/wsgi.py b/ecommerce/wsgi.py
new file mode 100644
index 0000000..ccf146c
--- /dev/null
+++ b/ecommerce/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for ecommerce project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ecommerce.settings")
+
+application = get_wsgi_application()
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..561bf0f
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ecommerce.settings")
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError:
+ # The above import may fail for some other reason. Ensure that the
+ # issue is really that Django is missing to avoid masking other
+ # exceptions on Python 2.
+ try:
+ import django
+ except ImportError:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ )
+ raise
+ execute_from_command_line(sys.argv)
diff --git a/orders/__init__.py b/orders/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/orders/admin.py b/orders/admin.py
new file mode 100644
index 0000000..6b1c02d
--- /dev/null
+++ b/orders/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from .models import Order
+
+admin.site.register(Order)
diff --git a/orders/apps.py b/orders/apps.py
new file mode 100644
index 0000000..384ab43
--- /dev/null
+++ b/orders/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class OrdersConfig(AppConfig):
+ name = 'orders'
diff --git a/orders/migrations/0001_initial.py b/orders/migrations/0001_initial.py
new file mode 100644
index 0000000..097d16c
--- /dev/null
+++ b/orders/migrations/0001_initial.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-01 11:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('carts', '0002_cart_subtotal'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Order',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('order_id', models.CharField(max_length=100)),
+ ('status', models.CharField(choices=[('created', 'Created'), ('paid', 'Paid'), ('shipped', 'Shipped'), ('refunded', 'Refunded')], default='created', max_length=120)),
+ ('shipping_total', models.DecimalField(decimal_places=2, default=5.99, max_digits=100)),
+ ('total', models.DecimalField(decimal_places=2, default=0.0, max_digits=100)),
+ ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='carts.Cart')),
+ ],
+ ),
+ ]
diff --git a/orders/migrations/0002_auto_20180801_1332.py b/orders/migrations/0002_auto_20180801_1332.py
new file mode 100644
index 0000000..fc76e94
--- /dev/null
+++ b/orders/migrations/0002_auto_20180801_1332.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-01 13:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orders', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='order',
+ name='order_id',
+ field=models.CharField(blank=True, max_length=100),
+ ),
+ ]
diff --git a/orders/migrations/0003_auto_20180802_0823.py b/orders/migrations/0003_auto_20180802_0823.py
new file mode 100644
index 0000000..a1ef2f1
--- /dev/null
+++ b/orders/migrations/0003_auto_20180802_0823.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-02 08:23
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('billing', '0002_auto_20180801_1339'),
+ ('orders', '0002_auto_20180801_1332'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='order',
+ name='active',
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='billing_profile',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='billing.BillingProfile'),
+ ),
+ ]
diff --git a/orders/migrations/0004_order_shipping_address.py b/orders/migrations/0004_order_shipping_address.py
new file mode 100644
index 0000000..d57c20f
--- /dev/null
+++ b/orders/migrations/0004_order_shipping_address.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-02 14:17
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('addresses', '0001_initial'),
+ ('orders', '0003_auto_20180802_0823'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='order',
+ name='shipping_address',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='addresses.Address'),
+ ),
+ ]
diff --git a/orders/migrations/0005_auto_20180802_1436.py b/orders/migrations/0005_auto_20180802_1436.py
new file mode 100644
index 0000000..6f9a41b
--- /dev/null
+++ b/orders/migrations/0005_auto_20180802_1436.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-08-02 14:36
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orders', '0004_order_shipping_address'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='order',
+ old_name='shipping_address',
+ new_name='address',
+ ),
+ ]
diff --git a/orders/migrations/__init__.py b/orders/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/orders/models.py b/orders/models.py
new file mode 100644
index 0000000..4a59331
--- /dev/null
+++ b/orders/models.py
@@ -0,0 +1,102 @@
+from math import fsum
+
+from django.db import models
+from django.db.models.signals import pre_save, post_save
+from django.dispatch import receiver
+
+from billing.models import BillingProfile
+from carts.models import Cart
+from ecommerce.utils import unique_order_id_generator
+from addresses.models import Address
+
+ORDER_STATUS_CHOICES = (
+ ('created', 'Created'),
+ ('paid', 'Paid'),
+ ('shipped', 'Shipped'),
+ ('refunded', 'Refunded')
+)
+
+
+class OrderManager(models.Manager):
+ def new_or_get(self, billing_profile, cart_obj):
+ created = False
+ qs = self.get_queryset().filter(billing_profile=billing_profile,
+ cart=cart_obj, active=True,
+ status='created')
+ if qs.count() == 1:
+ obj = qs.first()
+ else:
+ obj = self.model.objects.create(billing_profile=billing_profile,
+ cart=cart_obj)
+ created = True
+ return obj, created
+
+class Order(models.Model):
+ billing_profile = models.ForeignKey(BillingProfile, null=True, blank=True)
+ order_id = models.CharField(max_length=100, blank=True)
+ address = models.ForeignKey(Address, null=True, blank=True)
+ cart = models.ForeignKey(Cart)
+ status = models.CharField(max_length=120, default='created', choices=ORDER_STATUS_CHOICES)
+ shipping_total = models.DecimalField(default=5.99, max_digits=100, decimal_places=2)
+ total = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
+ active = models.BooleanField(default=True)
+
+ objects = OrderManager()
+
+ def __str__(self):
+ return self.order_id
+
+ def update_total(self):
+ cart_total = self.cart.total
+ shipping_total = self.shipping_total
+ total = fsum([cart_total, shipping_total])
+ total = format(total, '.2f')
+ self.total = total
+ self.save()
+ return total
+
+ def check_done(self):
+ billing_profile = self.billing_profile
+ address = self.address
+ total = self.total
+ if billing_profile and address and total > 0:
+ return True
+ return False
+
+ def mark_paid(self):
+ if self.check_done():
+ self.status = "paid"
+ self.save()
+ return self.status
+
+
+
+
+@receiver(pre_save, sender=Order)
+def create_order_id(sender, instance, *args, **kwargs):
+ print('ord_id')
+ if not instance.order_id:
+ instance.order_id = unique_order_id_generator(instance)
+ qs = Order.objects.filter(cart=instance.cart).exclude(billing_profile=instance.billing_profile)
+ if qs.exists():
+ qs.update(active=False)
+
+
+@receiver(post_save, sender=Cart)
+def create_cart_total(sender, instance, created, *args, **kwargs):
+ if not created:
+ cart_obj = instance
+ cart_total = cart_obj.total
+ cart_id = cart_obj.id
+ qs = Order.objects.filter(cart__id=cart_id)
+ if qs.count() == 1:
+ order_obj = qs.first()
+ order_obj.update_total()
+
+
+@receiver(post_save, sender=Order)
+def create_order(sender, instance, created, *args, **kwargs):
+ if created:
+ instance.update_total()
+
+
diff --git a/orders/tests.py b/orders/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/orders/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/orders/views.py b/orders/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/orders/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/products/__init__.py b/products/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/products/admin.py b/products/admin.py
new file mode 100644
index 0000000..7f7988f
--- /dev/null
+++ b/products/admin.py
@@ -0,0 +1,9 @@
+from django.contrib import admin
+
+from . models import Product
+
+@admin.register(Product)
+class ProductAdmin(admin.ModelAdmin):
+ # fields = ('image_tag',)
+ list_display = ('title', 'slug', 'time', 'image_tag')
+ readonly_fields = ('image_tag',)
diff --git a/products/apps.py b/products/apps.py
new file mode 100644
index 0000000..864c43e
--- /dev/null
+++ b/products/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ProductsConfig(AppConfig):
+ name = 'products'
diff --git a/products/migrations/0001_initial.py b/products/migrations/0001_initial.py
new file mode 100644
index 0000000..3f467ae
--- /dev/null
+++ b/products/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-29 08:17
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Product',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=120)),
+ ('description', models.TextField()),
+ ('price', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
+ ],
+ ),
+ ]
diff --git a/products/migrations/0002_product_image.py b/products/migrations/0002_product_image.py
new file mode 100644
index 0000000..0b6537c
--- /dev/null
+++ b/products/migrations/0002_product_image.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-29 09:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='product',
+ name='image',
+ field=models.FileField(blank=True, null=True, upload_to='products/'),
+ ),
+ ]
diff --git a/products/migrations/0003_auto_20180729_0956.py b/products/migrations/0003_auto_20180729_0956.py
new file mode 100644
index 0000000..d5137d5
--- /dev/null
+++ b/products/migrations/0003_auto_20180729_0956.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-29 09:56
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0002_product_image'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='product',
+ name='image',
+ field=models.ImageField(blank=True, null=True, upload_to='products/'),
+ ),
+ ]
diff --git a/products/migrations/0004_product_slug.py b/products/migrations/0004_product_slug.py
new file mode 100644
index 0000000..c05e806
--- /dev/null
+++ b/products/migrations/0004_product_slug.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-29 10:59
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0003_auto_20180729_0956'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='product',
+ name='slug',
+ field=models.SlugField(blank=True),
+ ),
+ ]
diff --git a/products/migrations/0005_auto_20180729_1104.py b/products/migrations/0005_auto_20180729_1104.py
new file mode 100644
index 0000000..1d869ca
--- /dev/null
+++ b/products/migrations/0005_auto_20180729_1104.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-29 11:04
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0004_product_slug'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='product',
+ name='slug',
+ field=models.SlugField(blank=True, unique=True),
+ ),
+ ]
diff --git a/products/migrations/0006_product_time.py b/products/migrations/0006_product_time.py
new file mode 100644
index 0000000..828cbab
--- /dev/null
+++ b/products/migrations/0006_product_time.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2018-07-29 13:49
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('products', '0005_auto_20180729_1104'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='product',
+ name='time',
+ field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+ preserve_default=False,
+ ),
+ ]
diff --git a/products/migrations/__init__.py b/products/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/products/models.py b/products/models.py
new file mode 100644
index 0000000..a66ad5d
--- /dev/null
+++ b/products/models.py
@@ -0,0 +1,44 @@
+from django.db import models
+from django.conf import settings
+from ecommerce.utils import unique_slug_generator
+from django.db.models.signals import pre_save
+from django.dispatch import receiver
+from django.urls import reverse
+
+
+class ProductManager(models.Manager):
+ def get_by_id(self, id):
+ return self.get_queryset().filter(id=id).first() or None
+
+
+class Product(models.Model):
+ title = models.CharField(max_length=120)
+ slug = models.SlugField(blank=True, unique=True)
+ description = models.TextField()
+ price = models.DecimalField(decimal_places=2, max_digits=10, default=0)
+ image = models.ImageField(upload_to='products/', null=True, blank=True)
+ time = models.DateTimeField(auto_now_add=True)
+
+ objects = ProductManager()
+
+ def get_absolute_url(self):
+ return reverse("products:details", kwargs={'slug': self.slug})
+ # return "/{slug}/".format(slug=self.slug)
+
+
+ def __str__(self):
+ return self.title
+
+ def image_tag(self):
+ if self.image:
+ return ''.format(self.image.url)
+ return 'No file'
+
+ image_tag.short_description = 'Image_preview'
+ image_tag.allow_tags = True
+
+
+@receiver(pre_save, sender=Product)
+def product_save(sender, instance, **kwargs):
+ if not instance.slug:
+ instance.slug = unique_slug_generator(instance)
\ No newline at end of file
diff --git a/products/templates/products/details.html b/products/templates/products/details.html
new file mode 100644
index 0000000..6f0b3d9
--- /dev/null
+++ b/products/templates/products/details.html
@@ -0,0 +1,18 @@
+{% extends 'base.html' %}
+{% block content %}
+
+
+ {{ object.title }}
+ {{ object.time|timesince}} ago
+ {{ object.description|linebreaks }}
+ {% if object.image %}
+
+ {% endif %}
+
+
+ {{ cart }}
+ {% include "carts/form.html" %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/list.html b/products/templates/products/list.html
new file mode 100644
index 0000000..a317130
--- /dev/null
+++ b/products/templates/products/list.html
@@ -0,0 +1,19 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
+ {% for obj in object_list %}
+
+ {{ forloop.counter }}
+ {% include 'products/snippets/card.html' with instance=obj %}
+ {% if forloop.counter|divisibleby:2 %}
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/products/templates/products/snippets/card.html b/products/templates/products/snippets/card.html
new file mode 100644
index 0000000..f4fcd81
--- /dev/null
+++ b/products/templates/products/snippets/card.html
@@ -0,0 +1,10 @@
+
+ {% if instance.image %}
+
+ {% endif%}
+
+
{{ instance.title}}
+
{{ instance.description|linebreaks|truncatewords:20 }}
+
View
+
+
\ No newline at end of file
diff --git a/products/tests.py b/products/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/products/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/products/urls.py b/products/urls.py
new file mode 100644
index 0000000..2913136
--- /dev/null
+++ b/products/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls import url
+from .views import ProductListView, ProductDetailView
+
+urlpatterns = [
+ url(r'^$', ProductListView.as_view(), name='list'),
+ url(r'^(?P[\w-]+)/?$', ProductDetailView.as_view(), name='details')
+]
\ No newline at end of file
diff --git a/products/views.py b/products/views.py
new file mode 100644
index 0000000..16b02ea
--- /dev/null
+++ b/products/views.py
@@ -0,0 +1,45 @@
+from django.shortcuts import render
+from django.views.generic import ListView, DetailView
+from .models import Product
+from django.contrib.auth import get_user_model
+from django.shortcuts import get_object_or_404
+from django.http import Http404
+from carts.models import Cart
+
+User = get_user_model()
+
+class ProductListView(ListView):
+ queryset = Product.objects.all()
+ template_name = "products/list.html"
+
+ # def get_context_data(self, *args, **kwargs):
+ # context = super().get_context_data(*args, **kwargs)
+ # return context
+
+
+class ProductDetailView(DetailView):
+ queryset = Product.objects.all()
+ template_name = "products/details.html"
+
+ def get_context_data(self, *args, **kwargs):
+ context = super().get_context_data(*args, **kwargs)
+
+ cart_obj, new_cart = Cart.objects.new_or_get(self.request)
+ context["cart"] = cart_obj
+ return context
+
+ def get_object(self, *args, **kwargs):
+ # request = self.request
+ slug = self.kwargs.get('slug')
+ inst = get_object_or_404(Product, slug=slug)
+ # if inst is None:
+ # raise Http404("Product doesn't exist")
+ # return inst
+ # try:
+ # inst = Product.objects.get(slug=slug)
+ #
+ # except ObjectDoesNotExist:
+ # raise Http404("not found...")
+ # except Product.MultipleObjectsReturned:
+ # inst = Product.objects.filter(slug=slug)[0]
+ return inst
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..89f43fc
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+Django==1.11.4
+Pillow==5.2.0
+pytz==2018.5