gapguide-api / apps /progress /models.py
arifRB's picture
Deploy GapGuide backend (Docker)
ffd36e0 verified
Raw
History Blame Contribute Delete
3.26 kB
from django.conf import settings
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils import timezone
class UserProgress(models.Model):
STATUS_CHOICES = [
('NOT_STARTED', 'Not Started'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
]
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='resource_progress',
)
resource = models.ForeignKey(
'resources.Resource',
on_delete=models.CASCADE,
related_name='user_progress',
)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='NOT_STARTED')
progress = models.IntegerField(
default=0,
validators=[MinValueValidator(0), MaxValueValidator(100)],
help_text='0-100. Derived from checkpoint rollup when checkpoints exist, else manual slider.',
)
started_at = models.DateTimeField(null=True, blank=True)
completed_at = models.DateTimeField(null=True, blank=True)
class Meta:
unique_together = [['user', 'resource']]
indexes = [models.Index(fields=['user', 'status'])]
def __str__(self):
return f'{self.user.email} - {self.resource.title} ({self.status})'
def recalculate_from_checkpoints(self, save=True):
"""Recompute status + progress from UserCheckpointProgress rollup.
Returns True when the resource has checkpoints (rollup applied); False
when the resource is manual-slider-only (caller owns status/progress).
"""
checkpoints = list(self.resource.checkpoints.all())
if not checkpoints:
return False
total = len(checkpoints)
done = UserCheckpointProgress.objects.filter(
user=self.user,
checkpoint__in=checkpoints,
completed_at__isnull=False,
).count()
self.progress = round(done / total * 100)
now = timezone.now()
if done == 0:
self.status = 'NOT_STARTED'
self.completed_at = None
elif done == total:
self.status = 'COMPLETED'
if not self.started_at:
self.started_at = now
if not self.completed_at:
self.completed_at = now
else:
self.status = 'IN_PROGRESS'
if not self.started_at:
self.started_at = now
self.completed_at = None
if save:
self.save()
return True
class UserCheckpointProgress(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='checkpoint_progress',
)
checkpoint = models.ForeignKey(
'resources.ResourceCheckpoint',
on_delete=models.CASCADE,
related_name='user_progress',
)
completed_at = models.DateTimeField(null=True, blank=True)
class Meta:
unique_together = [['user', 'checkpoint']]
indexes = [models.Index(fields=['user', 'checkpoint'])]
def __str__(self):
state = 'done' if self.completed_at else 'open'
return f'{self.user.email} - cp#{self.checkpoint_id} ({state})'