From 9962f2622bf5f47468144fa7cbcb228566b00acf Mon Sep 17 00:00:00 2001
From: Benjamin Bach
Date: Mon, 12 Aug 2024 22:22:11 +0200
Subject: [PATCH] Send order emails via admin action
---
src/accounting/admin.py | 27 ++++++++++++++++---
src/membership/emails.py | 16 +++++++++++
.../templates/membership/emails/base.txt | 2 +-
.../templates/membership/emails/order.txt | 6 +++++
src/project/templates/index.html | 4 +++
src/project/views.py | 7 ++++-
6 files changed, 56 insertions(+), 6 deletions(-)
create mode 100644 src/membership/templates/membership/emails/order.txt
diff --git a/src/accounting/admin.py b/src/accounting/admin.py
index 11d2219..1b09077 100644
--- a/src/accounting/admin.py
+++ b/src/accounting/admin.py
@@ -2,7 +2,11 @@
from django import forms
from django.contrib import admin
+from django.contrib import messages
+from django.db.models import QuerySet
+from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
+from membership.emails import OrderEmail
from . import models
@@ -26,7 +30,7 @@ class OrderAdminForm(forms.ModelForm):
model = models.Order
exclude = () # noqa: DJ006
- def clean(self): # noqa: D102, ANN201
+ def clean(self): # noqa: ANN201
cd = super().clean()
if not cd["account"] and cd["member"]:
try:
@@ -43,10 +47,25 @@ class OrderAdmin(admin.ModelAdmin):
inlines = (OrderProductInline,)
form = OrderAdminForm
+ actions = ("send_order",)
+
list_display = ("member", "description", "created", "is_paid", "total_with_vat")
search_fields = ("member__email", "membership__membership_type__name", "description")
list_filter = ("is_paid", "membership__membership_type")
+ @admin.action(description="Send order link to selected unpaid orders")
+ def send_order(self, request: HttpRequest, queryset: QuerySet[models.Order]) -> None:
+ for order in queryset:
+ if order.is_paid:
+ messages.error(
+ request,
+ f"Order pk={order.id} is already marked paid, not sending email to: {order.member.email}",
+ )
+ continue
+ email = OrderEmail(order, request)
+ email.send()
+ messages.success(request, f"Sent an order for order pk={order.id} link to: {order.member.email}")
+
@admin.register(models.Payment)
class PaymentAdmin(admin.ModelAdmin):
@@ -61,15 +80,15 @@ class PaymentAdmin(admin.ModelAdmin):
@admin.register(models.Product)
-class ProductAdmin(admin.ModelAdmin): # noqa: D101
+class ProductAdmin(admin.ModelAdmin):
list_display = ("name", "price", "vat")
-class TransactionInline(admin.TabularInline): # noqa: D101
+class TransactionInline(admin.TabularInline):
model = models.Transaction
@admin.register(models.Account)
-class AccountAdmin(admin.ModelAdmin): # noqa: D101
+class AccountAdmin(admin.ModelAdmin):
list_display = ("owner", "balance")
inlines = (TransactionInline,)
diff --git a/src/membership/emails.py b/src/membership/emails.py
index f1d96fc..6a57718 100644
--- a/src/membership/emails.py
+++ b/src/membership/emails.py
@@ -5,6 +5,7 @@
* Generally, an email consists of templates (for body and subject) and a get_context() method.
"""
+from accounting.models import Order
from django.contrib import messages
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
@@ -108,3 +109,18 @@ class InviteEmail(BaseEmail):
c["token"] = default_token_generator.make_token(self.membership.user)
c["referral_code"] = self.membership.referral_code
return c
+
+
+class OrderEmail(BaseEmail):
+ template = "membership/emails/order.txt"
+ default_subject = _("Your data.coop order and payment")
+
+ def __init__(self, order: Order, request: HttpRequest, *args, **kwargs) -> None:
+ self.order = order
+ kwargs["user"] = order.member
+ super().__init__(request, *args, **kwargs)
+
+ def get_context_data(self) -> dict:
+ c = super().get_context_data()
+ c["order"] = self.order
+ return c
diff --git a/src/membership/templates/membership/emails/base.txt b/src/membership/templates/membership/emails/base.txt
index 9a86384..636aee0 100644
--- a/src/membership/templates/membership/emails/base.txt
+++ b/src/membership/templates/membership/emails/base.txt
@@ -3,7 +3,7 @@
{% block content %}{% endblock %}
-Regards,
+{% trans "Cooperatively yours," %}
{{ site_name }}
{{ protocol }}://{{ domain }}
diff --git a/src/membership/templates/membership/emails/order.txt b/src/membership/templates/membership/emails/order.txt
new file mode 100644
index 0000000..e976444
--- /dev/null
+++ b/src/membership/templates/membership/emails/order.txt
@@ -0,0 +1,6 @@
+{% extends "membership/emails/base.txt" %}{% load i18n %}
+
+{% block content %}{% url 'order:detail' order_id=order.id as order_url %}{% blocktrans %}You have an order in our system, you can pay it here:
+
+{{ protocol }}://{{ domain }}{{ order_url }}
+{% endblocktrans %}{% endblock %}
diff --git a/src/project/templates/index.html b/src/project/templates/index.html
index 2cc3490..1cc49e6 100644
--- a/src/project/templates/index.html
+++ b/src/project/templates/index.html
@@ -14,6 +14,10 @@
It is very much under construction.
+ {% for order in unpaid_orders %}
+ You have an unpaid order: View Order ID {{ order.id }}
+ {% endfor %}
+
{% comment %}
diff --git a/src/project/views.py b/src/project/views.py
index 7fbde77..affd1e7 100644
--- a/src/project/views.py
+++ b/src/project/views.py
@@ -4,6 +4,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from accounting.models import Order
from django_view_decorator import view
from utils.view_utils import render
@@ -19,7 +20,11 @@ if TYPE_CHECKING:
)
def index(request: HttpRequest) -> HttpResponse:
"""View to show the index page."""
- return render(request, "index.html")
+ unpaid_orders = Order.objects.filter(member=request.user, is_paid=False)
+
+ context = {"unpaid_orders": list(unpaid_orders)}
+
+ return render(request, "index.html", context=context)
@view(