class EventOccurrence(PhotosMixin,OccurrenceModel): event = models.ForeignKey(Event) publish_dt = models.DateTimeField(default=datetime.datetime.now) # for rss feed get_admin_url = lambda self: "/admin/event/event/%s/"%self.event.id name_override = models.CharField(null=True,blank=True,max_length=128) name = property(lambda self: self.name_override or self.event.name) short_name = property(lambda self: self.name_override or self.event.get_short_name()) url = property(lambda self: self.url_override or self.event.url) description_override = wmd_models.MarkDownField(blank=True,null=True) description = property(lambda self: self.description_override or self.event.description) get_room = lambda self: self.event.room #! depracate me room = cached_property(lambda self: self.event.room,name="room") no_conflict = property(lambda self: self.event.no_conflict) url_override = models.CharField(max_length=256,null=True,blank=True) _get_absolute_url = lambda self: reverse('event:occurrence_detail',args=(self.id,slugify(self.name))) get_absolute_url = lambda self: self.url_override or self.event.url or self._get_absolute_url() get_absolute_url = cached_method(get_absolute_url,name="get_absolute_url") rsvp_cutoff = property(lambda self: self.start - datetime.timedelta(self.event.rsvp_cutoff)) total_rsvp = property(lambda self: sum([r.quantity for r in self.get_rsvps()])) full = property(lambda self: self.total_rsvp >= self.event.max_rsvp) icon = property(lambda self: self.event.icon) _cid = ContentType.objects.get(model="eventoccurrence").id @cached_method def get_rsvps(self): return RSVP.objects.filter(object_id=self.id,content_type_id=self._cid) def save(self,*args,**kwargs): # set the publish_dt to a week before the event self.publish_dt = self.start - datetime.timedelta(7) super(EventOccurrence,self).save(*args,**kwargs) @property def past(self): now = datetime.datetime.now() return (self.end < now) or (self.rsvp_cutoff < now and not self.get_rsvps().count()) @property def as_json(self): return { 'room_id': self.event.room_id, 'name': self.name, 'start': str(self.start), 'end': str(self.end), } class Meta: ordering = ('start',)
class Course(PhotosMixin, ToolsMixin, FilesMixin, models.Model): name = models.CharField(max_length=64) slug = property(lambda self: slugify(self.name)) active = models.BooleanField( default=True) # only used with the reshedule view _ht = "Used for the events page." short_name = models.CharField(max_length=64, null=True, blank=True, help_text=_ht) get_short_name = lambda self: self.short_name or self.name subjects = models.ManyToManyField(Subject) no_discount = models.BooleanField(default=False) @cached_property def first_room(self): return (self.courseroomtime_set.all() or [self])[0].room presentation = models.BooleanField("Evaluate Presentation", default=True) visuals = models.BooleanField("Evaluate Visuals", default=True) content = models.BooleanField("Evaluate Content", default=True) _ht = "The dashboard (/admin/) won't bug you to reschedule until after this date" reschedule_on = models.DateField(default=datetime.date.today, help_text=_ht) first_date = property(lambda self: self.active_sessions[0].first_date) last_date = property(lambda self: self.active_sessions[-1].last_date) @property def as_json(self): image = get_thumbnail(get_override(self.first_photo, 'landscape_crop'), "298x199", crop="center") out = { 'id': self.pk, 'name': self.name, 'subject_names': [s.name for s in self.subjects.all()], 'subject_ids': [s.pk for s in self.subjects.all()], 'url': self.get_absolute_url(), 'im': { 'width': image.width, 'height': image.height, 'url': image.url }, 'next_time': time.mktime(self.first_date.timetuple()) if self.active_sessions else 0, 'fee': self.fee, 'active_sessions': [s.as_json for s in self.active_sessions], 'past_session_count': len(self.archived_sessions), 'short_description': self.get_short_description(), 'requirements': self.requirements, 'no_discount': self.no_discount, } out['enrolled_status'] = "Enroll" if out[ 'active_sessions'] else "Details" return out fee = models.IntegerField(null=True, blank=True, default=0) fee_notes = models.CharField(max_length=256, null=True, blank=True) requirements = models.CharField(max_length=256, null=True, blank=True) prerequisites = models.CharField(max_length=256, null=True, blank=True) description = models.TextField(null=True, blank=True) short_description = models.TextField(null=True, blank=True) get_short_description = lambda self: self.short_description or truncatewords( striptags(self.description), 40) safety = models.BooleanField(default=False) room = models.ForeignKey(Room) def get_location_string(self): if self.first_room != self.room: s = "This class meets in the %s and then moves to the %s after a half hour lecture." return s % (self.first_room.name.lower(), self.room.name.lower()) return "This class meets in the %s." % (self.room.name.lower()) _ht = "If true, this class will not raise conflict warnings for events in the same room." no_conflict = models.BooleanField(default=False, help_text=_ht) max_students = models.IntegerField(default=16) objects = CourseManager() __unicode__ = lambda self: self.name get_absolute_url = lambda self: reverse("course:detail", args=[self.pk, self.slug]) get_admin_url = lambda self: "/admin/course/course/%s/" % self.id @cached_method def get_tools(self): criterion_ids = Criterion.objects.filter(courses=self).values_list( "id", flat=True) return Tool.objects.filter( permission__criteria__id__in=criterion_ids).distinct() @cached_property def active_sessions(self): # sessions haven't ended yet (and maybe haven't started) first_date = datetime.datetime.now() - datetime.timedelta(0.5) return list(self.sessions.filter(last_date__gte=first_date)) @property def archived_sessions(self): # opposite of active_sessions first_date = datetime.datetime.now() - datetime.timedelta(0.5) last_year = first_date - datetime.timedelta(365) sessions = self.session_set.filter(last_date__lt=first_date, last_date__gte=last_year) return list(sessions.order_by("-first_date")) sessions = lambda self: Session.objects.filter(course=self, active=True) sessions = cached_property(sessions, name="sessions") last_session = lambda self: (list(self.sessions) or [None])[-1] def save(self, *args, **kwargs): super(Course, self).save(*args, **kwargs) #this has to be repeated in the admin because of how that works subjects = self.subjects.all() for subject in subjects: if subject.parent and not (subject.parent in subjects): self.subjects.add(subject.parent) reset_classes_json("Classes reset during course save") #! inherited from section, may not be necessary def get_notes(self): notes = [] if self.requirements: notes.append(('Requirements', self.requirements)) if self.fee_notes: notes.append(('Fee Notes', self.fee_notes)) if self.safety: notes.append(( 'Safety', "This class has a 20 minute safety session before the first session." )) if self.prerequisites: notes.append(('Prerequisites', self.prerequisites)) return notes @property def list_users(self): return list(set([s.user for s in self.active_sessions])) class Meta: ordering = ("name", )
class ClassTime(OccurrenceModel): session = models.ForeignKey(Session) emailed = models.DateTimeField(null=True, blank=True) def short_name(self): times = list(self.session.classtime_set.all()) s = self.session.course.get_short_name() if len(times) != 1: s = "%s (%s/%s)" % (s, times.index(self) + 1, len(times)) if self.session.private: return "[PRIVATE] " + s return s get_absolute_url = lambda self: self.session.get_absolute_url() get_absolute_url = cached_method(get_absolute_url, name='get_absolute_url') get_admin_url = lambda self: "/admin/course/session/%s/" % self.session.id get_room = lambda self: self.session.course.room no_conflict = lambda self: self.session.course.no_conflict description = cached_property(lambda self: self.session.course.description, name="description") name = cached_property(lambda self: self.session.course.name, name="name") room = cached_property(lambda self: self.session.course.room, name="room") icon = property(lambda self: 'private' if self.session.private else 'course') @property def as_json(self): room = self.session.course.first_room return { 'room_id': self.session.course.room_id, 'session_id': self.session_id, 'name': self.short_name(), 'start': str(self.start), 'end': str(self.end), #! this should just be an id on session or course with a separate lookup 'first_room': { 'id': room.id, 'name': room.name } } @cached_method def build_class_times(self): course = self.session.course # 1-indexed day number daynumber = self.session.classtime_set.filter( start__gte=self.start).count() roomtimes = course.courseroomtime_set.filter(day=daynumber) roomtimes = roomtimes or course.courseroomtime_set.filter(day=0) if not roomtimes: return [self] out = [] current_datetime = self.start for roomtime in roomtimes: remaining = self.end - current_datetime occurrence = SessionRoomTime( start=current_datetime, end_time=(current_datetime + datetime.timedelta(0, roomtime.seconds_at) or remaining).time(), name="%s at %s for %s hours" % (course, roomtime.room, roomtime.hours_at), room=roomtime.room, session=self.session) current_datetime = occurrence.end out.append(occurrence) if occurrence.end != self.end: remaining = current_datetime - self.end occurrence = SessionRoomTime( start=current_datetime, end_time=self.end_time, room=self.room, name="%s at %s until end (%s)" % (course, course.room, occurrence.end_time), session=self.session, ) out.append(occurrence) return out class Meta: ordering = ("start", )
class Session(UserModel, PhotosMixin, models.Model): def __init__(self, *args, **kwargs): super(Session, self).__init__(*args, **kwargs) if self.pk: # this sets self.first_date to the first ClassTime.start if they aren't equal # also sets self.last_date to the last ClassTime.end # handled in the admin by /static/js/course_admin.js _a = self.all_occurrences if not _a: return if not _a[0].start == self.first_date: self.first_date = _a[0].start self.save() if not _a[-1].end == self.last_date: self.last_date = _a[-1].end self.save() get_ics_url = lambda self: reverse_ics(self) course = models.ForeignKey(Course) cancelled = models.BooleanField(default=False) active = models.BooleanField(default=True) _ht = "This session will not appear as overbooked if it is less than X seats overbooked." overbook = models.IntegerField(default=0, help_text=_ht) _ht = "Private classes cannot be signed up for and do not appear on the session page unless " \ "the user is manually enrolled. It will appear on calendar but it will be marked in red." private = models.BooleanField(default=False, help_text=_ht) notified = models.DateTimeField(null=True, blank=True) publish_dt = models.DateTimeField(null=True, blank=True) _ht = "This will be automatically updated when you save the model. Do not change" first_date = models.DateTimeField(default=datetime.datetime.now, help_text=_ht) # for filtering last_date = models.DateTimeField(default=datetime.datetime.now, help_text=_ht) # for filtering created = models.DateTimeField( auto_now_add=True) # for emailing new classes # depracated? branding = models.ForeignKey(Branding, null=True, blank=True) _ht = "Date the instructor marked students in the class as completed" instructor_completed = models.DateField(null=True, blank=True) needed = models.TextField("What is needed?", blank=True, default="") needed_completed = models.DateField(null=True, blank=True) __unicode__ = lambda self: latin1_to_ascii("%s (%s - %s)" % (self.course, self.user, self.first_date.date())) title = property(lambda self: "%s (%s)" % (self.course.name, self.first_date.date())) in_progress = property(lambda self: self.first_date < datetime.datetime. now() < self.last_date) past = property(lambda self: datetime.datetime.now() > self.last_date) closed = property(lambda self: self.cancelled or (self.past and not self.in_progress)) @property def as_json(self): short_dates = self.get_short_dates() enrolled_status = "Enrolled: %s" % short_dates if datetime.datetime.now() > self.last_date: d = self.last_date enrolled_status = "Completed: %s/%s/%s" % (d.month, d.day, d.year) closed_status = self.closed_string if (self.closed or self.full) else None if self.private: closed_status = 'private' return { 'id': self.pk, 'name': "<b>%s</b> %s" % (self.course, short_dates), 'course_name': self.course.name, 'closed_status': closed_status, 'short_dates': short_dates, 'instructor_name': self.get_instructor_name(), 'instructor_pk': self.user_id, 'course_id': self.course_id, 'enrolled_status': enrolled_status, 'classtimes': [c.as_json for c in self.classtime_set.all()], 'product_id': self.sessionproduct.id, 'private': True, } json = property(lambda self: dumps(self.as_json)) get_room = lambda self: self.course.room total_students = property( lambda self: sum([e.quantity for e in self.enrollment_set.all()])) evaluated_students = property(lambda self: self.get_evaluations().count()) completed_students = property(lambda self: self.enrollment_set.filter( completed__isnull=False).count()) full = property( lambda self: self.total_students >= self.course.max_students) list_users = property(lambda self: [self.user]) #! mucch of this if deprecated after course remodel description = property(lambda self: self.course.description) @cached_property def first_photo(self): return (self.get_photos() or [super(Session, self).first_photo])[0] @cached_method def get_photos(self): return self._get_photos() or self.course.get_photos() #calendar crap name = property(lambda self: self.course.name) all_occurrences = cached_property( lambda self: list(self.classtime_set.all()), name='all_occurrences') get_ics_url = lambda self: reverse_ics(self) @cached_method def get_week(self): sunday = self.first_date.date() - datetime.timedelta( self.first_date.weekday()) return (sunday, sunday + datetime.timedelta(6)) subjects = cached_property( lambda self: self.course.subjects.filter(parent__isnull=True), name="subjects") all_subjects = cached_property(lambda self: self.course.subjects.all(), name="all_subjects") @cached_property def related_sessions(self): sessions = Session.objects.filter( first_date__gte=datetime.datetime.now(), active=True) sessions = sessions.exclude(course=self.course) sub_subjects = self.course.subjects.filter(parent__isnull=False) sub_sessions = list( sessions.filter(course__subjects__in=sub_subjects).distinct()) if len(sub_sessions) >= 5: return sub_sessions sessions = sessions.filter(course__subjects__in=self.subjects.all()) sessions = list(sessions.exclude(course__subjects__in=sub_subjects)) return sub_sessions + sessions @property def closed_string(self): #! may be depracated if self.cancelled: return "cancelled" if self.past: return "past" return "full" def save(self, *args, **kwargs): #this may be depracated, basically the site fails hard if instructors don't have membership profiles from membership.models import UserMembership if not self.pk and self.course: c = self.course c.active = True c.save() if self.active and not self.publish_dt: publish_dt = datetime.datetime.now() profile, _ = UserMembership.objects.get_or_create(user=self.user) super(Session, self).save(*args, **kwargs) SessionProduct.objects.get_or_create(session=self)[0].update() @cached_method def get_absolute_url(self): return self.course.get_absolute_url() get_admin_url = lambda self: "/admin/course/session/%s/" % self.id get_rsvp_url = cached_method( lambda self: reverse('course:rsvp', args=[self.id]), name="get_rsvp_url") def get_instructor_name(self): if self.user.first_name and self.user.last_name: return "%s %s." % (self.user.first_name, self.user.last_name[0]) return self.user.username def get_short_dates(self): dates = [ct.start for ct in self.all_occurrences] month = None out = [] for d in dates: if month != d.month: month = d.month out.append(d.strftime("%b %e")) else: out.append(d.strftime("%e")) return ', '.join(out) def get_evaluations(self): return Evaluation.objects.filter(enrollment__session=self) def has_completed_permission(self, user): return user.is_superuser or user.is_toolmaster or user.id == self.user_id class Meta: ordering = ('first_date', )
class Level(models.Model): name = models.CharField(max_length=64) order = models.IntegerField("Level") products = cached_property( lambda self: self.product_set.filter(active=True), name="products") monthly_product = property(lambda self: self.products.filter(months=1)[0]) yearly_product = property(lambda self: self.products.filter(months=12)[0]) discount_percentage = models.IntegerField(default=0) group = models.ForeignKey(Group, null=True, blank=True) features = cached_property( lambda self: [a.feature for a in self.membershipfeature_set.all()], name="features") permission_description = models.TextField(blank=True, default="") # Corporate membership features machine_credits = models.IntegerField(default=0) simultaneous_users = models.IntegerField(default=0) cost_per_credit = models.DecimalField(max_digits=30, decimal_places=2, default=0) custom_training_cost = models.DecimalField(max_digits=30, decimal_places=2, default=0) custom_training_max = models.DecimalField(max_digits=30, decimal_places=2, default=0) # access schedule defaults tool_schedule = models.ForeignKey("tool.Schedule", null=True, blank=True, related_name="+") door_schedule = models.ForeignKey("tool.Schedule", null=True, blank=True, related_name="+") holiday_access = models.BooleanField(default=False) def get_schedule_id(self, obj): if obj._meta.model_name == 'permission': return self.tool_schedule_id elif obj._meta.model_name == 'doorgroup': try: return self.leveldoorgroupschedule_set.get( doorgroup=obj).schedule_id except LevelDoorGroupSchedule.DoesNotExist: return self.door_schedule_id def get_features(self): if not self.cost_per_credit: return self.features credits = '%s Machine credits.' % self.machine_credits if self.machine_credits: credits = "%s Monthly machine credits." % self.machine_credits discount = "%s%% Discount on all training classes" % self.discount_percentage if self.custom_training_cost: d2 = " and custom training sessions @ $%s/hr (max %s hrs per month)" d2 = d2 % (int( self.custom_training_cost), int(self.custom_training_max)) discount = discount + d2 features = [ credits, "Max %s simultaneous users." % self.simultaneous_users, discount + ".", "Machine credits can be purchased @ $%s per credit." % self.cost_per_credit, ] return [{'text': f} for f in features] + self.features @cached_property def all_users(self): return get_user_model().objects.filter(subscrition__level=self) def count_all_users(self): return self.all_users.count() def count_active_users(self): return self.all_users.filter( subscription__canceled__isnull=True).distinct().count() def profiles(self): return self.profile_set.all() class Meta: verbose_name = "Membership Level" ordering = ("order", ) __unicode__ = lambda self: self.name