158 lines
5.0 KiB
Python
158 lines
5.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright: Damien Elmes <oldanki@ichi2.net>
|
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
|
|
|
"""\
|
|
Facts
|
|
========
|
|
"""
|
|
__docformat__ = 'restructuredtext'
|
|
|
|
import time
|
|
from oldanki.db import *
|
|
from oldanki.errors import *
|
|
from oldanki.models import Model, FieldModel, fieldModelsTable
|
|
from oldanki.utils import genID, stripHTMLMedia
|
|
from oldanki.hooks import runHook
|
|
|
|
# Fields in a fact
|
|
##########################################################################
|
|
|
|
fieldsTable = Table(
|
|
'fields', metadata,
|
|
Column('id', Integer, primary_key=True),
|
|
Column('factId', Integer, ForeignKey("facts.id"), nullable=False),
|
|
Column('fieldModelId', Integer, ForeignKey("fieldModels.id"),
|
|
nullable=False),
|
|
Column('ordinal', Integer, nullable=False),
|
|
Column('value', UnicodeText, nullable=False))
|
|
|
|
class Field(object):
|
|
"A field in a fact."
|
|
|
|
def __init__(self, fieldModel=None):
|
|
if fieldModel:
|
|
self.fieldModel = fieldModel
|
|
self.ordinal = fieldModel.ordinal
|
|
self.value = u""
|
|
self.id = genID()
|
|
|
|
def getName(self):
|
|
return self.fieldModel.name
|
|
name = property(getName)
|
|
|
|
mapper(Field, fieldsTable, properties={
|
|
'fieldModel': relation(FieldModel)
|
|
})
|
|
|
|
# Facts: a set of fields and a model
|
|
##########################################################################
|
|
# mapped in cards.py
|
|
|
|
factsTable = Table(
|
|
'facts', metadata,
|
|
Column('id', Integer, primary_key=True),
|
|
Column('modelId', Integer, ForeignKey("models.id"), nullable=False),
|
|
Column('created', Float, nullable=False, default=time.time),
|
|
Column('modified', Float, nullable=False, default=time.time),
|
|
Column('tags', UnicodeText, nullable=False, default=u""),
|
|
# spaceUntil is reused as a html-stripped cache of the fields
|
|
Column('spaceUntil', UnicodeText, nullable=False, default=u""),
|
|
# obsolete
|
|
Column('lastCardId', Integer, ForeignKey(
|
|
"cards.id", use_alter=True, name="lastCardIdfk")))
|
|
|
|
class Fact(object):
|
|
"A single fact. Fields exposed as dict interface."
|
|
|
|
def __init__(self, model=None):
|
|
self.model = model
|
|
self.id = genID()
|
|
if model:
|
|
for fm in model.fieldModels:
|
|
self.fields.append(Field(fm))
|
|
self.new = True
|
|
|
|
def isNew(self):
|
|
return getattr(self, 'new', False)
|
|
|
|
def keys(self):
|
|
return [field.name for field in self.fields]
|
|
|
|
def values(self):
|
|
return [field.value for field in self.fields]
|
|
|
|
def __getitem__(self, key):
|
|
try:
|
|
return [f.value for f in self.fields if f.name == key][0]
|
|
except IndexError:
|
|
raise KeyError(key)
|
|
|
|
def __setitem__(self, key, value):
|
|
try:
|
|
[f for f in self.fields if f.name == key][0].value = value
|
|
except IndexError:
|
|
raise KeyError
|
|
|
|
def get(self, key, default):
|
|
try:
|
|
return self[key]
|
|
except (IndexError, KeyError):
|
|
return default
|
|
|
|
def assertValid(self):
|
|
"Raise an error if required fields are empty."
|
|
for field in self.fields:
|
|
if not self.fieldValid(field):
|
|
raise FactInvalidError(type="fieldEmpty",
|
|
field=field.name)
|
|
|
|
def fieldValid(self, field):
|
|
return not (field.fieldModel.required and not field.value.strip())
|
|
|
|
def assertUnique(self, s):
|
|
"Raise an error if duplicate fields are found."
|
|
for field in self.fields:
|
|
if not self.fieldUnique(field, s):
|
|
raise FactInvalidError(type="fieldNotUnique",
|
|
field=field.name)
|
|
|
|
def fieldUnique(self, field, s):
|
|
if not field.fieldModel.unique:
|
|
return True
|
|
req = ("select value from fields "
|
|
"where fieldModelId = :fmid and value = :val")
|
|
if field.id:
|
|
req += " and id != %s" % field.id
|
|
return not s.scalar(req, val=field.value, fmid=field.fieldModel.id)
|
|
|
|
def focusLost(self, field):
|
|
runHook('fact.focusLost', self, field)
|
|
|
|
def setModified(self, textChanged=False, deck=None, media=True):
|
|
"Mark modified and update cards."
|
|
self.modified = time.time()
|
|
if textChanged:
|
|
if not deck:
|
|
# FIXME: compat code
|
|
import ankiqt
|
|
if not getattr(ankiqt, 'setModWarningShown', None):
|
|
import sys; sys.stderr.write(
|
|
"plugin needs to pass deck to fact.setModified()")
|
|
ankiqt.setModWarningShown = True
|
|
deck = ankiqt.mw.deck
|
|
assert deck
|
|
self.spaceUntil = stripHTMLMedia(u" ".join(
|
|
self.values()))
|
|
for card in self.cards:
|
|
card.rebuildQA(deck)
|
|
|
|
# Fact deletions
|
|
##########################################################################
|
|
|
|
factsDeletedTable = Table(
|
|
'factsDeleted', metadata,
|
|
Column('factId', Integer, ForeignKey("facts.id"),
|
|
nullable=False),
|
|
Column('deletedTime', Float, nullable=False))
|