I am developing a Django task management app where I need to create two models: one for planned tasks (PlanTask) and one for actual tasks (ActualTask).
The PlanTask model includes fields like Task Area, Task Type, Task Assigned Date, Assigned to, and Supporting member. The ActualTask model references PlanTask and calculates various progress metrics (planning, field work, reporting, and approval progress) based on several DecimalFields and date fields. The models are defined as follows:
class PlanTask(models.Model):
id = models.AutoField(primary_key=True)
task_area = models.CharField(max_length=255, null=False)
task_type = models.CharField(max_length=255, null=False)
task_assigned_date = models.DateField(null=False)
assigned_to = models.ForeignKey(User, null=True, blank=True, on_delete=models.DO_NOTHING)
supporting_member = models.ForeignKey(User, null=True, blank=True, on_delete=models.DO_NOTHING)
planned_start_date = models.DateField(null=True, blank=True)
planned_planning_start = models.DateField(null=True, blank=True)
planned_planning_end = models.DateField(null=True, blank=True)
planned_field_work_start = models.DateField(null=True, blank=True)
planned_field_work_end = models.DateField(null=True, blank=True)
planned_reporting_start = models.DateField(null=True, blank=True)
planned_reporting_end = models.DateField(null=True, blank=True)
planned_approval_start = models.DateField(null=True, blank=True)
planned_approval_end = models.DateField(null=True, blank=True)
planned_end_date = models.DateField(null=True, blank=True)
This is the ActualTask model:
class ActualTask(models.Model):
id = models.AutoField(primary_key=True)
task_area = models.ForeignKey(PlanTask, null=True, blank=True, on_delete=models.CASCADE) # Updated to correct model
STATUS_CHOICES = [
('Assigned', 'Assigned'),
('WIP', 'WIP'),
('Completed', 'Completed'),
]
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='Assigned', null=False)
planning_stage = models.CharField(max_length=255, null=True, blank=True)
research_planning = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
task_plan = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
initial_task_meeting = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
process_walkover = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
@property
def planning_progress(self):
return (self.research_planning or 0) + (self.task_plan or 0) + (self.initial_task_meeting or 0) + (
self.process_walkover or 0)
field_work_stage = models.CharField(max_length=255, null=True, blank=True)
documents_verification = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
research_work_data_analysis = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
research_work_field = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
query_clarification = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
@property
def field_work_progress(self):
return (self.documents_verification or 0) + (self.research_work_data_analysis or 0) + (
self.research_work_field or 0) + (self.query_clarification or 0)
reporting_stage = models.CharField(max_length=255, null=True, blank=True)
drafting_report = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
task_presentation = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
taskee_feedback_pending = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
final_presentation = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
@property
def reporting_progress(self):
return (self.drafting_report or 0) + (self.task_presentation or 0) + (self.taskee_feedback_pending or 0) + (
self.final_presentation or 0)
approval_stage = models.CharField(max_length=255, null=True, blank=True)
mgmt_approval = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
ic_to_taskee = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
gr_to_taskee = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
completed = models.DecimalField(max_digits=5, decimal_places=2,
choices=[(0, '0'), (10, '10'), (20, '20'), (25, '25')],
validators=[MaxValueValidator(25)], null=True, blank=True)
@property
def approval_progress(self):
return (self.mgmt_approval or 0) + (self.ic_to_taskee or 0) + (self.gr_to_taskee or 0) + (self.completed or 0)
@property
def overall_progress(self):
return (self.planning_progress * 0.15) + (self.field_work_progress * 0.5) + (self.reporting_progress * 0.25) + (
self.approval_progress * 0.1)
actual_start_date = models.DateField(null=True, blank=True)
actual_planning_start = models.DateField(null=True, blank=True)
actual_planning_end = models.DateField(null=True, blank=True)
delay_planning = models.IntegerField(default=0)
actual_field_work_start = models.DateField(null=True, blank=True)
actual_field_work_end = models.DateField(null=True, blank=True)
delay_field_work = models.IntegerField(default=0)
actual_reporting_start = models.DateField(null=True, blank=True)
actual_reporting_end = models.DateField(null=True, blank=True)
delay_reporting = models.IntegerField(default=0)
actual_approval_start = models.DateField(null=True, blank=True)
actual_approval_end = models.DateField(null=True, blank=True)
delay_approval = models.IntegerField(default=0)
actual_end_date = models.DateField(null=True, blank=True)
delay = models.IntegerField(default=0)
def save(self, *args, **kwargs):
# Update actual dates and delays
if self.status == 'WIP' and not self.actual_start_date:
self.actual_start_date = timezone.now().date()
if self.planning_progress > 0 and not self.actual_planning_start:
self.actual_planning_start = timezone.now().date()
if self.planning_progress == 100 and not self.actual_planning_end:
self.actual_planning_end = timezone.now().date()
if self.field_work_progress > 0 and not self.actual_field_work_start:
self.actual_field_work_start = timezone.now().date()
if self.field_work_progress == 100 and not self.actual_field_work_end:
self.actual_field_work_end = timezone.now().date()
if self.reporting_progress > 0 and not self.actual_reporting_start:
self.actual_reporting_start = timezone.now().date()
if self.reporting_progress == 100 and not self.actual_reporting_end:
self.actual_reporting_end = timezone.now().date()
if self.approval_progress > 0 and not self.actual_approval_start:
self.actual_approval_start = timezone.now().date()
if self.approval_progress == 100 and not self.actual_approval_end:
self.actual_approval_end = timezone.now().date()
if self.status == 'Completed' and not self.actual_end_date:
self.actual_end_date = timezone.now().date()
# Calculate delays
# Assuming planned_* fields are provided and should be datetime.date objects
today = timezone.now().date()
if hasattr(self, 'planned_planning_end') and self.planned_planning_end:
self.delay_planning = max((today - self.planned_planning_end).days,
0) if self.actual_planning_end is None else max(
(self.actual_planning_end - self.planned_planning_end).days, 0)
if hasattr(self, 'planned_field_work_end') and self.planned_field_work_end:
self.delay_field_work = max((today - self.planned_field_work_end).days,
0) if self.actual_field_work_end is None else max(
(self.actual_field_work_end - self.planned_field_work_end).days, 0)
if hasattr(self, 'planned_reporting_end') and self.planned_reporting_end:
self.delay_reporting = max((today - self.planned_reporting_end).days,
0) if self.actual_reporting_end is None else max(
(self.actual_reporting_end - self.planned_reporting_end).days, 0)
if hasattr(self, 'planned_approval_end') and self.planned_approval_end:
self.delay_approval = max((today - self.planned_approval_end).days,
0) if self.actual_approval_end is None else max(
(self.actual_approval_end - self.planned_approval_end).days, 0)
self.delay = self.delay_planning + self.delay_field_work + self.delay_reporting + self.delay_approval
super().save(*args, **kwargs)
def __str__(self):
return f"task {self.id}: {self.task_area}"
My intention is to have team leaders assign tasks which then appear on the assigned list for particular team members. Team members can move a task to their work-in-progress list and later to a completed list once done. Additionally, I plan to archive the tasks when the year concludes. The PlanTask model acts as a reference for tracking progress and delays in the ActualTask model.
However, using two ForeignKey fields (assigned_to and supporting_member) that both reference the User model produces the following error:
Reverse accessor ‘User.plan_set’ for ‘task_management.PlanTask.assigned_to’ clashes with reverse accessor for ‘task_management.PlanTask.supporting_member’
HINT: Add or change a related_name argument to the definition.
Is it possible to link the PlanTask and ActualTask models in this way? What is the best approach to resolve this reverse accessor clash when using multiple ForeignKey fields to the same model?