8000 Do not display repeated sponsorship applications (#2003) · python/pythondotorg@ceb0bf9 · GitHub
[go: up one dir, main page]

Skip to content

Commit ceb0bf9

Browse files
authored
Do not display repeated sponsorship applications (#2003)
* Ignore envrc files used by direnv * Add new FK on sponsorship to point to most recent sponsorship for that sponsor * Explicitly set up end date on contract recipes * Refactor code by extracting query to look up for application active on an specific date * Link overlapped applications by the new one which is being executed * Overlapped sponsorship applications shouldn't be enabled to be visible * Add overlapped by field to django admin so PSF staff can configure it * Only list applications from the same sponsor if editing an application * Do not display overlapped by field when approving an sponsorship
1 parent 077a7b9 commit ceb0bf9

File tree

10 files changed

+108
-4
lines changed
  • migrations
  • models
  • tests
  • 10 files changed

    +108
    -4
    lines changed

    .gitignore

    Lines changed: 1 addition & 8000 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -24,3 +24,4 @@ __pycache__
    2424
    .coverage
    2525
    .env
    2626
    .DS_Store
    27+
    .envrc

    sponsors/admin.py

    Lines changed: 1 addition & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -295,6 +295,7 @@ class SponsorshipAdmin(admin.ModelAdmin):
    295295
    "end_date",
    296296
    "get_contract",
    297297
    "level_name",
    298+
    "overlapped_by",
    298299
    ),
    299300
    },
    300301
    ),

    sponsors/forms.py

    Lines changed: 5 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -375,11 +375,16 @@ def user_with_previous_sponsors(self):
    375375
    class SponsorshipReviewAdminForm(forms.ModelForm):
    376376
    start_date = forms.DateField(widget=AdminDateWidget(), required=False)
    377377
    end_date = forms.DateField(widget=AdminDateWidget(), required=False)
    378+
    overlapped_by = forms.ModelChoiceField(queryset=Sponsorship.objects.select_related("sponsor", "package"), required=False)
    378379

    379380
    def __init__(self, *args, **kwargs):
    380381
    force_required = kwargs.pop("force_required", False)
    381382
    super().__init__(*args, **kwargs)
    383+
    if self.instance:
    384+
    qs = self.fields["overlapped_by"].queryset.exclude(id=self.instance.id)
    385+
    self.fields["overlapped_by"].queryset = qs.filter(sponsor_id=self.instance.sponsor_id)
    382386
    if force_required:
    387+
    self.fields.pop("overlapped_by") # overlapped should never be displayed on approval
    383388
    for field_name in self.fields:
    384389
    self.fields[field_name].required = True
    385390

    Lines changed: 20 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,20 @@
    1+
    # Generated by Django 2.2.24 on 2022-03-03 20:23
    2+
    3+
    from django.db import migrations, models
    4+
    import django.db.models.deletion
    5+
    import django.db.models.manager
    6+
    7+
    8+
    class Migration(migrations.Migration):
    9+
    10+
    dependencies = [
    11+
    ('sponsors', '0074_auto_20220211_1659'),
    12+
    ]
    13+
    14+
    operations = [
    15+
    migrations.AddField(
    16+
    model_name='sponsorship',
    17+
    name='overlapped_by',
    18+
    field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='sponsors.Sponsorship'),
    19+
    ),
    20+
    ]

    sponsors/models/managers.py

    Lines changed: 4 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -25,11 +25,13 @@ def visible_to(self, user):
    2525
    def finalized(self):
    2626
    return self.filter(status=self.model.FINALIZED)
    2727

    28+
    def active_on_date(self, ref_date):
    29+
    return self.filter(start_date__lte=ref_date, end_date__gte=ref_date)
    30+
    2831
    def enabled(self):
    2932
    """Sponsorship which are finalized and enabled"""
    3033
    today = timezone.now().date()
    31-
    qs = self.finalized()
    32-
    return qs.filter(start_date__lte=today, end_date__gte=today)
    34+
    return self.finalized().active_on_date(today).exclude(overlapped_by__isnull=False)
    3335

    3436
    def with_logo_placement(self, logo_place=None, publisher=None):
    3537
    from sponsors.models import LogoPlacement, SponsorBenefit

    sponsors/models/sponsorship.py

    Lines changed: 1 addition & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -151,6 +151,7 @@ class Sponsorship(models.Model):
    151151
    "name")
    152152
    package = models.ForeignKey(SponsorshipPackage, null=True, on_delete=models.SET_NULL)
    153153
    sponsorship_fee = models.PositiveIntegerField(null=True, blank=True)
    154+
    overlapped_by = models.ForeignKey("self", null=True, on_delete=models.SET_NULL)
    154155

    155156
    assets = GenericRelation(GenericAsset)
    156157

    sponsors/tests/baker_recipes.py

    Lines changed: 3 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -7,11 +7,13 @@
    77

    88
    today = date.today()
    99
    two_days = timedelta(days=2)
    10+
    thirty_days = timedelta(days=30)
    1011

    1112
    empty_contract = Recipe(
    1213
    Contract,
    1314
    sponsorship__sponsor__name="Sponsor",
    1415
    sponsorship__start_date=today,
    16+
    sponsorship__end_date=today + thirty_days,
    1517
    benefits_list="",
    1618
    legal_clauses="",
    1719
    )
    @@ -20,6 +22,7 @@
    2022
    Contract,
    2123
    sponsorship__sponsor__name="Awaiting Sponsor",
    2224
    sponsorship__start_date=today,
    25+
    sponsorship__end_date=today + thirty_days,
    2326
    benefits_list="- benefit 1",
    2427
    legal_clauses="",
    2528
    status=Contract.AWAITING_SIGNATURE,

    sponsors/tests/test_managers.py

    Lines changed: 8 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -63,6 +63,14 @@ def test_enabled_sponsorships(self):
    6363
    start_date=today - 2 * two_days,
    6464
    end_date=today - two_days
    6565
    )
    66+
    # shouldn't list overlapped sponsorships
    67+
    baker.make(
    68+
    Sponsorship,
    69+
    status=Sponsorship.FINALIZED,
    70+
    start_date=today - two_days,
    71+
    end_date=today + two_days,
    72+
    overlapped_by=enabled,
    73+
    )
    6674

    6775
    qs = Sponsorship.objects.enabled()
    6876

    sponsors/tests/test_use_cases.py

    Lines changed: 59 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -12,7 +12,7 @@
    1212

    1313
    from sponsors import use_cases
    1414
    from sponsors.notifications import *
    15-
    from sponsors.models import Sponsorship, Contract, SponsorEmailNotificationTemplate
    15+
    from sponsors.models import Sponsorship, Contract, SponsorEmailNotificationTemplate, Sponsor
    1616

    1717

    1818
    class CreateSponsorshipApplicationUseCaseTests(TestCase):
    @@ -220,7 +220,7 @@ def test_execute_and_update_database_object(self):
    220220
    self.assertEqual(b"Contract content", self.contract.signed_document.read())
    221221
    self.assertEqual(f"{Contract.SIGNED_PDF_DIR}1234.txt", self.contract.signed_document.name)
    222222

    223-
    def test_build_use_case_with_default_notificationss(self):
    223+
    def test_build_use_case_with_default_notifications(self):
    224224
    uc = use_cases.ExecuteExistingContractUseCase.build()
    225225
    self.assertEqual(len(uc.notifications), 2)
    226226
    self.assertIsInstance(
    @@ -230,6 +230,63 @@ def test_build_use_case_with_default_notificationss(self):
    230230
    uc.notifications[1], RefreshSponsorshipsCache,
    231231
    )
    232232

    233+
    def test_execute_contract_flag_overlapping_sponsorships(self):
    234+
    sponsorship = self.contract.sponsorship
    235+
    self.use_case.execute(self.contract, self.file)
    236+
    self.contract.refresh_from_db()
    237+
    recent_contract = baker.make_recipe(
    238+
    "sponsors.tests.empty_contract",
    239+
    status=Contract.DRAFT,
    240+
    sponsorship__sponsor=sponsorship.sponsor,
    241+
    sponsorship__start_date=sponsorship.start_date + timedelta(days=5),
    242+
    sponsorship__end_date=sponsorship.end_date + timedelta(days=5),
    243+
    )
    244+
    245+
    self.use_case.execute(recent_contract, self.file)
    246+
    recent_contract.refresh_from_db()
    247+
    sponsorship.refresh_from_db()
    248+
    249+
    self.assertEqual(recent_contract.status, Contract.EXECUTED)
    250+
    self.assertEqual(sponsorship.overlapped_by, recent_contract.sponsorship)
    251+
    252+
    def test_execute_contract_do_not_flag_overlap_if_no_date_range_conflict(self):
    253+
    sponsorship = self.contract.sponsorship
    254+
    self.use_case.execute(self.contract, self.file)
    255+
    self.contract.refresh_from_db()
    256+
    recent_contract = baker.make_recipe(
    257+
    "sponsors.tests.empty_contract",
    258+
    status=Contract.DRAFT,
    259+
    sponsorship__sponsor=sponsorship.sponsor,
    260+
    sponsorship__start_date=sponsorship.end_date + timedelta(days=1),
    261+
    sponsorship__end_date=sponsorship.end_date + timedelta(days=5),
    262+
    )
    263+
    264+
    self.use_case.execute(recent_contract, self.file)
    265+
    recent_contract.refresh_from_db()
    266+
    sponsorship.refresh_from_db()
    267+
    268+
    self.assertEqual(recent_contract.status, Contract.EXECUTED)
    269+
    self.assertIsNone(sponsorship.overlapped_by)
    270+
    271+
    def test_execute_contract_do_not_flag_overlap_if_from_other_sponsor(self):
    272+
    sponsorship = self.contract.sponsorship
    273+
    self.use_case.execute(self.contract, self.file)
    274+
    self.contract.refresh_from_db()
    275+
    recent_contract = baker.make_recipe(
    276+
    "sponsors.tests.empty_contract",
    277+
    status=Contract.DRAFT,
    278+
    sponsorship__sponsor=baker.make(Sponsor),
    279+
    sponsorship__start_date=sponsorship.start_date + timedelta(days=5),
    280+
    sponsorship__end_date=sponsorship.end_date + timedelta(days=5),
    281+
    )
    282+
    283+
    self.use_case.execute(recent_contract, self.file)
    284+
    recent_contract.refresh_from_db()
    285+
    sponsorship.refresh_from_db()
    286+
    287+
    self.assertEqual(recent_contract.status, Contract.EXECUTED)
    288+
    self.assertIsNone(sponsorship.overlapped_by)
    289+
    233290

    234291
    class NullifyContractUseCaseTests(TestCase):
    235292
    def setUp(self):

    sponsors/use_cases.py

    Lines changed: 6 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -101,6 +101,12 @@ class ExecuteExistingContractUseCase(BaseUseCaseWithNotifications):
    101101
    def execute(self, contract, contract_file, **kwargs):
    102102
    contract.signed_document = contract_file
    103103
    contract.execute(force=self.force_execute)
    104+
    overlapping_sponsorship = Sponsorship.objects.filter(
    105+
    sponsor=contract.sponsorship.sponsor,
    106+
    ).exclude(
    107+
    id=contract.sponsorship.id
    108+
    ).enabled().active_on_date(contract.sponsorship.start_date)
    109+
    overlapping_sponsorship.update(overlapped_by=contract.sponsorship)
    104110
    self.notify(
    105111
    request=kwargs.get("request"),
    106112
    contract=contract,

    0 commit comments

    Comments
     (0)
    0