Need help/suggestion on a model

Need to create Models in django for a task management app. The columns for planned_task are

  1. Task Area
  2. Task Type
  3. Task Assigned Date
  4. Assigned to
  5. Supporting member

This is the model for task plan(tasks will be followed according to this schedule)

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 model for Actual task

class ActualTask(models.Model):
    id = models.AutoField(primary_key=True)
    task_area = models.ForeignKey(Plan, null=True, blank=True, on_delete=models.CASCADE)
    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 idea is that when the team leader assigns a task it goes to the assigned list for the particular team member. Then they can add the task to their work-in-progress list. If they completed it then it will go to their completed list.(and I also want to archive the tasks once the year is completed.)

PlanTask will be a reference for ActualTasks, and I have to link PlanTask model with ActualTasks model so that we can track delay in the progress.

The “Assigned to” and “Supporting member” are foreign keys taken from Users model. But when I gave these two, I got a error

Reverse accessor ‘User.plan_set’ for ‘task_management.Plan.assigned_to’ clashes with reverse accessor for ‘task_management.Plan.supporting_member’ HINT: Add or change a related_name argument to the definition

Is this linking PlanTask with ActualTasks possible? or is there any easy way to do this?

Since nobody else has replied in this, I will take a swipe at an answer. Be nice, first time responder here. :slight_smile:

I use MySQL in my implementations, and when I don’t see an “easy” way in Django to accomplish the types of linking you are asking to do, I use database triggers in MySQL (Postgres has them too) to modify the data I need at the time of change.

My use case and hopefully this helps you:

  • When a field changes (a member is added or drops out) a database change is noted in the after insert SQL trigger (again, outside Django).
  • Once this change is detected, it notes the time of change and writes it to another table.

You can kind of equate this to the workflow you are looking at. When an action is completed, things happen.

The downside to this method is if I wanted to switch from MySQL to Postgre, I need to write the triggers in the other database language, not to mention migrate all the data, which is painful.

I prefer using all native Django code, but this was the best way to solve my problem at least. Hope it helped you or at least gave you an idea.