@@ -156,12 +156,14 @@ class Sponsorship(models.Model):
156156 REJECTED = "rejected"
157157 APPROVED = "approved"
158158 FINALIZED = "finalized"
159+ CANCELLED = "cancelled"
159160
160161 STATUS_CHOICES = [
161162 (APPLIED , "Applied" ),
162163 (REJECTED , "Rejected" ),
163164 (APPROVED , "Approved" ),
164165 (FINALIZED , "Finalized" ),
166+ (CANCELLED , "Cancelled" ),
165167 ]
166168
167169 objects = SponsorshipQuerySet .as_manager ()
@@ -180,6 +182,7 @@ class Sponsorship(models.Model):
180182 applied_on = models .DateField (auto_now_add = True )
181183 approved_on = models .DateField (null = True , blank = True )
182184 rejected_on = models .DateField (null = True , blank = True )
185+ cancelled_on = models .DateField (null = True , blank = True )
183186 finalized_on = models .DateField (null = True , blank = True )
184187 year = models .PositiveIntegerField (null = True , validators = YEAR_VALIDATORS , db_index = True )
185188
@@ -218,7 +221,14 @@ def level_name(self, value):
218221 @cached_property
219222 def user_customizations (self ):
220223 benefits = [b .sponsorship_benefit for b in self .benefits .select_related ("sponsorship_benefit" )]
221- return self .package .get_user_customization (benefits )
224+ if self .package :
225+ return self .package .get_user_customization (benefits )
226+ else :
227+ # Return default customization structure for sponsorships without packages
228+ return {
229+ "added_by_user" : [],
230+ "removed_by_user" : []
231+ }
222232
223233 def __str__ (self ):
224234 repr = f"{ self .level_name } - { self .year } - ({ self .get_status_display ()} ) for sponsor { self .sponsor .name } "
@@ -327,8 +337,17 @@ def approve(self, start_date, end_date):
327337 self .end_date = end_date
328338 self .approved_on = timezone .now ().date ()
329339
340+
341+ def cancel (self ):
342+ if self .CANCELLED not in self .next_status :
343+ msg = f"Can't cancel a { self .get_status_display ()} sponsorship."
344+ raise InvalidStatusException (msg )
345+ self .status = self .CANCELLED
346+ self .locked = True
347+ self .cancelled_on = timezone .now ().date ()
348+
330349 def rollback_to_editing (self ):
331- accepts_rollback = [self .APPLIED , self .APPROVED , self .REJECTED ]
350+ accepts_rollback = [self .APPLIED , self .APPROVED , self .REJECTED , self . CANCELLED ]
332351 if self .status not in accepts_rollback :
333352 msg = f"Can't rollback to edit a { self .get_status_display ()} sponsorship."
334353 raise InvalidStatusException (msg )
@@ -345,6 +364,7 @@ def rollback_to_editing(self):
345364 self .status = self .APPLIED
346365 self .approved_on = None
347366 self .rejected_on = None
367+ self .cancelled_on = None
348368
349369 @property
350370 def unlocked (self ):
@@ -388,10 +408,11 @@ def open_for_editing(self):
388408 @property
389409 def next_status (self ):
390410 states_map = {
391- self .APPLIED : [self .APPROVED , self .REJECTED ],
392- self .APPROVED : [self .FINALIZED ],
411+ self .APPLIED : [self .APPROVED , self .REJECTED , self . CANCELLED ],
412+ self .APPROVED : [self .FINALIZED , self . CANCELLED ],
393413 self .REJECTED : [],
394414 self .FINALIZED : [],
415+ self .CANCELLED : [],
395416 }
396417 return states_map [self .status ]
397418
0 commit comments