Merge branch 'release/1.3.6'

This commit is contained in:
Nathan Chapman 2022-05-15 21:29:11 -06:00
commit a6fb7feb1b
12 changed files with 424 additions and 110 deletions

View File

@ -3,15 +3,16 @@ from allauth.account.models import EmailAddress
from .models import Address, User
from .tasks import send_account_created_email
def get_or_create_customer(request, form, shipping_address):
address, a_created = Address.objects.get_or_create(
first_name = shipping_address['first_name'],
last_name = shipping_address['last_name'],
street_address_1 = shipping_address['street_address_1'],
street_address_2 = shipping_address['street_address_2'],
city = shipping_address['city'],
state = shipping_address['state'],
postal_code = shipping_address['postal_code']
first_name=shipping_address['first_name'],
last_name=shipping_address['last_name'],
street_address_1=shipping_address['street_address_1'],
street_address_2=shipping_address['street_address_2'],
city=shipping_address['city'],
state=shipping_address['state'],
postal_code=shipping_address['postal_code']
)
if request.user.is_authenticated:
@ -23,8 +24,8 @@ def get_or_create_customer(request, form, shipping_address):
else:
user, u_created = User.objects.get_or_create(
email=form.cleaned_data['email'],
defaults = {
'username': form.cleaned_data['email'],
defaults={
'username': form.cleaned_data['email'].lower(),
'is_staff': False,
'is_active': True,
'is_superuser': False,
@ -39,7 +40,9 @@ def get_or_create_customer(request, form, shipping_address):
user.addresses.add(address)
user.save()
EmailAddress.objects.create(user=user, email=user.email, primary=True, verified=False)
EmailAddress.objects.create(
user=user, email=user.email, primary=True, verified=False
)
u = {
'full_name': user.get_full_name(),

View File

@ -1 +1,190 @@
[{"model": "core.product", "pk": 1, "fields": {"name": "Ethiopia", "description": "Spicy espresso reminiscent of Northern Italy, on the mild side. Perfect for espresso, and steamed milk drinks. Also, a full-bodied, earthy sweet drip or Americano. Contains organic beans from Indonesia, Africa and America.", "sku": "23468", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 4, "created_at": "2022-02-19T20:15:36.292Z", "updated_at": "2022-03-28T17:29:26.300Z"}}, {"model": "core.product", "pk": 2, "fields": {"name": "Sumatra", "description": "Dark heavy-bodied roast with a lingering chocolatey taste. Organic Single origin.", "sku": "89765", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 3, "created_at": "2022-02-19T20:15:59.741Z", "updated_at": "2022-03-28T17:29:08.706Z"}}, {"model": "core.product", "pk": 3, "fields": {"name": "Pantomime", "description": "Very Dark French Roast\r\nOur darkest drip. A blend of five different beans roasted two ways. Organic Africa, Indonesia, and South and Central America.", "sku": "565656", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 1, "created_at": "2022-02-23T17:59:00.711Z", "updated_at": "2022-03-28T17:28:58.670Z"}}, {"model": "core.product", "pk": 4, "fields": {"name": "Decaf", "description": "French Roast (Water Processed)\r\n\r\n“I cant believe its decaf!”. The best-tasting Swiss water process decaf we have developed over the past 30 years, for an unbelievable espresso or drip coffee. Organic Africa, Indonesia and South and Central America.", "sku": "566565", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 2, "created_at": "2022-02-23T17:59:32.099Z", "updated_at": "2022-03-28T17:28:45.512Z"}}, {"model": "core.product", "pk": 5, "fields": {"name": "Moka Java Blend", "description": "Dark Roast\r\n\r\nA classic Moka Java style blend dark roasted with organic beans for a perfect body and sweetness with a hint of citrus.", "sku": "56466", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": false, "sorting": 5, "created_at": "2022-02-23T18:05:41.742Z", "updated_at": "2022-03-28T17:29:39.761Z"}}, {"model": "core.product", "pk": 6, "fields": {"name": "Loop d Loop", "description": "Mild Dark Roast\r\n\r\nOur most popular blend reminiscent of Central Italy. A dark, chocolaty flavor perfect for espresso or drip. Its dark Vienna roast properties make it ideal for steamed milk drinks. Organic Indonesia, Africa and America.", "sku": "53264", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 6, "created_at": "2022-02-23T18:06:09.881Z", "updated_at": "2022-03-28T17:29:53.104Z"}}, {"model": "core.product", "pk": 7, "fields": {"name": "Dantes Tornado", "description": "Medium Roast\r\n\r\nFull City spicy espresso roast reminiscent of Northern Italy, on the mild side. A full- bodied, earthy sweet drip or Americano. Organic Indonesia, Africa and America.", "sku": "78945", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 7, "created_at": "2022-02-23T18:06:35.593Z", "updated_at": "2022-03-28T17:30:11.445Z"}}, {"model": "core.product", "pk": 8, "fields": {"name": "Nicaragua", "description": "Mild Roast\r\n\r\nOur mildest roast with sweet and fruity notes, containing organic beans from Nicaragua. Single origin.", "sku": "12365", "price": "13.40", "weight": "16.0:oz", "visible_in_listings": true, "sorting": 8, "created_at": "2022-02-23T18:06:57.624Z", "updated_at": "2022-03-28T17:30:20.941Z"}}, {"model": "core.productphoto", "pk": 1, "fields": {"product": 1, "image": "products/images/slice2.png"}}, {"model": "core.productphoto", "pk": 2, "fields": {"product": 2, "image": "products/images/slice1.png"}}, {"model": "core.productphoto", "pk": 5, "fields": {"product": 5, "image": "products/images/moka_java.png"}}, {"model": "core.productphoto", "pk": 6, "fields": {"product": 6, "image": "products/images/loop_d_loop.png"}}, {"model": "core.productphoto", "pk": 7, "fields": {"product": 7, "image": "products/images/dantes_tornado.png"}}, {"model": "core.productphoto", "pk": 8, "fields": {"product": 8, "image": "products/images/nicaragua.png"}}, {"model": "core.productphoto", "pk": 15, "fields": {"product": 3, "image": "products/images/pantomime_800.png"}}, {"model": "core.productphoto", "pk": 16, "fields": {"product": 3, "image": "products/images/pantomime_beans.png"}}, {"model": "core.productphoto", "pk": 18, "fields": {"product": 2, "image": "products/images/pantomime_beans_J2bFBiH.png"}}, {"model": "core.productphoto", "pk": 19, "fields": {"product": 4, "image": "products/images/decaf_800.png"}}, {"model": "core.productphoto", "pk": 20, "fields": {"product": 4, "image": "products/images/pantomime_beans_Lo0hJRx.png"}}]
[{
"model": "core.product",
"pk": 1,
"fields": {
"name": "Ethiopia",
"description": "Spicy espresso reminiscent of Northern Italy, on the mild side. Perfect for espresso, and steamed milk drinks. Also, a full-bodied, earthy sweet drip or Americano. Contains organic beans from Indonesia, Africa and America.",
"sku": "23468",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": true,
"sorting": 4,
"created_at": "2022-02-19T20:15:36.292Z",
"updated_at": "2022-03-28T17:29:26.300Z"
}
}, {
"model": "core.product",
"pk": 2,
"fields": {
"name": "Sumatra",
"description": "Dark heavy-bodied roast with a lingering chocolatey taste. Organic Single origin.",
"sku": "89765",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": true,
"sorting": 3,
"created_at": "2022-02-19T20:15:59.741Z",
"updated_at": "2022-03-28T17:29:08.706Z"
}
}, {
"model": "core.product",
"pk": 3,
"fields": {
"name": "Pantomime",
"description": "Very Dark French Roast\r\nOur darkest drip. A blend of five different beans roasted two ways. Organic Africa, Indonesia, and South and Central America.",
"sku": "565656",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": true,
"sorting": 1,
"created_at": "2022-02-23T17:59:00.711Z",
"updated_at": "2022-03-28T17:28:58.670Z"
}
}, {
"model": "core.product",
"pk": 4,
"fields": {
"name": "Decaf",
"description": "French Roast (Water Processed)\r\n\r\n“I cant believe its decaf!”. The best-tasting Swiss water process decaf we have developed over the past 30 years, for an unbelievable espresso or drip coffee. Organic Africa, Indonesia and South and Central America.",
"sku": "566565",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": true,
"sorting": 2,
"created_at": "2022-02-23T17:59:32.099Z",
"updated_at": "2022-03-28T17:28:45.512Z"
}
}, {
"model": "core.product",
"pk": 5,
"fields": {
"name": "Moka Java Blend",
"description": "Dark Roast\r\n\r\nA classic Moka Java style blend dark roasted with organic beans for a perfect body and sweetness with a hint of citrus.",
"sku": "56466",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": false,
"sorting": 5,
"created_at": "2022-02-23T18:05:41.742Z",
"updated_at": "2022-03-28T17:29:39.761Z"
}
}, {
"model": "core.product",
"pk": 6,
"fields": {
"name": "Loop d Loop",
"description": "Mild Dark Roast\r\n\r\nOur most popular blend reminiscent of Central Italy. A dark, chocolaty flavor perfect for espresso or drip. Its dark Vienna roast properties make it ideal for steamed milk drinks. Organic Indonesia, Africa and America.",
"sku": "53264",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": true,
"sorting": 6,
"created_at": "2022-02-23T18:06:09.881Z",
"updated_at": "2022-03-28T17:29:53.104Z"
}
}, {
"model": "core.product",
"pk": 7,
"fields": {
"name": "Dantes Tornado",
"description": "Medium Roast\r\n\r\nFull City spicy espresso roast reminiscent of Northern Italy, on the mild side. A full- bodied, earthy sweet drip or Americano. Organic Indonesia, Africa and America.",
"sku": "78945",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": true,
"sorting": 7,
"created_at": "2022-02-23T18:06:35.593Z",
"updated_at": "2022-03-28T17:30:11.445Z"
}
}, {
"model": "core.product",
"pk": 8,
"fields": {
"name": "Nicaragua",
"description": "Mild Roast\r\n\r\nOur mildest roast with sweet and fruity notes, containing organic beans from Nicaragua. Single origin.",
"sku": "12365",
"price": "13.40",
"weight": "16.0:oz",
"visible_in_listings": true,
"sorting": 8,
"created_at": "2022-02-23T18:06:57.624Z",
"updated_at": "2022-03-28T17:30:20.941Z"
}
}, {
"model": "core.productphoto",
"pk": 1,
"fields": {
"product": 1,
"image": "products/images/slice2.png"
}
}, {
"model": "core.productphoto",
"pk": 2,
"fields": {
"product": 2,
"image": "products/images/slice1.png"
}
}, {
"model": "core.productphoto",
"pk": 5,
"fields": {
"product": 5,
"image": "products/images/moka_java.png"
}
}, {
"model": "core.productphoto",
"pk": 6,
"fields": {
"product": 6,
"image": "products/images/loop_d_loop.png"
}
}, {
"model": "core.productphoto",
"pk": 7,
"fields": {
"product": 7,
"image": "products/images/dantes_tornado.png"
}
}, {
"model": "core.productphoto",
"pk": 8,
"fields": {
"product": 8,
"image": "products/images/nicaragua.png"
}
}, {
"model": "core.productphoto",
"pk": 15,
"fields": {
"product": 3,
"image": "products/images/pantomime_800.png"
}
}, {
"model": "core.productphoto",
"pk": 16,
"fields": {
"product": 3,
"image": "products/images/pantomime_beans.png"
}
}, {
"model": "core.productphoto",
"pk": 18,
"fields": {
"product": 2,
"image": "products/images/pantomime_beans_J2bFBiH.png"
}
}, {
"model": "core.productphoto",
"pk": 19,
"fields": {
"product": 4,
"image": "products/images/decaf_800.png"
}
}, {
"model": "core.productphoto",
"pk": 20,
"fields": {
"product": 4,
"image": "products/images/pantomime_beans_Lo0hJRx.png"
}
}]

View File

@ -28,5 +28,24 @@
<span class="object__item">No orders</span>
{% endfor %}
</section>
<section>
<div class="pagination">
<p class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">&laquo; first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</p>
</div>
</section>
</article>
{% endblock content %}

View File

@ -134,6 +134,7 @@ class CouponDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
class OrderListView(LoginRequiredMixin, ListView):
model = Order
template_name = 'dashboard/order_list.html'
paginate_by = 50
def get_queryset(self):
query = self.request.GET.get('status')

View File

@ -6,7 +6,7 @@ load_dotenv()
DEBUG = os.environ.get('DEBUG', 'True') == 'True'
DATABASE_CONFIG = {
'ENGINE' : 'django.db.backends.postgresql',
'ENGINE': 'django.db.backends.postgresql',
'OPTIONS': {
'service': 'pg_service',
'passfile': '.pgpass'
@ -14,8 +14,8 @@ DATABASE_CONFIG = {
}
SECRET_KEY = os.environ.get('SECRET_KEY', '')
CACHE_CONFIG = {
'LOCATION' : 'redis://127.0.0.1:6379',
'BACKEND' : 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
}
PAYPAL_CLIENT_ID = os.environ.get('PAYPAL_CLIENT_ID', '')

View File

@ -240,6 +240,16 @@ input[type=submit]:hover,
width: 100%;
}
@media screen and (max-width: 600px) {
.contact-form {
grid-template-columns: 1fr;
}
.contact-form p:nth-child(6),
.contact-form p:last-child {
grid-column: 1;
}
}

View File

@ -15,6 +15,7 @@ from core import CoffeeGrind
logger = logging.getLogger(__name__)
class PayPalClient:
def __init__(self):
self.client_id = settings.PAYPAL_CLIENT_ID
@ -25,10 +26,14 @@ class PayPalClient:
"""Setting up and Returns PayPal SDK environment with PayPal Access credentials.
For demo purpose, we are using SandboxEnvironment. In production this will be
LiveEnvironment."""
if settings.PAYPAL_ENVIRONMENT == 'LIVE':
self.environment = LiveEnvironment(client_id=self.client_id, client_secret=self.client_secret)
if settings.PAYPAL_ENVIRONMENT == "LIVE":
self.environment = LiveEnvironment(
client_id=self.client_id, client_secret=self.client_secret
)
else:
self.environment = SandboxEnvironment(client_id=self.client_id, client_secret=self.client_secret)
self.environment = SandboxEnvironment(
client_id=self.client_id, client_secret=self.client_secret
)
""" Returns PayPal HTTP client instance with environment which has access
credentials context. This can be used invoke PayPal API's provided the
@ -44,97 +49,122 @@ class PayPalClient:
itr = json_data.__dict__.iteritems()
else:
itr = json_data.__dict__.items()
for key,value in itr:
for key, value in itr:
# Skip internal attributes.
if key.startswith("__") or key.startswith("_"):
continue
result[key] = self.array_to_json_array(value) if isinstance(value, list) else\
self.object_to_json(value) if not self.is_primittive(value) else\
value
result[key] = (
self.array_to_json_array(value)
if isinstance(value, list)
else self.object_to_json(value)
if not self.is_primittive(value)
else value
)
return result
def array_to_json_array(self, json_array):
result =[]
result = []
if isinstance(json_array, list):
for item in json_array:
result.append(self.object_to_json(item) if not self.is_primittive(item) \
else self.array_to_json_array(item) if isinstance(item, list) else item)
result.append(
self.object_to_json(item)
if not self.is_primittive(item)
else self.array_to_json_array(item)
if isinstance(item, list)
else item
)
return result
def is_primittive(self, data):
return isinstance(data, str) or isinstance(data, unicode) or isinstance(data, int)
return (
isinstance(data, str) or isinstance(data, unicode) or isinstance(data, int)
)
class CreateOrder(PayPalClient):
"""Setting up the JSON request body for creating the Order. The Intent in the
request body should be set as "CAPTURE" for capture intent flow."""
request body should be set as "CAPTURE" for capture intent flow."""
def build_request_body(self, params):
"""Method to create body with CAPTURE intent"""
processed_items = [{
processed_items = [
{
# Shows within upper-right dropdown during payment approval
'name': f'{item["product"]}: ' + ', '.join([next((f"{value['quantity']} x {v[1]}" for i, v in enumerate(CoffeeGrind.GRIND_CHOICES) if v[0] == key), None)
for key, value in item['variations'].items()])[:100],
# Item details will also be in the completed paypal.com transaction view
'description': item['product'].subtitle,
'unit_amount': {
'currency_code': settings.DEFAULT_CURRENCY,
'value': f'{item["price"]}'
"name": f'{item["product"]}: '
", ".join(
[
next(
(
f"{value['quantity']} x {v[1]}"
for i, v in enumerate(CoffeeGrind.GRIND_CHOICES)
if v[0] == key
),
None,
)
for key, value in item["variations"].items()
]
)[:100],
# Item details will also be in the completed paypal.com
# transaction view
"description": item["product"].subtitle,
"unit_amount": {
"currency_code": settings.DEFAULT_CURRENCY,
"value": f'{item["price"]}',
},
'quantity': f'{item["quantity"]}'
} for item in params['items']]
"quantity": f'{item["quantity"]}',
}
for item in params["items"]
]
request_body = {
"intent": "CAPTURE",
"application_context": {
"return_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
"cancel_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}",
"brand_name": f"{self.site_name}",
# "landing_page": "BILLING",
# "shipping_preference": "SET_PROVIDED_ADDRESS",
# "user_action": "CONTINUE"
},
"purchase_units": [
{
# "reference_id": "PUHF",
"description": "Coffee",
# "custom_id": "CUST-HighFashions",
# "soft_descriptor": "HighFashions",
"amount": {
"currency_code": "USD",
"value": params['total_price'],
"breakdown": {
"item_total": {
"currency_code": "USD",
"value": params['item_total']
},
"shipping": {
"currency_code": "USD",
"value": params['shipping_price']
},
"tax_total": {
"currency_code": "USD",
"value": params['tax_total']
},
"discount": {
"currency_code": "USD",
"value": params['discount']
}
}
"intent": "CAPTURE",
"application_context": {
"return_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
"cancel_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}",
"brand_name": f"{self.site_name}",
# "landing_page": "BILLING",
# "shipping_preference": "SET_PROVIDED_ADDRESS",
# "user_action": "CONTINUE"
},
"purchase_units": [
{
# "reference_id": "PUHF",
"description": "Coffee",
# "custom_id": "CUST-HighFashions",
# "soft_descriptor": "HighFashions",
"amount": {
"currency_code": "USD",
"value": params["total_price"],
"breakdown": {
"item_total": {
"currency_code": "USD",
"value": params["item_total"],
},
"shipping": {
"currency_code": "USD",
"value": params["shipping_price"],
},
"tax_total": {
"currency_code": "USD",
"value": params["tax_total"],
},
"discount": {
"currency_code": "USD",
"value": params["discount"],
},
},
"items": processed_items,
"shipping": {
"method": params['shipping_method'],
"address": params['shipping_address']
}
}
]
}
},
"items": processed_items,
"shipping": {
"method": params["shipping_method"],
"address": params["shipping_address"],
},
}
],
}
logger.info(f'\nRequest body: {request_body}\n')
logger.info(f"\nRequest body: {request_body}\n")
return request_body
@ -143,29 +173,37 @@ class CreateOrder(PayPalClient):
def create_order(self, params, debug=False):
request = OrdersCreateRequest()
request.headers['prefer'] = 'return=representation'
request.headers["prefer"] = "return=representation"
request.request_body(self.build_request_body(params))
response = self.client.execute(request)
if debug:
logger.debug(f'\nStatus Code: {response.status_code}', )
logger.debug(f'\nStatus: {response.result.status}', )
logger.debug(f'\nOrder ID: {response.result.id}', )
logger.debug(f'\nIntent: {response.result.intent}', )
logger.debug(
f"\nStatus Code: {response.status_code}",
)
logger.debug(
f"\nStatus: {response.result.status}",
)
logger.debug(
f"\nOrder ID: {response.result.id}",
)
logger.debug(
f"\nIntent: {response.result.intent}",
)
logger.debug(f"\njson_data: {response.result}")
return response
class CaptureOrder(PayPalClient):
class CaptureOrder(PayPalClient):
def capture_order(self, order_id, debug=False):
"""Method to capture order using order_id"""
request = OrdersCaptureRequest(order_id)
response = self.client.execute(request)
data = response.result.__dict__['_dict']
data['redirect_urls'] = {
'return_url': f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
'cancel_url': f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}"
data = response.result.__dict__["_dict"]
data["redirect_urls"] = {
"return_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
"cancel_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}",
}
return data

View File

@ -0,0 +1,25 @@
{% extends 'base.html' %}
{% load i18n %}
{% load account %}
{% block head_title %}{% trans "Password Reset" %} | {% endblock %}
{% block content %}
<article>
<h1>{% trans "Password Reset" %}</h1>
{% if user.is_authenticated %}
{% include "account/snippets/already_logged_in.html" %}
{% endif %}
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="{% trans 'Reset My Password' %}" />
</form>
<p>{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}</p>
</article>
{% endblock %}

View File

@ -1,7 +1,18 @@
{% extends 'base.html' %}
{% extends "base.html" %}
{% load i18n %}
{% load account %}
{% block head_title %}{% trans "Password Reset" %}{% endblock %}
{% block content %}
<article class="panel">
<p>An email with password reset instructions has been sent.</p>
<article>
<h1>{% trans "Password Reset" %}</h1>
{% if user.is_authenticated %}
{% include "account/snippets/already_logged_in.html" %}
{% endif %}
<p>{% blocktrans %}We have sent you an e-mail. If you have not received it please check your spam folder. Otherwise contact us if you do not receive it in a few minutes.{% endblocktrans %}</p>
</article>
{% endblock %}

View File

@ -1,14 +0,0 @@
{% extends 'base.html' %} {% block content %}
<article class="panel">
<h1>Reset your password</h1>
<p>Enter your email address below and we'll send you instructions on how to reset your password.</p>
<form method="post" action="{% url 'password_reset' %}">
{% csrf_token %}
{{ form.as_p }}
<p>
<input type="submit" value="Send me instructions" class="action-button">
</p>
</form>
</article>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Change Password" %}{% endblock %}
{% block content %}
<article>
<h1>{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}</h1>
{% if token_fail %}
{% url 'account_reset_password' as passwd_reset_url %}
<p>{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a <a href="{{ passwd_reset_url }}">new password reset</a>.{% endblocktrans %}</p>
{% else %}
<form method="POST" action="{{ action_url }}">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="action" value="{% trans 'change password' %}"/>
</form>
{% endif %}
</article>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Change Password" %}{% endblock %}
{% block content %}
<article>
<h1>{% trans "Change Password" %}</h1>
<p>{% trans 'Your password is now changed.' %}</p>
</article>
{% endblock %}