Spaces:
Runtime error
Runtime error
File size: 3,260 Bytes
ffd36e0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 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})'
|