1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-02-25 13:06:30 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Emmy D'Anello
de504398d2
Improve Django-admin interface, inlines and filters
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-02-23 21:43:44 +01:00
Emmy D'Anello
cae1c6fdb8
Send payment confirmation mail after payment, and send weekly reminders for people that have not paid
Signed-off-by: Emmy D'Anello <emmy.danello@animath.fr>
2024-02-23 18:02:24 +01:00
16 changed files with 464 additions and 291 deletions

View File

@ -7,11 +7,34 @@ from django.utils.translation import gettext_lazy as _
from .models import Draw, Pool, Round, TeamDraw from .models import Draw, Pool, Round, TeamDraw
class RoundInline(admin.TabularInline):
model = Round
extra = 0
autocomplete_fields = ('draw', 'current_pool',)
show_change_link = True
class PoolInline(admin.TabularInline):
model = Pool
extra = 0
autocomplete_fields = ('round', 'current_team', 'associated_pool',)
show_change_link = True
class TeamDrawInline(admin.TabularInline):
model = TeamDraw
extra = 0
autocomplete_fields = ('participation', 'round', 'pool',)
show_change_link = True
@admin.register(Draw) @admin.register(Draw)
class DrawAdmin(admin.ModelAdmin): class DrawAdmin(admin.ModelAdmin):
list_display = ('tournament', 'teams', 'current_round', 'get_state',) list_display = ('tournament', 'teams', 'current_round', 'get_state',)
list_filter = ('tournament', 'current_round',) list_filter = ('tournament', 'current_round__number',)
search_fields = ('tournament__name', 'tournament__participation__team__trigram',) search_fields = ('tournament__name', 'tournament__participation__team__trigram',)
autocomplete_fields = ('tournament',)
inlines = (RoundInline,)
@admin.display(description=_("teams")) @admin.display(description=_("teams"))
def teams(self, record: Draw): def teams(self, record: Draw):
@ -20,10 +43,16 @@ class DrawAdmin(admin.ModelAdmin):
@admin.register(Round) @admin.register(Round)
class RoundAdmin(admin.ModelAdmin): class RoundAdmin(admin.ModelAdmin):
list_display = ('draw', 'number', 'teams',) list_display = ('draw', 'tournament', 'number', 'teams',)
list_filter = ('draw__tournament', 'number',) list_filter = ('draw__tournament', 'number',)
search_fields = ('draw__tournament__name', 'pool__teamdraw__participation__team__trigram') search_fields = ('draw__tournament__name', 'pool__teamdraw__participation__team__trigram')
ordering = ('draw__tournament__name', 'number') ordering = ('draw__tournament__name', 'number')
autocomplete_fields = ('draw', 'current_pool',)
inlines = (PoolInline,)
@admin.display(description=_("tournament"), ordering='draw__tournament__name')
def tournament(self, record):
return record.draw.tournament
@admin.display(description=_("teams")) @admin.display(description=_("teams"))
def teams(self, record: Round): def teams(self, record: Round):
@ -36,6 +65,8 @@ class PoolAdmin(admin.ModelAdmin):
list_filter = ('round__draw__tournament', 'round__number', 'letter') list_filter = ('round__draw__tournament', 'round__number', 'letter')
ordering = ('round__draw__tournament__name', 'round', 'letter') ordering = ('round__draw__tournament__name', 'round', 'letter')
search_fields = ('round__draw__tournament__name', 'teamdraw__participation__team__trigram',) search_fields = ('round__draw__tournament__name', 'teamdraw__participation__team__trigram',)
autocomplete_fields = ('round', 'current_team', 'associated_pool',)
inlines = (TeamDrawInline,)
@admin.display(ordering='round__draw__tournament__name', description=_("tournament")) @admin.display(ordering='round__draw__tournament__name', description=_("tournament"))
def tournament(self, record): def tournament(self, record):
@ -52,6 +83,7 @@ class TeamDrawAdmin(admin.ModelAdmin):
'passage_index', 'choose_index', 'passage_dice', 'choice_dice',) 'passage_index', 'choose_index', 'passage_dice', 'choice_dice',)
list_filter = ('round__draw__tournament', 'round__number', 'pool__letter',) list_filter = ('round__draw__tournament', 'round__number', 'pool__letter',)
search_fields = ('round__draw__tournament__name', 'participation__team__trigram',) search_fields = ('round__draw__tournament__name', 'participation__team__trigram',)
autocomplete_fields = ('participation', 'round', 'pool',)
@admin.display(ordering='round__draw__tournament__name', description=_("tournament")) @admin.display(ordering='round__draw__tournament__name', description=_("tournament"))
def tournament(self, record): def tournament(self, record):

View File

@ -89,6 +89,7 @@ class Draw(models.Model):
return 'WAITING_DRAW_PROBLEM' return 'WAITING_DRAW_PROBLEM'
else: else:
return 'WAITING_CHOOSE_PROBLEM' return 'WAITING_CHOOSE_PROBLEM'
get_state.short_description = _('State')
@property @property
def information(self): def information(self):

File diff suppressed because it is too large Load Diff

View File

@ -7,11 +7,74 @@ from django.utils.translation import gettext_lazy as _
from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak from .models import Note, Participation, Passage, Pool, Solution, Synthesis, Team, Tournament, Tweak
class ParticipationInline(admin.StackedInline):
model = Participation
extra = 0
autocomplete_fields = ('team', 'tournament',)
show_change_link = True
class ParticipationTabularInline(admin.TabularInline):
model = Participation
extra = 0
fields = ('team', 'valid', 'final',)
readonly_fields = ('team',)
ordering = ('final', 'valid', 'team__trigram',)
autocomplete_fields = ('tournament',)
show_change_link = True
class SolutionInline(admin.TabularInline):
model = Solution
extra = 0
ordering = ('problem',)
autocomplete_fields = ('participation',)
show_change_link = True
class SynthesisInline(admin.TabularInline):
model = Synthesis
extra = 0
ordering = ('passage__solution_number', 'type',)
autocomplete_fields = ('passage',)
show_change_link = True
class PoolInline(admin.TabularInline):
model = Pool
extra = 0
autocomplete_fields = ('tournament', 'participations', 'juries',)
show_change_link = True
class PassageInline(admin.TabularInline):
model = Passage
extra = 0
ordering = ('position',)
autocomplete_fields = ('defender', 'opponent', 'reporter', 'observer',)
show_change_link = True
class NoteInline(admin.TabularInline):
model = Note
extra = 0
autocomplete_fields = ('jury',)
show_change_link = True
class TweakInline(admin.TabularInline):
model = Tweak
extra = 0
autocomplete_fields = ('participation', 'pool',)
show_change_link = True
@admin.register(Team) @admin.register(Team)
class TeamAdmin(admin.ModelAdmin): class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'trigram', 'tournament', 'valid', 'final',) list_display = ('name', 'trigram', 'tournament', 'valid', 'final',)
search_fields = ('name', 'trigram',) search_fields = ('name', 'trigram',)
list_filter = ('participation__valid', 'participation__tournament', 'participation__final',) list_filter = ('participation__valid', 'participation__tournament', 'participation__final',)
inlines = (ParticipationInline,)
@admin.display(description=_("tournament")) @admin.display(description=_("tournament"))
def tournament(self, record): def tournament(self, record):
@ -32,6 +95,7 @@ class ParticipationAdmin(admin.ModelAdmin):
search_fields = ('team__name', 'team__trigram',) search_fields = ('team__name', 'team__trigram',)
list_filter = ('valid',) list_filter = ('valid',)
autocomplete_fields = ('team', 'tournament',) autocomplete_fields = ('team', 'tournament',)
inlines = (SolutionInline, SynthesisInline,)
@admin.register(Pool) @admin.register(Pool)
@ -40,6 +104,7 @@ class PoolAdmin(admin.ModelAdmin):
list_filter = ('tournament', 'round', 'letter',) list_filter = ('tournament', 'round', 'letter',)
search_fields = ('participations__team__name', 'participations__team__trigram',) search_fields = ('participations__team__name', 'participations__team__trigram',)
autocomplete_fields = ('tournament', 'participations', 'juries',) autocomplete_fields = ('tournament', 'participations', 'juries',)
inlines = (PassageInline, TweakInline,)
@admin.display(description=_("teams")) @admin.display(description=_("teams"))
def teams(self, record: Pool): def teams(self, record: Pool):
@ -49,28 +114,30 @@ class PoolAdmin(admin.ModelAdmin):
@admin.register(Passage) @admin.register(Passage)
class PassageAdmin(admin.ModelAdmin): class PassageAdmin(admin.ModelAdmin):
list_display = ('__str__', 'defender_trigram', 'solution_number', 'opponent_trigram', 'reporter_trigram', list_display = ('__str__', 'defender_trigram', 'solution_number', 'opponent_trigram', 'reporter_trigram',
'pool_abbr', 'tournament') 'pool_abbr', 'position', 'tournament')
list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',) list_filter = ('pool__tournament', 'pool__round', 'pool__letter', 'solution_number',)
search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',) search_fields = ('pool__participations__team__name', 'pool__participations__team__trigram',)
ordering = ('pool__tournament', 'pool__round', 'pool__letter', 'position',)
autocomplete_fields = ('pool', 'defender', 'opponent', 'reporter', 'observer',) autocomplete_fields = ('pool', 'defender', 'opponent', 'reporter', 'observer',)
inlines = (NoteInline,)
@admin.display(description=_("defender")) @admin.display(description=_("defender"), ordering='defender__team__trigram')
def defender_trigram(self, record: Passage): def defender_trigram(self, record: Passage):
return record.defender.team.trigram return record.defender.team.trigram
@admin.display(description=_("opponent")) @admin.display(description=_("opponent"), ordering='opponent__team__trigram')
def opponent_trigram(self, record: Passage): def opponent_trigram(self, record: Passage):
return record.opponent.team.trigram return record.opponent.team.trigram
@admin.display(description=_("reporter")) @admin.display(description=_("reporter"), ordering='reporter__team__trigram')
def reporter_trigram(self, record: Passage): def reporter_trigram(self, record: Passage):
return record.reporter.team.trigram return record.reporter.team.trigram
@admin.display(description=_("pool")) @admin.display(description=_("pool"), ordering='pool__letter')
def pool_abbr(self, record): def pool_abbr(self, record):
return f"{record.pool.get_letter_display()}{record.pool.round}" return f"{record.pool.get_letter_display()}{record.pool.round}"
@admin.display(description=_("tournament")) @admin.display(description=_("tournament"), ordering='pool__tournament__name')
def tournament(self, record: Passage): def tournament(self, record: Passage):
return record.pool.tournament return record.pool.tournament
@ -124,9 +191,11 @@ class SynthesisAdmin(admin.ModelAdmin):
@admin.register(Tournament) @admin.register(Tournament)
class TournamentAdmin(admin.ModelAdmin): class TournamentAdmin(admin.ModelAdmin):
list_display = ('name',) list_display = ('name', 'date_start', 'date_end',)
search_fields = ('name',) search_fields = ('name',)
ordering = ('date_start', 'name',)
autocomplete_fields = ('organizers',) autocomplete_fields = ('organizers',)
inlines = (ParticipationTabularInline, PoolInline,)
@admin.register(Tweak) @admin.register(Tweak)

View File

@ -1,87 +0,0 @@
# Copyright (C) 2020 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import os
from django.contrib.auth.models import User
from django.core.management import BaseCommand
from django.db.models import Q
import requests
class Command(BaseCommand):
def handle(self, *args, **options): # noqa: C901
# Get access token
response = requests.post('https://api.helloasso.com/oauth2/token', headers={
'Content-Type': 'application/x-www-form-urlencoded',
}, data={
'client_id': os.getenv('HELLOASSO_CLIENT_ID', ''),
'client_secret': os.getenv('HELLOASSO_CLIENT_SECRET', ''),
'grant_type': 'client_credentials',
}).json()
token = response['access_token']
organization = "animath"
form_slug = "tfjm-2023-tournois-regionaux"
from_date = "2000-01-01"
url = f"https://api.helloasso.com/v5/organizations/{organization}/forms/Event/{form_slug}/payments" \
f"?from={from_date}&pageIndex=1&pageSize=100&retrieveOfflineDonations=false"
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
}
http_response = requests.get(url, headers=headers)
response = http_response.json()
if http_response.status_code != 200:
message = response["message"]
self.stderr.write(f"Error while querying Hello Asso: {message}")
return
for payment in response["data"]:
if payment["state"] != "Authorized":
continue
payer = payment["payer"]
email = payer["email"]
last_name = payer["lastName"]
first_name = payer["firstName"]
base_filter = Q(
registration__participantregistration__isnull=False,
registration__participantregistration__team__isnull=False,
registration__participantregistration__team__participation__valid=True,
)
qs = User.objects.filter(
base_filter,
email=email,
)
if not qs.exists():
qs = User.objects.filter(
base_filter,
last_name__icontains=last_name,
)
if qs.count() >= 2:
qs = qs.filter(first_name__icontains=first_name)
if not qs.exists():
self.stderr.write(f"Warning: a payment was found by {first_name} {last_name} ({email}), "
"but this user is unknown.")
continue
if qs.count() > 1:
self.stderr.write(f"Warning: a payment was found by {first_name} {last_name} ({email}), "
f"but there are {qs.count()} matching users.")
continue
user = qs.get()
if not user.registration.participates:
self.stderr.write(f"Warning: a payment was found by the email address {email}, "
"but this user is not a participant.")
continue
payment_obj = user.registration.payment
payment_obj.valid = True
payment_obj.type = "helloasso"
payment_obj.additional_information = f"Identifiant de transation : {payment['id']}\n" \
f"Date : {payment['date']}\n" \
f"Reçu : {payment['paymentReceiptUrl']}\n" \
f"Montant : {payment['amount'] / 100:.2f}"
payment_obj.save()
self.stdout.write(f"{payment_obj} is validated")

View File

@ -3,13 +3,45 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import ModelAdmin from django.contrib.admin import ModelAdmin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin, \
PolymorphicInlineSupportMixin, StackedPolymorphicInline
from .models import CoachRegistration, ParticipantRegistration, Payment, Registration, \ from .models import CoachRegistration, ParticipantRegistration, Payment, Registration, \
StudentRegistration, VolunteerRegistration StudentRegistration, VolunteerRegistration
class RegistrationInline(StackedPolymorphicInline):
class StudentRegistrationInline(StackedPolymorphicInline.Child):
model = StudentRegistration
autocomplete_fields = ('team',)
show_change_link = True
class CoachRegistrationInline(StackedPolymorphicInline.Child):
model = CoachRegistration
autocomplete_fields = ('team',)
show_change_link = True
class VolunteerRegistrationInline(StackedPolymorphicInline.Child):
model = VolunteerRegistration
show_change_link = True
model = Registration
child_inlines = (
StudentRegistrationInline,
CoachRegistrationInline,
VolunteerRegistrationInline,
)
class PaymentInline(admin.TabularInline):
model = Payment
extra = 0
autocomplete_fields = ('registrations',)
@admin.register(Registration) @admin.register(Registration)
class RegistrationAdmin(PolymorphicParentModelAdmin): class RegistrationAdmin(PolymorphicParentModelAdmin):
child_models = (StudentRegistration, CoachRegistration, VolunteerRegistration,) child_models = (StudentRegistration, CoachRegistration, VolunteerRegistration,)
@ -97,12 +129,34 @@ class VolunteerRegistrationAdmin(PolymorphicChildModelAdmin):
@admin.register(Payment) @admin.register(Payment)
class PaymentAdmin(ModelAdmin): class PaymentAdmin(ModelAdmin):
list_display = ('id', 'concerned_people', 'grouped', 'type', 'valid', ) list_display = ('concerned_people', 'tournament', 'team', 'grouped', 'type', 'amount', 'valid', )
search_fields = ('registrations__user__last_name', 'registrations__user__first_name', 'registrations__user__email',) search_fields = ('registrations__user__last_name', 'registrations__user__first_name', 'registrations__user__email',
'registrations__team__name', 'registrations__team__participation__team__trigram',)
list_filter = ('registrations__team__participation__valid', 'type', list_filter = ('registrations__team__participation__valid', 'type',
'grouped', 'valid', 'registrations__polymorphic_ctype',) 'grouped', 'valid', 'registrations__team__participation__tournament', 'final',)
autocomplete_fields = ('registrations',) autocomplete_fields = ('registrations',)
actions = ('mark_as_valid', 'mark_as_pending', 'mark_as_invalid',)
@admin.display(description=_('concerned people')) @admin.display(description=_('concerned people'))
def concerned_people(self, record: Payment): def concerned_people(self, record: Payment):
return ", ".join(f"{reg.user.first_name} {reg.user.last_name}" for reg in record.registrations.all()) return ", ".join(f"{reg.user.first_name} {reg.user.last_name}" for reg in record.registrations.all())
@admin.action(description=_('Mark as valid'))
def mark_as_valid(self, request, queryset):
queryset.update(valid=True)
@admin.action(description=_('Mark as pending'))
def mark_as_pending(self, request, queryset):
queryset.update(valid=None)
@admin.action(description=_('Mark as invalid'))
def mark_as_invalid(self, request, queryset):
queryset.update(valid=False)
admin.site.unregister(User)
@admin.register(User)
class UserCustomAdmin(PolymorphicInlineSupportMixin, UserAdmin):
inlines = [RegistrationInline]

View File

@ -0,0 +1,25 @@
# Copyright (C) 2024 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
import json
from django.core.management import BaseCommand
from registration.models import Payment
class Command(BaseCommand):
"""
This command checks if the initiated Hello Asso payments are validated or not.
"""
help = "Vérifie si les paiements Hello Asso initiés sont validés ou non. Si oui, valide les inscriptions."
def handle(self, *args, **options):
for payment in Payment.objects.exclude(valid=True).filter(checkout_intent_id__isnull=False).all():
checkout_intent = payment.get_checkout_intent()
if checkout_intent is not None and 'order' in checkout_intent:
payment.type = 'helloasso'
payment.valid = True
payment.additional_information = json.dumps(checkout_intent['order'])
payment.save()
payment.send_helloasso_payment_confirmation_mail()

View File

@ -0,0 +1,17 @@
# Copyright (C) 2024 by Animath
# SPDX-License-Identifier: GPL-3.0-or-later
from django.core.management import BaseCommand
from registration.models import Payment
class Command(BaseCommand):
"""
This command sends a mail to each participant who has not yet paid.
"""
help = "Envoie un mail de rappel à toustes les participant⋅es qui n'ont pas encore payé ou déclaré de paiement."
def handle(self, *args, **options):
for payment in Payment.objects.filter(valid=False).filter(registrations__team__participation__valid=True).all():
payment.send_remind_mail()

View File

@ -4,11 +4,12 @@
from datetime import date, datetime from datetime import date, datetime
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.template import loader from django.template import loader
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.utils import timezone from django.utils import timezone, translation
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode from django.utils.http import urlsafe_base64_encode
@ -588,6 +589,8 @@ class Payment(models.Model):
@property @property
def team(self): def team(self):
return self.registrations.first().team return self.registrations.first().team
team.fget.short_description = _("team")
team.fget.admin_order_field = 'registrations__team__trigram'
@property @property
def tournament(self): def tournament(self):
@ -595,6 +598,8 @@ class Payment(models.Model):
from participation.models import Tournament from participation.models import Tournament
return Tournament.final_tournament() return Tournament.final_tournament()
return self.registrations.first().team.participation.tournament return self.registrations.first().team.participation.tournament
tournament.fget.short_description = _("tournament")
tournament.fget.admin_order_field = 'registrations__team__participation__tournament'
def get_checkout_intent(self, none_if_link_disabled=False): def get_checkout_intent(self, none_if_link_disabled=False):
if self.checkout_intent_id is None: if self.checkout_intent_id is None:
@ -634,6 +639,37 @@ class Payment(models.Model):
return checkout_intent return checkout_intent
def send_remind_mail(self):
translation.activate('fr')
subject = "[TFJM²] " + str(_("Reminder for your payment"))
site = Site.objects.first()
for registration in self.registrations.all():
message = loader.render_to_string('registration/mails/payment_reminder.txt',
dict(registration=registration, payment=self, domain=site.domain))
html = loader.render_to_string('registration/mails/payment_reminder.html',
dict(registration=registration, payment=self, domain=site.domain))
registration.user.email_user(subject, message, html_message=html)
def send_helloasso_payment_confirmation_mail(self):
translation.activate('fr')
subject = "[TFJM²] " + str(_("Payment confirmation"))
site = Site.objects.first()
for registration in self.registrations.all():
message = loader.render_to_string('registration/mails/payment_confirmation.txt',
dict(registration=registration, payment=self, domain=site.domain))
html = loader.render_to_string('registration/mails/payment_confirmation.html',
dict(registration=registration, payment=self, domain=site.domain))
registration.user.email_user(subject, message, html_message=html)
payer = self.get_checkout_intent()['order']['payer']
payer_name = f"{payer['firstName']} {payer['lastName']}"
if not self.registrations.filter(user__email=payer['email']).exists():
message = loader.render_to_string('registration/mails/payment_confirmation.txt',
dict(registration=payer_name, payment=self, domain=site.domain))
html = loader.render_to_string('registration/mails/payment_confirmation.html',
dict(registration=payer_name, payment=self, domain=site.domain))
send_mail(subject, message, None, [payer['email']], html_message=html)
def get_absolute_url(self): def get_absolute_url(self):
return reverse_lazy("registration:update_payment", args=(self.pk,)) return reverse_lazy("registration:update_payment", args=(self.pk,))

View File

@ -9,7 +9,7 @@
<body> <body>
<p> <p>
{% trans "Hi" %} {{ user.registration }}, {% trans "Hi" %} {{ registration }},
</p> </p>
<p> <p>
@ -28,7 +28,7 @@
<ul> <ul>
<li>{% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }}</li> <li>{% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }}</li>
<li>{% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }}</li> <li>{% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }}</li>
<li>{% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.start|date }} {% trans "to" %} {{ payment.tournament.end|date }}</li> <li>{% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.date_start|date }} {% trans "to" %} {{ payment.tournament.date_end|date }}</li>
</ul> </ul>
</p> </p>

View File

@ -1,6 +1,5 @@
{% load i18n %} {% load i18n %}
{% trans "Hi" %} {{ registration|safe }},
{% trans "Hi" %} {{ user.registration }},
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %} {% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament.name %}
We successfully received the payment of {{ amount }} € for the TFJM² registration in the team {{ team }} for the tournament {{ tournament }}! We successfully received the payment of {{ amount }} € for the TFJM² registration in the team {{ team }} for the tournament {{ tournament }}!
@ -12,7 +11,7 @@ We successfully received the payment of {{ amount }} € for the TFJM² registra
{% trans "As a reminder, here are the following important dates:" %} {% trans "As a reminder, here are the following important dates:" %}
* {% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }} * {% trans "Deadline to send the solutions:" %} {{ payment.tournament.solution_limit|date }}
* {% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }} * {% trans "Problems draw:" %} {{ payment.tournament.solutions_draw|date }}
* {% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.start|date }} {% trans "to" %} {{ payment.tournament.end|date }} * {% trans "Tournament dates:" %} {% trans "From" %} {{ payment.tournament.date_start|date }} {% trans "to" %} {{ payment.tournament.date_end|date }}
{% trans "Please note that these dates may be subject to change. If your local organizers gave you different dates, trust them." %} {% trans "Please note that these dates may be subject to change. If your local organizers gave you different dates, trust them." %}

View File

@ -9,7 +9,7 @@
<body> <body>
<p> <p>
{% trans "Hi" %} {{ user.registration }}, {% trans "Hi" %} {{ registration }},
</p> </p>
<p> <p>
@ -19,19 +19,19 @@
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<p> {% if payment.grouped %}
{% if payment.grouped %} <p>
{% trans "This price includes the registrations of all members of your team." %} {% trans "This price includes the registrations of all members of your team." %}
{% endif %} </p>
</p> {% endif %}
<p> <p>
{% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %} {% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %}
</p> </p>
<p> <p>
<a href="https://{{ domain }}{% url "registration.update_payment" pk=payment.pk %}"> <a href="https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %}">
https://{{ domain }}{% url "registration.update_payment" pk=payment.pk %} https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %}
</a> </a>
</p> </p>

View File

@ -1,19 +1,16 @@
{% load i18n %} {% load i18n %}
{% trans "Hi" %} {{ registration|safe }},
{% trans "Hi" %} {{ user.registration }},
{% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %} {% blocktrans trimmed with amount=payment.amount team=payment.team.trigram tournament=payment.tournament %}
You are registered for the TFJM² of {{ tournament }}. Your team {{ team }} has been successfully validated. You are registered for the TFJM² of {{ tournament }}. Your team {{ team }} has been successfully validated.
To end your inscription, you must pay the amount of {{ amount }} €. To end your inscription, you must pay the amount of {{ amount }} €.
{% endblocktrans %} {% endblocktrans %}
{% if payment.grouped %} {% if payment.grouped %}
{% trans "This price includes the registrations of all members of your team." %} {% trans "This price includes the registrations of all members of your team." %}
{% endif %} {% endif %}
{% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %} {% trans "You can pay by credit card or by bank transfer. You can read full instructions on the payment page:" %}
https://{{ domain }}{% url "registration.update_payment" pk=payment.pk %} https://{{ domain }}{% url "registration:update_payment" pk=payment.pk %}
{% trans "If you have a scholarship, then the registration is free for you. You must then upload it on the payment page using the above link." %} {% trans "If you have a scholarship, then the registration is free for you. You must then upload it on the payment page using the above link." %}

View File

@ -623,6 +623,7 @@ class PaymentHelloAssoReturnView(DetailView):
payment.save() payment.save()
messages.success(request, _("The payment has been successfully validated! " messages.success(request, _("The payment has been successfully validated! "
"Your registration is now complete.")) "Your registration is now complete."))
payment.send_helloasso_payment_confirmation_mail()
else: else:
payment.type = "helloasso" payment.type = "helloasso"
payment.valid = None payment.valid = None

View File

@ -9,6 +9,8 @@
# Check payments from Hello Asso # Check payments from Hello Asso
*/6 * * * * cd /code && python manage.py check_hello_asso &> /dev/null */6 * * * * cd /code && python manage.py check_hello_asso &> /dev/null
# Send reminders for payments
30 6 * * 1 cd /code && python manage.py remind_payments &> /dev/null
# Clean temporary files # Clean temporary files
30 * * * * rm -rf /tmp/* 30 * * * * rm -rf /tmp/*

View File

@ -46,7 +46,7 @@ def get_hello_asso_access_token():
return _access_token return _access_token
if response.status_code == 400: if response.status_code == 400:
raise ValueError(str(response.json()['errors'])) raise ValueError(str(response.json()))
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()