def produceTest(self, t): """Someone has told us they produced (made PDF) for this paper. Args: t (int): a paper number. Exceptions: IndexError: no such paper exists. ValueError: you already told us you made it. """ tref = Test.get_or_none(test_number=t) if tref is None: log.error( 'Cannot set paper {} to "produced" - it does not exist'.format(t)) raise IndexError("Paper number does not exist: out of range?") else: # TODO - work out how to make this more efficient? Multiple updates in one op? with plomdb.atomic(): if tref.produced: # TODO be less harsh if we have the same md5sum log.error('Paper {} was already "produced"!'.format(t)) raise ValueError("Paper was already produced") tref.produced = True tref.save() log.info('Paper {} is set to "produced"'.format(t))
def createDNMGroup(self, t, pages): tref = Test.get_or_none(test_number=t) if tref is None: log.warning("Create DNM - No test with number {}".format(t)) return False gid = "d{}".format(str(t).zfill(4)) with plomdb.atomic(): # make the dnmgroup try: # A DNM group may have 0 pages, in that case mark it as scanned and set status = "complete" sc = True if len(pages) == 0 else False gref = Group.create( test=tref, gid=gid, group_type="d", scanned=sc, queue_position=self.nextqueue_position(), ) except pw.IntegrityError as e: log.error( "Create DNM - cannot make Group {} of Test {} error - {}". format(gid, t, e)) return False try: dref = DNMGroup.create(test=tref, group=gref) except pw.IntegrityError as e: log.error( "Create DNM - cannot create DNMGroup {} of group {} error - {}." .format(dref, gref, e)) return False return self.addTPages(tref, gref, t, pages, 1)
def createIDGroup(self, t, pages): tref = Test.get_or_none(test_number=t) if tref is None: log.warning("Create IDGroup - No test with number {}".format(t)) return False with plomdb.atomic(): # make the Group gid = "i{}".format(str(t).zfill(4)) try: gref = Group.create( test=tref, gid=gid, group_type="i", queue_position=self.nextqueue_position(), ) # must be unique except pw.IntegrityError as e: log.error("Create ID for gid={} test={}: cannot create Group - {}". format(gid, t, e)) return False # make the IDGroup try: iref = IDGroup.create(test=tref, group=gref) except pw.IntegrityError as e: log.error( "Create ID for gid={} test={} Group={}: cannot create IDGroup - {}." .format(gid, t, gref, e)) return False return self.addTPages(tref, gref, t, pages, 1) # always version 1.
def createTest(self, t): with plomdb.atomic(): try: tref = Test.create(test_number=t) # must be unique except pw.IntegrityError as e: log.error("Create test {} error - {}".format(t, e)) return False return True
def id_paper(self, paper_num, user_name, sid, sname): """Associate student name and id with a paper in the database. See also :func:`plom.db.db_identify.ID_id_paper` which is similar. Args: paper_num (int) user_name (str): User who did the IDing. sid (str): student id. sname (str): student name. Returns: tuple: `(True, None, None)` if succesful, `(False, 409, msg)` means `sid` is in use elsewhere, a serious problem for the caller to deal with. `(False, int, msg)` covers all other errors. `msg` gives details about errors. Some of these should not occur, and indicate possible bugs. `int` gives a hint of suggested HTTP status code, currently it can be 404 or 409. TODO: perhaps several sorts of exceptions would be better. """ uref = User.get(name=user_name) # TODO: or hardcode HAL like before # since user authenticated, this will always return legit ref. logbase = 'User "{}" tried to ID paper {}'.format(user_name, paper_num) with plomdb.atomic(): tref = Test.get_or_none(Test.test_number == paper_num) if tref is None: msg = "denied b/c paper not found" log.error("{}: {}".format(logbase, msg)) return False, 404, msg iref = tref.idgroups[0] iref.user = uref iref.status = "done" iref.student_id = sid iref.student_name = sname iref.identified = True iref.time = datetime.now() try: iref.save() except pw.IntegrityError: msg = "student id {} already entered elsewhere".format( censorID(sid)) log.error("{} but {}".format(logbase, msg)) return False, 409, msg tref.identified = True tref.save() log.info('Paper {} ID\'d by "{}" as "{}" "{}"'.format( paper_num, user_name, censorID(sid), censorName(sname))) return True, None, None
def MmodifyRubric(self, user_name, key, change): """Modify or create a rubric based on an existing rubric in the DB. Currently this modifies the existing rubric, increasing its revision number. However, this is subject to change and should be considered an implementation detail. Its very likely we will move to an immutable model. At any rate, the returned `new_key` should be considered as replacing the original and the old key should not be used to place new annotations. It might however be used to find outdated ones to tag or otherwise update papers. Args: user_name (str): name of user creating the rubric element key(str): key for the rubric change (dict): dict containing the changes to make to the rubric. Must contain these fields: `{kind: "relative", delta: "-1", text: "blah", tags: "blah", meta: "blah"}` Other fields will be ignored. Note this means you can think you are changing, e.g., the question but this will silently not happen. TODO: in the future we might prevent changing the "kind" or the sign of the delta. Returns: tuple: `(True, new_key)` containing the newly generated key (which might be the old key but this is not promised), or `(False, "incomplete")`, or `(False, "noSuchRubric")`. """ need_fields = ("delta", "text", "tags", "meta", "kind") if any(x not in change for x in need_fields): return (False, "incomplete") uref = User.get(name=user_name) # authenticated, so not-None # check if the rubric exists made by this user - cannot modify other user's rubric # TODO: should we have another bail case here `(False, "notYours")`? # TODO: maybe manager will be able modify all rubrics. rref = Rubric.get_or_none(key=key, user=uref) if rref is None: return (False, "noSuchRubric") with plomdb.atomic(): rref.kind = change["kind"] rref.delta = change["delta"] rref.text = change["text"] rref.modificationTime = datetime.now() rref.revision += 1 rref.meta = change["meta"] rref.tags = change["tags"] rref.save() return (True, key)
def McreateRubric(self, user_name, rubric): """Create a new rubric entry in the DB Args: user_name (str): name of user creating the rubric element rubric (dict): dict containing the rubric details. Must contain these fields: `{kind: "relative", delta: "-1", text: "blah", question: 2}` The following fields are optional and empty strings will be substituted: `{tags: "blah", meta: "blah"}` Currently, its ok if it contains other fields: they are ignored. Returns: tuple: `(True, key)` or `(False, err_msg)` where `key` is the key for the new rubric. Can fail if missing fields. """ need_fields = ("kind", "delta", "text", "question") optional_fields = ("tags", "meta") if any(x not in rubric for x in need_fields): return (False, "Must have all fields {}".format(need_field)) for f in optional_fields: if f not in rubric: rubric = rubric.copy() # in case caller uses reference rubric[f] = "" uref = User.get(name=user_name) # authenticated, so not-None with plomdb.atomic(): # build unique key while holding atomic access key = generate_new_comment_ID() while Rubric.get_or_none(key=key) is not None: key = generate_new_comment_ID() Rubric.create( key=key, user=uref, question=rubric["question"], kind=rubric["kind"], delta=rubric["delta"], text=rubric["text"], creationTime=datetime.now(), modificationTime=datetime.now(), meta=rubric["meta"], tags=rubric["tags"], ) return (True, key)
def createQGroup(self, t, q, v, pages): tref = Test.get_or_none(test_number=t) if tref is None: log.warning("Create Q - No test with number {}".format(t)) return False gid = "q{}g{}".format(str(t).zfill(4), q) with plomdb.atomic(): # make the qgroup try: gref = Group.create( test=tref, gid=gid, group_type="q", version=v, queue_position=self.nextqueue_position(), ) except pw.IntegrityError as e: log.error( "Create Q - cannot create group {} of Test {} error - {}". format(gid, t, e)) return False try: # qref = QGroup.create( # test=tref, group=gref, question=q, version=v, fullmark=mark # ) qref = QGroup.create(test=tref, group=gref, question=q, version=v) except pw.IntegrityError as e: log.error( "Create Q - cannot create QGroup of question {} error - {}.". format(gid, e)) return False ## create annotation 0 owned by HAL try: uref = User.get(name="HAL") aref = Annotation.create(qgroup=qref, edition=0, user=uref) except pw.IntegrityError as e: log.error( "Create Q - cannot create Annotation of question {} error - {}." .format(gid, e)) return False return self.addTPages(tref, gref, t, pages, v)
def addTPages(self, tref, gref, t, pages, v): """ For initial construction of test-pages for a test. We use these so we know what structured pages we should have. """ flag = True with plomdb.atomic(): for p in pages: try: TPage.create( test=tref, group=gref, page_number=p, version=v, scanned=False, ) except pw.IntegrityError as e: log.error("Adding page {} for test {} error - {}".format( p, t, e)) flag = False return flag