Beispiel #1
0
class PrintRequest(models.Model):
    printer = models.ForeignKey(
        Printer, blank=True,
        null=True)  #   Leave blank to allow any printer to be used.
    user = AjaxForeignKey(ESPUser)
    time_requested = models.DateTimeField(auto_now_add=True)
    time_executed = models.DateTimeField(blank=True, null=True)
Beispiel #2
0
class NavBarEntry(models.Model):
    """ An entry for the secondary navigation bar """

    path = AjaxForeignKey(DataTree,
                          related_name='navbar',
                          blank=True,
                          null=True)

    sort_rank = models.IntegerField()
    link = models.CharField(max_length=256, blank=True, null=True)
    text = models.CharField(max_length=64)
    indent = models.BooleanField()

    category = models.ForeignKey(NavBarCategory,
                                 default=NavBarCategory.default)

    def can_edit(self, user):
        return user.isAdmin()

    def __unicode__(self):
        return u'%s:%s (%s) [%s]' % (self.category, self.sort_rank, self.text,
                                     self.link)

    def makeTitle(self):
        return self.text

    def makeUrl(self):
        return self.link

    def is_link(self):
        return (self.link is not None) and (len(self.link) > 0)

    class Meta:
        verbose_name_plural = 'Nav Bar Entries'
Beispiel #3
0
class Survey(models.Model):
    """ A single survey. """
    name = models.CharField(max_length=255)
    anchor = AjaxForeignKey(DataTree, related_name='surveys',
                            help_text="Usually the program.")

    category = models.CharField(max_length=32) # teach|learn|etc
    
    def __unicode__(self):
        return '%s (%s) for %s' % (self.name, self.category, unicode(self.anchor))
    
    def num_participants(self):
        #   If there is a program anchored to the anchor, select the appropriate number
        #   of participants based on the category.
        from esp.program.models import Program
        
        progs = Program.objects.filter(anchor=self.anchor)
        if progs.count() == 1:
            prog = progs[0]
            if self.category == 'teach':
                return prog.teachers()['class_approved'].count()
            elif self.category == 'learn':
                return prog.students()['confirmed'].count()
            else:
                return 0
        else:
            return 0
Beispiel #4
0
class StudentAppReview(BaseAppElement, models.Model):
    """ An individual review for a student application question.
    The application can be reviewed by any director of the program or
    teacher of a class for which the student applied. """

    reviewer = AjaxForeignKey(ESPUser, editable=False)
    date = models.DateTimeField(default=datetime.datetime.now, editable=False)
    score = models.PositiveIntegerField(null=True,
                                        blank=True,
                                        help_text='Please rate each student',
                                        choices=((10, "Yes"), (5, "Maybe"),
                                                 (1, "No")))
    comments = models.TextField()
    reject = models.BooleanField(default=False, editable=False)

    _element_name = 'review'
    _field_names = ['score', 'comments', 'reject']

    def __unicode__(self):
        return '%s by %s: %s...' % (self.score, self.reviewer.username,
                                    self.comments[:80])

    class Meta:
        app_label = 'program'
        db_table = 'program_studentappreview'
Beispiel #5
0
class AJAXChangeLog(models.Model):
    # program this change log stores changes for
    program = AjaxForeignKey(Program)

    # many to many for entries in this change log
    entries = models.ManyToManyField(AJAXChangeLogEntry)

    # log entries older than this are deleted
    max_log_age = timedelta(hours=12).total_seconds()

    def update(self, program):
        self.program = program
        self.age = time.time()

    def prune(self):
        max_time = time.time() - self.max_log_age
        self.entries.filter(time__lte=max_time).delete()
        self.save()

    def append(self, entry, user=None):
        entry.index = self.get_latest_index() + 1
        if user:
            entry.user = user

        entry.save()
        self.save()
        self.entries.add(entry)
        self.save()

    def appendScheduling(self, timeslots, room_name, cls_id, user=None):
        entry = AJAXChangeLogEntry()
        entry.setScheduling(timeslots, room_name, cls_id)
        self.append(entry, user)

    def appendComment(self, comment, lock, cls_id, user=None):
        entry = AJAXChangeLogEntry()
        entry.setComment(comment, lock, cls_id)
        self.append(entry, user)

    def get_latest_index(self):
        index = self.entries.all().aggregate(models.Max('index'))['index__max']

        if index is None:
            index = 0

        return index

    def get_earliest_index(self):
        return self.entries.all().aggregate(models.Min('index'))['index__min']

    def get_log(self, last_index):
        new_entries = self.entries.filter(
            index__gt=last_index).order_by('index')
        entry_list = list()

        for entry in new_entries:
            entry_list.append(entry.toDict())

        return entry_list
Beispiel #6
0
class NavBarEntry(models.Model):
    """ An entry for the secondary navigation bar """
    
    #   ONLY the program related nav bars (i.e. "Splash Registration pages") should be anchored.
    #   This is to allow automatically generated links to appear.
    path = AjaxForeignKey(DataTree, related_name = 'navbar', blank=True, null=True)
    
    sort_rank = models.IntegerField()
    link = models.CharField(max_length=256, blank=True, null=True)
    text = models.CharField(max_length=64)
    indent = models.BooleanField()

    category = models.ForeignKey(NavBarCategory, default=NavBarCategory.default)

    def can_edit(self, user):
        return UserBit.UserHasPerms(user, self.path, GetNode('V/Administer/Edit/QSD'))
    
    def __unicode__(self):
        return u'%s:%s (%s) [%s]' % (self.category, self.sort_rank, self.text, self.link)

    def makeTitle(self):
        return self.text

    def makeUrl(self):
        return self.link
    
    def is_link(self):
        return (self.link is not None) and (len(self.link) > 0)
    
    class Meta:
        verbose_name_plural = 'Nav Bar Entries'

    def save(self, *args, **kwargs):
        from django.core.cache import cache

        super(NavBarEntry, self).save(*args, **kwargs)
        
        cache.delete('LEFTBAR')

    
    @staticmethod
    def find_by_url_parts(parts):
        """ Fetch a QuerySet of NavBarEntry objects by the url parts """
        # Get the Q_Web root
        Q_Web = GetNode('Q/Web')
        
        # Remove the last component
        parts.pop()
        
        # Find the branch
        try:
            branch = Q_Web.tree_decode( parts )
        except DataTree.NoSuchNodeException, ex:
            branch = ex.anchor
            if branch is None:
                raise NavBarEntry.DoesNotExist
            
        # Find the valid entries
        return NavBarEntry.objects.filter(QTree(path__above =branch)).order_by('sort_rank')
Beispiel #7
0
class LineItemType(models.Model):
    """ A set of default values for a line item """
    text = models.TextField() # description of line item
    amount = models.DecimalField(help_text='This should be negative for student costs charged to a program.', max_digits=9, decimal_places=2, default=Decimal('0.0')) # default amount
    anchor = AjaxForeignKey(DataTree,related_name='accounting_lineitemtype',null=True) # account to post the line item
    finaid_amount = models.DecimalField(max_digits=9, decimal_places=2, default=Decimal('0.0')) # amount after financial aid
    finaid_anchor = AjaxForeignKey(DataTree,null=True,related_name='accounting_finaiditemtype')
    
    def negative_amount(self):
        return -self.amount

    objects = LineItemTypeManager()
    
    def __unicode__(self):
        #if self.anchor: url = self.anchor.get_uri()
        #else: url = 'NULL'
        return "LIType:%s" % (self.id)
Beispiel #8
0
class LineItem(models.Model):
    """ A transaction line item """
    transaction = models.ForeignKey(Transaction)
    user = AjaxForeignKey(ESPUser,related_name='accounting_lineitem')
    anchor = AjaxForeignKey(DataTree,related_name='accounting_lineitem')
    amount = models.DecimalField(max_digits=9, decimal_places=2)
    text = models.TextField()
    li_type = models.ForeignKey(LineItemType)
    posted_to = models.ForeignKey(Balance, blank=True, null=True)
    
    objects = LineItemManager()

    def negative_amount(self):
        return -(self.amount)

    def __unicode__(self):
        return "L-%u (T-%u): %.02f %s - %s, %s" % (self.id, self.transaction.id, self.amount, self.anchor.uri, self.user.username, self.text)
Beispiel #9
0
class SATPrepTeacherModuleInfo(models.Model):
    from esp.users.models import User
    from esp.program.models import Program
    """ Module that links a user with a program and has SATPrep teacher info"""
    SAT_SUBJECTS = (('M', 'Math'), ('V', 'Verbal'), ('W', 'Writing'))

    SUBJECT_DICT = {'M': 'Math', 'V': 'Verbal', 'W': 'Writing'}
    #   This is the unanimous decision of the ESP office, as of 9:30pm Thursday Feb 20, 2009.
    #   Old category labels are kept commented below.   -Michael P
    SECTION_DICT = {
        'A': 'Java',
        'B': 'Python',
        'C': 'QB',
        'D': 'C++',
        'E': 'MATLAB',
        'F': 'Scheme',
        'G': 'SQL'
    }
    #   SECTION_DICT = {'A': 'Java', 'B': 'Python', 'C': 'QB', 'D': 'C++', 'E': 'MATLAB', 'F': 'Scheme'}
    #    SECTION_DICT = {'A': 'Libra', 'B': 'Scorpio', 'C': 'Sagittarius', 'D': 'Capricorn', 'E': 'Aquarius', 'F': 'Pisces'}
    #   SECTION_DICT = {'A': 'Helium', 'B': 'Neon', 'C': 'Argon', 'D': 'Krypton', 'E': 'Xenon', 'F': 'Radon'}
    #   SECTION_DICT = {'A': 'Mercury', 'B': 'Venus', 'C': 'Mars', 'D': 'Jupiter', 'E': 'Saturn', 'F': 'Neptune'}
    #   SECTION_DICT = {'A': 'Red', 'B': 'Orange', 'C': 'Yellow', 'D': 'Green', 'E': 'Blue', 'F': 'Violet'}

    sat_math = models.PositiveIntegerField(blank=True, null=True)
    sat_writ = models.PositiveIntegerField(blank=True, null=True)
    sat_verb = models.PositiveIntegerField(blank=True, null=True)

    mitid = models.PositiveIntegerField(blank=True, null=True)

    subject = models.CharField(max_length=32, choices=SAT_SUBJECTS)

    user = AjaxForeignKey(ESPUser, blank=True, null=True)
    program = models.ForeignKey(Program, blank=True, null=True)
    section = models.CharField(max_length=5)

    def __unicode__(self):
        return 'SATPrep Information for teacher %s in %s' % \
                 (str(self.user), str(self.program))

    class Meta:
        unique_together = ('user', 'program')

    def get_subject_display(self):
        if self.subject in SATPrepTeacherModuleInfo.SUBJECT_DICT:
            return SATPrepTeacherModuleInfo.SUBJECT_DICT[self.subject]
        else:
            return 'Unknown'

    def get_section_display(self):
        if self.section in SATPrepTeacherModuleInfo.SECTION_DICT:
            return SATPrepTeacherModuleInfo.SECTION_DICT[self.section]
        else:
            return 'Unknown'

    @staticmethod
    def subjects():
        return SATPrepTeacherModuleInfo.SAT_SUBJECTS
Beispiel #10
0
class QSDBulkMoveForm(forms.Form):
    id_list = forms.CharField(widget=forms.HiddenInput)
    destination_path = AjaxForeignKeyNewformField(field=AjaxForeignKey(DataTree), label='Destination path', field_name='destination_path', help_text='Begin to type the tree location of the destination (starting with \'Q/\') and select the proper choice from the list that appears.')
    alter_destinations = forms.BooleanField(required=False, initial=False, help_text='Check this only if you would like to change the section of the site (teach, learn, etc.) that the pages appear in.')
    destination_section = forms.ChoiceField(required=False, choices=section_choices, help_text='Choose \'None\' to eliminate the pages\' section designations, which will place them under /programs/ if they are associated with a program.')

    def load_data(self, qsd_list):
        anchor = destination_path(qsd_list)
        if anchor:
            self.fields['id_list'].initial = ','.join([str(q.id) for q in qsd_list])
            self.fields['destination_path'].initial = anchor.id
            return anchor
        else:
            return False
        
    def save_data(self):
        qsd_list = QuasiStaticData.objects.filter(id__in=[int(x) for x in self.cleaned_data['id_list'].split(',')])
        orig_anchor = destination_path(qsd_list)
        
        #   For each QSD in the list:
        for main_qsd in qsd_list:
            uri_orig = main_qsd.path.get_uri()
            #   Find its URI relative to the original anchor
            uri_relative = uri_orig[(len(orig_anchor.get_uri())):]
            #   Add that URI to that of the new anchor
            dest_tree = self.cleaned_data['destination_path']
            uri_new = dest_tree.get_uri() + uri_relative
            #   Set the path
            other_qsds = QuasiStaticData.objects.filter(path=main_qsd.path, name=main_qsd.name)
            for qsd in other_qsds:
                #   Move them over
                qsd.path = DataTree.get_by_uri(uri_new, create=True)
            
                #   Now update the name if need be
                if self.cleaned_data['alter_destinations']:
                    name_parts = qsd.name.split(':')
                    if len(name_parts) > 1:
                        orig_section = name_parts[0]
                        orig_name = name_parts[1]
                    else:
                        orig_section = ''
                        orig_name = name_parts[0]
                        
                    new_section = self.cleaned_data['destination_section']
                    
                    if len(new_section) > 0:
                        qsd.name = new_section + ':' + orig_name
                    else:
                        qsd.name = orig_name
                        
                qsd.save()
Beispiel #11
0
class Entry(models.Model):
    """ A Markdown-encoded miniblog entry """
    title = models.CharField(
        max_length=256
    )  # Plaintext; shouldn't contain HTML, for security reasons, though HTML will probably be passed through intact
    slug = models.SlugField(default="General",
                            help_text="(will determine the URL)")

    timestamp = models.DateTimeField(default=datetime.datetime.now,
                                     editable=False)
    highlight_begin = models.DateTimeField(
        blank=True,
        null=True,
        help_text="When this should start being showcased.")
    highlight_expire = models.DateTimeField(
        blank=True,
        null=True,
        help_text="When this should stop being showcased.")
    content = models.TextField(
        help_text='Yes, you can use markdown.')  # Markdown-encoded
    sent = models.BooleanField(editable=False, default=False)
    email = models.BooleanField(editable=False, default=False)
    fromuser = AjaxForeignKey(ESPUser, blank=True, null=True, editable=False)
    fromemail = models.CharField(max_length=80,
                                 blank=True,
                                 null=True,
                                 editable=False)
    priority = models.IntegerField(
        blank=True, null=True
    )  # Message priority (role of this field not yet well-defined -- aseering 8-10-2006)
    section = models.CharField(max_length=32,
                               blank=True,
                               null=True,
                               help_text="e.g. 'teach' or 'learn' or blank")

    def __unicode__(self):
        if self.slug:
            return "%s" % (self.slug, )
        else:
            return "%s" % (self.title, )

    def html(self):
        return markdown(self.content)

    def makeTitle(self):
        return self.title

    class Meta:
        verbose_name_plural = 'Entries'
        ordering = ['-timestamp']
Beispiel #12
0
class FinancialAidGrant(models.Model):
    request = AjaxForeignKey(FinancialAidRequest)
    amount_max_dec = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True, help_text='Enter a number here to grant a dollar value of financial aid.  The grant will cover this amount or the full cost of the program, whichever is less.')
    percent = models.PositiveIntegerField(blank=True, null=True, help_text='Enter an integer between 0 and 100 here to grant a certain percentage discount to the program after the above dollar credit is applied.  0 means no additional discount, 100 means no payment required.')
    timestamp = models.DateTimeField(auto_now=True)
    finalized = models.BooleanField(default=False)

    @property
    def amount_max(self):
        if self.amount_max_dec is None:
            return None
        else:
            return float(self.amount_max_dec)

    @property
    def user(self):
        return self.request.user
    @property
    def program(self):
        return self.request.program

    def finalize(self):
        #   Create a transfer for the amount of this grant
        if self.finalized:
            return

        from esp.accounting.controllers import IndividualAccountingController
        iac = IndividualAccountingController(self.program, self.user)
        source_account = iac.default_finaid_account()
        dest_account = iac.default_source_account()
        line_item_type = iac.default_finaid_lineitemtype()

        (transfer, created) = Transfer.objects.get_or_create(source=source_account, destination=dest_account, user=self.user, line_item=line_item_type, amount_dec=iac.amount_finaid())
        self.finalized = True
        self.save()
        return transfer

    def __unicode__(self):
        if self.percent and self.amount_max_dec:
            return u'Grant %s (max $%s, %d%% discount) at %s' % (self.user, self.amount_max_dec, self.percent, self.program)
        elif self.percent:
            return u'Grant %s (%d%% discount) at %s' % (self.user, self.percent, self.program)
        elif self.amount_max_dec:
            return u'Grant %s (max $%s) at %s' % (self.user, self.amount_max_dec, self.program)
        else:
            return u'Grant %s (no aid specified) at %s' % (self.user, self.program)

    class Meta:
        unique_together = ('request',)
Beispiel #13
0
class AnnouncementLink(models.Model):
    anchor = AjaxForeignKey(DataTree)
    title = models.CharField(max_length=256)
    category = models.CharField(max_length=32)  # Plaintext
    timestamp = models.DateTimeField(default=datetime.datetime.now,
                                     editable=False)
    highlight_begin = models.DateTimeField(
        blank=True,
        null=True,
        help_text="When this should start being showcased.")
    highlight_expire = models.DateTimeField(
        blank=True,
        null=True,
        help_text="When this should stop being showcased.")
    section = models.CharField(max_length=32,
                               blank=True,
                               null=True,
                               help_text="e.g. 'teach' or 'learn' or blank")
    href = models.URLField(help_text="The URL the link should point to.")

    def __unicode__(self):
        return "%s (links to %s)" % (self.title, self.href)

    def get_absolute_url(self):
        return self.href

    makeUrl = get_absolute_url

    def makeTitle(self):
        return self.title

    def content(self):
        return '<a href="%s">Click Here</a> for details' % self.href

    @staticmethod
    def find_posts_by_perms(user, verb, qsc=None):
        """ Fetch a list of relevant posts for a given user and verb """
        if qsc == None:
            return UserBit.find_by_anchor_perms(AnnouncementLink, user, verb)
        else:
            return UserBit.find_by_anchor_perms(AnnouncementLink,
                                                user,
                                                verb,
                                                qsc=qsc)

    def html(self):
        return '<p><a href="%s">%s</a></p>' % (self.href, self.title)
Beispiel #14
0
class PurchaseOrder(models.Model):
    """ A purchase order available for invoicing in a given accounting ledger """
    anchor = AjaxForeignKey(DataTree)
    address = models.TextField()
    fax = models.CharField(blank=True, max_length=16)
    phone = models.CharField(blank=True, max_length=16)
    email = models.EmailField(blank=True)
    reference = models.TextField()
    rules = models.TextField()
    notes = models.TextField()

    def __unicode__(self):
        return u'PurchaseOrder account %u (ref: %s)' % (self.id,
                                                        self.reference)

    class Admin:
        pass
Beispiel #15
0
class AJAXSectionDetail(models.Model):
    program = AjaxForeignKey(Program)
    cls_id = models.IntegerField()
    comment = models.CharField(max_length=256)
    locked = models.BooleanField(default=False)

    def initialize(self, program, cls_id, comment, locked):
        self.program = program
        self.cls_id = cls_id
        self.comment = comment
        self.locked = locked
        self.save()

    def update(self, comment, locked):
        self.comment = comment
        self.locked = locked
        self.save()
Beispiel #16
0
class DataTree(models.Model):

    lock_choices = (
        (0, "UNLOCKED"),
        (1, "SOFT LOCK"),
        (2, "HARD LOCK"),
    )

    # some fields
    name = models.CharField(max_length=64)
    friendly_name = models.TextField()
    parent = AjaxForeignKey('self',
                            blank=True,
                            null=True,
                            related_name='child_set')
    rangestart = models.IntegerField(editable=False)
    rangeend = models.IntegerField(editable=False)
    uri = models.CharField(editable=False, max_length=1024)
    #^ a charfield for indexing purposes
    uri_correct = models.BooleanField(editable=False, default=False)
    lock_table = models.IntegerField(editable=False,
                                     default=0,
                                     choices=lock_choices)
    range_correct = models.BooleanField(editable=False, default=True)

    class Meta:
        # parent and name should be unique
        unique_together = (("name", "parent"), )
        # ordering should be by rangestart
        #ordering = ['rangestart','-rangeend']

    class Admin:
        ordering = ('rangestart', '-rangeend')

    def delete(self, recurse=False, superdelete=False):
        raise Exception('Attempted to delete a DataTree')

    def save(self,
             create_root=False,
             uri_fix=False,
             old_save=False,
             start_size=None,
             *args,
             **kwargs):
        raise Exception('Attempted to save a DataTree')
Beispiel #17
0
class Comment(models.Model):

    author = AjaxForeignKey(ESPUser)
    entry = models.ForeignKey(Entry)

    post_ts = models.DateTimeField(default=datetime.datetime.now,
                                   editable=False)

    subject = models.CharField(max_length=256)

    content = models.TextField(help_text="HTML not allowed.")

    def __unicode__(self):
        return 'Comment for %s by %s on %s' % (self.entry, self.author,
                                               self.post_ts.date())

    class Meta:
        ordering = ['-post_ts']
Beispiel #18
0
class Answer(models.Model):
    """ An answer for a single question for a single survey response. """

    survey_response = models.ForeignKey(SurveyResponse, db_index=True,
                                        related_name='answers')
    anchor = AjaxForeignKey(DataTree)                                        
    question = models.ForeignKey(Question, db_index=True)
    value = models.TextField()

    def _answer_getter(self):
        """ The actual, unpickled answer. """
        if not self.value:
            return None
        if hasattr(self, '_answer'):
            return self._answer

        if self.value[0] == '+':
            try:
                value = pickle.loads(str(self.value[1:]))
            except:
                value = self.value[1:]
        else:
            value = self.value[1:]

        self._answer = value
        return value

    def _answer_setter(self, value):
        self._answer = value
        if not isinstance(value, basestring):
            self.value = '+' + pickle.dumps(value)
        else:
            self.value = ':' + value

    answer = property(_answer_getter, _answer_setter)

    class Admin:
        pass

    def __unicode__(self):
        return "Answer for question #%d: %s" % (self.question.id, self.value)
Beispiel #19
0
class RemoteProfile(models.Model):
    from esp.users.models import User
    from esp.program.models import Program
    from esp.cal.models import Event

    user = AjaxForeignKey(ESPUser, blank=True, null=True)
    program = models.ForeignKey(Program, blank=True, null=True)
    volunteer = models.BooleanField(default=False)
    need_bus = models.BooleanField(default=False)
    bus_runs = models.ManyToManyField(DataTree,
                                      related_name="bus_teachers",
                                      blank=True)
    volunteer_times = models.ManyToManyField(
        Event, related_name='teacher_volunteer_set', blank=True)

    def __unicode__(self):
        return 'Remote participation info for teacher %s in %s' % \
                 (str(self.user), str(self.program))

    class Admin:
        pass
Beispiel #20
0
class NavBarCategory(models.Model):
    anchor = AjaxForeignKey(DataTree, blank=True, null=True)
    include_auto_links = models.BooleanField()
    name = models.CharField(max_length=64)
    long_explanation = models.TextField()

    def get_navbars(self):
        return self.navbarentry_set.all().select_related('category').order_by('sort_rank')
    
    @classmethod
    def default(cls):
        """ Default navigation category.  For now, the one with the lowest ID. """
        if not hasattr(cls, '_default'):
            cls._default = cls.objects.all().order_by('id')[0]
        return cls._default
    
    def __unicode__(self):
        if self.anchor:
            return u'%s at %s' % (self.name, unicode(self.anchor))
        else:
            return u'%s' % self.name
Beispiel #21
0
class Series(models.Model):
    """ A container object for grouping Events.  Can be nested. """
    description = models.TextField()
    target = AjaxForeignKey(DataTree) # location for this Series in the datatree

    def __unicode__(self):
        return unicode(self.description)

    def is_happening(self, time=datetime.now()):
        """ Returns True if any Event contained by this Series, or any event contained by any Series nested beneath this Series, returns is_happening(time)==True """
        for event in self.event_set.all():
            if event.is_happening(time):
                return True

        for series in self.series_set.all():
            if series.is_happening(time):
                return True;

        return False;

    class Meta:
        verbose_name_plural = 'Series'
Beispiel #22
0
class AJAXChangeLogEntry(models.Model):

    # unique index in change_log of this entry
    index = models.IntegerField()

    # comma-separated list of integer timeslots
    timeslots = models.CharField(max_length=256)

    # name of the room involved in scheduling update
    room_name = models.CharField(max_length=256)

    # class ID to update
    cls_id = models.IntegerField()

    # user responsible for this entry
    user = AjaxForeignKey(ESPUser, blank=True, null=True)

    # time we entered this
    time = models.FloatField()

    def update(self, index, timeslots, room_name, cls_id):
        self.index = index
        self.timeslots = ','.join([str(x) for x in timeslots])
        self.room_name = room_name
        self.cls_id = cls_id

    def save(self, *args, **kwargs):
        self.time = time.time()
        super(AJAXChangeLogEntry, self).save(*args, **kwargs)

    def getTimeslots(self):
        return self.timeslots.split(',')

    def getUserName(self):
        if self.user:
            return self.user.username
        else:
            return "unknown"
Beispiel #23
0
class StudentApplication(models.Model):
    """ Student applications for Junction and any other programs that need them. """
    from esp.program.models import Program

    program = models.ForeignKey(Program, editable=False)
    user = AjaxForeignKey(ESPUser, editable=False)

    questions = models.ManyToManyField(StudentAppQuestion)
    responses = models.ManyToManyField(StudentAppResponse)
    reviews = models.ManyToManyField(StudentAppReview)

    done = models.BooleanField(default=False, editable=False)

    #   Legacy fields
    teacher_score = models.PositiveIntegerField(editable=False,
                                                null=True,
                                                blank=True)
    director_score = models.PositiveIntegerField(editable=False,
                                                 null=True,
                                                 blank=True)
    rejected = models.BooleanField(default=False, editable=False)

    def __unicode__(self):
        return str(self.user)

    def __init__(self, *args, **kwargs):
        super(StudentApplication, self).__init__(*args, **kwargs)
        self.save()
        self.set_questions()

    def set_questions(self):
        new_user = ESPUser(self.user)
        existing_list = self.questions.all().values_list('id', flat=True)
        new_list = list(
            StudentAppQuestion.objects.filter(
                program=self.program).values_list('id', flat=True))
        new_list += list(
            StudentAppQuestion.objects.filter(
                subject__in=new_user.getAppliedClasses(
                    self.program)).values_list('id', flat=True))

        to_remove = [e for e in existing_list if (e not in new_list)]
        for i in to_remove:
            self.questions.remove(i)
        to_add = [e for e in new_list if (e not in existing_list)]
        for i in to_add:
            self.questions.add(i)

    def get_forms(self, data={}):
        """ Get a list of forms for the student to fill out.
        This function sets a target attribute on each form so that
        the update function can be called directly on target. """

        #   Get forms for already existing responses.
        forms = []
        new_user = ESPUser(self.user)
        applied_classes = new_user.getAppliedClasses(self.program)
        for r in self.responses.filter(question__subject__in=applied_classes):
            f = r.get_form(data)
            f.target = r
            forms.append(f)

        #   Create responses if necessary for the other questions, and get their forms.
        for q in self.questions.all():
            if self.responses.filter(question=q).count() == 0:
                r = StudentAppResponse(question=q)
                r.save()
                self.responses.add(r)
                f = r.get_form(data)
                forms.append(f)

        return forms

    def update(self, form):
        """ Use this if you're not sure what response the form is relevant to. """
        for r in self.responses.all():
            r.update(form)

    class Meta:
        app_label = 'program'
        db_table = 'program_junctionstudentapp'
Beispiel #24
0
class Resource(models.Model):
    """ An individual resource, such as a class room or piece of equipment.  Categorize by
    res_type, attach to a user if necessary. """

    name = models.CharField(max_length=80)
    res_type = models.ForeignKey(ResourceType)
    num_students = models.IntegerField(blank=True, default=-1)
    group_id = models.IntegerField(
        default=-1
    )  # Default value of -1 means ungrouped, or at least so I'm assuming for now in grouped_resources(). -ageng 2008-05-13
    is_unique = models.BooleanField(default=False)
    user = AjaxForeignKey(ESPUser, null=True, blank=True)
    event = models.ForeignKey(Event)

    def __unicode__(self):
        if self.user is not None:
            return 'For %s: %s (%s)' % (unicode(
                self.user), self.name, unicode(self.res_type))
        else:
            if self.num_students != -1:
                return 'For %d students: %s (%s)' % (
                    self.num_students, self.name, unicode(self.res_type))
            else:
                return '%s (%s)' % (self.name, unicode(self.res_type))

    def save(self, *args, **kwargs):
        if self.group_id == -1:
            #   Give this a new group id.
            vals = Resource.objects.all().order_by('-group_id').values_list(
                'group_id', flat=True)
            max_id = 0
            if len(vals) > 0:
                max_id = vals[0]

            self.group_id = max_id + 1
            self.is_unique = True
        else:
            self.is_unique = False

        super(Resource, self).save(*args, **kwargs)

    def distance(self, other):
        """
        Using the custom distance function defined in the ResourceType,
        compute the distance between this resource and another.
        Bear in mind that this is cached using a python global registry.
        """
        if self.res_type_id != other.res_type_id:
            raise ValueError(
                "Both resources must be of the same type to compare!")

        if self.res_type_id in DISTANCE_FUNC_REGISTRY:
            return DISTANCE_FUNC_REGISTRY[self.res_type_id](self, other)

        distancefunc = self.res_type.distancefunc

        if distancefunc and distancefunc.strip():
            funcstr = distancefunc.strip().replace('\r\n', '\n')
        else:
            funcstr = "return 0"
        funcstr = """def _cmpfunc(r1, r2):\n%s""" % ('\n'.join(
            '    %s' % l for l in funcstr.split('\n')))
        exec funcstr
        DISTANCE_FUNC_REGISTRY[self.res_type_id] = _cmpfunc
        return _cmpfunc(self, other)

    __sub__ = distance

    def identical_resources(self):
        res_list = Resource.objects.filter(name=self.name)
        return res_list

    def satisfies_requests(self, req_class):
        #   Returns a list of 2 items.  The first element is boolean and the second element is a list of the unsatisfied requests.
        #   If there are no unsatisfied requests but the room isn't big enough, the first element will be false.

        result = [True, []]
        request_list = req_class.getResourceRequests()
        furnishings = self.associated_resources()
        id_list = []

        for req in request_list:
            if furnishings.filter(res_type=req.res_type).count() < 1:
                result[0] = False
                id_list.append(req.id)

        result[1] = ResourceRequest.objects.filter(id__in=id_list)

        if self.num_students < req_class.num_students():
            result[0] = False

        return result

    def grouped_resources(self):
        if self.group_id == -1:
            return Resource.objects.filter(id=self.id)
        return Resource.objects.filter(group_id=self.group_id)

    def associated_resources(self):
        return self.grouped_resources().exclude(id=self.id).exclude(
            res_type__name='Classroom')

    #   Modified to handle assigning rooms to both classes and their individual sections.
    #   Resource assignments are always handled at the section level now.
    #   The assign_to_class function is copied for backwards compatibility.

    def assign_to_subject(self, new_class, check_constraint=True):
        for sec in new_class.sections.all():
            self.assign_to_section(sec, check_constraint)

    def assign_to_section(self,
                          section,
                          check_constraint=True,
                          override=False):
        if override:
            self.clear_assignments()
        if self.is_available():
            new_ra = ResourceAssignment()
            new_ra.resource = self
            new_ra.target = section
            new_ra.save()
        else:
            raise ESPError_Log, 'Attempted to assign class section %d to conflicted resource; and constraint check was on.' % section.id

    assign_to_class = assign_to_section

    def clear_assignments(self, program=None):
        self.assignments().delete()

    def assignments(self):
        return ResourceAssignment.objects.filter(
            resource__in=self.grouped_resources())

    def schedule_sequence(self, program):
        """ Returns a list of strings, which are the status of the room (and its identical
        companions) at each time block belonging to the program. """

        sequence = []
        event_list = list(program.getTimeSlots())
        room_list = self.identical_resources().filter(event__in=event_list)
        for timeslot in event_list:
            single_room = room_list.filter(event=timeslot)
            if single_room.count() == 1:
                room = single_room[0]
                asl = list(room.assignments())

                if len(asl) == 0:
                    sequence.append('Empty')
                elif len(asl) == 1:
                    sequence.append(asl[0].getTargetOrSubject().emailcode())
                else:
                    init_str = 'Conflict: '
                    for ra in asl:
                        init_str += ra.getTargetOrSubject().emailcode() + ' '
                    sequence.append(init_str)
            else:
                sequence.append('N/A')

        return sequence

    def is_conflicted(self):
        return (self.assignments().count() > 1)

    def available_any_time(self, program=None):
        return (len(self.available_times(program)) > 0)

    def available_times_html(self, program=None):
        return '<br /> '.join([
            unicode(e) for e in Event.collapse(self.available_times(program))
        ])

    def available_times(self, program=None):
        event_list = filter(lambda x: self.is_available(timeslot=x),
                            list(self.matching_times(program)))
        return event_list

    def matching_times(self, program=None):
        #   Find all times for which a resource of the same name is available.
        event_list = self.identical_resources().values_list('event', flat=True)
        if program:
            return Event.objects.filter(id__in=event_list,
                                        program=program).order_by('start')
        else:
            return Event.objects.filter(id__in=event_list).order_by('start')

    def is_independent(self):
        if self.is_unique:
            return True
        else:
            return False

    @cache_function
    def is_available(self, QObjects=False, timeslot=None):
        if timeslot is None:
            test_resource = self
        else:
            test_resource = self.identical_resources().filter(
                event=timeslot)[0]

        if QObjects:
            return ~Q(test_resource.is_taken(True))
        else:
            return not test_resource.is_taken(False)

    is_available.depend_on_row(lambda: ResourceAssignment,
                               lambda instance: {'self': instance.resource})
    is_available.depend_on_row(lambda: Event,
                               lambda instance: {'timeslot': instance})

    def is_taken(self, QObjects=False):
        if QObjects:
            return Q(resource=self)
        else:
            collision = ResourceAssignment.objects.filter(resource=self)
            return (collision.count() > 0)

    class Admin:
        pass
Beispiel #25
0
class Resource(models.Model):
    """ An individual resource, such as a class room or piece of equipment.  Categorize by
    res_type, attach to a user if necessary. """

    name = models.CharField(max_length=80)
    res_type = models.ForeignKey(ResourceType, on_delete=models.PROTECT)
    num_students = models.IntegerField(blank=True, default=-1)
    # do not use group_id, use res_group instead
    # group_id can be removed with a future migration after all sites
    # have successfully run the migration to res_group
    group_id = models.IntegerField(default=-1)
    res_group = models.ForeignKey(ResourceGroup, null=True, blank=True)
    is_unique = models.BooleanField(default=False)
    user = AjaxForeignKey(ESPUser, null=True, blank=True)
    event = models.ForeignKey(Event)
    # If the resource has a value, which one might request in the desired_value
    # field of ResourceRequest.
    attribute_value = models.TextField(default="", blank=True)

    def __unicode__(self):
        if self.user is not None:
            return 'For %s: %s (%s)' % (unicode(self.user), self.name, unicode(self.res_type))
        else:
            if self.num_students != -1:
                return 'For %d students: %s (%s)' % (self.num_students, self.name, unicode(self.res_type))
            else:
                return '%s (%s)' % (self.name, unicode(self.res_type))

    def save(self, *args, **kwargs):
        if self.res_group is None:
            #   Make a new group for this
            new_group = ResourceGroup.objects.create()
            self.res_group = new_group
            self.is_unique = True
        else:
            self.is_unique = False

        super(Resource, self).save(*args, **kwargs)

    # I'd love to kill this, but since it's set as the __sub__, it's hard to
    # grep to be sure it's not used.
    def distance(self, other):
        """
        Deprecated.
        """
        logger.warning("Resource.distance() is deprecated.")
        return 0

    __sub__ = distance


    def identical_resources(self):
        res_list = Resource.objects.filter(name=self.name)
        return res_list

    def satisfies_requests(self, req_class):
        #   Returns a list of 2 items.  The first element is boolean and the second element is a list of the unsatisfied requests.
        #   If there are no unsatisfied requests but the room isn't big enough, the first element will be false.

        result = [True, []]
        request_list = req_class.getResourceRequests()
        furnishings = self.associated_resources()
        id_list = []

        for req in request_list:
            if furnishings.filter(res_type=req.res_type).count() < 1:
                result[0] = False
                id_list.append(req.id)

        result[1] = ResourceRequest.objects.filter(id__in=id_list)

        if self.num_students < req_class.num_students():
            result[0] = False

        return result

    def grouped_resources(self):
        if self.res_group_id is None:
            return Resource.objects.filter(id=self.id)
        return Resource.objects.filter(res_group=self.res_group_id)

    def associated_resources(self):
        return self.grouped_resources().exclude(id=self.id).exclude(res_type__name='Classroom')

    def assign_to_section(self, section, check_constraint=True, override=False):
        if override:
            self.clear_assignments()
        if self.is_available():
            new_ra = ResourceAssignment()
            new_ra.resource = self
            new_ra.target = section
            new_ra.save()
        else:
            raise ESPError('Attempted to assign class section %d to conflicted resource; and constraint check was on.' % section.id, log=True)

    assign_to_class = assign_to_section

    def clear_assignments(self, program=None):
        self.assignments().delete()

    def assignments(self):
        return ResourceAssignment.objects.filter(resource__in=self.grouped_resources())

    def schedule_sequence(self, program):
        """ Returns a list of strings, which are the status of the room (and its identical
        companions) at each time block belonging to the program. """

        sequence = []
        event_list = list(program.getTimeSlots())
        room_list = self.identical_resources().filter(event__in=event_list)
        for timeslot in event_list:
            single_room = room_list.filter(event=timeslot)
            if single_room.count() == 1:
                room = single_room[0]
                asl = list(room.assignments())

                if len(asl) == 0:
                    sequence.append('Empty')
                elif len(asl) == 1:
                    sequence.append(asl[0].getTargetOrSubject().emailcode())
                else:
                    init_str = 'Conflict: '
                    for ra in asl:
                        init_str += ra.getTargetOrSubject().emailcode() + ' '
                    sequence.append(init_str)
            else:
                sequence.append('N/A')

        return sequence

    def available_any_time(self, program=None):
        return (len(self.available_times(program)) > 0)

    def available_times_html(self, program=None):
        return '<br /> '.join([unicode(e) for e in Event.collapse(self.available_times(program))])

    def available_times(self, program=None):
        event_list = filter(lambda x: self.is_available(timeslot=x), list(self.matching_times(program)))
        return event_list

    def matching_times(self, program=None):
        #   Find all times for which a resource of the same name is available.
        event_list = self.identical_resources().values_list('event', flat=True)
        if program:
            return Event.objects.filter(id__in=event_list, program=program).order_by('start')
        else:
            return Event.objects.filter(id__in=event_list).order_by('start')

    @cache_function
    def is_available(self, QObjects=False, timeslot=None):
        if timeslot is None:
            test_resource = self
        else:
            test_resource = self.identical_resources().filter(event=timeslot)[0]

        if QObjects:
            return ~Q(test_resource.is_taken(True))
        else:
            return not test_resource.is_taken(False)
    is_available.depend_on_row('resources.ResourceAssignment', lambda instance: {'self': instance.resource})
    is_available.depend_on_row('cal.Event', lambda instance: {'timeslot': instance})

    def is_taken(self, QObjects=False):
        if QObjects:
            return Q(resource=self)
        else:
            collision = ResourceAssignment.objects.filter(resource=self)
            return (collision.count() > 0)
Beispiel #26
0
class QuasiStaticData(models.Model):
    """ A Markdown-encoded web page """

    objects = QSDManager(8, 'QuasiStaticData')

    url = models.CharField(max_length=256,
                           help_text="Full url, without the trailing .html")
    name = models.SlugField(blank=True)
    title = models.CharField(max_length=256)
    content = models.TextField()

    nav_category = models.ForeignKey(NavBarCategory,
                                     default=NavBarCategory.default)

    create_date = models.DateTimeField(default=datetime.now, editable=False)
    author = AjaxForeignKey(ESPUser)
    disabled = models.BooleanField(default=False)
    keywords = models.TextField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)

    def edit_id(self):
        return qsd_edit_id(self.url)

    def get_file_id(self):
        """Get the file_id of the object.

        This is used by the FileDBManager as a cache key, so be careful when updating.
        Changes here *may* cause caching to break in annoying ways elsewhere. We
        recommend grepping through any related files for "cache".
        
        In particular, IF you change this, update qsd/models.py's QSDManager class
        Otherwise, the cache *may* be used wrong elsewhere."""
        return qsd_cache_key(self.url,
                             None)  # DB access cache --- user invariant

    def copy(self, ):
        """Returns a copy of the current QSD.

        This could be used for versioning QSDs, for example. It will not be
        saved to the DB until .save is called.
        
        Note that this method maintains the author and created date.
        Client code should probably reset the author to request.user
        and date to datetime.now (possibly with load_cur_user_time)"""
        qsd_new = QuasiStaticData()
        qsd_new.url = self.url
        qsd_new.author = self.author
        qsd_new.content = self.content
        qsd_new.title = self.title
        qsd_new.description = self.description
        qsd_new.nav_category = self.nav_category
        qsd_new.keywords = self.keywords
        qsd_new.disabled = self.disabled
        qsd_new.create_date = self.create_date
        return qsd_new

    def load_cur_user_time(
        self,
        request,
    ):
        self.author = request.user
        self.create_date = datetime.now()

    def __unicode__(self):
        return self.url

    @cache_function
    def html(self):
        return markdown(self.content)

    html.depend_on_row(lambda: QuasiStaticData, 'self')

    @staticmethod
    def prog_qsd_url(prog, name):
        """Return the url for a program-qsd with given name
        
        Will have .html at the end iff name does"""
        parts = name.split(":")
        if len(parts) > 1:
            return "/".join([parts[0], prog.url, ":".join(parts[1:])])
        else:
            return "/".join(["programs", prog.url, name])

    @staticmethod
    def program_from_url(url):
        """ If the QSD pertains to a program, figure out which one,
            and return a tuple of the Program object and the QSD name.
            Otherwise return None.  """
        from esp.program.models import Program

        url_parts = url.split('/')
        #   The first part url_parts[0] could be anything, since prog_qsd_url()
        #   takes whatever was specified in the old qsd name
        #   (e.g. 'learn:extrasteps' results in a URL starting with 'learn/',
        #   but you could also have 'foo:extrasteps' etc.)
        #   So, allow any QSD with a program URL in the right place to match.
        if len(url_parts) > 3 and len(url_parts[3]) > 0:
            prog_url = '/'.join(url_parts[1:3])
            progs = Program.objects.filter(url=prog_url)
            if progs.count() == 1:
                if url_parts[0] == 'programs':
                    return (progs[0], '/'.join(url_parts[3:]))
                else:
                    return (progs[0],
                            '%s:' % url_parts[0] + '/'.join(url_parts[3:]))

        return None
Beispiel #27
0
class UserForwarder(models.Model):
    """
    Links source user to target user, to make all login sessions under target.

    """
    source = AjaxForeignKey(ESPUser,
                            related_name='forwarders_out',
                            unique=True)
    target = AjaxForeignKey(ESPUser, related_name='forwarders_in')

    # Django tries to figure out the correct app label by going one level up.
    # Since we've had to shard users.models, this isn't quite enough.
    # So we need to specify this explicitly.
    class Meta:
        app_label = 'users'

    def updateTarget(self, target, flatten=True, save=True):
        """
        Updates target, avoiding circularity.

        Sets self.target to:
            if target never forwards to self.source:
                the user to which target ultimately forwards
            else (if target forwards to self.source):
                target, as given

        If save is set, saves self. You basically always want this set.

        If flatten is set, rewrites intervening forwarders to point directly
        to the actual target. This includes those pointing to self.source.
        This has no effect if save is not set.

        Assuming no circularity initially, preserves absence of circularity.
        Vulnerable to race conditions.
        May hit the database a lot if MAX_DEPTH is large.

        """
        # Prepare rewrites
        rewrites = []
        deletes = []
        if not save:
            flatten = False
        if flatten:
            rewrites.extend(self.source.forwarders_in.all())
        # Find the real target
        original_target = target
        for i in xrange(MAX_DEPTH):
            # Get the next forwarder if it exists
            if target.forwarders_out.count() == 0:
                break
            f = target.forwarders_out.get()
            # Check for circular references
            if f.target == self.source:
                target = original_target
                break
            # Follow to the next user
            rewrites.append(f)  # Don't bother checking flatten -- do it later
            target = f.target
        # Update
        self.target = target
        if save:
            self.save()
            # Flatten
            if flatten:
                for f in rewrites:
                    f.target = target
                    if f.source == f.target:
                        f.delete()
                    else:
                        f.save()

    @staticmethod
    def forward(source, target):
        """Forward from source to target, creating a forwarder if needed."""
        if source.forwarders_out.count() > 0:
            f = source.forwarders_out.get()
        else:
            f = UserForwarder()
            f.source = source
        f.updateTarget(target)

    @staticmethod
    def follow(user):
        """
        Follow any forwarder from user.

        Returns (new user, True if forwarded).

        Remark: We don't handle chained forwarders. That's done at write time
        (with forward() or updateTarget()), to save computation.

        """
        if user.forwarders_out.count() > 0:
            ans = user.forwarders_out.get().target
            for extra in ['backend']:
                if hasattr(user, extra):
                    ans.__dict__[extra] = user.__dict__[extra]
            return (ans, True)
        else:
            return (user, False)

    def __unicode__(self):
        return u'%s to %s' % (unicode(self.source), unicode(self.target))
Beispiel #28
0
class MessageRequest(models.Model):
    """ An initial request to broadcast an e-mail message """
    id = models.AutoField(primary_key=True)
    subject = models.TextField(null=True, blank=True)
    msgtext = models.TextField(blank=True, null=True)
    special_headers = models.TextField(blank=True, null=True)
    recipients = models.ForeignKey(
        PersistentQueryFilter)  # We will get the user from a query filter
    sender = models.TextField(
        blank=True,
        null=True)  # E-mail sender; should be a valid SMTP sender string
    creator = AjaxForeignKey(ESPUser)  # the person who sent this message
    processed = models.BooleanField(
        default=False, db_index=True
    )  # Have we made EmailRequest objects from this MessageRequest yet?
    processed_by = models.DateTimeField(
        null=True, default=None,
        db_index=True)  # When should this be processed by?
    email_all = models.BooleanField(
        default=True
    )  # do we just want to create an emailrequest for each user?
    priority_level = models.IntegerField(
        null=True, blank=True
    )  # Priority of a message; may be used in the future to make a message non-digested, or to prevent a low-priority message from being sent

    def __unicode__(self):
        return unicode(self.subject)

    # Access special_headers as a dictionary
    def special_headers_dict_get(self):
        if not self.special_headers:
            return {}
        import cPickle as pickle
        return pickle.loads(
            str(self.special_headers)
        )  # We call str here because pickle hates unicode. -ageng 2008-11-18

    def special_headers_dict_set(self, value):
        import cPickle as pickle
        if type(value) is not dict:
            value = {}
        self.special_headers = pickle.dumps(value)

    special_headers_dict = property(special_headers_dict_get,
                                    special_headers_dict_set)

    @staticmethod
    def createRequest(var_dict=None, *args, **kwargs):
        """ To create a new MessageRequest, you should provide a dictionary of
            the variables you want substituted, if you want any. """
        new_request = MessageRequest(*args, **kwargs)

        if var_dict is not None:
            new_request.save()
            MessageVars.createMessageVars(
                new_request, var_dict)  # create the message Variables
        return new_request

    def parseSmartText(self, text, user):
        """ Takes a text and user, and, within the confines of this message, will make it better. """

        # prepare variables
        text = unicode(text)
        user = ESPUser(user)

        context = MessageVars.getContext(self, user)

        newtext = ''
        template = Template(text)

        return template.render(context)

    def process(self, processoverride=False):
        """ Process this request...if it's an email, create all the necessary email requests. """

        # if we already processed, return
        if self.processed and not processoverride:
            return

        # there's no real thing for this...yet
        if not self.email_all:
            return

        # this is for thread-safeness...
        self.processed = True
        self.save()

        # figure out who we're sending from...
        if self.sender is not None and len(self.sender.strip()) > 0:
            send_from = self.sender
        else:
            if self.creator is not None:
                send_from = '%s <%s>' % (ESPUser(
                    self.creator).name(), self.creator.email)
            else:
                send_from = 'ESP Web Site <*****@*****.**>'

        users = self.recipients.getList(ESPUser)
        try:
            users = users.distinct()
        except:
            pass

        # go through each user and parse the text...then create the proper
        # emailrequest and textofemail object
        for user in users:
            user = ESPUser(user)
            newemailrequest = EmailRequest(target=user, msgreq=self)

            newtxt = TextOfEmail(
                send_to='%s <%s>' % (user.name(), user.email),
                send_from=send_from,
                subject=self.parseSmartText(self.subject, user),
                msgtext=self.parseSmartText(self.msgtext, user),
                sent=None)

            newtxt.save()

            newemailrequest.textofemail = newtxt

            newemailrequest.save()

        print 'Prepared e-mails to send for message request %d: %s' % (
            self.id, self.subject)

    class Admin:
        pass
Beispiel #29
0
class Media(models.Model):
    """ A generic container for 'media': videos, pictures, papers, etc. """
    anchor = AjaxForeignKey(DataTree, blank=True,
                            null=True)  # Relevant node in the tree
    friendly_name = models.TextField(
    )  # Human-readable description of the media
    target_file = models.FileField(
        upload_to=root_file_path)  # Target media file
    size = models.IntegerField(blank=True, null=True,
                               editable=False)  # Size of the file, in bytes
    format = models.TextField(
        blank=True, null=True
    )  # Format string; should be human-readable (string format is currently unspecified)
    mime_type = models.CharField(blank=True,
                                 null=True,
                                 max_length=256,
                                 editable=False)
    file_extension = models.TextField(
        blank=True, null=True, max_length=16, editable=False
    )  # Windows file extension for this file type, in case it's something archaic / Windows-centric enough to not get a unique MIME type
    file_name = models.TextField(
        blank=True, null=True, max_length=256, editable=False
    )  # original filename that this file should be downloaded as
    hashed_name = models.TextField(blank=True,
                                   null=True,
                                   max_length=256,
                                   editable=False)  # safe hashed filename

    #   Generic Foreign Key to object this media is associated with.
    #   Currently limited to be either a ClassSubject or Program.
    owner_type = models.ForeignKey(
        ContentType,
        blank=True,
        null=True,
        limit_choices_to=Q(name__in=['ClassSubject', 'Program']))
    owner_id = models.PositiveIntegerField(blank=True, null=True)
    owner = generic.GenericForeignKey(ct_field='owner_type',
                                      fk_field='owner_id')

    #def get_target_file_relative_url(self):a
    #    return str(self.target_file)[ len(root_file_path): ]

    def get_target_file_url(self):
        return str(self.target_file.url)

    target_url = property(get_target_file_url)

    def safe_filename(self, filename):
        """ Compute the MD5 hash of the original filename. 
            The data is saved under this hashed filename to reduce
            security risk.  """
        m = hashlib.md5()
        m.update(filename)
        return m.hexdigest()

    def handle_file(self, file, filename):
        """ Saves a file from request.FILES. """
        from os.path import basename, dirname

        # Do we actually need this?
        splitname = basename(filename).split('.')
        if len(splitname) > 1:
            self.file_extension = splitname[-1]
        else:
            self.file_extension = ''

        self.mime_type = file.content_type
        self.size = file.size

        # hash the filename, easy way to prevent bad filename attacks
        self.file_name = filename
        self.hashed_name = self.safe_filename(filename)
        self.target_file.save(self.hashed_name, file)

    def delete(self, *args, **kwargs):
        """ Delete entry; provide hack to fix old absolute-path-storing. """
        import os
        # If needby, strip URL prefix
        if os.path.isabs(
                self.target_file.name) and self.target_file.name.startswith(
                    settings.MEDIA_URL):
            self.target_file.name = self.target_file.name[len(settings.
                                                              MEDIA_URL):]
            # In case trailing slash missing
            if self.target_file.name[0] is '/':
                self.target_file.name = self.target_file.name[1:]
        super(Media, self).delete(*args, **kwargs)

    def __unicode__(self):
        return unicode(self.friendly_name)
Beispiel #30
0
class QuasiStaticData(models.Model):
    """ A Markdown-encoded web page """

    objects = QSDManager(8, 'QuasiStaticData')

    path = AjaxForeignKey(DataTree)
    name = models.SlugField()
    title = models.CharField(max_length=256)
    content = models.TextField()

    nav_category = models.ForeignKey(NavBarCategory,
                                     default=NavBarCategory.default)

    create_date = models.DateTimeField(default=datetime.now, editable=False)
    author = AjaxForeignKey(ESPUser)
    disabled = models.BooleanField(default=False)
    keywords = models.TextField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)

    def get_file_id(self):
        """Get the file_id of the object.

        This is used by the FileDBManager as a cache key, so be careful when updating.
        Changes here *may* cause caching to break in annoying ways elsewhere. We
        recommend grepping through any related files for "cache".
        
        In particular, IF you change this, update qsd/models.py's QSDManager class
        Otherwise, the cache *may* be used wrong elsewhere."""
        return qsd_cache_key(self.path, self.name,
                             None)  # DB access cache --- user invariant

    def copy(self, ):
        """Returns a copy of the current QSD.

        This could be used for versioning QSDs, for example. It will not be
        saved to the DB until .save is called.
        
        Note that this method maintains the author and created date.
        Client code should probably reset the author to request.user
        and date to datetime.now (possibly with load_cur_user_time)"""
        qsd_new = QuasiStaticData()
        qsd_new.path = self.path
        qsd_new.name = self.name
        qsd_new.author = self.author
        qsd_new.content = self.content
        qsd_new.title = self.title
        qsd_new.description = self.description
        qsd_new.nav_category = self.nav_category
        qsd_new.keywords = self.keywords
        qsd_new.disabled = self.disabled
        qsd_new.create_date = self.create_date
        return qsd_new

    def load_cur_user_time(
        self,
        request,
    ):
        self.author = request.user
        self.create_date = datetime.now()

    # Really, I think the correct solution here is to key it by path.get_uri and name
    # is_descendant_of is slightly more expensive, but whatever.
    @cache_function
    def url(self):
        """ Get the relative URL of a page (i.e. /learn/Splash/eligibility.html) """

        my_path = self.path
        path_parts = self.path.get_uri().split('/')
        program_top = DataTree.get_by_uri('Q/Programs')
        web_top = DataTree.get_by_uri('Q/Web')
        if my_path.is_descendant_of(program_top):
            name_parts = self.name.split(':')
            if len(name_parts) > 1:
                result = '/' + name_parts[0] + '/' + '/'.join(
                    path_parts[2:]) + '/' + name_parts[1] + '.html'
            else:
                result = '/programs/' + '/'.join(
                    path_parts[2:]) + '/' + name_parts[0] + '.html'
        elif my_path.is_descendant_of(web_top):
            result = '/' + '/'.join(path_parts[2:]) + '/' + self.name + '.html'
        else:
            result = '/' + '/'.join(path_parts[1:]) + '/' + self.name + '.html'

        return result

    url.depend_on_row(lambda: QuasiStaticData, 'self')

    # This never really happens in this case, still... something to think about:
    #
    #    We can either do a query on Datatree modification and then delete the
    #    relevant cache, or we could add a Token to the cache and then we can
    #    delete by DataTree nodes. Although this is offloaded work from data
    #    modification to data retrieval, the modified form of work is also MUCH
    #    cheaper. As in, we can just grab qsd.path_id and not incur any
    #    database load, whereas the current setup is going to force us to do a
    #    database query AND do it at times a DataTree node is deleted... many
    #    of these aren't relevant.
    #
    #    That said, how can we propogate stuff then? With fully general Tokens,
    #    mapping functions are hard to write. Something more like the old
    #    partitions idea?
    #
    #    Special-case DataTree?? :-(
    #
    # url.depend_on_row(lambda:DataTree, lambda instance: {'self': QuasiStaticData.objects.blahbalh})

    def __unicode__(self):
        return (self.path.full_name() + ':' + self.name + '.html')

    @cache_function
    def html(self):
        return markdown(self.content)

    html.depend_on_row(lambda: QuasiStaticData, 'self')

    @staticmethod
    def find_by_url_parts(base, parts):
        """ Fetch a QSD record by its url parts """
        # Extract the last part
        filename = parts.pop()

        # Find the branch
        try:
            branch = base.tree_decode(parts)
        except DataTree.NoSuchNodeException:
            raise QuasiStaticData.DoesNotExist

        # Find the record
        qsd = QuasiStaticData.objects.filter(path=branch, name=filename)
        if len(qsd) < 1:
            raise QuasiStaticData.DoesNotExist

        # Operation Complete!
        return qsd[0]