Add whole system for managing a broader more general range of products

This commit is contained in:
Nathan Chapman 2022-10-15 20:05:43 -06:00
parent 504bc3a146
commit 59aa204860
37 changed files with 713 additions and 86 deletions

View File

@ -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,

View File

@ -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',
),
]

View File

@ -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),
),
]

View File

@ -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',
),
]

View File

@ -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}'

View File

@ -0,0 +1,76 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static 'images/cubes.png' %}" alt=""> Catalog</h1>
<div>
<a href="{% url 'dashboard:option-create' %}" class="action-button">+ New product option</a>
<a href="{% url 'dashboard:category-create' %}" class="action-button">+ New category</a>
<a href="{% url 'dashboard:product-create' %}" class="action-button">+ New product</a>
</div>
</header>
{% for category in category_list %}
<section class="object__list">
<div class="object__item panel__header object__item--col3">
<span>
Category:
<h4><a href="{% url 'dashboard:category-detail' category.pk %}">{{ category }}</a></h4>
</span>
<span>Name</span>
<span>Visible in listings</span>
</div>
{% for product in category.product_set.all %}
<a class="object__item object__item--link object__item--col3" href="{% url 'dashboard:product-detail' product.pk %}">
<figure class="product__figure">
<img class="product__image" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
</figure>
<strong>{{product.name}}</strong>
<span>{{product.visible_in_listings|yesno:"Yes,No"}}</span>
</a>
{% endfor %}
</section>
{% endfor %}
</article>
<article>
<header class="object__header">
<h2>Uncategorized Products</h2>
</header>
<section class="object__list">
<div class="object__item panel__header object__item--col4">
<span></span>
<span>Name</span>
<span>Visible</span>
<span>Price</span>
</div>
{% for product in uncategorized_products %}
<a class="object__item object__item--link object__item--col4" href="{% url 'dashboard:product-detail' product.pk %}">
<figure class="product__figure">
<img class="product__image" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
</figure>
<strong>{{product.name}}</strong>
<span>{{product.visible_in_listings|yesno:"Yes,No"}}</span>
<span>${{product.price}}</span>
</a>
{% endfor %}
</section>
</article>
<article>
<header class="object__header">
<h2>Product Options</h2>
</header>
<section class="object__list">
<div class="object__item panel__header object__item--col4">
<span></span>
<span>Name</span>
</div>
{% for option in option_list %}
<a class="object__item object__item--link object__item--col4" href="{% url 'dashboard:option-detail' option.pk %}">
<strong>{{option.name}}</strong>
<span>{{ option.options }}</span>
</a>
{% endfor %}
</section>
</article>
{% endblock content %}

View File

@ -0,0 +1,19 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static 'images/cubes.png' %}" alt=""> {{ category }}</h1>
</header>
<section class="category__detail object__panel">
<form method="post" class="panel__item">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
{{ form.as_p }}
<p>
<input class="action-button action-button--warning" type="submit" value="Confirm"> or <a href="{% url 'dashboard:category-detail' category.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock content %}

View File

@ -0,0 +1,18 @@
{% extends "dashboard.html" %}
{% block content %}
<article class="product">
<header class="object__header">
<h1>Create category</h1>
</header>
<section class="object__panel">
<form class="panel__item" method="POST" action="{% url 'dashboard:category-create' %}">
{% csrf_token %}
{{form.as_p}}
<p class="form__submit">
<input class="action-button" type="submit" value="Create category"> or <a href="{% url 'dashboard:catalog' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<div>
<h1><img src="{% static 'images/cubes.png' %}" alt=""> {{ category.name }}</h1>
<p><strong>Is a main category</strong>: {{ category.main_category|yesno:"Yes,No" }}</p>
</div>
<div class="object__menu">
<a href="{% url 'dashboard:category-delete' category.pk %}" class="action-button action-button--warning">Delete</a>
<a href="{% url 'dashboard:category-update' category.pk %}" class="action-button">Edit</a>
</div>
</header>
<section class="product__detail object__panel">
{% for product in category.product_set.all %}
<a href="{% url 'dashboard:product-detail' product.pk %}">{{ product }}</a>
{% empty %}
<p>No products</p>
{% endfor %}
</section>
</article>
{% endblock content %}

View File

@ -0,0 +1,18 @@
{% extends "dashboard.html" %}
{% block content %}
<article class="product">
<header class="object__header">
<h1>Update category</h1>
</header>
<section class="object__panel">
<form class="panel__item" method="POST" action="{% url 'dashboard:category-update' category.pk %}">
{% csrf_token %}
{{form.as_p}}
<p class="form__submit">
<input class="action-button" type="submit" value="Create category"> or <a href="{% url 'dashboard:category-detail' category.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static 'images/cubes.png' %}" alt=""> Categories</h1>
<div class="object__menu">
<a href="{% url 'dashboard:category-create' %}" class="action-button order__fulfill">+ New category</a>
</div>
</header>
<section class="object__list">
<div class="object__item panel__header object__item--col5" href="category-detail">
<span>Name</span>
</div>
{% for category in category_list %}
<a class="object__item object__item--link object__item--col5" href="{% url 'dashboard:category-detail' category.pk %}">
<span>{{ category.name }}</span>
</a>
{% empty %}
<span class="object__item">No categories</span>
{% endfor %}
</section>
</article>
{% endblock content %}

View File

@ -10,27 +10,18 @@
<section class="object__panel">
<div class="object__item panel__header panel__header--flex">
<h4>Shipping methods</h4>
<a href="{% url 'dashboard:shipmeth-create' %}" class="action-button order__fulfill">+ New method</a>
<h4>Shipping rates</h4>
<a href="{% url 'dashboard:rate-create' %}" class="action-button order__fulfill">+ New rate</a>
</div>
<div class="panel__item">
{% for method in shipping_method_list %}
{% for rate in shipping_rate_list %}
<p>
<a href="{% url 'dashboard:shipmeth-detail' method.pk %}">{{method.name}} | {{method.type}} | {{method.price}}</a>
<a href="{% url 'dashboard:rate-detail' rate.pk %}">{{ rate }}</a>
</p>
{% empty %}
<p>No shipping methods yet.</p>
<p>No shipping rates yet.</p>
{% endfor %}
</div>
</section>
<section class="object__panel">
<div class="object__item panel__header panel__header--flex">
<h4>Staff</h4>
<a href="" class="action-button order__fulfill">+ New staff</a>
</div>
<div class="panel__item">
</div>
</section>
</article>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1>Option</h1>
</header>
<section class="option__detail object__panel">
<form method="post" class="panel__item">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
{{ form.as_p }}
<p>
<input class="action-button action-button--warning" type="submit" value="Confirm"> or <a href="{% url 'dashboard:option-detail' option.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock content %}

View File

@ -0,0 +1,18 @@
{% extends "dashboard.html" %}
{% block content %}
<article class="product">
<header class="object__header">
<h1>Create option</h1>
</header>
<section class="object__panel">
<form class="panel__item" method="POST" action="{% url 'dashboard:option-create' %}">
{% csrf_token %}
{{form.as_p}}
<p class="form__submit">
<input class="action-button" type="submit" value="Create option"> or <a href="{% url 'dashboard:catalog' %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1>{{ option.name }}</h1>
<div class="object__menu">
<a href="{% url 'dashboard:option-delete' option.pk %}" class="action-button action-button--warning">Delete</a>
<a href="{% url 'dashboard:option-update' option.pk %}" class="action-button">Edit</a>
</div>
</header>
<section class="object__panel">
<div class="object__item panel__header">
<h4>Products</h4>
</div>
{% for product in option.products.all %}
<div class="panel__item">
<h3><a href="{% url 'dashboard:product-detail' product.pk %}">{{ product.name }}</a></h3>
</div>
{% endfor %}
</section>
</article>
{% endblock content %}

View File

@ -43,6 +43,18 @@
</div>
{% endfor %}
</section>
<section class="object__panel">
<div class="object__item panel__header panel__header--flex">
<h4>Options</h4>
<p><em>To create more product options go to the <a href="{% url 'dashboard:catalog' %}">catalog</a></em></p>
</div>
{% for option in product.options.all %}
<div class="panel__item">
<h3>{{ option.name }}</h3>
<p>{{ option.options }}</p>
</div>
{% endfor %}
</section>
<section class="object__panel">
<div class="object__item panel__header panel__header--flex">
<h4>Photos</h4>

View File

@ -4,24 +4,23 @@
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static "images/cubes.png" %}" alt=""> Catalog</h1>
<h1><img src="{% static 'images/cubes.png' %}" alt=""> Catalog</h1>
<a href="{% url 'dashboard:category-create' %}" class="action-button">+ New category</a>
<a href="{% url 'dashboard:product-create' %}" class="action-button">+ New product</a>
</header>
<section class="object__list">
<div class="object__item panel__header object__item--col4">
<div class="object__item panel__header object__item--col3">
<span></span>
<span>Name</span>
<span>Visible</span>
<span>Price</span>
</div>
{% for product in product_list %}
<a class="object__item object__item--link object__item--col4" href="{% url 'dashboard:product-detail' product.pk %}">
<a class="object__item object__item--link object__item--col3" href="{% url 'dashboard:product-detail' product.pk %}">
<figure class="product__figure">
<img class="product__image" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
</figure>
<strong>{{product.name}}</strong>
<span>{{product.visible_in_listings|yesno:"Yes,No"}}</span>
<span>${{product.price}}</span>
</a>
{% endfor %}
</section>

View File

@ -0,0 +1,19 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static 'images/gear.png' %}" alt=""> {{ rate }}</h1>
</header>
<section class="rate__detail object__panel">
<form method="post" class="panel__item">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
{{ form.as_p }}
<p>
<input class="action-button action-button--warning" type="submit" value="Confirm"> or <a href="{% url 'dashboard:rate-detail' rate.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock content %}

View File

@ -2,13 +2,13 @@
{% block content %}
<article>
<h1>Create Shipping Method</h1>
<h1>Create Shipping Rate</h1>
<section>
<form method="POST" action="{% url 'dashboard:shipmeth-create' %}">
<form method="POST" action="{% url 'dashboard:rate-create' %}">
{% csrf_token %}
{{form.as_p}}
<p class="form__submit">
<input class="action-button" type="submit" value="Create method"> or <a href="{% url 'dashboard:config' %}">cancel</a>
<input class="action-button" type="submit" value="Create rate"> or <a href="{% url 'dashboard:config' %}">cancel</a>
</p>
</form>
</section>

View File

@ -0,0 +1,22 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static 'images/gear.png' %}" alt=""> Shipping Rate</h1>
<div class="object__menu">
<a href="{% url 'dashboard:rate-delete' rate.pk %}" class="action-button action-button--warning">Delete</a>
<a href="{% url 'dashboard:rate-update' rate.pk %}" class="action-button">Edit</a>
</div>
</header>
<section class="product__detail object__panel">
<div>
<h1>{{rate.name}}</h1>
<p><strong>Shipping Provider</strong>: {{ rate.shipping_provider }}</p>
<p><strong>Container</strong>: {{ rate.get_container_display }}</p>
<p><strong>Weight range</strong>: {{ rate.min_order_weight }} &ndash; {{ rate.max_order_weight }}</p>
</div>
</section>
</article>
{% endblock content %}

View File

@ -0,0 +1,18 @@
{% extends "dashboard.html" %}
{% block content %}
<article class="product">
<header class="object__header">
<h1>Update rate</h1>
</header>
<section class="object__panel">
<form class="panel__item" method="POST" action="{% url 'dashboard:rate-update' rate.pk %}">
{% csrf_token %}
{{form.as_p}}
<p class="form__submit">
<input class="action-button" type="submit" value="Create rate"> or <a href="{% url 'dashboard:rate-detail' rate.pk %}">cancel</a>
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -1,21 +0,0 @@
{% extends "dashboard.html" %}
{% load static %}
{% block content %}
<article>
<header class="object__header">
<h1><img src="{% static 'images/gear.png' %}" alt=""> Shipping Method</h1>
<div class="object__menu">
<a href="" class="action-button action-button--warning">Delete</a>
<a href="" class="action-button">Edit</a>
</div>
</header>
<section class="product__detail object__panel">
<div>
<h1>{{shippingmethod.name}}</h1>
<p>{{shippingmethod.get_type_display}}</p>
<p>$<strong>{{shippingmethod.price}}</strong></p>
</div>
</section>
</article>
{% endblock content %}

View File

@ -4,6 +4,7 @@
<article class="product">
<header class="object__header">
<h1>Update variant</h1>
<a href="{% url 'dashboard:variant-delete' product.pk variant.pk %}" class="action-button action-button--warning">Delete</a>
</header>
<section class="object__panel">
<form class="panel__item" method="POST" action="{% url 'dashboard:variant-update' product.pk variant.pk %}">

View File

@ -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/<int:pk>/', include([
path('shipping-rates/<int:pk>/', 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('<int:pk>/', 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('<int:pk>/', 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(),

View File

@ -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'

View File

@ -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',
],
},
},

View File

@ -367,7 +367,7 @@ main article {
}
.product__figure img {
max-height: 400px;
max-height: 200px;
}

View File

@ -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
========================================================================== */

View File

@ -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)

View File

@ -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()
}

View File

@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% load static %}
{% block head %}
<script defer src="{% static 'scripts/product_list.js' %}"></script>
{% endblock %}
{% block content %}
<div class="site__banner site__banner--site">
<h1>Welcome to our new website!</h1>
<h4>NEW COOL LOOK, SAME GREAT COFFEE</h4>
</div>
{# Home > Category > "Coffee/Merchandise" #}
<article>
<div class="breadcrumbs">
<menu>
<li><strong><a href="{% url 'storefront:product-list' %}">Shop</a></strong></li>
<span></span>
<li><strong>{{ category }}</strong></a></li>
</menu>
</div>
<section class="product__list">
{% for product in category.product_set.all %}
<a class="product__item" href="{% url 'storefront:product-detail' product.pk %}">
<figure class="product__figure">
<img class="product__image product__with-img-swap" data-altimg-src="{{product.get_second_img.image.url}}" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
</figure>
<div>
<h3>{{ product.name }}</h3>
<h5>{{ product.subtitle }}</h5>
<p>{{product.description|truncatewords:20}}</p>
<p>$<strong>{{product.variants.first.price}}</strong></p>
</div>
</a>
{% endfor %}
</section>
</article>
{% endblock %}

View File

@ -5,6 +5,14 @@
<script defer src="{% static 'scripts/product_list.js' %}"></script>
{% endblock %}
{% block product_categories %}
<ul class="nav__dropdown">
{% for category in category_list %}
<li><a class="nav__link" href="">{{ category }}</a></li>
{% endfor %}
</ul>
{% endblock product_categories %}
{% block content %}
<div class="site__banner site__banner--site">
<h1>Welcome to our new website!</h1>

View File

@ -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/<int:pk>/',
views.ProductCategoryDetailView.as_view(),
name='category-detail'
),
path('', views.ProductListView.as_view(), name='product-list'),
path('products/<int:pk>/', include([
path('', views.ProductDetailView.as_view(), name='product-detail'),

View File

@ -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())

View File

@ -46,7 +46,14 @@
<nav class="site__nav">
<a class="site__logo" href="{% url 'storefront:product-list' %}"><img src="{% static 'images/site_logo.svg' %}" alt="Port Townsend Roasting Co."></a>
<ul class="nav__list nav__main">
<li><a class="nav__link" href="{% url 'storefront:product-list' %}">Shop</a></li>
<li class="nav__menu">
<a class="nav__link" href="{% url 'storefront:product-list' %}">Shop ▼</a>
<ul class="nav__dropdown">
{% for category in category_list %}
<li><a class="nav__link" href="{% url 'storefront:category-detail' category.pk %}">{{ category }}</a></li>
{% endfor %}
</ul>
</li>
<li><a class="nav__link" href="{% url 'storefront:subscriptions' %}">Subscriptions</a></li>
<li><a class="nav__link" href="{% url 'storefront:fair-trade' %}">Fair trade</a></li>
<li><a class="nav__link" href="{% url 'storefront:reviews' %}">Reviews</a></li>

View File

@ -29,7 +29,7 @@
<img src="{% static 'images/store.png' %}" alt="">
Home
</a>
<a href="{% url 'dashboard:product-list' %}">
<a href="{% url 'dashboard:catalog' %}">
<img src="{% static 'images/cubes.png' %}" alt="">
Catalog
</a>