Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug withdrawn application backend #1594

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,42 +1,126 @@
from __future__ import annotations

from random import randint
from random import choice, randint
from datetime import timedelta

from django.db import transaction
from django.utils import timezone

from samfundet.models.general import User
from samfundet.models.recruitment import RecruitmentPosition, RecruitmentApplication
from samfundet.models.recruitment import (
RecruitmentPosition,
RecruitmentStatistics,
RecruitmentApplication,
RecruitmentStatusChoices,
RecruitmentPriorityChoices,
)

APPLICATION_TEXTS = [
'I am very interested in this position and would love to contribute to Samfundet.',
'I have relevant experience and am excited about this opportunity.',
'This position aligns perfectly with my interests and skills.',
'I would be honored to join the team and contribute my expertise.',
'I am passionate about this role and eager to learn and grow.',
]


def ensure_valid_recruitment_dates(position):
"""
Updates recruitment dates to ensure applications can be created.
Also ensures the recruitment has associated statistics.
"""
with transaction.atomic():
recruitment = position.recruitment
now = timezone.now()

# Only update if the deadline has passed
if recruitment.actual_application_deadline <= now:
recruitment.visible_from = now - timedelta(days=1)
recruitment.shown_application_deadline = now + timedelta(days=29)
recruitment.actual_application_deadline = now + timedelta(days=30)
recruitment.reprioritization_deadline_for_applicant = now + timedelta(days=35)
recruitment.reprioritization_deadline_for_groups = now + timedelta(days=40)

# Ensure statistics exist before saving recruitment
if not hasattr(recruitment, 'statistics'):
RecruitmentStatistics.objects.create(recruitment=recruitment)

# Some example data to use for the new RecruitmentApplication instances
APPLICATION_DATA = {
'application_text': 'This is the application text',
'applicant_priority': 0,
'recruiter_priority': 0,
'recruiter_status': 0,
}
recruitment.save()

# Double-check statistics exist even if dates weren't updated
if not hasattr(recruitment, 'statistics'):
RecruitmentStatistics.objects.create(recruitment=recruitment)

def seed():
return recruitment


def get_priority_for_user(user, recruitment):
"""Determines the next available priority for a user's applications."""
existing_count = RecruitmentApplication.objects.filter(user=user, recruitment=recruitment, withdrawn=False).count()
return existing_count + 1


def seed(): # noqa: C901
"""
Seeds recruitment applications with realistic data while respecting application
constraints and deadlines.
"""
yield 0, 'recruitment_applications'

# Clear existing applications
RecruitmentApplication.objects.all().delete()
yield 0, 'Deleted old applications'
yield 10, 'Deleted old applications'

positions = RecruitmentPosition.objects.all()
users = User.objects.all()
users = list(User.objects.all()) # Convert to list to avoid multiple DB hits
created_count = 0

for position_index, position in enumerate(positions):
for _ in range(randint(0, 5)): # Create between 0 and 5 instances for each position
application_data = APPLICATION_DATA.copy()
application_data.update(
{
'recruitment_position': position,
'recruitment': position.recruitment,
'user': users[randint(0, len(users) - 1)], # random user from all users
}
)
_application, created = RecruitmentApplication.objects.get_or_create(**application_data)

if created:
created_count += 1
yield (position_index + 1) / len(positions), 'recruitment_applications'

yield 100, f'Created {created_count} recruitment_applications'
try:
# Ensure recruitment dates and statistics are valid
recruitment = ensure_valid_recruitment_dates(position)

# Create between 0 and 5 applications for each position
num_applications = randint(0, 5)

for _ in range(num_applications):
# Select a random user who hasn't exceeded application limit
user = choice(users)

# Check if user has reached max applications (if limit exists)
if recruitment.max_applications:
existing_apps = RecruitmentApplication.objects.filter(user=user, recruitment=recruitment, withdrawn=False).count()
if existing_apps >= recruitment.max_applications:
continue

# Check if user already applied for this position
if RecruitmentApplication.objects.filter(user=user, recruitment_position=position, withdrawn=False).exists():
continue

# Create application with transaction to ensure consistency
with transaction.atomic():
application_data = {
'recruitment_position': position,
'recruitment': recruitment,
'user': user,
'application_text': choice(APPLICATION_TEXTS),
'applicant_priority': get_priority_for_user(user, recruitment),
'recruiter_priority': choice(list(RecruitmentPriorityChoices)).value,
'recruiter_status': choice(list(RecruitmentStatusChoices)).value,
'withdrawn': False,
}

application = RecruitmentApplication.objects.create(**application_data)
created_count += 1

# Update applicant state after creation
application.update_applicant_state()

progress = ((position_index + 1) / len(positions)) * 100
yield progress, f'Processing position {position_index + 1} of {len(positions)}'

except Exception as e:
yield (position_index + 1) / len(positions), f'Error processing position {position_index + 1}: {str(e)}'
continue

yield 100, f'Successfully created {created_count} recruitment applications'
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Generated by Django 5.1.1 on 2024-11-03 02:31

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('samfundet', '0010_recruitment_promo_media'),
]

operations = [
migrations.AlterField(
model_name='recruitmentapplication',
name='applicant_priority',
field=models.PositiveIntegerField(blank=True, help_text='Applicant priority of the position(recruitment_position) to which this application related.', null=True),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='applicant_state',
field=models.IntegerField(choices=[(0, 'Unprocessed by all above on priority'), (1, 'Highest priority, and reserve'), (2, 'Highest priority, and wanted'), (3, 'Another position has this on reserve, with higher priority'), (4, 'Another position has this on reserve, with higher priority, but you have reserved'), (5, 'Another position has this on reserve, with higher priority, but you have them as wanted'), (6, 'Another position has this on reserve, with higher priority'), (7, 'Another position has this on wanted, with higher priority, but you have reserved'), (8, 'Another position has this on wanted, with higher priority, but you have them as wanted'), (10, 'Other position has priority')], default=0, help_text="Recruiter's view of the applicant's status."),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='application_text',
field=models.TextField(help_text='Motivation text submitted by the applicant'),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='interview',
field=models.ForeignKey(blank=True, help_text='Interview associated with this application.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applications', to='samfundet.interview'),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='recruiter_priority',
field=models.IntegerField(choices=[(0, 'Not Set'), (1, 'Reserve'), (2, 'Wanted'), (3, 'Not Wanted')], default=0, help_text="Recruiter's priority for this application - should not be visible to applicant"),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='recruiter_status',
field=models.IntegerField(choices=[(0, 'Not Set'), (1, 'Called and Accepted'), (2, 'Called and Rejected'), (3, 'Rejection'), (4, 'Automatic Rejection')], default=0, help_text='Current status of this application.'),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='recruitment',
field=models.ForeignKey(help_text='Recruitment to which this application is related.', on_delete=django.db.models.deletion.CASCADE, related_name='applications', to='samfundet.recruitment'),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='recruitment_position',
field=models.ForeignKey(help_text='Position to which this application is related.', on_delete=django.db.models.deletion.CASCADE, related_name='applications', to='samfundet.recruitmentposition'),
),
migrations.AlterField(
model_name='recruitmentapplication',
name='user',
field=models.ForeignKey(help_text='User who submitted this application.', on_delete=django.db.models.deletion.CASCADE, related_name='applications', to=settings.AUTH_USER_MODEL),
),
]
Loading
Loading