refactor(finance/models): replace `submitted` and `confirmed` by a `status` field (#3)

This is in preparation for a [new statement admin
view](#179).
testing
Christian Merten 2 months ago committed by GitHub
parent c6bf9fe915
commit 69a4560ea4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -64,7 +64,7 @@ def decorate_statement_view(model, perm=None):
@admin.register(StatementUnSubmitted) @admin.register(StatementUnSubmitted)
class StatementUnSubmittedAdmin(CommonAdminMixin, admin.ModelAdmin): class StatementUnSubmittedAdmin(CommonAdminMixin, admin.ModelAdmin):
fields = ['short_description', 'explanation', 'excursion', 'submitted'] fields = ['short_description', 'explanation', 'excursion', 'status']
list_display = ['__str__', 'excursion', 'created_by'] list_display = ['__str__', 'excursion', 'created_by']
inlines = [BillOnStatementInline] inlines = [BillOnStatementInline]
@ -74,7 +74,7 @@ class StatementUnSubmittedAdmin(CommonAdminMixin, admin.ModelAdmin):
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
readonly_fields = ['submitted', 'excursion'] readonly_fields = ['status', 'excursion']
if obj is not None and obj.submitted: if obj is not None and obj.submitted:
return readonly_fields + self.fields return readonly_fields + self.fields
else: else:
@ -167,7 +167,7 @@ class BillOnSubmittedStatementInline(BillOnStatementInline):
@admin.register(StatementSubmitted) @admin.register(StatementSubmitted)
class StatementSubmittedAdmin(admin.ModelAdmin): class StatementSubmittedAdmin(admin.ModelAdmin):
fields = ['short_description', 'explanation', 'excursion', 'submitted'] fields = ['short_description', 'explanation', 'excursion', 'status']
list_display = ['__str__', 'is_valid', 'submitted_date', 'submitted_by'] list_display = ['__str__', 'is_valid', 'submitted_date', 'submitted_by']
ordering = ('-submitted_date',) ordering = ('-submitted_date',)
inlines = [BillOnSubmittedStatementInline, TransactionOnSubmittedStatementInline] inlines = [BillOnSubmittedStatementInline, TransactionOnSubmittedStatementInline]
@ -186,7 +186,7 @@ class StatementSubmittedAdmin(admin.ModelAdmin):
return False return False
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
readonly_fields = ['submitted'] readonly_fields = ['status']
if obj is not None and obj.submitted: if obj is not None and obj.submitted:
return readonly_fields + self.fields return readonly_fields + self.fields
else: else:
@ -274,7 +274,7 @@ class StatementSubmittedAdmin(admin.ModelAdmin):
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name))) return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if "reject" in request.POST: if "reject" in request.POST:
statement.submitted = False statement.status = Statement.UNSUBMITTED
statement.save() statement.save()
messages.success(request, messages.success(request,
_("Successfully rejected %(name)s. The requestor can reapply, when needed.") _("Successfully rejected %(name)s. The requestor can reapply, when needed.")
@ -315,7 +315,7 @@ class StatementSubmittedAdmin(admin.ModelAdmin):
@admin.register(StatementConfirmed) @admin.register(StatementConfirmed)
class StatementConfirmedAdmin(admin.ModelAdmin): class StatementConfirmedAdmin(admin.ModelAdmin):
fields = ['short_description', 'explanation', 'excursion', 'confirmed'] fields = ['short_description', 'explanation', 'excursion', 'status']
#readonly_fields = fields #readonly_fields = fields
list_display = ['__str__', 'total_pretty', 'confirmed_date', 'confirmed_by'] list_display = ['__str__', 'total_pretty', 'confirmed_date', 'confirmed_by']
ordering = ('-confirmed_date',) ordering = ('-confirmed_date',)
@ -365,7 +365,7 @@ class StatementConfirmedAdmin(admin.ModelAdmin):
_("%(name)s is not yet confirmed.") % {'name': str(statement)}) _("%(name)s is not yet confirmed.") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,))) return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
if "unconfirm" in request.POST: if "unconfirm" in request.POST:
statement.confirmed = False statement.status = Statement.SUBMITTED
statement.confirmed_date = None statement.confirmed_date = None
statement.confired_by = None statement.confired_by = None
statement.save() statement.save()

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-03 19:06+0200\n" "POT-Creation-Date: 2025-10-12 11:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,11 +18,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "Statement not found." msgid "Statement not found."
msgstr "Abrechnung nicht gefunden." msgstr "Abrechnung nicht gefunden."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "Insufficient permissions." msgid "Insufficient permissions."
msgstr "Unzureichende Berechtigungen." msgstr "Unzureichende Berechtigungen."
@ -31,7 +31,7 @@ msgstr "Unzureichende Berechtigungen."
msgid "%(name)s is already submitted." msgid "%(name)s is already submitted."
msgstr "%(name)s ist bereits eingereicht." msgstr "%(name)s ist bereits eingereicht."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
#, python-format #, python-format
msgid "" msgid ""
"Successfully submited %(name)s. The finance department will notify the " "Successfully submited %(name)s. The finance department will notify the "
@ -40,11 +40,11 @@ msgstr ""
"Rechnung %(name)s erfolgreich eingereicht. Das Finanzreferat wird auf dich " "Rechnung %(name)s erfolgreich eingereicht. Das Finanzreferat wird auf dich "
"sobald wie möglich zukommen." "sobald wie möglich zukommen."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "Finance overview" msgid "Finance overview"
msgstr "Kostenübersicht" msgstr "Kostenübersicht"
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "Submit statement" msgid "Submit statement"
msgstr "Rechnung einreichen" msgstr "Rechnung einreichen"
@ -64,11 +64,11 @@ msgstr ""
"Beim Abwickeln von %(name)s ist ein Fehler aufgetreten. Bitte versuche es " "Beim Abwickeln von %(name)s ist ein Fehler aufgetreten. Bitte versuche es "
"erneut." "erneut."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "Successfully sent receipt to the office." msgid "Successfully sent receipt to the office."
msgstr "Abrechnungsbeleg an die Geschäftsstelle gesendet." msgstr "Abrechnungsbeleg an die Geschäftsstelle gesendet."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
#, python-format #, python-format
msgid "" msgid ""
"Successfully confirmed %(name)s. I hope you executed the associated " "Successfully confirmed %(name)s. I hope you executed the associated "
@ -84,11 +84,11 @@ msgstr ""
"Hier kannst du den Abrechnungsbeleg <a href='%(link)s', " "Hier kannst du den Abrechnungsbeleg <a href='%(link)s', "
"target='_blank'>herunterladen</a>." "target='_blank'>herunterladen</a>."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "Statement confirmed" msgid "Statement confirmed"
msgstr "Abrechnung abgewickelt" msgstr "Abrechnung abgewickelt"
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "" msgid ""
"Transactions do not match the covered expenses. Please correct the mistakes " "Transactions do not match the covered expenses. Please correct the mistakes "
"listed below." "listed below."
@ -96,12 +96,12 @@ msgstr ""
"Überweisungen stimmen nicht mit den übernommenen Kosten überein. Bitte " "Überweisungen stimmen nicht mit den übernommenen Kosten überein. Bitte "
"korrigiere die unten aufgeführten Fehler." "korrigiere die unten aufgeführten Fehler."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "Some transactions have no ledger configured. Please fill in the gaps." msgid "Some transactions have no ledger configured. Please fill in the gaps."
msgstr "" msgstr ""
"Manche Überweisungen haben kein Geldtopf eingestellt. Bitte trage das nach." "Manche Überweisungen haben kein Geldtopf eingestellt. Bitte trage das nach."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "" msgid ""
"The configured recipients for the allowance don't match the regulations. " "The configured recipients for the allowance don't match the regulations. "
"Please correct this on the excursion." "Please correct this on the excursion."
@ -117,14 +117,14 @@ msgstr ""
"Der berechnete Gesamtbetrag stimmt nicht mit der Summe aller Überweisungen " "Der berechnete Gesamtbetrag stimmt nicht mit der Summe aller Überweisungen "
"überein. Das ist höchstwahrscheinlich ein Fehler in der Implementierung." "überein. Das ist höchstwahrscheinlich ein Fehler in der Implementierung."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
#, python-format #, python-format
msgid "Successfully rejected %(name)s. The requestor can reapply, when needed." msgid "Successfully rejected %(name)s. The requestor can reapply, when needed."
msgstr "" msgstr ""
"Die Rechnung %(name)s wurde abgelehnt. Die Person kann die Rechnung erneut " "Die Rechnung %(name)s wurde abgelehnt. Die Person kann die Rechnung erneut "
"einstellen, wenn es benötigt wird." "einstellen, wenn es benötigt wird."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
#, python-format #, python-format
msgid "" msgid ""
"%(name)s already has transactions. Please delete them first, if you want to " "%(name)s already has transactions. Please delete them first, if you want to "
@ -133,12 +133,12 @@ msgstr ""
"%(name)s hat bereits Überweisungen. Bitte lösche diese zunächst, bevor du " "%(name)s hat bereits Überweisungen. Bitte lösche diese zunächst, bevor du "
"neue generierst." "neue generierst."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
#, python-format #, python-format
msgid "Successfully generated transactions for %(name)s" msgid "Successfully generated transactions for %(name)s"
msgstr "Automatisch Überweisungsträger für %(name)s generiert." msgstr "Automatisch Überweisungsträger für %(name)s generiert."
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
#, python-format #, python-format
msgid "" msgid ""
"Error while generating transactions for %(name)s. Do all bills have a payer " "Error while generating transactions for %(name)s. Do all bills have a payer "
@ -150,11 +150,11 @@ msgstr ""
"einer Ausfahrt gehört, wurde eine Person als Empfänger*in der Übernachtungs- " "einer Ausfahrt gehört, wurde eine Person als Empfänger*in der Übernachtungs- "
"und Fahrtkostenzuschüsse ausgewählt?" "und Fahrtkostenzuschüsse ausgewählt?"
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
msgid "View submitted statement" msgid "View submitted statement"
msgstr "Eingereichte Abrechnung einsehen" msgstr "Eingereichte Abrechnung einsehen"
#: finance/admin.py #: finance/admin.py finance/tests/admin.py
#, python-format #, python-format
msgid "Successfully reduced transactions for %(name)s." msgid "Successfully reduced transactions for %(name)s."
msgstr "Überweisungsträger für %(name)s minimiert." msgstr "Überweisungsträger für %(name)s minimiert."
@ -172,6 +172,7 @@ msgstr ""
"was du machst." "was du machst."
#: finance/admin.py finance/templates/admin/unconfirm_statement.html #: finance/admin.py finance/templates/admin/unconfirm_statement.html
#: finance/tests/admin.py
msgid "Unconfirm statement" msgid "Unconfirm statement"
msgstr "Bestätigung zurücknehmen" msgstr "Bestätigung zurücknehmen"
@ -196,6 +197,18 @@ msgstr "Geldtopf"
msgid "Ledgers" msgid "Ledgers"
msgstr "Geldtöpfe" msgstr "Geldtöpfe"
#: finance/models.py
msgid "In preparation"
msgstr "In Vorbereitung"
#: finance/models.py
msgid "Submitted"
msgstr "Eingereicht"
#: finance/models.py
msgid "Confirmed"
msgstr "Abgewickelt"
#: finance/models.py #: finance/models.py
msgid "Short description" msgid "Short description"
msgstr "Kurzbeschreibung" msgstr "Kurzbeschreibung"
@ -247,17 +260,13 @@ msgid "Price per night"
msgstr "Preis pro Nacht" msgstr "Preis pro Nacht"
#: finance/models.py #: finance/models.py
msgid "Submitted" msgid "Status"
msgstr "Eingereicht" msgstr "Status"
#: finance/models.py #: finance/models.py
msgid "Submitted on" msgid "Submitted on"
msgstr "Eingereicht am" msgstr "Eingereicht am"
#: finance/models.py
msgid "Confirmed"
msgstr "Abgewickelt"
#: finance/models.py #: finance/models.py
msgid "Paid on" msgid "Paid on"
msgstr "Bezahlt am" msgstr "Bezahlt am"
@ -288,6 +297,7 @@ msgid "Statement: %(excursion)s"
msgstr "Abrechnung: %(excursion)s" msgstr "Abrechnung: %(excursion)s"
#: finance/models.py #: finance/models.py
#, python-format
msgid "Excursion %(excursion)s" msgid "Excursion %(excursion)s"
msgstr "Ausfahrt %(excursion)s" msgstr "Ausfahrt %(excursion)s"
@ -305,7 +315,7 @@ msgstr "Aufwandsentschädigung für %(excu)s"
msgid "Night and travel costs for %(excu)s" msgid "Night and travel costs for %(excu)s"
msgstr "Übernachtungs- und Fahrtkosten für %(excu)s" msgstr "Übernachtungs- und Fahrtkosten für %(excu)s"
#: finance/models.py #: finance/models.py finance/tests/models.py
msgid "reduced by org fee" msgid "reduced by org fee"
msgstr "reduziert um Org-Beitrag" msgstr "reduziert um Org-Beitrag"

@ -0,0 +1,39 @@
# Generated by Django 4.2.20 on 2025-10-11 15:43
from django.db import migrations, models
def set_status_from_old_fields(apps, schema_editor):
"""
Set the status field based on the existing submitted and confirmed fields.
- If confirmed is True, status = CONFIRMED (2)
- If submitted is True but confirmed is False, status = SUBMITTED (1)
- Otherwise, status = UNSUBMITTED (0)
"""
Statement = apps.get_model('finance', 'Statement')
UNSUBMITTED, SUBMITTED, CONFIRMED = 0, 1, 2
for statement in Statement.objects.all():
if statement.confirmed:
statement.status = CONFIRMED
elif statement.submitted:
statement.status = SUBMITTED
else:
statement.status = UNSUBMITTED
statement.save(update_fields=['status'])
class Migration(migrations.Migration):
dependencies = [
('finance', '0009_statement_ljp_to'),
]
operations = [
migrations.AddField(
model_name='statement',
name='status',
field=models.IntegerField(choices=[(0, 'In preparation'), (1, 'Submitted'), (2, 'Confirmed')], default=0, verbose_name='Status'),
),
migrations.RunPython(set_status_from_old_fields, reverse_code=migrations.RunPython.noop),
]

@ -0,0 +1,21 @@
# Generated by Django 4.2.20 on 2025-10-11 16:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('finance', '0010_statement_status'),
]
operations = [
migrations.RemoveField(
model_name='statement',
name='confirmed',
),
migrations.RemoveField(
model_name='statement',
name='submitted',
),
]

@ -46,11 +46,15 @@ class TransactionIssue:
class StatementManager(models.Manager): class StatementManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(submitted=False, confirmed=False) return super().get_queryset().filter(status=Statement.UNSUBMITTED)
class Statement(CommonModel): class Statement(CommonModel):
MISSING_LEDGER, NON_MATCHING_TRANSACTIONS, INVALID_ALLOWANCE_TO, INVALID_TOTAL, VALID = 0, 1, 2, 3, 4 MISSING_LEDGER, NON_MATCHING_TRANSACTIONS, INVALID_ALLOWANCE_TO, INVALID_TOTAL, VALID = 0, 1, 2, 3, 4
UNSUBMITTED, SUBMITTED, CONFIRMED = 0, 1, 2
STATUS_CHOICES = [(UNSUBMITTED, _('In preparation')),
(SUBMITTED, _('Submitted')),
(CONFIRMED, _('Confirmed'))]
short_description = models.CharField(verbose_name=_('Short description'), short_description = models.CharField(verbose_name=_('Short description'),
max_length=30, max_length=30,
@ -82,9 +86,10 @@ class Statement(CommonModel):
night_cost = models.DecimalField(verbose_name=_('Price per night'), default=0, decimal_places=2, max_digits=5) night_cost = models.DecimalField(verbose_name=_('Price per night'), default=0, decimal_places=2, max_digits=5)
submitted = models.BooleanField(verbose_name=_('Submitted'), default=False) status = models.IntegerField(verbose_name=_('Status'),
choices=STATUS_CHOICES,
default=UNSUBMITTED)
submitted_date = models.DateTimeField(verbose_name=_('Submitted on'), default=None, null=True) submitted_date = models.DateTimeField(verbose_name=_('Submitted on'), default=None, null=True)
confirmed = models.BooleanField(verbose_name=_('Confirmed'), default=False)
confirmed_date = models.DateTimeField(verbose_name=_('Paid on'), default=None, null=True) confirmed_date = models.DateTimeField(verbose_name=_('Paid on'), default=None, null=True)
created_by = models.ForeignKey(Member, verbose_name=_('Created by'), created_by = models.ForeignKey(Member, verbose_name=_('Created by'),
@ -131,8 +136,16 @@ class Statement(CommonModel):
else: else:
return self.short_description return self.short_description
@property
def submitted(self):
return self.status == Statement.SUBMITTED or self.status == Statement.CONFIRMED
@property
def confirmed(self):
return self.status == Statement.CONFIRMED
def submit(self, submitter=None): def submit(self, submitter=None):
self.submitted = True self.status = self.SUBMITTED
self.submitted_date = timezone.now() self.submitted_date = timezone.now()
self.submitted_by = submitter self.submitted_by = submitter
self.save() self.save()
@ -231,7 +244,7 @@ class Statement(CommonModel):
if not self.validity == Statement.VALID: if not self.validity == Statement.VALID:
return False return False
self.confirmed = True self.status = self.CONFIRMED
self.confirmed_date = timezone.now() self.confirmed_date = timezone.now()
self.confirmed_by = confirmer self.confirmed_by = confirmer
for trans in self.transaction_set.all(): for trans in self.transaction_set.all():
@ -569,7 +582,7 @@ class Statement(CommonModel):
class StatementUnSubmittedManager(models.Manager): class StatementUnSubmittedManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(submitted=False, confirmed=False) return super().get_queryset().filter(status=Statement.UNSUBMITTED)
class StatementUnSubmitted(Statement): class StatementUnSubmitted(Statement):
@ -589,7 +602,7 @@ class StatementUnSubmitted(Statement):
class StatementSubmittedManager(models.Manager): class StatementSubmittedManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(submitted=True, confirmed=False) return super().get_queryset().filter(status=Statement.SUBMITTED)
class StatementSubmitted(Statement): class StatementSubmitted(Statement):
@ -606,7 +619,7 @@ class StatementSubmitted(Statement):
class StatementConfirmedManager(models.Manager): class StatementConfirmedManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(confirmed=True) return super().get_queryset().filter(status=Statement.CONFIRMED)
class StatementConfirmed(Statement): class StatementConfirmed(Statement):

@ -1,3 +1,4 @@
from .admin import * from .admin import *
from .models import * from .models import *
from .rules import * from .rules import *
from .migrations import *

@ -99,16 +99,16 @@ class StatementUnSubmittedAdminTestCase(AdminTestCase):
def test_get_readonly_fields_submitted(self): def test_get_readonly_fields_submitted(self):
"""Test readonly fields when statement is submitted""" """Test readonly fields when statement is submitted"""
# Mark statement as submitted # Mark statement as submitted
self.statement.submitted = True self.statement.status = Statement.SUBMITTED
readonly_fields = self.admin.get_readonly_fields(None, self.statement) readonly_fields = self.admin.get_readonly_fields(None, self.statement)
self.assertIn('submitted', readonly_fields) self.assertIn('status', readonly_fields)
self.assertIn('excursion', readonly_fields) self.assertIn('excursion', readonly_fields)
self.assertIn('short_description', readonly_fields) self.assertIn('short_description', readonly_fields)
def test_get_readonly_fields_not_submitted(self): def test_get_readonly_fields_not_submitted(self):
"""Test readonly fields when statement is not submitted""" """Test readonly fields when statement is not submitted"""
readonly_fields = self.admin.get_readonly_fields(None, self.statement) readonly_fields = self.admin.get_readonly_fields(None, self.statement)
self.assertEqual(readonly_fields, ['submitted', 'excursion']) self.assertEqual(readonly_fields, ['status', 'excursion'])
def test_submit_view_insufficient_permission(self): def test_submit_view_insufficient_permission(self):
url = reverse('admin:finance_statementunsubmitted_submit', url = reverse('admin:finance_statementunsubmitted_submit',
@ -163,7 +163,7 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
self.statement = Statement.objects.create( self.statement = Statement.objects.create(
short_description='Submitted Statement', short_description='Submitted Statement',
explanation='Test explanation', explanation='Test explanation',
submitted=True, status=Statement.SUBMITTED,
submitted_by=self.member, submitted_by=self.member,
submitted_date=timezone.now(), submitted_date=timezone.now(),
night_cost=25 night_cost=25
@ -198,7 +198,7 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
self.statement_no_trans_success = Statement.objects.create( self.statement_no_trans_success = Statement.objects.create(
short_description='No Transactions Success', short_description='No Transactions Success',
explanation='Test explanation', explanation='Test explanation',
submitted=True, status=Statement.SUBMITTED,
submitted_by=self.member, submitted_by=self.member,
submitted_date=timezone.now(), submitted_date=timezone.now(),
night_cost=25 night_cost=25
@ -206,7 +206,7 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
self.statement_no_trans_error = Statement.objects.create( self.statement_no_trans_error = Statement.objects.create(
short_description='No Transactions Error', short_description='No Transactions Error',
explanation='Test explanation', explanation='Test explanation',
submitted=True, status=Statement.SUBMITTED,
submitted_by=self.member, submitted_by=self.member,
submitted_date=timezone.now(), submitted_date=timezone.now(),
night_cost=25 night_cost=25
@ -294,7 +294,7 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
"""Test overview_view with statement that can't be found in StatementSubmitted queryset""" """Test overview_view with statement that can't be found in StatementSubmitted queryset"""
# When trying to access an unsubmitted statement via StatementSubmitted admin, # When trying to access an unsubmitted statement via StatementSubmitted admin,
# the decorator will fail to find it and show "Statement not found" # the decorator will fail to find it and show "Statement not found"
self.statement.submitted = False self.statement.status = Statement.UNSUBMITTED
self.statement.save() self.statement.save()
url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,)) url = reverse('admin:finance_statementsubmitted_overview', args=(self.statement.pk,))
@ -496,8 +496,7 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
base_statement = Statement.objects.create( base_statement = Statement.objects.create(
short_description='Confirmed Statement', short_description='Confirmed Statement',
explanation='Test explanation', explanation='Test explanation',
submitted=True, status=Statement.CONFIRMED,
confirmed=True,
confirmed_by=self.member, confirmed_by=self.member,
confirmed_date=timezone.now(), confirmed_date=timezone.now(),
night_cost=25 night_cost=25
@ -510,8 +509,7 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
self.unconfirmed_statement = Statement.objects.create( self.unconfirmed_statement = Statement.objects.create(
short_description='Unconfirmed Statement', short_description='Unconfirmed Statement',
explanation='Test explanation', explanation='Test explanation',
submitted=True, status=Statement.SUBMITTED,
confirmed=False,
night_cost=25 night_cost=25
) )
@ -528,8 +526,7 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
confirmed_with_excursion_base = Statement.objects.create( confirmed_with_excursion_base = Statement.objects.create(
short_description='Confirmed with Excursion', short_description='Confirmed with Excursion',
explanation='Test explanation', explanation='Test explanation',
submitted=True, status=Statement.CONFIRMED,
confirmed=True,
confirmed_by=self.member, confirmed_by=self.member,
confirmed_date=timezone.now(), confirmed_date=timezone.now(),
excursion=self.excursion, excursion=self.excursion,

@ -0,0 +1,70 @@
import django.test
from django.apps import apps
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
class StatusMigrationTestCase(django.test.TransactionTestCase):
"""Test the migration from submitted/confirmed fields to status field."""
app = 'finance'
migrate_from = [('finance', '0009_statement_ljp_to')]
migrate_to = [('finance', '0010_statement_status')]
def setUp(self):
# Get the state before migration
executor = MigrationExecutor(connection)
executor.migrate(self.migrate_from)
# Get the old models (before migration)
old_apps = executor.loader.project_state(self.migrate_from).apps
self.Statement = old_apps.get_model(self.app, 'Statement')
# Create statements with different combinations of submitted/confirmed
# created_by is nullable, so we don't need to create a Member
self.unsubmitted = self.Statement.objects.create(
short_description='Unsubmitted Statement',
submitted=False,
confirmed=False
)
self.submitted = self.Statement.objects.create(
short_description='Submitted Statement',
submitted=True,
confirmed=False
)
self.confirmed = self.Statement.objects.create(
short_description='Confirmed Statement',
submitted=True,
confirmed=True
)
def test_status_field_migration(self):
"""Test that status field is correctly set from old submitted/confirmed fields."""
# Run the migration
executor = MigrationExecutor(connection)
executor.loader.build_graph()
executor.migrate(self.migrate_to)
# Get the new models (after migration)
new_apps = executor.loader.project_state(self.migrate_to).apps
Statement = new_apps.get_model(self.app, 'Statement')
# Constants from the Statement model
UNSUBMITTED = 0
SUBMITTED = 1
CONFIRMED = 2
# Verify the migration worked correctly
unsubmitted = Statement.objects.get(pk=self.unsubmitted.pk)
self.assertEqual(unsubmitted.status, UNSUBMITTED,
'Statement with submitted=False, confirmed=False should have status=UNSUBMITTED')
submitted = Statement.objects.get(pk=self.submitted.pk)
self.assertEqual(submitted.status, SUBMITTED,
'Statement with submitted=True, confirmed=False should have status=SUBMITTED')
confirmed = Statement.objects.get(pk=self.confirmed.pk)
self.assertEqual(confirmed.status, CONFIRMED,
'Statement with submitted=True, confirmed=True should have status=CONFIRMED')

@ -505,11 +505,11 @@ class ManagerTestCase(TestCase):
self.st_submitted = Statement.objects.create(short_description='A statement', self.st_submitted = Statement.objects.create(short_description='A statement',
explanation='Important!', explanation='Important!',
night_cost=0, night_cost=0,
submitted=True) status=Statement.SUBMITTED)
self.st_confirmed = Statement.objects.create(short_description='A statement', self.st_confirmed = Statement.objects.create(short_description='A statement',
explanation='Important!', explanation='Important!',
night_cost=0, night_cost=0,
confirmed=True) status=Statement.CONFIRMED)
def test_get_queryset(self): def test_get_queryset(self):
# TODO: remove this manager, since it is not used # TODO: remove this manager, since it is not used

@ -52,15 +52,15 @@ class FinanceRulesTestCase(TestCase):
def test_not_submitted_statement(self): def test_not_submitted_statement(self):
"""Test not_submitted predicate returns True when statement is not submitted""" """Test not_submitted predicate returns True when statement is not submitted"""
self.statement.submitted = False self.statement.status = Statement.UNSUBMITTED
self.assertTrue(not_submitted(self.user1, self.statement)) self.assertTrue(not_submitted(self.user1, self.statement))
self.statement.submitted = True self.statement.status = Statement.SUBMITTED
self.assertFalse(not_submitted(self.user1, self.statement)) self.assertFalse(not_submitted(self.user1, self.statement))
def test_not_submitted_freizeit_with_statement(self): def test_not_submitted_freizeit_with_statement(self):
"""Test not_submitted predicate with Freizeit having unsubmitted statement""" """Test not_submitted predicate with Freizeit having unsubmitted statement"""
self.freizeit.statement = self.statement self.freizeit.statement = self.statement
self.statement.submitted = False self.statement.status = Statement.UNSUBMITTED
self.assertTrue(not_submitted(self.user1, self.freizeit)) self.assertTrue(not_submitted(self.user1, self.freizeit))
def test_not_submitted_freizeit_without_statement(self): def test_not_submitted_freizeit_without_statement(self):

@ -88,12 +88,12 @@ class RulesTestCase(TestCase):
self.statement_unsubmitted = Statement.objects.create( self.statement_unsubmitted = Statement.objects.create(
short_description='Unsubmitted Statement', short_description='Unsubmitted Statement',
excursion=self.excursion, excursion=self.excursion,
submitted=False status=Statement.UNSUBMITTED
) )
self.statement_submitted = Statement.objects.create( self.statement_submitted = Statement.objects.create(
short_description='Submitted Statement', short_description='Submitted Statement',
submitted=True status=Statement.SUBMITTED
) )
def test_is_oneself(self): def test_is_oneself(self):

Loading…
Cancel
Save