Skip to content

Instantly share code, notes, and snippets.

@chrischambers
Created February 20, 2017 17:31
Show Gist options
  • Save chrischambers/06f471b3d60c1ff78489d217d64847d2 to your computer and use it in GitHub Desktop.
Save chrischambers/06f471b3d60c1ff78489d217d64847d2 to your computer and use it in GitHub Desktop.
# Essentially, the reason you're finding this hard to test is because the
# business logic is baked into the save_model method - this is effectively a
# "fat controller". You want to pull that logic out and give it names. This has
# several outcomes:
# * it makes your code easier to unit test because you're
# not coupled to the form and the admin, and in fact you don't even
# need a real expense object here, it could be just a stub.
# * it makes code reuse easier - it's rarely a good idea to have
# business logic living *only* in an admin method.
# * it's more legible - if you want to know what happens when the state
# changes for an expense, it lives in one place.
# in finance/utils.py
class ExpenseHandler:
"""
You give me an expense and a new state, I tell you what happens.
"""
def __init__(self, expense, new_state):
# dispatch on state
mapping = {
ExpenseStatuses.DRAFT: 'handle_draft_expense_creation',
ExpenseStatuses.SUBMITTED: 'handle_expense_submitted',
ExpenseStatuses.REJECT: 'handle_expense_rejection',
ExpenseStatuses.PAID: 'handle_expense_success',
}
return getattr(self, mapping[new_state])(expense)
def handle_draft_expense_creation(self, expense):
expense.remove_all_authorisations()
return expense
def handle_expense_submitted(self, expense):
expense.notify_next_authoriser()
return expense
def handle_expense_rejection(self, expense):
expense.send_rejection_email()
return expense
def handle_expense_success(self, expense):
expense.status = ExpenseStatuses.PAID
expense.reimbursed_by = editor
expense.reimbursed_on = datetime.now()
expense.send_reimbursed_email()
return expense
# Usage:
def save_model(self, request, obj, form, change):
# This bit should just handle the form parsing and basically defer all the
# business logic to something more clever:
expense = obj
if change:
if 'reimbursed' in form.changed_data and form.cleaned_data['reimbursed']:
expense = ExpenseHandler(expense, new_state=ExpenseStatuses.PAID)
expense.save()
if phase:
# Re-save all expense items to ensure the details text contains
# the correct phase ID:
for ei in expense.items.all():
ei.save()
if 'advance_status_to' in form.changed_data:
advance_status_to = form.cleaned_data['advance_status_to']
expense = ExpenseHandler(expense, new_state=advance_status_to)
expense.save()
else:
# more stuff
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment