diff --git a/src/accounts/utils.py b/src/accounts/utils.py index 11d4cf4..f1b24a9 100644 --- a/src/accounts/utils.py +++ b/src/accounts/utils.py @@ -23,7 +23,7 @@ def get_or_create_customer(request, form, shipping_address): user.save() else: user, u_created = User.objects.get_or_create( - email=form.cleaned_data['email'], + email=form.cleaned_data['email'].lower(), defaults={ 'username': form.cleaned_data['email'].lower(), 'is_staff': False, diff --git a/src/core/migrations/0014_alter_productvariant_options_and_more.py b/src/core/migrations/0014_alter_productvariant_options_and_more.py new file mode 100644 index 0000000..44602cf --- /dev/null +++ b/src/core/migrations/0014_alter_productvariant_options_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0.2 on 2022-10-13 01:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0013_rename_total_net_amount_order_subtotal_amount_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='productvariant', + options={'ordering': ['weight']}, + ), + migrations.RenameField( + model_name='productoption', + old_name='product', + new_name='products', + ), + ] diff --git a/src/core/migrations/0015_productcategory_main_product.py b/src/core/migrations/0015_productcategory_main_product.py new file mode 100644 index 0000000..ba0d3e2 --- /dev/null +++ b/src/core/migrations/0015_productcategory_main_product.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.2 on 2022-10-15 22:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0014_alter_productvariant_options_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='productcategory', + name='main_product', + field=models.BooleanField(default=True), + ), + ] diff --git a/src/core/migrations/0016_rename_main_product_productcategory_main_category.py b/src/core/migrations/0016_rename_main_product_productcategory_main_category.py new file mode 100644 index 0000000..3a6c009 --- /dev/null +++ b/src/core/migrations/0016_rename_main_product_productcategory_main_category.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.2 on 2022-10-15 22:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0015_productcategory_main_product'), + ] + + operations = [ + migrations.RenameField( + model_name='productcategory', + old_name='main_product', + new_name='main_category', + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 69dc9ef..e5c8bc9 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -69,10 +69,14 @@ class SiteSettings(SingletonBase): class ProductCategory(models.Model): name = models.CharField(max_length=255) + main_category = models.BooleanField(default=True) def __str__(self): return self.name + def get_absolute_url(self): + return reverse('dashboard:category-detail', kwargs={'pk': self.pk}) + class Meta: verbose_name = 'Product Category' verbose_name_plural = 'Product Categories' @@ -169,7 +173,7 @@ class ProductOption(models.Model): """ Description: Consistent accross all variants """ - product = models.ManyToManyField( + products = models.ManyToManyField( Product, related_name='options' ) @@ -178,6 +182,9 @@ class ProductOption(models.Model): models.CharField(max_length=255) ) + def get_absolute_url(self): + return reverse('dashboard:option-detail', kwargs={'pk': self.pk}) + def __str__(self): return f'{self.name}' diff --git a/src/dashboard/templates/dashboard/catalog.html b/src/dashboard/templates/dashboard/catalog.html new file mode 100644 index 0000000..99bfced --- /dev/null +++ b/src/dashboard/templates/dashboard/catalog.html @@ -0,0 +1,76 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

Catalog

+
+ + New product option + + New category + + New product +
+
+ {% for category in category_list %} +
+
+ + Category: +

{{ category }}

+
+ Name + Visible in listings +
+ {% for product in category.product_set.all %} + +
+ {{product.get_first_img.image}} +
+ {{product.name}} + {{product.visible_in_listings|yesno:"Yes,No"}} +
+ {% endfor %} +
+ {% endfor %} +
+
+
+

Uncategorized Products

+
+
+
+ + Name + Visible + Price +
+ {% for product in uncategorized_products %} + +
+ {{product.get_first_img.image}} +
+ {{product.name}} + {{product.visible_in_listings|yesno:"Yes,No"}} + ${{product.price}} +
+ {% endfor %} +
+
+
+
+

Product Options

+
+
+
+ + Name +
+ {% for option in option_list %} + + {{option.name}} + {{ option.options }} + + {% endfor %} +
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/category_confirm_delete.html b/src/dashboard/templates/dashboard/category_confirm_delete.html new file mode 100644 index 0000000..2afeb77 --- /dev/null +++ b/src/dashboard/templates/dashboard/category_confirm_delete.html @@ -0,0 +1,19 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

{{ category }}

+
+
+
{% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ {{ form.as_p }} +

+ or cancel +

+
+
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/category_create_form.html b/src/dashboard/templates/dashboard/category_create_form.html new file mode 100644 index 0000000..3cff841 --- /dev/null +++ b/src/dashboard/templates/dashboard/category_create_form.html @@ -0,0 +1,18 @@ +{% extends "dashboard.html" %} + +{% block content %} +
+
+

Create category

+
+
+
+ {% csrf_token %} + {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/dashboard/templates/dashboard/category_detail.html b/src/dashboard/templates/dashboard/category_detail.html new file mode 100644 index 0000000..3629f8f --- /dev/null +++ b/src/dashboard/templates/dashboard/category_detail.html @@ -0,0 +1,24 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+
+

{{ category.name }}

+

Is a main category: {{ category.main_category|yesno:"Yes,No" }}

+
+
+ Delete + Edit +
+
+
+ {% for product in category.product_set.all %} + {{ product }} + {% empty %} +

No products

+ {% endfor %} +
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/category_form.html b/src/dashboard/templates/dashboard/category_form.html new file mode 100644 index 0000000..b71aa94 --- /dev/null +++ b/src/dashboard/templates/dashboard/category_form.html @@ -0,0 +1,18 @@ +{% extends "dashboard.html" %} + +{% block content %} +
+
+

Update category

+
+
+
+ {% csrf_token %} + {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/dashboard/templates/dashboard/category_list.html b/src/dashboard/templates/dashboard/category_list.html new file mode 100644 index 0000000..62f826d --- /dev/null +++ b/src/dashboard/templates/dashboard/category_list.html @@ -0,0 +1,25 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

Categories

+ +
+
+
+ Name +
+ {% for category in category_list %} + + {{ category.name }} + + {% empty %} + No categories + {% endfor %} +
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/config.html b/src/dashboard/templates/dashboard/config.html index 0874ed3..5e56eb0 100644 --- a/src/dashboard/templates/dashboard/config.html +++ b/src/dashboard/templates/dashboard/config.html @@ -10,27 +10,18 @@
-

Shipping methods

- + New method +

Shipping rates

+ + New rate
- {% for method in shipping_method_list %} + {% for rate in shipping_rate_list %}

- {{method.name}} | {{method.type}} | {{method.price}} + {{ rate }}

{% empty %} -

No shipping methods yet.

+

No shipping rates yet.

{% endfor %}
- -
-
-

Staff

- + New staff -
-
-
-
{% endblock %} diff --git a/src/dashboard/templates/dashboard/option_confirm_delete.html b/src/dashboard/templates/dashboard/option_confirm_delete.html new file mode 100644 index 0000000..fb744f8 --- /dev/null +++ b/src/dashboard/templates/dashboard/option_confirm_delete.html @@ -0,0 +1,19 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

Option

+
+
+
{% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ {{ form.as_p }} +

+ or cancel +

+
+
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/option_create_form.html b/src/dashboard/templates/dashboard/option_create_form.html new file mode 100644 index 0000000..b6ab6db --- /dev/null +++ b/src/dashboard/templates/dashboard/option_create_form.html @@ -0,0 +1,18 @@ +{% extends "dashboard.html" %} + +{% block content %} +
+
+

Create option

+
+
+
+ {% csrf_token %} + {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/dashboard/templates/dashboard/option_detail.html b/src/dashboard/templates/dashboard/option_detail.html new file mode 100644 index 0000000..d5e231b --- /dev/null +++ b/src/dashboard/templates/dashboard/option_detail.html @@ -0,0 +1,24 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

{{ option.name }}

+
+ Delete + Edit +
+
+
+
+

Products

+
+ {% for product in option.products.all %} + + {% endfor %} +
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/option_form.html b/src/dashboard/templates/dashboard/option_form.html new file mode 100644 index 0000000..e69de29 diff --git a/src/dashboard/templates/dashboard/product_detail.html b/src/dashboard/templates/dashboard/product_detail.html index 6bb34f3..4279395 100644 --- a/src/dashboard/templates/dashboard/product_detail.html +++ b/src/dashboard/templates/dashboard/product_detail.html @@ -43,6 +43,18 @@ {% endfor %} +
+
+

Options

+

To create more product options go to the catalog

+
+ {% for option in product.options.all %} +
+

{{ option.name }}

+

{{ option.options }}

+
+ {% endfor %} +

Photos

diff --git a/src/dashboard/templates/dashboard/product_list.html b/src/dashboard/templates/dashboard/product_list.html index 2f266d3..06e75b1 100644 --- a/src/dashboard/templates/dashboard/product_list.html +++ b/src/dashboard/templates/dashboard/product_list.html @@ -4,24 +4,23 @@ {% block content %}
-

Catalog

+

Catalog

+ + New category + New product
-
+
Name Visible - Price
{% for product in product_list %} - +
{{product.get_first_img.image}}
{{product.name}} {{product.visible_in_listings|yesno:"Yes,No"}} - ${{product.price}}
{% endfor %}
diff --git a/src/dashboard/templates/dashboard/rate_confirm_delete.html b/src/dashboard/templates/dashboard/rate_confirm_delete.html new file mode 100644 index 0000000..446ee6b --- /dev/null +++ b/src/dashboard/templates/dashboard/rate_confirm_delete.html @@ -0,0 +1,19 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

{{ rate }}

+
+
+
{% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ {{ form.as_p }} +

+ or cancel +

+
+
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/shipmeth_create_form.html b/src/dashboard/templates/dashboard/rate_create_form.html similarity index 62% rename from src/dashboard/templates/dashboard/shipmeth_create_form.html rename to src/dashboard/templates/dashboard/rate_create_form.html index 88cd9bc..7fbbca5 100644 --- a/src/dashboard/templates/dashboard/shipmeth_create_form.html +++ b/src/dashboard/templates/dashboard/rate_create_form.html @@ -2,13 +2,13 @@ {% block content %}
-

Create Shipping Method

+

Create Shipping Rate

-
+ {% csrf_token %} {{form.as_p}}

- or cancel + or cancel

diff --git a/src/dashboard/templates/dashboard/rate_detail.html b/src/dashboard/templates/dashboard/rate_detail.html new file mode 100644 index 0000000..421280a --- /dev/null +++ b/src/dashboard/templates/dashboard/rate_detail.html @@ -0,0 +1,22 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

Shipping Rate

+
+ Delete + Edit +
+
+
+
+

{{rate.name}}

+

Shipping Provider: {{ rate.shipping_provider }}

+

Container: {{ rate.get_container_display }}

+

Weight range: {{ rate.min_order_weight }} – {{ rate.max_order_weight }}

+
+
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/rate_form.html b/src/dashboard/templates/dashboard/rate_form.html new file mode 100644 index 0000000..81e364e --- /dev/null +++ b/src/dashboard/templates/dashboard/rate_form.html @@ -0,0 +1,18 @@ +{% extends "dashboard.html" %} + +{% block content %} +
+
+

Update rate

+
+
+
+ {% csrf_token %} + {{form.as_p}} +

+ or cancel +

+
+
+
+{% endblock %} diff --git a/src/dashboard/templates/dashboard/shipmeth_detail.html b/src/dashboard/templates/dashboard/shipmeth_detail.html deleted file mode 100644 index dad3e49..0000000 --- a/src/dashboard/templates/dashboard/shipmeth_detail.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "dashboard.html" %} -{% load static %} - -{% block content %} -
-
-

Shipping Method

-
- Delete - Edit -
-
-
-
-

{{shippingmethod.name}}

-

{{shippingmethod.get_type_display}}

-

${{shippingmethod.price}}

-
-
-
-{% endblock content %} diff --git a/src/dashboard/templates/dashboard/variant_form.html b/src/dashboard/templates/dashboard/variant_form.html index 85ad698..03f496e 100644 --- a/src/dashboard/templates/dashboard/variant_form.html +++ b/src/dashboard/templates/dashboard/variant_form.html @@ -4,6 +4,7 @@

Update variant

+ Delete
diff --git a/src/dashboard/urls.py b/src/dashboard/urls.py index 4fde679..b054de5 100644 --- a/src/dashboard/urls.py +++ b/src/dashboard/urls.py @@ -12,17 +12,32 @@ urlpatterns = [ views.DashboardConfigView.as_view(), name='config' ), + path( + 'catalog/', + views.CatalogView.as_view(), + name='catalog' + ), path( - 'shipping-methods/new/', + 'shipping-rates/new/', views.ShippingRateCreateView.as_view(), - name='shipmeth-create' + name='rate-create' ), - path('shipping-methods//', include([ + path('shipping-rates//', include([ path( '', views.ShippingRateDetailView.as_view(), - name='shipmeth-detail' + name='rate-detail' + ), + path( + 'update/', + views.ShippingRateUpdateView.as_view(), + name='rate-update' + ), + path( + 'delete/', + views.ShippingRateDeleteView.as_view(), + name='rate-delete' ), ])), @@ -82,6 +97,37 @@ urlpatterns = [ ), ])), + # Categories + path('categories/', include([ + path( + '', + views.CategoryListView.as_view(), + name='category-list' + ), + path( + 'new/', + views.CategoryCreateView.as_view(), + name='category-create' + ), + path('/', include([ + path( + '', + views.CategoryDetailView.as_view(), + name='category-detail' + ), + path( + 'update/', + views.CategoryUpdateView.as_view(), + name='category-update' + ), + path( + 'delete/', + views.CategoryDeleteView.as_view(), + name='category-delete' + ), + ])), + ])), + path( 'products/', views.ProductListView.as_view(), @@ -144,6 +190,32 @@ urlpatterns = [ ])), ])), + # ProductOptions + path('options/', include([ + path( + 'new/', + views.ProductOptionCreateView.as_view(), + name='option-create' + ), + path('/', include([ + path( + '', + views.ProductOptionDetailView.as_view(), + name='option-detail' + ), + path( + 'update/', + views.ProductOptionUpdateView.as_view(), + name='option-update' + ), + path( + 'delete/', + views.ProductOptionDeleteView.as_view(), + name='option-delete' + ), + ])), + ])), + path( 'customers/', views.CustomerListView.as_view(), diff --git a/src/dashboard/views.py b/src/dashboard/views.py index 93f3ecc..1da356a 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -26,9 +26,11 @@ from accounts.models import User from accounts.utils import get_or_create_customer from accounts.forms import AddressForm from core.models import ( + ProductCategory, Product, ProductPhoto, ProductVariant, + ProductOption, Order, OrderLine, ShippingRate, @@ -81,23 +83,52 @@ class DashboardConfigView(TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - today = timezone.localtime(timezone.now()).date() - - context['shipping_method_list'] = ShippingRate.objects.all() - + context['shipping_rate_list'] = ShippingRate.objects.all() return context -class ShippingRateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): - model = ShippingRate - template_name = 'dashboard/shipmeth_create_form.html' - fields = '__all__' - success_message = '%(name)s created.' +class CatalogView(ListView): + model = ProductCategory + context_object_name = 'category_list' + template_name = 'dashboard/catalog.html' + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['uncategorized_products'] = Product.objects.filter( + category=None + ) + context['option_list'] = ProductOption.objects.all() + return context class ShippingRateDetailView(LoginRequiredMixin, DetailView): model = ShippingRate - template_name = 'dashboard/shipmeth_detail.html' + context_object_name = 'rate' + template_name = 'dashboard/rate_detail.html' + + +class ShippingRateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = ShippingRate + context_object_name = 'rate' + template_name = 'dashboard/rate_create_form.html' + fields = '__all__' + success_message = '%(name)s created.' + + +class ShippingRateUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): + model = ShippingRate + context_object_name = 'rate' + template_name = 'dashboard/rate_form.html' + success_message = 'ShippingRate saved.' + fields = '__all__' + + +class ShippingRateDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): + model = ShippingRate + context_object_name = 'rate' + template_name = 'dashboard/rate_confirm_delete.html' + success_message = 'ShippingRate deleted.' + success_url = reverse_lazy('dashboard:config') class CouponListView(LoginRequiredMixin, ListView): @@ -182,9 +213,9 @@ class OrderDetailView(LoginRequiredMixin, DetailView): class OrderFulfillView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): model = Order - template_name = "dashboard/order_fulfill.html" + template_name = 'dashboard/order_fulfill.html' form_class = OrderLineFormset - success_message = "Order saved." + success_message = 'Order saved.' def form_valid(self, form): form.save() @@ -221,6 +252,42 @@ class OrderTrackingView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk}) +class CategoryListView(ListView): + model = ProductCategory + context_object_name = 'category_list' + template_name = 'dashboard/category_list.html' + + +class CategoryCreateView(SuccessMessageMixin, CreateView): + model = ProductCategory + context_object_name = 'category' + success_message = 'Category created.' + template_name = 'dashboard/category_create_form.html' + fields = '__all__' + + +class CategoryDetailView(DetailView): + model = ProductCategory + context_object_name = 'category' + template_name = 'dashboard/category_detail.html' + + +class CategoryUpdateView(SuccessMessageMixin, UpdateView): + model = ProductCategory + context_object_name = 'category' + success_message = 'Category saved.' + template_name = 'dashboard/category_form.html' + fields = '__all__' + + +class CategoryDeleteView(SuccessMessageMixin, DeleteView): + model = ProductCategory + context_object_name = 'category' + success_message = 'Category deleted.' + template_name = 'dashboard/category_confirm_delete.html' + success_url = reverse_lazy('dashboard:catalog') + + class ProductListView(LoginRequiredMixin, ListView): model = Product template_name = 'dashboard/product_list.html' @@ -374,6 +441,44 @@ class ProductVariantDeleteView(SuccessMessageMixin, DeleteView): return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']}) +class ProductOptionDetailView(LoginRequiredMixin, DetailView): + model = ProductOption + template_name = 'dashboard/option_detail.html' + context_object_name = 'option' + + +class ProductOptionCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = ProductOption + template_name = 'dashboard/option_create_form.html' + fields = [ + 'name', + 'options', + 'products', + ] + success_message = '%(name)s created.' + + +class ProductOptionUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): + model = ProductOption + success_message = 'Option saved.' + template_name = 'dashboard/option_form.html' + fields = [ + 'name', + 'options', + 'products', + ] + context_object_name = 'option' + success_url = reverse_lazy('dashboard:catalog') + + +class ProductOptionDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): + model = ProductOption + success_message = 'ProductOption deleted.' + template_name = 'dashboard/option_confirm_delete.html' + context_object_name = 'option' + success_url = reverse_lazy('dashboard:catalog') + + class CustomerListView(LoginRequiredMixin, ListView): model = User template_name = 'dashboard/customer_list.html' diff --git a/src/ptcoffee/settings.py b/src/ptcoffee/settings.py index 45c79c0..39b2ebf 100644 --- a/src/ptcoffee/settings.py +++ b/src/ptcoffee/settings.py @@ -87,6 +87,7 @@ TEMPLATES = [ 'django.contrib.messages.context_processors.messages', 'core.context_processors.site_settings', 'storefront.context_processors.cart', + 'storefront.context_processors.product_categories', ], }, }, diff --git a/src/static/styles/dashboard.css b/src/static/styles/dashboard.css index 239348e..672105f 100644 --- a/src/static/styles/dashboard.css +++ b/src/static/styles/dashboard.css @@ -367,7 +367,7 @@ main article { } .product__figure img { - max-height: 400px; + max-height: 200px; } diff --git a/src/static/styles/main.css b/src/static/styles/main.css index f8d1853..937ad1d 100644 --- a/src/static/styles/main.css +++ b/src/static/styles/main.css @@ -526,6 +526,22 @@ section:not(:last-child) { } +/* Breadcrumbs + ========================================================================== */ +.breadcrumbs { + margin-bottom: 1.5rem; +} +.breadcrumbs menu { + margin: 0; + padding: 0 1rem; + line-height: 1.75; + list-style: none; + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + + /* ========================================================================== Articles ========================================================================== */ diff --git a/src/storefront/cart.py b/src/storefront/cart.py index 716eb68..65d0f28 100644 --- a/src/storefront/cart.py +++ b/src/storefront/cart.py @@ -39,23 +39,18 @@ class CartItem: def get_update_form(self, index): return self.update_form(initial={ - 'item_pk': index, - 'quantity': self.quantity - }) + 'item_pk': index, + 'quantity': self.quantity + }) - def build_paypal_dict(self): - return { - # Shows within upper-right dropdown during payment approval - "name": str(self.variant), - # Item details will also be in the completed paypal.com - # transaction view - "description": self.variant.product.subtitle, - "unit_amount": { - "currency_code": settings.DEFAULT_CURRENCY, - "value": f'{self.variant.price}', - }, - "quantity": f'{item["quantity"]}', - } + def __iter__(self): + yield ('name', str(self.variant)) + yield ('description', self.variant.product.subtitle) + yield ('unit_amount', { + 'currency_code': settings.DEFAULT_CURRENCY, + 'value': f'{self.variant.price}', + }) + yield ('quantity', f'{item["quantity"]}') def __str__(self): return str(self.variant) diff --git a/src/storefront/context_processors.py b/src/storefront/context_processors.py index 1a1dab4..7ae0783 100644 --- a/src/storefront/context_processors.py +++ b/src/storefront/context_processors.py @@ -1,3 +1,4 @@ +from core.models import ProductCategory from .cart import Cart @@ -5,3 +6,9 @@ def cart(request): return { 'cart': Cart(request) } + + +def product_categories(self): + return { + 'category_list': ProductCategory.objects.all() + } diff --git a/src/storefront/templates/storefront/category_detail.html b/src/storefront/templates/storefront/category_detail.html new file mode 100644 index 0000000..1ab698e --- /dev/null +++ b/src/storefront/templates/storefront/category_detail.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+

Welcome to our new website!

+

NEW COOL LOOK, SAME GREAT COFFEE

+
+{# Home > Category > "Coffee/Merchandise" #} + +{% endblock %} + diff --git a/src/storefront/templates/storefront/product_list.html b/src/storefront/templates/storefront/product_list.html index 5368c8b..ae9b85f 100644 --- a/src/storefront/templates/storefront/product_list.html +++ b/src/storefront/templates/storefront/product_list.html @@ -5,6 +5,14 @@ {% endblock %} +{% block product_categories %} + +{% endblock product_categories %} + {% block content %}

Welcome to our new website!

diff --git a/src/storefront/urls.py b/src/storefront/urls.py index 9c95e0a..3512184 100644 --- a/src/storefront/urls.py +++ b/src/storefront/urls.py @@ -6,8 +6,17 @@ urlpatterns = [ path('fair-trade/', views.FairTradeView.as_view(), name='fair-trade'), path('reviews/', views.ReviewListView.as_view(), name='reviews'), path('contact/', views.ContactFormView.as_view(), name='contact'), - path('subscriptions/', views.SubscriptionCreateView.as_view(), name='subscriptions'), + path( + 'subscriptions/', + views.SubscriptionCreateView.as_view(), + name='subscriptions' + ), + path( + 'categories//', + views.ProductCategoryDetailView.as_view(), + name='category-detail' + ), path('', views.ProductListView.as_view(), name='product-list'), path('products//', include([ path('', views.ProductDetailView.as_view(), name='product-detail'), diff --git a/src/storefront/views.py b/src/storefront/views.py index 2388792..b5120f7 100644 --- a/src/storefront/views.py +++ b/src/storefront/views.py @@ -22,6 +22,9 @@ from django.contrib import messages from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from django.forms.models import model_to_dict +from django.db.models import ( + Exists, OuterRef, Prefetch, Subquery, Count, Sum, Avg, F, Q, Value +) from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment @@ -32,7 +35,8 @@ from accounts.forms import ( AddressForm as AccountAddressForm, CustomerUpdateForm ) from core.models import ( - Product, ProductOption, Order, Transaction, OrderLine, Coupon, ShippingRate + ProductCategory, Product, ProductOption, + Order, Transaction, OrderLine, Coupon, ShippingRate ) from core.forms import ShippingRateForm from core import OrderStatus, ShippingContainer @@ -76,7 +80,7 @@ class CartAddProductView(SingleObjectMixin, FormView): def get_form(self, form_class=None): variants = self.get_object().variants.all() - options = ProductOption.objects.filter(product__pk=self.get_object().pk) + options = ProductOption.objects.filter(products__pk=self.get_object().pk) if form_class is None: form_class = self.get_form_class() return form_class(variants, options, **self.get_form_kwargs()) @@ -154,14 +158,31 @@ class CouponApplyView(FormView): return super().form_valid(form) +class ProductCategoryDetailView(DetailView): + model = ProductCategory + template_name = 'storefront/category_detail.html' + context_object_name = 'category' + + def get_queryset(self): + object_list = ProductCategory.objects.prefetch_related( + Prefetch( + 'product_set', + queryset=Product.objects.filter( + visible_in_listings=True + ) + ) + ) + return object_list + + class ProductListView(ListView): model = Product template_name = 'storefront/product_list.html' - # form_class = AddToCartForm ordering = 'sorting' queryset = Product.objects.filter( - visible_in_listings=True + visible_in_listings=True, + category__main_category=True ) @@ -172,7 +193,7 @@ class ProductDetailView(FormMixin, DetailView): def get_form(self, form_class=None): variants = self.object.variants.all() - options = ProductOption.objects.filter(product__pk=self.object.pk) + options = ProductOption.objects.filter(products__pk=self.object.pk) if form_class is None: form_class = self.get_form_class() return form_class(variants, options, **self.get_form_kwargs()) diff --git a/src/templates/base.html b/src/templates/base.html index 7d2ed20..c8c82d5 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -46,7 +46,14 @@