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()
|
||||
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,
|
||||
|
||||
@ -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):
|
||||
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}'
|
||||
|
||||
|
||||
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">
|
||||
<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 %}
|
||||
|
||||
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>
|
||||
{% 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>
|
||||
|
||||
@ -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>
|
||||
|
||||
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 %}
|
||||
<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>
|
||||
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">
|
||||
<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 %}">
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@ -367,7 +367,7 @@ main article {
|
||||
}
|
||||
|
||||
.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
|
||||
========================================================================== */
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
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>
|
||||
{% 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>
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user