Add whole system for managing a broader more general range of products
This commit is contained in:
parent
504bc3a146
commit
59aa204860
@ -23,7 +23,7 @@ def get_or_create_customer(request, form, shipping_address):
|
|||||||
user.save()
|
user.save()
|
||||||
else:
|
else:
|
||||||
user, u_created = User.objects.get_or_create(
|
user, u_created = User.objects.get_or_create(
|
||||||
email=form.cleaned_data['email'],
|
email=form.cleaned_data['email'].lower(),
|
||||||
defaults={
|
defaults={
|
||||||
'username': form.cleaned_data['email'].lower(),
|
'username': form.cleaned_data['email'].lower(),
|
||||||
'is_staff': False,
|
'is_staff': False,
|
||||||
|
|||||||
@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
src/core/migrations/0015_productcategory_main_product.py
Normal file
18
src/core/migrations/0015_productcategory_main_product.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -69,10 +69,14 @@ class SiteSettings(SingletonBase):
|
|||||||
|
|
||||||
class ProductCategory(models.Model):
|
class ProductCategory(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
main_category = models.BooleanField(default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('dashboard:category-detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'Product Category'
|
verbose_name = 'Product Category'
|
||||||
verbose_name_plural = 'Product Categories'
|
verbose_name_plural = 'Product Categories'
|
||||||
@ -169,7 +173,7 @@ class ProductOption(models.Model):
|
|||||||
"""
|
"""
|
||||||
Description: Consistent accross all variants
|
Description: Consistent accross all variants
|
||||||
"""
|
"""
|
||||||
product = models.ManyToManyField(
|
products = models.ManyToManyField(
|
||||||
Product,
|
Product,
|
||||||
related_name='options'
|
related_name='options'
|
||||||
)
|
)
|
||||||
@ -178,6 +182,9 @@ class ProductOption(models.Model):
|
|||||||
models.CharField(max_length=255)
|
models.CharField(max_length=255)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('dashboard:option-detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.name}'
|
return f'{self.name}'
|
||||||
|
|
||||||
|
|||||||
76
src/dashboard/templates/dashboard/catalog.html
Normal file
76
src/dashboard/templates/dashboard/catalog.html
Normal 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 %}
|
||||||
@ -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 %}
|
||||||
18
src/dashboard/templates/dashboard/category_create_form.html
Normal file
18
src/dashboard/templates/dashboard/category_create_form.html
Normal 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 %}
|
||||||
24
src/dashboard/templates/dashboard/category_detail.html
Normal file
24
src/dashboard/templates/dashboard/category_detail.html
Normal 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 %}
|
||||||
18
src/dashboard/templates/dashboard/category_form.html
Normal file
18
src/dashboard/templates/dashboard/category_form.html
Normal 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 %}
|
||||||
25
src/dashboard/templates/dashboard/category_list.html
Normal file
25
src/dashboard/templates/dashboard/category_list.html
Normal 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 %}
|
||||||
@ -10,27 +10,18 @@
|
|||||||
|
|
||||||
<section class="object__panel">
|
<section class="object__panel">
|
||||||
<div class="object__item panel__header panel__header--flex">
|
<div class="object__item panel__header panel__header--flex">
|
||||||
<h4>Shipping methods</h4>
|
<h4>Shipping rates</h4>
|
||||||
<a href="{% url 'dashboard:shipmeth-create' %}" class="action-button order__fulfill">+ New method</a>
|
<a href="{% url 'dashboard:rate-create' %}" class="action-button order__fulfill">+ New rate</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel__item">
|
<div class="panel__item">
|
||||||
{% for method in shipping_method_list %}
|
{% for rate in shipping_rate_list %}
|
||||||
<p>
|
<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>
|
</p>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p>No shipping methods yet.</p>
|
<p>No shipping rates yet.</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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>
|
</article>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
19
src/dashboard/templates/dashboard/option_confirm_delete.html
Normal file
19
src/dashboard/templates/dashboard/option_confirm_delete.html
Normal 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 %}
|
||||||
18
src/dashboard/templates/dashboard/option_create_form.html
Normal file
18
src/dashboard/templates/dashboard/option_create_form.html
Normal 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 %}
|
||||||
24
src/dashboard/templates/dashboard/option_detail.html
Normal file
24
src/dashboard/templates/dashboard/option_detail.html
Normal 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 %}
|
||||||
0
src/dashboard/templates/dashboard/option_form.html
Normal file
0
src/dashboard/templates/dashboard/option_form.html
Normal file
@ -43,6 +43,18 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</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">
|
<section class="object__panel">
|
||||||
<div class="object__item panel__header panel__header--flex">
|
<div class="object__item panel__header panel__header--flex">
|
||||||
<h4>Photos</h4>
|
<h4>Photos</h4>
|
||||||
|
|||||||
@ -4,24 +4,23 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article>
|
||||||
<header class="object__header">
|
<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>
|
<a href="{% url 'dashboard:product-create' %}" class="action-button">+ New product</a>
|
||||||
</header>
|
</header>
|
||||||
<section class="object__list">
|
<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></span>
|
||||||
<span>Name</span>
|
<span>Name</span>
|
||||||
<span>Visible</span>
|
<span>Visible</span>
|
||||||
<span>Price</span>
|
|
||||||
</div>
|
</div>
|
||||||
{% for product in product_list %}
|
{% 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">
|
<figure class="product__figure">
|
||||||
<img class="product__image" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
|
<img class="product__image" src="{{product.get_first_img.image.url}}" alt="{{product.get_first_img.image}}">
|
||||||
</figure>
|
</figure>
|
||||||
<strong>{{product.name}}</strong>
|
<strong>{{product.name}}</strong>
|
||||||
<span>{{product.visible_in_listings|yesno:"Yes,No"}}</span>
|
<span>{{product.visible_in_listings|yesno:"Yes,No"}}</span>
|
||||||
<span>${{product.price}}</span>
|
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
19
src/dashboard/templates/dashboard/rate_confirm_delete.html
Normal file
19
src/dashboard/templates/dashboard/rate_confirm_delete.html
Normal 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 %}
|
||||||
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article>
|
||||||
<h1>Create Shipping Method</h1>
|
<h1>Create Shipping Rate</h1>
|
||||||
<section>
|
<section>
|
||||||
<form method="POST" action="{% url 'dashboard:shipmeth-create' %}">
|
<form method="POST" action="{% url 'dashboard:rate-create' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{form.as_p}}
|
{{form.as_p}}
|
||||||
<p class="form__submit">
|
<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>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
22
src/dashboard/templates/dashboard/rate_detail.html
Normal file
22
src/dashboard/templates/dashboard/rate_detail.html
Normal 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 }} – {{ rate.max_order_weight }}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{% endblock content %}
|
||||||
18
src/dashboard/templates/dashboard/rate_form.html
Normal file
18
src/dashboard/templates/dashboard/rate_form.html
Normal 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 %}
|
||||||
@ -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 %}
|
|
||||||
@ -4,6 +4,7 @@
|
|||||||
<article class="product">
|
<article class="product">
|
||||||
<header class="object__header">
|
<header class="object__header">
|
||||||
<h1>Update variant</h1>
|
<h1>Update variant</h1>
|
||||||
|
<a href="{% url 'dashboard:variant-delete' product.pk variant.pk %}" class="action-button action-button--warning">Delete</a>
|
||||||
</header>
|
</header>
|
||||||
<section class="object__panel">
|
<section class="object__panel">
|
||||||
<form class="panel__item" method="POST" action="{% url 'dashboard:variant-update' product.pk variant.pk %}">
|
<form class="panel__item" method="POST" action="{% url 'dashboard:variant-update' product.pk variant.pk %}">
|
||||||
|
|||||||
@ -12,17 +12,32 @@ urlpatterns = [
|
|||||||
views.DashboardConfigView.as_view(),
|
views.DashboardConfigView.as_view(),
|
||||||
name='config'
|
name='config'
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
'catalog/',
|
||||||
|
views.CatalogView.as_view(),
|
||||||
|
name='catalog'
|
||||||
|
),
|
||||||
|
|
||||||
path(
|
path(
|
||||||
'shipping-methods/new/',
|
'shipping-rates/new/',
|
||||||
views.ShippingRateCreateView.as_view(),
|
views.ShippingRateCreateView.as_view(),
|
||||||
name='shipmeth-create'
|
name='rate-create'
|
||||||
),
|
),
|
||||||
path('shipping-methods/<int:pk>/', include([
|
path('shipping-rates/<int:pk>/', include([
|
||||||
path(
|
path(
|
||||||
'',
|
'',
|
||||||
views.ShippingRateDetailView.as_view(),
|
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(
|
path(
|
||||||
'products/',
|
'products/',
|
||||||
views.ProductListView.as_view(),
|
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(
|
path(
|
||||||
'customers/',
|
'customers/',
|
||||||
views.CustomerListView.as_view(),
|
views.CustomerListView.as_view(),
|
||||||
|
|||||||
@ -26,9 +26,11 @@ from accounts.models import User
|
|||||||
from accounts.utils import get_or_create_customer
|
from accounts.utils import get_or_create_customer
|
||||||
from accounts.forms import AddressForm
|
from accounts.forms import AddressForm
|
||||||
from core.models import (
|
from core.models import (
|
||||||
|
ProductCategory,
|
||||||
Product,
|
Product,
|
||||||
ProductPhoto,
|
ProductPhoto,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
|
ProductOption,
|
||||||
Order,
|
Order,
|
||||||
OrderLine,
|
OrderLine,
|
||||||
ShippingRate,
|
ShippingRate,
|
||||||
@ -81,23 +83,52 @@ class DashboardConfigView(TemplateView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
today = timezone.localtime(timezone.now()).date()
|
context['shipping_rate_list'] = ShippingRate.objects.all()
|
||||||
|
|
||||||
context['shipping_method_list'] = ShippingRate.objects.all()
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class ShippingRateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
class CatalogView(ListView):
|
||||||
model = ShippingRate
|
model = ProductCategory
|
||||||
template_name = 'dashboard/shipmeth_create_form.html'
|
context_object_name = 'category_list'
|
||||||
fields = '__all__'
|
template_name = 'dashboard/catalog.html'
|
||||||
success_message = '%(name)s created.'
|
|
||||||
|
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):
|
class ShippingRateDetailView(LoginRequiredMixin, DetailView):
|
||||||
model = ShippingRate
|
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):
|
class CouponListView(LoginRequiredMixin, ListView):
|
||||||
@ -182,9 +213,9 @@ class OrderDetailView(LoginRequiredMixin, DetailView):
|
|||||||
|
|
||||||
class OrderFulfillView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
class OrderFulfillView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
model = Order
|
model = Order
|
||||||
template_name = "dashboard/order_fulfill.html"
|
template_name = 'dashboard/order_fulfill.html'
|
||||||
form_class = OrderLineFormset
|
form_class = OrderLineFormset
|
||||||
success_message = "Order saved."
|
success_message = 'Order saved.'
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.save()
|
form.save()
|
||||||
@ -221,6 +252,42 @@ class OrderTrackingView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
|||||||
return reverse('dashboard:order-detail', kwargs={'pk': self.object.pk})
|
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):
|
class ProductListView(LoginRequiredMixin, ListView):
|
||||||
model = Product
|
model = Product
|
||||||
template_name = 'dashboard/product_list.html'
|
template_name = 'dashboard/product_list.html'
|
||||||
@ -374,6 +441,44 @@ class ProductVariantDeleteView(SuccessMessageMixin, DeleteView):
|
|||||||
return reverse('dashboard:product-detail', kwargs={'pk': self.kwargs['pk']})
|
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):
|
class CustomerListView(LoginRequiredMixin, ListView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'dashboard/customer_list.html'
|
template_name = 'dashboard/customer_list.html'
|
||||||
|
|||||||
@ -87,6 +87,7 @@ TEMPLATES = [
|
|||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'core.context_processors.site_settings',
|
'core.context_processors.site_settings',
|
||||||
'storefront.context_processors.cart',
|
'storefront.context_processors.cart',
|
||||||
|
'storefront.context_processors.product_categories',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -367,7 +367,7 @@ main article {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product__figure img {
|
.product__figure img {
|
||||||
max-height: 400px;
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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
|
Articles
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|||||||
@ -43,19 +43,14 @@ class CartItem:
|
|||||||
'quantity': self.quantity
|
'quantity': self.quantity
|
||||||
})
|
})
|
||||||
|
|
||||||
def build_paypal_dict(self):
|
def __iter__(self):
|
||||||
return {
|
yield ('name', str(self.variant))
|
||||||
# Shows within upper-right dropdown during payment approval
|
yield ('description', self.variant.product.subtitle)
|
||||||
"name": str(self.variant),
|
yield ('unit_amount', {
|
||||||
# Item details will also be in the completed paypal.com
|
'currency_code': settings.DEFAULT_CURRENCY,
|
||||||
# transaction view
|
'value': f'{self.variant.price}',
|
||||||
"description": self.variant.product.subtitle,
|
})
|
||||||
"unit_amount": {
|
yield ('quantity', f'{item["quantity"]}')
|
||||||
"currency_code": settings.DEFAULT_CURRENCY,
|
|
||||||
"value": f'{self.variant.price}',
|
|
||||||
},
|
|
||||||
"quantity": f'{item["quantity"]}',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.variant)
|
return str(self.variant)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from core.models import ProductCategory
|
||||||
from .cart import Cart
|
from .cart import Cart
|
||||||
|
|
||||||
|
|
||||||
@ -5,3 +6,9 @@ def cart(request):
|
|||||||
return {
|
return {
|
||||||
'cart': Cart(request)
|
'cart': Cart(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def product_categories(self):
|
||||||
|
return {
|
||||||
|
'category_list': ProductCategory.objects.all()
|
||||||
|
}
|
||||||
|
|||||||
39
src/storefront/templates/storefront/category_detail.html
Normal file
39
src/storefront/templates/storefront/category_detail.html
Normal 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 %}
|
||||||
|
|
||||||
@ -5,6 +5,14 @@
|
|||||||
<script defer src="{% static 'scripts/product_list.js' %}"></script>
|
<script defer src="{% static 'scripts/product_list.js' %}"></script>
|
||||||
{% endblock %}
|
{% 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 %}
|
{% block content %}
|
||||||
<div class="site__banner site__banner--site">
|
<div class="site__banner site__banner--site">
|
||||||
<h1>Welcome to our new website!</h1>
|
<h1>Welcome to our new website!</h1>
|
||||||
|
|||||||
@ -6,8 +6,17 @@ urlpatterns = [
|
|||||||
path('fair-trade/', views.FairTradeView.as_view(), name='fair-trade'),
|
path('fair-trade/', views.FairTradeView.as_view(), name='fair-trade'),
|
||||||
path('reviews/', views.ReviewListView.as_view(), name='reviews'),
|
path('reviews/', views.ReviewListView.as_view(), name='reviews'),
|
||||||
path('contact/', views.ContactFormView.as_view(), name='contact'),
|
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('', views.ProductListView.as_view(), name='product-list'),
|
||||||
path('products/<int:pk>/', include([
|
path('products/<int:pk>/', include([
|
||||||
path('', views.ProductDetailView.as_view(), name='product-detail'),
|
path('', views.ProductDetailView.as_view(), name='product-detail'),
|
||||||
|
|||||||
@ -22,6 +22,9 @@ from django.contrib import messages
|
|||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
from django.forms.models import model_to_dict
|
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.orders import OrdersCreateRequest, OrdersCaptureRequest
|
||||||
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
|
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
|
||||||
@ -32,7 +35,8 @@ from accounts.forms import (
|
|||||||
AddressForm as AccountAddressForm, CustomerUpdateForm
|
AddressForm as AccountAddressForm, CustomerUpdateForm
|
||||||
)
|
)
|
||||||
from core.models import (
|
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.forms import ShippingRateForm
|
||||||
from core import OrderStatus, ShippingContainer
|
from core import OrderStatus, ShippingContainer
|
||||||
@ -76,7 +80,7 @@ class CartAddProductView(SingleObjectMixin, FormView):
|
|||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
variants = self.get_object().variants.all()
|
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:
|
if form_class is None:
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
return form_class(variants, options, **self.get_form_kwargs())
|
return form_class(variants, options, **self.get_form_kwargs())
|
||||||
@ -154,14 +158,31 @@ class CouponApplyView(FormView):
|
|||||||
return super().form_valid(form)
|
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):
|
class ProductListView(ListView):
|
||||||
model = Product
|
model = Product
|
||||||
template_name = 'storefront/product_list.html'
|
template_name = 'storefront/product_list.html'
|
||||||
# form_class = AddToCartForm
|
|
||||||
ordering = 'sorting'
|
ordering = 'sorting'
|
||||||
|
|
||||||
queryset = Product.objects.filter(
|
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):
|
def get_form(self, form_class=None):
|
||||||
variants = self.object.variants.all()
|
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:
|
if form_class is None:
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
return form_class(variants, options, **self.get_form_kwargs())
|
return form_class(variants, options, **self.get_form_kwargs())
|
||||||
|
|||||||
@ -46,7 +46,14 @@
|
|||||||
<nav class="site__nav">
|
<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>
|
<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">
|
<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: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:fair-trade' %}">Fair trade</a></li>
|
||||||
<li><a class="nav__link" href="{% url 'storefront:reviews' %}">Reviews</a></li>
|
<li><a class="nav__link" href="{% url 'storefront:reviews' %}">Reviews</a></li>
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
<img src="{% static 'images/store.png' %}" alt="">
|
<img src="{% static 'images/store.png' %}" alt="">
|
||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'dashboard:product-list' %}">
|
<a href="{% url 'dashboard:catalog' %}">
|
||||||
<img src="{% static 'images/cubes.png' %}" alt="">
|
<img src="{% static 'images/cubes.png' %}" alt="">
|
||||||
Catalog
|
Catalog
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user