Membership invitations and order emails #47
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
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 django.utils.translation import gettext_lazy as _
|
||||||
|
from membership.emails import OrderEmail
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
@ -26,7 +30,7 @@ class OrderAdminForm(forms.ModelForm):
|
||||||
model = models.Order
|
model = models.Order
|
||||||
exclude = () # noqa: DJ006
|
exclude = () # noqa: DJ006
|
||||||
|
|
||||||
def clean(self): # noqa: D102, ANN201
|
def clean(self): # noqa: ANN201
|
||||||
cd = super().clean()
|
cd = super().clean()
|
||||||
if not cd["account"] and cd["member"]:
|
if not cd["account"] and cd["member"]:
|
||||||
try:
|
try:
|
||||||
|
@ -43,10 +47,25 @@ class OrderAdmin(admin.ModelAdmin):
|
||||||
inlines = (OrderProductInline,)
|
inlines = (OrderProductInline,)
|
||||||
form = OrderAdminForm
|
form = OrderAdminForm
|
||||||
|
|
||||||
|
actions = ("send_order",)
|
||||||
|
|
||||||
list_display = ("member", "description", "created", "is_paid", "total_with_vat")
|
list_display = ("member", "description", "created", "is_paid", "total_with_vat")
|
||||||
search_fields = ("member__email", "membership__membership_type__name", "description")
|
search_fields = ("member__email", "membership__membership_type__name", "description")
|
||||||
list_filter = ("is_paid", "membership__membership_type")
|
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)
|
@admin.register(models.Payment)
|
||||||
class PaymentAdmin(admin.ModelAdmin):
|
class PaymentAdmin(admin.ModelAdmin):
|
||||||
|
@ -61,15 +80,15 @@ class PaymentAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Product)
|
@admin.register(models.Product)
|
||||||
class ProductAdmin(admin.ModelAdmin): # noqa: D101
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
list_display = ("name", "price", "vat")
|
list_display = ("name", "price", "vat")
|
||||||
|
|
||||||
|
|
||||||
class TransactionInline(admin.TabularInline): # noqa: D101
|
class TransactionInline(admin.TabularInline):
|
||||||
model = models.Transaction
|
model = models.Transaction
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Account)
|
@admin.register(models.Account)
|
||||||
class AccountAdmin(admin.ModelAdmin): # noqa: D101
|
class AccountAdmin(admin.ModelAdmin):
|
||||||
list_display = ("owner", "balance")
|
list_display = ("owner", "balance")
|
||||||
inlines = (TransactionInline,)
|
inlines = (TransactionInline,)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* Generally, an email consists of templates (for body and subject) and a get_context() method.
|
* 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 import messages
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
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["token"] = default_token_generator.make_token(self.membership.user)
|
||||||
c["referral_code"] = self.membership.referral_code
|
c["referral_code"] = self.membership.referral_code
|
||||||
return c
|
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
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
Regards,
|
{% trans "Cooperatively yours," %}
|
||||||
{{ site_name }}
|
{{ site_name }}
|
||||||
|
|
||||||
{{ protocol }}://{{ domain }}
|
{{ protocol }}://{{ domain }}
|
||||||
|
|
6
src/membership/templates/membership/emails/order.txt
Normal file
6
src/membership/templates/membership/emails/order.txt
Normal file
|
@ -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 %}
|
|
@ -14,6 +14,10 @@
|
||||||
It is very much under construction.
|
It is very much under construction.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{% for order in unpaid_orders %}
|
||||||
|
<p>You have an unpaid order: <a href="{% url "order:detail" order_id=order.id %}">View Order ID {{ order.id }}</a></p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{% comment %}
|
{% comment %}
|
||||||
<hr>
|
<hr>
|
||||||
<br>
|
<br>
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from accounting.models import Order
|
||||||
from django_view_decorator import view
|
from django_view_decorator import view
|
||||||
from utils.view_utils import render
|
from utils.view_utils import render
|
||||||
|
|
||||||
|
@ -19,7 +20,11 @@ if TYPE_CHECKING:
|
||||||
)
|
)
|
||||||
def index(request: HttpRequest) -> HttpResponse:
|
def index(request: HttpRequest) -> HttpResponse:
|
||||||
"""View to show the index page."""
|
"""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(
|
@view(
|
||||||
|
|
Loading…
Reference in a new issue