Template
Add shopper accounts, reviews, tracking, and seed_demo (#9)
Closes #9. Shop-gated buyer accounts, purchase reviews, Stripe customer ids, shipment tracking, slim public contact form, and a template-neutral seed_demo command.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
{% extends "accounts/account_base.html" %}
|
||||
{% block title %}Order {{ order.number }} · {{ SITE_NAME }}{% endblock %}
|
||||
{% block account_content %}
|
||||
<h3>Order {{ order.number }}</h3>
|
||||
<p>{{ order.get_status_display }} · ${{ order.amount }} {{ order.currency|upper }}</p>
|
||||
{% if order.customer_name %}<p>{{ order.customer_name }}</p>{% endif %}
|
||||
{% if order.shipping_address.line1 %}
|
||||
<p>
|
||||
{{ order.shipping_address.line1 }}{% if order.shipping_address.line2 %}, {{ order.shipping_address.line2 }}{% endif %}<br>
|
||||
{{ order.shipping_address.city }} {{ order.shipping_address.state }} {{ order.shipping_address.zip }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for item in order.items.all %}
|
||||
<li>{{ item.quantity }}× {{ item.name }} — ${{ item.line_total }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if "shipping" in enabled_features %}
|
||||
<h4>Shipments</h4>
|
||||
{% for shipment in order.shipments.all %}
|
||||
<div>
|
||||
<p>
|
||||
{{ shipment.carrier }} {{ shipment.service }}
|
||||
· {{ shipment.get_status_display }}
|
||||
{% if shipment.tracking_status %} · {{ shipment.get_tracking_status_display }}{% endif %}
|
||||
</p>
|
||||
{% if shipment.tracking_number %}
|
||||
<p>
|
||||
Tracking {{ shipment.tracking_number }}
|
||||
{% if shipment.tracking_url %}
|
||||
· <a href="{{ shipment.tracking_url }}" target="_blank" rel="noopener">Track package</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if shipment.tracking_events %}
|
||||
<ul>
|
||||
{% for event in shipment.tracking_events %}
|
||||
<li>{{ event.datetime }} — {{ event.message|default:event.status }}{% if event.location %} ({{ event.location }}){% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>Not shipped yet. Tracking appears here once a label is bought or a tracking number is added.</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<p><a href="{% url 'account:orders' %}">← All orders</a></p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{% extends "accounts/account_base.html" %}
|
||||
{% block title %}Orders · {{ SITE_NAME }}{% endblock %}
|
||||
{% block account_content %}
|
||||
<h3>Order history</h3>
|
||||
{% if orders %}
|
||||
<ul>
|
||||
{% for order in orders %}
|
||||
<li>
|
||||
<a href="{% url 'account:order_detail' order.pk %}">{{ order.number }}</a>
|
||||
· {{ order.get_status_display }}
|
||||
· {{ order.amount }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No orders yet. <a href="{% url 'shop:list' %}">Browse the shop</a>.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,21 +1,35 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}Checkout · {{ SITE_NAME }}{% endblock %}
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="{% static 'css/address-autocomplete.css' %}">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section class="section section-lg">
|
||||
<div class="container">
|
||||
<h1>Checkout</h1>
|
||||
<p>Total: {{ total }}</p>
|
||||
{% if user.is_authenticated %}
|
||||
<p>Signed in as {{ user.email }}. Cards stay on Stripe; we never store card numbers. <a href="{% url 'account:profile' %}">Edit profile</a></p>
|
||||
{% else %}
|
||||
<p>Have an account? <a href="{% url 'account:login' %}?next={{ request.path }}">Sign in</a> to prefill shipping and save cards on Stripe. Or <a href="{% url 'account:register' %}">create one</a>.</p>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p><label>Email <input name="email" type="email" required></label></p>
|
||||
<p><label>Name <input name="customer_name"></label></p>
|
||||
<p><label>Address <input name="address_line1"></label></p>
|
||||
<p><label>Address 2 <input name="address_line2"></label></p>
|
||||
<p><label>City <input name="address_city"></label></p>
|
||||
<p><label>State <input name="address_state"></label></p>
|
||||
<p><label>ZIP <input name="address_zip"></label></p>
|
||||
<div data-address-autocomplete data-suggest-url="{% url 'address_suggest' %}">
|
||||
<p><label>Email <input class="form-input" name="email" type="email" required value="{{ checkout_initial.email }}" {% if user.is_authenticated %}readonly{% endif %}></label></p>
|
||||
<p><label>Name <input class="form-input" name="customer_name" value="{{ checkout_initial.customer_name }}"></label></p>
|
||||
<p><label>Address <input class="form-input" name="address_line1" data-ac="line1" autocomplete="off" value="{{ checkout_initial.address.line1|default:'' }}"></label></p>
|
||||
<p><label>Address 2 <input class="form-input" name="address_line2" data-ac="line2" autocomplete="address-line2" value="{{ checkout_initial.address.line2|default:'' }}"></label></p>
|
||||
<p><label>City <input class="form-input" name="address_city" data-ac="city" autocomplete="address-level2" value="{{ checkout_initial.address.city|default:'' }}"></label></p>
|
||||
<p><label>State <input class="form-input" name="address_state" data-ac="state" autocomplete="address-level1" value="{{ checkout_initial.address.state|default:'' }}"></label></p>
|
||||
<p><label>ZIP <input class="form-input" name="address_zip" data-ac="zip" autocomplete="postal-code" value="{{ checkout_initial.address.zip|default:'' }}"></label></p>
|
||||
</div>
|
||||
<button class="button button-primary" type="submit">Pay with Stripe</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
{% block extra_js %}
|
||||
<script src="{% static 'js/address-autocomplete.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -15,6 +15,47 @@
|
||||
<label>Qty <input name="quantity" type="number" min="1" value="1"></label>
|
||||
<button class="button button-primary" type="submit">Add to cart</button>
|
||||
</form>
|
||||
<h2>Reviews</h2>
|
||||
{% if review_count %}
|
||||
<p>{{ review_avg|floatformat:1 }} / 5 · {{ review_count }} review{{ review_count|pluralize }}</p>
|
||||
{% else %}
|
||||
<p>No reviews yet.</p>
|
||||
{% endif %}
|
||||
{% if can_review %}
|
||||
<form method="post" action="{% url 'shop:review' product.slug %}">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<label for="review-rating">Rating</label>
|
||||
<select id="review-rating" name="rating" required>
|
||||
<option value="">Choose 1–5</option>
|
||||
<option value="5">5 — Excellent</option>
|
||||
<option value="4">4 — Good</option>
|
||||
<option value="3">3 — Okay</option>
|
||||
<option value="2">2 — Fair</option>
|
||||
<option value="1">1 — Poor</option>
|
||||
</select>
|
||||
</p>
|
||||
<p><label>Title (optional) <input name="title" maxlength="120"></label></p>
|
||||
<p><label>Review (optional) <textarea name="body" rows="4"></textarea></label></p>
|
||||
<button class="button button-primary" type="submit">Submit review</button>
|
||||
</form>
|
||||
{% elif already_reviewed %}
|
||||
<p>You already reviewed this product.</p>
|
||||
{% elif user.is_authenticated %}
|
||||
<p>Buy this product to leave a review.</p>
|
||||
{% else %}
|
||||
<p><a href="{% url 'account:login' %}?next={{ request.path }}">Sign in</a> after a purchase to leave a review.</p>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for review in reviews %}
|
||||
<li>
|
||||
<strong>{{ review.rating }}/5</strong>
|
||||
{% if review.title %} · {{ review.title }}{% endif %}
|
||||
<span> — {{ review.user.first_name|default:review.user.email }}</span>
|
||||
{% if review.body %}<p>{{ review.body|linebreaks }}</p>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -17,5 +17,26 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if "shipping" in enabled_features and order.shipments.all %}
|
||||
<h3>Shipments</h3>
|
||||
<table class="table">
|
||||
<thead><tr><th>Carrier</th><th>Tracking</th><th>Status</th></tr></thead>
|
||||
<tbody>
|
||||
{% for shipment in order.shipments.all %}
|
||||
<tr>
|
||||
<td>{{ shipment.carrier }} {{ shipment.service }}</td>
|
||||
<td>
|
||||
{% if shipment.tracking_url %}
|
||||
<a href="{{ shipment.tracking_url }}" target="_blank" rel="noopener">{{ shipment.tracking_number }}</a>
|
||||
{% else %}
|
||||
{{ shipment.tracking_number|default:"—" }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ shipment.get_tracking_status_display|default:shipment.get_status_display }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<p><a href="{% url 'shop_portal:order_list' %}">← All orders</a></p>
|
||||
{% endblock %}
|
||||
|
||||
@@ -5,5 +5,12 @@
|
||||
<h1>Thank you</h1>
|
||||
<p>Order {{ order.number }} is {{ order.get_status_display|lower }}.</p>
|
||||
<p>A confirmation will go to {{ order.email }}.</p>
|
||||
{% if user.is_authenticated %}
|
||||
<p><a class="button button-primary" href="{% url 'account:order_detail' order.pk %}">View order</a></p>
|
||||
{% else %}
|
||||
<p>Create an account to track shipping and review products you bought. Card details stay with Stripe.</p>
|
||||
<p><a class="button button-primary" href="{% url 'account:register' %}?email={{ order.email|urlencode }}">Create account</a></p>
|
||||
{% endif %}
|
||||
<p><a href="{% url 'shop:list' %}">Continue shopping</a></p>
|
||||
</div></section>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user