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

« back to all changes in this revision

Viewing changes to lib/common/db.py

  • Committer: dcoles
  • Date: 2008-02-29 02:11:58 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:624
forum: Removed the subsilver2 style and phpBB installer
Modified prosilver theme to be more IVLE integrated
Added db dumps for setup

setup.py: Added config.php generator code

doc/setup/install_proc.txt: New setup/install details

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
import conf
36
36
import md5
37
37
import copy
38
 
import time
39
38
 
40
39
from common import (caps, user)
41
40
 
42
 
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S'
43
 
 
44
41
def _escape(val):
45
42
    """Wrapper around pg.escape_string. Prepares the Python value for use in
46
43
    SQL. Returns a string, which may be safely placed verbatim into an SQL
61
58
    # WARNING: PostgreSQL-specific code
62
59
    if val is None:
63
60
        return "NULL"
64
 
    elif isinstance(val, str) or isinstance(val, unicode):
 
61
    elif isinstance(val, str):
65
62
        return "E'" + pg.escape_string(val) + "'"
66
63
    elif isinstance(val, bool):
67
64
        return "TRUE" if val else "FALSE"
70
67
        return str(val)
71
68
    elif isinstance(val, caps.Role):
72
69
        return _escape(str(val))
73
 
    elif isinstance(val, time.struct_time):
74
 
        return _escape(time.strftime(TIMESTAMP_FORMAT, val))
75
70
    else:
76
71
        raise DBException("Attempt to insert an unsupported type "
77
72
            "into the database")
387
382
        # Package into User objects
388
383
        return [user.User(**userdict) for userdict in userdicts]
389
384
 
390
 
    def get_user_loginid(self, login, dry=False):
391
 
        """Given a login, returns the integer loginid for this user.
392
 
 
393
 
        Raises a DBException if the login is not found in the DB.
394
 
        """
395
 
        userdict = self.get_single({"login": login}, "login",
396
 
            ['loginid'], self.login_primary,
397
 
            error_notfound="get_user_loginid: No user with that login name",
398
 
            dry=dry)
399
 
        if dry:
400
 
            return userdict     # Query string
401
 
        return userdict['loginid']
402
 
 
403
385
    def user_authenticate(self, login, password, dry=False):
404
386
        """Performs a password authentication on a user. Returns True if
405
387
        "passhash" is the correct passhash for the given login, False
408
390
        Also returns False if the login does not exist (so if you want to
409
391
        differentiate these cases, use get_user and catch an exception).
410
392
        """
411
 
        query = ("SELECT passhash FROM login WHERE login = %s;"
412
 
            % _escape(login))
 
393
        query = "SELECT passhash FROM login WHERE login = '%s';" % login
413
394
        if dry: return query
414
395
        result = self.db.query(query)
415
396
        if result.ntuples() == 1:
421
402
        else:
422
403
            return False
423
404
 
424
 
    # PROBLEM AND PROBLEM ATTEMPT FUNCTIONS #
425
 
 
426
 
    def get_problem_problemid(self, exercisename, dry=False):
427
 
        """Given an exercise name, returns the associated problemID.
428
 
        If the exercise name is NOT in the database, it inserts it and returns
429
 
        the new problemID. Hence this may mutate the DB, but is idempotent.
430
 
        """
431
 
        try:
432
 
            d = self.get_single({"identifier": exercisename}, "problem",
433
 
                ['problemid'], frozenset(["identifier"]),
434
 
                dry=dry)
435
 
            if dry:
436
 
                return d        # Query string
437
 
        except DBException:
438
 
            if dry:
439
 
                # Shouldn't try again, must have failed for some other reason
440
 
                raise
441
 
            # if we failed to get a problemid, it was probably because
442
 
            # the exercise wasn't in the db. So lets insert it!
443
 
            #
444
 
            # The insert can fail if someone else simultaneously does
445
 
            # the insert, so if the insert fails, we ignore the problem. 
446
 
            try:
447
 
                self.insert({'identifier': exercisename}, "problem",
448
 
                        frozenset(['identifier']))
449
 
            except Exception, e:
450
 
                pass
451
 
 
452
 
            # Assuming the insert succeeded, we should be able to get the
453
 
            # problemid now.
454
 
            d = self.get_single({"identifier": exercisename}, "problem",
455
 
                ['problemid'], frozenset(["identifier"]))
456
 
 
457
 
        return d['problemid']
458
 
 
459
 
    def insert_problem_attempt(self, login, exercisename, date, complete,
460
 
        attempt, dry=False):
461
 
        """Inserts the details of a problem attempt into the database.
462
 
        exercisename: Name of the exercise. (identifier field of problem
463
 
            table). If this exercise does not exist, also creates a new row in
464
 
            the problem table for this exercise name.
465
 
        login: Name of the user submitting the attempt. (login field of the
466
 
            login table).
467
 
        date: struct_time, the date this attempt was made.
468
 
        complete: bool. Whether the test passed or not.
469
 
        attempt: Text of the attempt.
470
 
 
471
 
        Note: Even if dry, will still physically call get_problem_problemid,
472
 
        which may mutate the DB, and get_user_loginid, which may fail.
473
 
        """
474
 
        problemid = self.get_problem_problemid(exercisename)
475
 
        loginid = self.get_user_loginid(login)  # May raise a DBException
476
 
 
477
 
        return self.insert({
478
 
                'problemid': problemid,
479
 
                'loginid': loginid,
480
 
                'date': date,
481
 
                'complete': complete,
482
 
                'attempt': attempt,
483
 
            }, 'problem_attempt',
484
 
            frozenset(['problemid','loginid','date','complete','attempt']),
485
 
            dry=dry)
486
 
 
487
 
    def write_problem_save(self, login, exercisename, date, text, dry=False):
488
 
        """Writes text to the problem_save table (for when the user saves an
489
 
        exercise). Creates a new row, or overwrites an existing one if the
490
 
        user has already saved that problem.
491
 
        (Unlike problem_attempt, does not keep historical records).
492
 
        """
493
 
        problemid = self.get_problem_problemid(exercisename)
494
 
        loginid = self.get_user_loginid(login)  # May raise a DBException
495
 
 
496
 
        try:
497
 
            return self.insert({
498
 
                    'problemid': problemid,
499
 
                    'loginid': loginid,
500
 
                    'date': date,
501
 
                    'text': text,
502
 
                }, 'problem_save',
503
 
                frozenset(['problemid','loginid','date','text']),
504
 
                dry=dry)
505
 
        except pg.ProgrammingError:
506
 
            # May have failed because this problemid/loginid row already
507
 
            # exists (they have a unique key constraint).
508
 
            # Do an update instead.
509
 
            if dry:
510
 
                # Shouldn't try again, must have failed for some other reason
511
 
                raise
512
 
            self.update({
513
 
                    'problemid': problemid,
514
 
                    'loginid': loginid,
515
 
                },
516
 
                {
517
 
                    'date': date,
518
 
                    'text': text,
519
 
                }, "problem_save",
520
 
                frozenset(['date', 'text']),
521
 
                frozenset(['problemid', 'loginid']))
522
 
 
523
 
    def get_problem_stored_text(self, login, exercisename, dry=False):
524
 
        """Given a login name and exercise name, returns the text of the
525
 
        last saved/submitted attempt for this question.
526
 
        Returns None if the user has not saved or made an attempt on this
527
 
        problem.
528
 
        (If the user has both saved and submitted, it returns whichever was
529
 
        made last).
530
 
 
531
 
        Note: Even if dry, will still physically call get_problem_problemid,
532
 
        which may mutate the DB, and get_user_loginid, which may fail.
533
 
        """
534
 
        problemid = self.get_problem_problemid(exercisename)
535
 
        loginid = self.get_user_loginid(login)  # May raise a DBException
536
 
        # This very complex query finds all submissions made by this user for
537
 
        # this problem, as well as the save made by this user for this
538
 
        # problem, and returns the text of the newest one.
539
 
        # (Whichever is newer out of the save or the submit).
540
 
        query = """SELECT text FROM
541
 
    (
542
 
        (SELECT * FROM problem_save WHERE loginid = %d AND problemid = %d)
543
 
    UNION
544
 
        (SELECT problemid, loginid, date, text FROM problem_attempt
545
 
         AS problem_attempt (problemid, loginid, date, text)
546
 
         WHERE loginid = %d AND problemid = %d)
547
 
    )
548
 
    AS _
549
 
    ORDER BY date DESC
550
 
    LIMIT 1;""" % (loginid, problemid, loginid, problemid)
551
 
        if dry: return query
552
 
        result = self.db.query(query)
553
 
        if result.ntuples() == 1:
554
 
            # The user has made at least 1 attempt. Return the newest.
555
 
            return result.getresult()[0][0]
556
 
        else:
557
 
            return None
558
 
 
559
 
    def get_problem_status(self, login, exercisename, dry=False):
560
 
        """Given a login name and exercise name, returns information about the
561
 
        user's performance on that problem.
562
 
        Returns a tuple of:
563
 
            - A boolean, whether they have successfully passed this exercise.
564
 
            - An int, the number of attempts they have made up to and
565
 
              including the first successful attempt (or the total number of
566
 
              attempts, if not yet successful).
567
 
        """
568
 
        problemid = self.get_problem_problemid(exercisename)
569
 
        loginid = self.get_user_loginid(login)  # May raise a DBException
570
 
 
571
 
        # ASSUME that it is completed, get the total number of attempts up to
572
 
        # and including the first successful attempt.
573
 
        # (Get the date of the first successful attempt. Then count the number
574
 
        # of attempts made <= that date).
575
 
        # Will return an empty table if the problem has never been
576
 
        # successfully completed.
577
 
        query = """SELECT COUNT(*) FROM problem_attempt
578
 
    WHERE loginid = %d AND problemid = %d AND date <=
579
 
        (SELECT date FROM problem_attempt
580
 
            WHERE loginid = %d AND problemid = %d AND complete = TRUE
581
 
            ORDER BY date ASC
582
 
            LIMIT 1);""" % (loginid, problemid, loginid, problemid)
583
 
        if dry: return query
584
 
        result = self.db.query(query)
585
 
        count = int(result.getresult()[0][0])
586
 
        if count > 0:
587
 
            # The user has made at least 1 successful attempt.
588
 
            # Return True for success, and the number of attempts up to and
589
 
            # including the successful one.
590
 
            return (True, count)
591
 
        else:
592
 
            # Returned 0 rows - this indicates that the problem has not been
593
 
            # completed.
594
 
            # Return the total number of attempts, and False for success.
595
 
            query = """SELECT COUNT(*) FROM problem_attempt
596
 
    WHERE loginid = %d AND problemid = %d;""" % (loginid, problemid)
597
 
            result = self.db.query(query)
598
 
            count = int(result.getresult()[0][0])
599
 
            return (False, count)
600
 
 
601
405
    def close(self):
602
406
        """Close the DB connection. Do not call any other functions after
603
407
        this. (The behaviour of doing so is undefined).