Newer
Older
from django.db import models
from django.contrib.auth.models import User
from django.utils.timezone import now
from django.core.urlresolvers import reverse
from django.core.exceptions import ValidationError
class user_status:
requested = "requested"
active = "active"
USER_STATUS_CHOICES = (
(user_status.requested, "Requested"),
(user_status.active, "Active"),
)
user = models.OneToOneField(User, db_index=True)
status = models.CharField(max_length=50,
choices=USER_STATUS_CHOICES)
registration_key = models.CharField(max_length=50,
null=True, unique=True, db_index=True)
key_time = models.DateTimeField(default=now)
class Meta:
verbose_name_plural = "user statuses"
ordering = ("key_time",)
# }}}
identifier = models.CharField(max_length=200, unique=True,
help_text="A URL identifier. Alphanumeric with dashes, "
db_index=True)
git_source = models.CharField(max_length=200, blank=True,
help_text="A Git URL from which to pull course updates")
ssh_private_key = models.CharField(max_length=2000, blank=True,
help_text="An SSH private key to use for Git authentication")
enrollment_approval_required = models.BooleanField(
default=False)
enrollment_required_email_suffix = models.CharField(
max_length=200, blank=True, null=True)
course_robot_email_address = models.EmailField()
course_xmpp_id = models.CharField(max_length=200, blank=True)
course_xmpp_password = models.CharField(max_length=200, blank=True)
active_git_commit_sha = models.CharField(max_length=200, null=False,
blank=False)
participants = models.ManyToManyField(User,
through='Participation')
def __unicode__(self):
return self.identifier
def get_absolute_url(self):
return reverse("course.views.course_page", args=(self.identifier,))
# }}}
class TimeMark(models.Model):
"""A time mark is an identifier that can be used in datespecs in lecture content.
"""
course = models.ForeignKey(Course)
kind = models.CharField(max_length=50,
help_text="Should be lower_case_with_underscores, no spaces allowed.")
ordinal = models.IntegerField(blank=True, null=True)
time = models.DateTimeField()
class Meta:
ordering = ("course", "time")
# {{{ participation
instructor = "instructor"
teaching_assistant = "ta"
student = "student"
PARTICIPATION_ROLE_CHOICES = (
(participation_role.instructor, "Instructor"),
(participation_role.teaching_assistant, "Teaching Assistant"),
(participation_role.student, "Student"),
requested = "requested"
active = "active"
dropped = "dropped"
(participation_status.requested, "Requested"),
(participation_status.active, "Active"),
(participation_status.dropped, "Dropped"),
)
class Participation(models.Model):
user = models.ForeignKey(User)
course = models.ForeignKey(Course)
enroll_time = models.DateTimeField(default=now)
role = models.CharField(max_length=50,
temporary_role = models.CharField(max_length=50,
choices=PARTICIPATION_ROLE_CHOICES, null=True, blank=True)
time_factor = models.DecimalField(
max_digits=10, decimal_places=2,
default=1)
preview_git_commit_sha = models.CharField(max_length=200, null=True,
blank=True)
def __unicode__(self):
return "%s in %s as %s" % (
self.user, self.course, self.role)
class Meta:
unique_together = (("user", "course"),)
class InstantFlowRequest(models.Model):
course = models.ForeignKey(Course)
flow_id = models.CharField(max_length=200)
start_time = models.DateTimeField(default=now)
end_time = models.DateTimeField()
# {{{ flow visit tracking
class flow_visit_state:
in_progress = "in_progress"
expired = "expired"
completed = "completed"
FLOW_VISIT_STATE_CHOICES = (
(flow_visit_state.in_progress, "In progress"),
(flow_visit_state.expired, "Expired"),
(flow_visit_state.completed, "Completed"),
)
participation = models.ForeignKey(Participation, null=True, blank=True)
active_git_commit_sha = models.CharField(max_length=200)
start_time = models.DateTimeField(default=now)
completion_time = models.DateTimeField(null=True, blank=True)
page_count = models.IntegerField(null=True, blank=True)
stipulations = JSONField(blank=True, null=True)
state = models.CharField(max_length=50, choices=FLOW_VISIT_STATE_CHOICES)
class Meta:
ordering = ("participation",)
class FlowPageData(models.Model):
flow_visit = models.ForeignKey(FlowVisit)
group_id = models.CharField(max_length=200)
data = JSONField(null=True, blank=True)
class Meta:
unique_together = (("flow_visit", "ordinal"),)
verbose_name_plural = "flow page data"
def __unicode__(self):
return "Data for %s's visit %d to '%s/%s' in '%s'" % (
self.flow_visit.participation.user,
self.flow_visit.id,
self.group_id,
self.page_id,
self.flow_visit.flow_id)
# Django's templates are a little daft.
def previous_ordinal(self):
return self.ordinal - 1
def next_ordinal(self):
return self.ordinal + 1
class FlowPageVisit(models.Model):
# This is redundant (because the FlowVisit is available through
# page_data), but it helps the admin site understand the link
# and provide editing.
flow_visit = models.ForeignKey(FlowVisit)
page_data = models.ForeignKey(FlowPageData, db_index=True)
visit_time = models.DateTimeField(default=now, db_index=True)
# }}}
# {{{ flow access
class flow_permission:
# access flow start page
view = "view"
# review past attempts
view_past = "view_past"
# start new for-credit visit
start_credit = "start_credit"
# start new not-for-credit visit
start_no_credit = "start_no_credit"
# see correct answer
see_correct_answer = "see_correct_answer"
FLOW_PERMISSION_CHOICES = (
(flow_permission.view, "View flow start page"),
(flow_permission.view_past, "Review past attempts"),
(flow_permission.start_credit, "Start for-credit visit"),
(flow_permission.start_no_credit, "Start not-for-credit visit"),
(flow_permission.see_correct_answer, "See correct answer"),
)
class FlowAccessException(models.Model):
participation = models.ForeignKey(Participation, db_index=True)
flow_id = models.CharField(max_length=200, blank=False, null=False)
expiration = models.DateTimeField(blank=True, null=True)
stipulations = JSONField(blank=True, null=True)
creator = models.ForeignKey(User, null=True)
creation_time = models.DateTimeField(default=now, db_index=True)
def __unicode__(self):
return "Access exception for '%s' to '%s' in '%s'" % (
self.participation.user, self.flow_id,
self.participation.course)
class FlowAccessExceptionEntry(models.Model):
exception = models.ForeignKey(FlowAccessException,
related_name="entries")
permission = models.CharField(max_length=50,
choices=FLOW_PERMISSION_CHOICES)
def __unicode__(self):
return self.permission
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# }}}
# {{{ grading
class GradingOpportunity(models.Model):
course = models.ForeignKey(Course)
identifier = models.CharField(max_length=200, blank=False, null=False,
help_text="A symbolic name for this grade. "
"lower_case_with_underscores, no spaces.")
name = models.CharField(max_length=200, blank=False, null=False,
help_text="A human-readable identifier for the grade.")
flow_id = models.CharField(max_length=200, blank=True, null=True,
help_text="Flow identifier that this grading opportunity "
"is linked to, if any")
max_points = models.DecimalField(max_digits=10, decimal_places=2)
due_time = models.DateTimeField(default=None, blank=True, null=True)
class Meta:
verbose_name_plural = "grading opportunities"
ordering = ("course", "due_time", "identifier")
def __unicode__(self):
return "%s in %s" % (self.name, self.course)
class grade_state_change_types:
grading_started = "grading_started"
graded = "graded"
retrieved = "retrieved"
unavailable = "unavailable"
extension = "extension"
report_sent = "report_sent"
do_over = "do_over"
exempt = "exempt"
GRADE_STATE_CHANGE_CHOICES = (
(grade_state_change_types.grading_started, 'Grading started'),
(grade_state_change_types.graded, 'Graded'),
(grade_state_change_types.retrieved, 'Retrieved'),
(grade_state_change_types.unavailable, 'Unavailable'),
(grade_state_change_types.extension, 'Extension'),
(grade_state_change_types.report_sent, 'Report sent'),
(grade_state_change_types.do_over, 'Do-over'),
(grade_state_change_types.exempt, 'Exempt'),
)
class GradeChange(models.Model):
opportunity = models.ForeignKey(GradingOpportunity)
participation = models.ForeignKey(Participation)
state = models.CharField(max_length=50,
choices=GRADE_STATE_CHANGE_CHOICES)
points = models.DecimalField(max_digits=10, decimal_places=2,
blank=True, null=True)
comment = models.TextField(blank=True, null=True)
due_time = models.DateTimeField(default=None, blank=True, null=True)
creator = models.ForeignKey(User, null=True)
grade_time = models.DateTimeField(default=now, db_index=True)
class Meta:
ordering = ("opportunity", "participation", "grade_time")
def __unicode__(self):
return "%s %s on %s" % (self.participation, self.state,
self.opportunity.name)
def clean(self):
if self.opportunity.course != self.participation.course:
raise ValidationError("Participation and opportunity must live "
"in the same course")
# }}}
# vim: foldmethod=marker