def test_0140(_): # A more complex set. p = Entr(_rdng=[Rdng(txt='の'), Rdng(txt='や'), Rdng(txt='ぬ')], _kanj=[Kanj(txt='野'), Kanj(txt='埜'), Kanj(txt='金')]) e = deepcopy(p) p._rdng[1]._freq.append(_.ichi1) p._kanj[0]._freq.append(_.ichi1) p._rdng[1]._freq.append(_.gai1) p._kanj[2]._freq.append(_.gai1) p._rdng[2]._freq.append(_.nf16) p._kanj[0]._freq.append(_.nf16) p._rdng[2]._freq.append(_.nf17) p._kanj[2]._freq.append(_.nf17) p._rdng[2]._freq.append(_.ichi2) p._kanj[2]._freq.append(_.ichi2a) jdb.copy_freqs(p, e) assertFreqOnLists(_, _.ichi1, e._rdng[1], e._kanj[0]) assertFreqOnLists(_, _.gai1, e._rdng[1], e._kanj[2]) assertFreqOnLists(_, _.nf16, e._rdng[2], e._kanj[0]) assertFreqOnLists(_, _.nf17, e._rdng[2], e._kanj[2]) assertFreqOnLists(_, _.ichi2, e._rdng[2], None) assertFreqOnLists(_, _.ichi2a, e._kanj[2], None) _.assertEqual(0, len(e._rdng[0]._freq)) _.assertEqual(2, len(e._rdng[1]._freq)) _.assertEqual(3, len(e._rdng[2]._freq)) _.assertEqual(2, len(e._kanj[0]._freq)) _.assertEqual(0, len(e._kanj[1]._freq)) _.assertEqual(3, len(e._kanj[2]._freq))
def test_0130(_): # Kanj only freq p = Entr(_rdng=[Rdng(txt='よ')], _kanj=[Kanj(txt='良')]) e = deepcopy(p) p._kanj[0]._freq = [_.ichi2] jdb.copy_freqs(p, e) assertFreqOnLists(_, _.ichi2, e._kanj[0]) _.assertEqual(0, len(e._rdng[0]._freq)) _.assertEqual(1, len(e._kanj[0]._freq))
def test_0040(_): # Test existing freq removal p = Entr(_rdng=[Rdng(txt='よ')], _kanj=[Kanj(txt='良')]) e = deepcopy(p) e._rdng[0]._freq = [_.ichi2] e._kanj[0]._freq = [_.gai1] jdb.copy_freqs(p, e) _.assertEqual([], e._rdng[0]._freq) _.assertEqual([], e._kanj[0]._freq)
def test_0160(_): # Same as test_0150 except reading and kanji swapped. p = Entr(_rdng=[Rdng(txt='の'), Rdng(txt='や')], _kanj=[Kanj(txt='野')]) e = deepcopy(p) p._rdng[0]._freq.append(_.ichi2) p._kanj[0]._freq.append(_.ichi2) p._rdng[1]._freq.append(_.ichi2a) p._kanj[0]._freq.append(_.ichi2a) del e._rdng[1] # Delete the second reading. jdb.copy_freqs(p, e) # The ichi2a tag should be gone from kanj[0]. assertFreqOnLists(_, _.ichi2, e._rdng[0], e._kanj[0], 1, 1)
def test_0150(_): # Check the senario noted in IS-209, note "2011-09-03 07:02:00" p = Entr(_rdng=[Rdng(txt='の')], _kanj=[Kanj(txt='野'), Kanj(txt='埜')]) e = deepcopy(p) p._rdng[0]._freq.append(_.ichi2) p._kanj[0]._freq.append(_.ichi2) p._rdng[0]._freq.append(_.ichi2a) p._kanj[1]._freq.append(_.ichi2a) del e._kanj[1] # Delete the second kanji. jdb.copy_freqs(p, e) # The ichi2a tag should be gone from rdng[0]. assertFreqOnLists(_, _.ichi2, e._rdng[0], e._kanj[0], 1, 1)
def test_0170(_): # Same as test_0160 except but different tags. p = Entr(_rdng=[Rdng(txt='の'), Rdng(txt='や')], _kanj=[Kanj(txt='野')]) e = deepcopy(p) p._rdng[0]._freq.append(_.ichi2) p._kanj[0]._freq.append(_.ichi2) p._rdng[1]._freq.append(_.ichi1) p._kanj[0]._freq.append(_.ichi1) del e._rdng[1] # Delete the second reading. jdb.copy_freqs(p, e) # The ichi1 tag should remain on kanj[0]. assertFreqOnLists(_, _.ichi2, e._rdng[0], e._kanj[0], 1, 2) assertFreqOnLists(_, _.ichi1, e._kanj[0])
def test_0110(_): # Single shared freq on 1 kanj and 1 rdng. p = Entr(_rdng=[Rdng(txt='よ')], _kanj=[Kanj(txt='良')]) e = deepcopy(p) # Note that we must use the same (as in 'is', not as in '==') # Freq object in the two freq lists, the lists themselves must # be distinct objects to accurately represent how things work # in the jmdictdb software. p._rdng[0]._freq = [_.ichi2] p._kanj[0]._freq = [_.ichi2] jdb.copy_freqs(p, e) assertFreqOnLists(_, _.ichi2, e._rdng[0], e._kanj[0]) _.assertEqual(1, len(e._rdng[0]._freq)) _.assertEqual(1, len(e._kanj[0]._freq))
def test_0410(_): # IS-209 # Setup the "from" entry like seq# 1589900. p = Entr(_rdng=[Rdng(txt='かき'), Rdng(txt='なつき')], _kanj=[Kanj(txt='夏期'), Kanj(txt='夏季')]) e = deepcopy(p) ichi1a = Freq(kw=1, value=1) ichi1b = Freq(kw=1, value=1) news1 = Freq(kw=7, value=1) nf13 = Freq(kw=5, value=13) p._rdng[0]._freq = [ichi1a, ichi1b, news1, nf13] p._kanj[0]._freq = [ichi1a] p._kanj[1]._freq = [ichi1b, news1, nf13] # Remove k0 from the "to" entry, which moves k1 to k0. del e._kanj[0] jdb.copy_freqs(p, e) # The r0/k1 freq (ichi1b) should have been moved to r0/k0. # The r0/k0 freq (ichi1a) should be gone with the old k0. assertFreqOnLists(_, ichi1b, e._rdng[0], e._kanj[0]) assertFreqOnLists(_, news1, e._rdng[0], e._kanj[0]) assertFreqOnLists(_, nf13, e._rdng[0], e._kanj[0]) _.assertEqual(3, len(e._rdng[0]._freq)) _.assertEqual(3, len(e._kanj[0]._freq)) _.assertEqual(0, len(e._rdng[1]._freq))
def test_0030(_): # No rdng. p = Entr(_kanj=[Kanj('良い')]) e = deepcopy(p) jdb.copy_freqs(p, e) _.assertEqual([], e._kanj[0]._freq)
def test_0020(_): # No kanj. p = Entr(_rdng=[Rdng('よい')]) e = deepcopy(p) jdb.copy_freqs(p, e) _.assertEqual([], e._rdng[0]._freq)
def test_0010(_): # No rdng or kanj. p, e = Entr(), Entr() jdb.copy_freqs(p, e) _.assertEqual([], e._rdng) _.assertEqual([], e._kanj)
def submission(dbh, entr, disp, errs, is_editor=False, userid=None): # Add a changed entry, 'entr', to the jmdictdb database accessed # by the open DBAPI cursor, 'dbh'. # # dbh -- An open DBAPI cursor # entr -- A populated Entr object that defines the entry to # be added. See below for description of how some of its # attributes affect the submission. # disp -- Disposition, one of three string values: # '' -- Submit as normal user. # 'a' -- Approve this submission. # 'r' -- Reject this submission. # errs -- A list to which an error messages will be appended. # Note that if the error message contains html it should be # wrapped in jmcgi.Markup() to prevent it from being escaped # in the template. Conversely, error messages that contain # text from user input should NOT be so wrapped since they # must be escaped in the template. # is_editor -- True is this submission is being performed by # a logged in editor. Approved or Rejected dispositions will # fail if this is false. Its value may be conveniently # obtained from jmcgi.is_editor(). False if a normal user. # userid -- The userid if submitter is logged in editor or # None if not. # # Note that we never modify existing database entries other # than to sometimes completetly erase them. Submissions # of all three types (submit, approve, reject) *always* # result in the creation of a new entry object in the database. # The new entry will be created by writing 'entr' to the # database. The following attributes in 'entr' are relevant: # # entr.dfrm -- If None, this is a new submission. Otherwise, # it must be the id number of the entry this submission # is an edit of. # entr.stat -- Must be consistent with changes requested. In # particular, if it is 4 (Delete), changes made in 'entr' # will be ignored, and a copy of the parent entry will be # submitted with stat D. # entr.src -- Required to be set, new entry will copy. # # FIXME: prohibit non-editors from making src # # different than parent? # entr.seq -- If set, will be copied. If not set, submission # will get a new seq number but this untested and very # likely to break something. # # FIXME: prohibit non-editors from making seq number # # different than parent, or non-null if no parent? # entr.hist -- The last hist item on the entry will supply # the comment, email and name fields to newly constructed # comment that will replace it in the database. The time- # stamp and diff are regenerated and the userid field is # set from our userid parameter. # # FIXME: should pass history record explicity so that # # we can be sure if the caller is or is not supplying # # one. That will make it easier to use this function # # from other programs. # The following entry attributes need not be set: # entr.id -- Ignored (reset to None). # entr.unap -- Ignored (reset based on 'disp'). # Additionally, if 'is_editor' is false, the rdng._freq and # kanj._freq items will be copied from the parent entr rather # than using the ones supplied on 'entr'. See jdb.copy_freqs() # for details about how the copy works when the rdng's or kanj's # differ between the parent and 'entr'. KW = jdb.KW L('cgi.edsubmit.submission').info( ("disp=%s, is_editor=%s, userid=%s, entry id=%s,\n" + " " * 36 + "parent=%s, stat=%s, unap=%s, seq=%s, src=%s") % (disp, is_editor, userid, entr.id, entr.dfrm, entr.stat, entr.unap, entr.seq, entr.src)) L('cgi.edsubmit.submission').info("entry text: %s %s" % ((';'.join(k.txt for k in entr._kanj)), (';'.join(r.txt for r in entr._rdng)))) L('cgi.edsubmit.submission').debug("seqset: %s" % logseq(dbh, entr.seq, entr.src)) oldid = entr.id entr.id = None # Submissions, approvals and rejections will entr.unap = not disp # always produce a new db entry object so merge_rev = False # nuke any id number. if not entr.dfrm: # This is a submission of a new entry. entr.stat = KW.STAT['A'].id entr.seq = None # Force addentr() to assign seq number. pentr = None # No parent entr. edtree = None else: # Modification of existing entry. edroot = get_edroot(dbh, entr.dfrm) edtree = get_subtree(dbh, edroot) # Get the parent entry and augment the xrefs so when hist diffs # are generated, they will show xref details. L('cgi.edsubmit.submission').debug("reading parent entry %d" % entr.dfrm) pentr, raw = jdb.entrList(dbh, None, [entr.dfrm], ret_tuple=True) if len(pentr) != 1: L('cgi.edsubmit.submission').debug("missing parent %d" % entr.dfrm) # The editset may have changed between the time our user # displayed the Confirmation screen and they clicked the # Submit button. Changes involving unapproved edits result # in the addition of entries and don't alter the preexisting # tree shape. Approvals of edits, deletes or rejects may # affect our subtree and if so will always manifest themselves # as the disappearance of our parent entry. errs.append( "The entry you are editing no loger exists because it " "was approved, deleted or rejected. " "Please search for entry '%s' seq# %s and reenter your changes " "if they are still applicable." % (KW.SRC[ent.src].kw, entr.seq)) return pentr = pentr[0] jdb.augment_xrefs(dbh, raw['xref']) if entr.stat == KW.STAT['D'].id: # If this is a deletion, set $merge_rev. When passed # to function merge_hist() it will tell it to return the # edited entry's parent, rather than the edited entry # itself. The reason is that if we are doing a delete, # we do not want to make any changes to the entry, even # if the submitter has done so. merge_rev = True # Merge_hist() will combine the history entry in the submitted # entry with the all the previous history records in the # parent entry, so the the new entry will have a continuous # history. In the process it checks that the parent entry # exists -- it might not if someone else has approved a # different edit in the meantime. # merge_hist also returns an entry. If 'merge_rev' is false, # the entry returned is 'entr'. If 'merge_rev' is true, # the entry returned is the entr pointed to by 'entr.dfrm' # (i.e. the original entry that the submitter edited.) # This is done when a delete is requested and we want to # ignore any edits the submitter may have made (which 'entr' # will contain.) # Before calling merge_hist() check for a condition that would # cause merge_hist() to fail. if entr.stat == KW.STAT['D'].id and not getattr(entr, 'dfrm', None): L('cgi.edsubmit.submission').debug("delete of new entry error") errs.append("Delete requested but this is a new entry.") if disp == 'a' and has_xrslv(entr) and entr.stat == KW.STAT['A'].id: L('cgi.edsubmit.submission').debug("unresolved xrefs error") errs.append("Can't approve because entry has unresolved xrefs") if not errs: # If this is a submission by a non-editor, restore the # original entry's freq items which non-editors are not # allowed to change. if not is_editor: if pentr: L('cgi.edsubmit.submission').debug("copying freqs from parent") jdb.copy_freqs(pentr, entr) # Note that non-editors can provide freq items on new # entries. We expect an editor to vet this when approving. # Entr contains the hist record generate by the edconf.py # but it is not trustworthy since it could be modified or # created from scratch before we get it. So we extract # the unvalidated info from it (name, email, notes, refs) # and recreate it. h = entr._hist[-1] # When we get here, if merge_rev is true, pentr will also be # true. If we are wrong, add_hist() will throw an exception # but will never return a None, so no need to check return val. L('cgi.edsubmit.submission').debug("adding hist for '%s', merge=%s" % (h.name, merge_rev)) entr = jdb.add_hist(entr, pentr, userid, h.name, h.email, h.notes, h.refs, merge_rev) if not errs: # Occasionally, often from copy-pasting, a unicode BOM # character finds its way into one of an entry's text # strings. We quietly remove any here. n = jdb.bom_fixall(entr) if n > 0: L('cgi.edsubmit.submission').debug("removed %s BOM character(s)" % n) if not errs: if not disp: added = submit(dbh, entr, edtree, errs) elif disp == "a": added = approve(dbh, entr, edtree, errs) elif disp == "r": added = reject(dbh, entr, edtree, errs, None) else: L('cgi.edsubmit.submission').debug("bad url parameter (disp=%s)" % disp) errs.append("Bad url parameter (disp=%s)" % disp) L('cgi.edsubmit.submission').debug("seqset: %s" % logseq(dbh, entr.seq, entr.src)) if not errs: return added # Note that changes have not been committed yet, caller is # expected to do that. return None
def main(args, opts): jdb.reset_encoding(sys.stdout, 'utf-8') errs = [] chklist = {} try: form, svc, dbg, cur, sid, sess, parms, cfg = jmcgi.parseform() except Exception as e: jmcgi.err_page([str(e)]) fv = form.getfirst fl = form.getlist KW = jdb.KW # 'eid' will be an integer if we are editing an existing # entry, or undefined if this is a new entry. pentr = None eid = url_int('id', form, errs) if eid: # Get the parent entry of the edited entry. This is what the # edited entry will be diff'd against for the history record. # It is also the entry that will be pointed to by the edited # entry's 'dfrm' field. pentr = jdb.entrList(cur, None, [eid]) #FIXME: Need a better message with more explanation. if not pentr: errs.append("The entry you are editing has been deleted.") else: pentr = pentr[0] # Desired disposition: 'a':approve, 'r':reject, undef:submit. disp = url_str('disp', form) if disp != 'a' and disp != 'r' and disp != '' and disp is not None: errs.append("Invalid 'disp' parameter: '%s'" % disp) # New status is A for edit of existing or new entry, D for # deletion of existing entry. delete = fv('delete') makecopy = fv('makecopy') if delete and makecopy: errs.append( "The 'delete' and 'treat as new'" " checkboxes are mutually exclusive; please select only one.") if makecopy: eid = None # FIXME: we need to disallow new entries with corp.seq # that matches an existing A, A*, R*, D*, D? entry. # Do same check in submit.py. seq = url_int('seq', form, errs) src = url_int('src', form, errs) notes = url_str('notes', form) srcnote = url_str('srcnote', form) # These are the JEL (JMdict Edit Language) texts which # we will concatenate into a string that is fed to the # JEL parser which will create an Entr object. kanj = (stripws(url_str('kanj', form))).strip() rdng = (stripws(url_str('rdng', form))).strip() sens = (url_str('sens', form)).strip() intxt = "\f".join((kanj, rdng, sens)) grpstxt = url_str('grp', form) # Get the meta-edit info which will go into the history # record for this change. comment = url_str('comment', form) refs = url_str('reference', form) name = url_str('name', form) email = url_str('email', form) if errs: jmcgi.err_page(errs) # Parse the entry data. Problems will be reported # by messages in 'perrs'. We do the parse even if # the request is to delete the entry (is this right # thing to do???) since on the edconf page we want # to display what the entry was. The edsubmit page # will do the actual deletion. entr, errs = parse(intxt) # 'errs' is a list which if not empty has a single item # which is a 2-seq of str's: (error-type, error-message). if errs or not entr: if not entr and not errs: errs = ([], "Unable to create an entry from your input.") jmcgi.err_page([errs[0][1]], prolog=errs[0][0], cssclass="errormsg") entr.dfrm = eid entr.unap = not disp # To display the xrefs and reverse xrefs in html, they # need to be augmented with additional info about their # targets. collect_refs() simply returns a list Xref # objects that are on the entr argument's .xref list # (forward xrefs) if rev not true, or the Xref objects # on the entr argument's ._xrer list (reverse xrefs) if # rev is true). This does not remove them from the entry # and is done simply for convenience so we can have # augment_xrefs() process them all in one shot. # augment_xrefs add an attribute, .TARG, to each Xref # object whose value is an Entr object for the entry the # xref points to if rev is not true, or the entry the xref # is from, if rev is true. These Entr objects can be used # to display info about the xref target or source such as # seq#, reading or kanji. See jdb.augment_xrefs() for details. # Note that <xrefs> and <xrers> below contain references # to the xrefs on the entries; thus the augmentation done # by jdb.augment_xrefs() alters the xref objects on those # entries. if pentr: x = jdb.collect_xrefs([pentr]) if x: jdb.augment_xrefs(cur, x) # Although we don't allow editing of an entry's reverse # xref, we still augment them (on the parent entry) # because we will display them. x = jdb.collect_xrefs([pentr], rev=True) if x: jdb.augment_xrefs(cur, x, rev=True) x = jdb.collect_xrefs([entr]) if x: jdb.augment_xrefs(cur, x) if delete: # Ignore any content changes made by the submitter by # restoring original values to the new entry. entr.seq = pentr.seq entr.src = pentr.src entr.stat = KW.STAT['D'].id entr.notes = pentr.notes entr.srcnote = pentr.srcnote entr._kanj = getattr(pentr, '_kanj', []) entr._rdng = getattr(pentr, '_rdng', []) entr._sens = getattr(pentr, '_sens', []) entr._snd = getattr(pentr, '_snd', []) entr._grp = getattr(pentr, '_grp', []) entr._cinf = getattr(pentr, '_cinf', []) else: # Migrate the entr details to the new entr object # which to this point has only the kanj/rdng/sens # info provided by jbparser. entr.seq = seq entr.src = src entr.stat = KW.STAT['A'].id entr.notes = notes entr.srcnote = srcnote entr._grp = jelparse.parse_grp(grpstxt) # This form and the JEL parser provide no way to change # some entry attributes such _cinf, _snd, reverse xrefs # and for non-editors, _freq. We need to copy these items # from the original entry to the new, edited entry to avoid # loosing them. The copy can be shallow since we won't be # changing the copied content. if pentr: if not jmcgi.is_editor(sess): jdb.copy_freqs(pentr, entr) if hasattr(pentr, '_cinf'): entr._cinf = pentr._cinf copy_snd(pentr, entr) # Copy the reverse xrefs that are on pentr to entr, # removing any that are no longer valid because they # refer to senses , readings or kanji no longer present # on the edited entry. Note that these have already # been augmented above. nuked_xrers = realign_xrers(entr, pentr) if nuked_xrers: chklist['xrers'] = format_for_warnings(nuked_xrers, pentr) # Add sound details so confirm page will look the same as the # original entry page. Otherwise, the confirm page will display # only the sound clip id(s). #FIXME? Should the following snd augmentation stuff be outdented # one level so that it is done in both the delete and non-delete # paths? snds = [] for s in getattr(entr, '_snd', []): snds.append(s) for r in getattr(entr, '_rdng', []): for s in getattr(r, '_snd', []): snds.append(s) if snds: jdb.augment_snds(cur, snds) # If any xrefs were given, resolve them to actual entries # here since that is the form used to store them in the # database. If any are unresolvable, an approriate error # is saved and will reported later. rslv_errs = jelparse.resolv_xrefs(cur, entr) if rslv_errs: chklist['xrslv'] = rslv_errs if errs: jmcgi.err_page(errs) # Append a new hist record details this edit. if not hasattr(entr, '_hist'): entr._hist = [] entr = jdb.add_hist(entr, pentr, sess.userid if sess else None, name, email, comment, refs, entr.stat == KW.STAT['D'].id) if not delete: check_for_errors(entr, errs) if errs: jmcgi.err_page(errs) pseq = pentr.seq if pentr else None check_for_warnings(cur, entr, pseq, chklist) # The following all expect a list of entries. jmcgi.add_filtered_xrefs([entr], rem_unap=False) serialized = serialize.serialize([entr]) jmcgi.htmlprep([entr]) entrs = [[entr, None]] # Package 'entr' as expected by entr.jinja. jmcgi.jinja_page("edconf.jinja", entries=entrs, serialized=serialized, chklist=chklist, disp=disp, parms=parms, svc=svc, dbg=dbg, sid=sid, session=sess, cfg=cfg, this_page='edconf.py')