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

Login

+
+
+ Continue as guest +
+ {% csrf_token %} + {{ guest_form.as_p }} +
+
+
+{% else %} + + {% if not object.address %} +
+
+

Adress

+
+
+
+
{% csrf_token %} + {% for addr in address_qs %} +
+ {% endfor %} + +
+
+ {% csrf_token %} + {{ address_form.as_p }} + + +
+
+
+
+ {% 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 }}

+
{% csrf_token %} + +
+{% 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 @@ +
{% csrf_token %} + + {% if object in cart.products.all %} + + {% else %} + + {% endif %} +
\ 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

+ + + + + + + + + + {% for product in cart.products.all %} + + + + + + {% endfor %} + + + + + + + + + + + + + +{% 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 %} + {{ instance.title}} logo + {% 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
#Product NamePrice
{{ forloop.counter }}{{ product.title }}{% include "carts/form.html" with object=product in_cart=True %} + {{ product.price }}
Subtotal {{ cart.subtotal }}
Total {{ cart.total }}
Checkout