~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to lib/common/db.py

  • Committer: mattgiuca
  • Date: 2008-04-06 15:04:54 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:725
The database now stores a cache of all the worksheets and what problems
are contained within each, for the purposes of determining marks.

This cache is automatically written to the database when someone visits a
worksheet in the website. If the worksheet file gets updated (touched), then
the database entries will be refreshed (so it is kept up to date).

At this stage, it isn't used to compute anything, it just gets stored in the
DB and updated.

Also added an "assessable" attribute to the worksheet element in the
worksheet XML, and an "optional" attribute to the exercise element, also in
the worksheet XML. (Both of these are optional, so it won't break existing
worksheets). Both default to False, so by default worksheets are not
assessable, and assessable worksheets have all-mandatory exercises.

users.sql: Added worksheet and worksheet_problem tables to the DB.
common/db.py: Added methods get_worksheet_mtime and create_worksheet.
tutorial/__init__.py:
    present_table_of_contents now gathers up details about all the exercises.
    present_worksheet now gets top level details.
    Added function update_db_worksheet, which checks the time stamps and
    updates the database if necessary.

Show diffs side-by-side

added added

removed removed

Lines of Context:
51
51
    * bool: Returns as "TRUE" or "FALSE", unquoted.
52
52
    * NoneType: Returns "NULL", unquoted.
53
53
    * common.caps.Role: Returns the role as a quoted, lowercase string.
 
54
    * time.struct_time: Returns the time as a quoted string for insertion into
 
55
        a TIMESTAMP column.
54
56
    Raises a DBException if val has an unsupported type.
55
57
    """
56
58
    # "E'" is postgres's way of making "escape" strings.
74
76
        return _escape(time.strftime(TIMESTAMP_FORMAT, val))
75
77
    else:
76
78
        raise DBException("Attempt to insert an unsupported type "
77
 
            "into the database")
 
79
            "into the database (%s)" % repr(type(val)))
78
80
 
79
81
def _passhash(password):
80
82
    return md5.md5(password).hexdigest()
598
600
            count = int(result.getresult()[0][0])
599
601
            return (False, count)
600
602
 
 
603
    # WORKSHEET/PROBLEM ASSOCIATION AND MARKS CALCULATION
 
604
 
 
605
    def get_worksheet_mtime(self, subject, worksheet, dry=False):
 
606
        """
 
607
        For a given subject/worksheet name, gets the time the worksheet was
 
608
        last updated in the DB, if any.
 
609
        This can be used to check if there is a newer version on disk.
 
610
        Returns the timestamp as a time.struct_time, or None if the worksheet
 
611
        is not found or has no stored mtime.
 
612
        """
 
613
        try:
 
614
            r = self.get_single(
 
615
                {"subject": subject, "identifier": worksheet},
 
616
                "worksheet", ["mtime"], ["subject", "identifier"],
 
617
                dry=dry)
 
618
        except DBException:
 
619
            # Assume the worksheet is not in the DB
 
620
            return None
 
621
        if dry:
 
622
            return r
 
623
        if r["mtime"] is None:
 
624
            return None
 
625
        return time.strptime(r["mtime"], TIMESTAMP_FORMAT)
 
626
 
 
627
    def create_worksheet(self, subject, worksheet, problems,
 
628
        assessable=False):
 
629
        """
 
630
        Inserts or updates rows in the worksheet and worksheet_problems
 
631
        tables, to create a worksheet in the database.
 
632
        This atomically performs all operations. If the worksheet is already
 
633
        in the DB, removes it and all its associated problems and rebuilds.
 
634
        Sets the timestamp to the current time.
 
635
 
 
636
        problems is a collection of pairs. The first element of the pair is
 
637
        the problem identifier ("identifier" column of the problem table). The
 
638
        second element is an optional boolean, "optional". This can be omitted
 
639
        (so it's a 1-tuple), and then it will default to False.
 
640
 
 
641
        Note: As with get_problem_problemid, if a problem name is not in the
 
642
        DB, it will be added to the problem table.
 
643
        """
 
644
        self.start_transaction()
 
645
        try:
 
646
            # Use the current time as the "mtime" field
 
647
            mtime = time.localtime()
 
648
            try:
 
649
                # Get the worksheetid
 
650
                r = self.get_single(
 
651
                    {"subject": subject, "identifier": worksheet},
 
652
                    "worksheet", ["worksheetid"], ["subject", "identifier"])
 
653
                worksheetid = r["worksheetid"]
 
654
 
 
655
                # Delete any problems which might exist
 
656
                query = ("DELETE FROM worksheet_problem "
 
657
                    "WHERE worksheetid = %d;" % worksheetid)
 
658
                self.db.query(query)
 
659
                # Update the row with the new details
 
660
                query = ("UPDATE worksheet "
 
661
                    "SET assessable = %s, mtime = %s "
 
662
                    "WHERE worksheetid = %d;"
 
663
                    % (_escape(assessable), _escape(mtime), worksheetid))
 
664
                self.db.query(query)
 
665
            except DBException:
 
666
                # Assume the worksheet is not in the DB
 
667
                # Create the worksheet row
 
668
                query = ("INSERT INTO worksheet "
 
669
                    "(subject, identifier, assessable, mtime) "
 
670
                    "VALUES (%s, %s, %s, %s);"""
 
671
                    % (_escape(subject), _escape(worksheet),
 
672
                    _escape(assessable), _escape(mtime)))
 
673
                self.db.query(query)
 
674
                # Now get the worksheetid again - should succeed
 
675
                r = self.get_single(
 
676
                    {"subject": subject, "identifier": worksheet},
 
677
                    "worksheet", ["worksheetid"], ["subject", "identifier"])
 
678
                worksheetid = r["worksheetid"]
 
679
 
 
680
            # Now insert each problem into the worksheet_problem table
 
681
            for problem in problems:
 
682
                if isinstance(problem, tuple):
 
683
                    prob_identifier = problem[0]
 
684
                    try:
 
685
                        optional = problem[1]
 
686
                    except IndexError:
 
687
                        optional = False
 
688
                else:
 
689
                    prob_identifier = problem
 
690
                    optional = False
 
691
                problemid = self.get_problem_problemid(prob_identifier)
 
692
                query = ("INSERT INTO worksheet_problem "
 
693
                    "(worksheetid, problemid, optional) "
 
694
                    "VALUES (%d, %d, %s);"
 
695
                    % (worksheetid, problemid, _escape(optional)))
 
696
                self.db.query(query)
 
697
 
 
698
            self.commit()
 
699
        except:
 
700
            self.rollback()
 
701
            raise
 
702
 
601
703
    def close(self):
602
704
        """Close the DB connection. Do not call any other functions after
603
705
        this. (The behaviour of doing so is undefined).