Delete cards from deck if absent in import

Hi,

I manage my anki deck in source and use genanki to create a apgk that I import into anki.

I manually assign id’s to each note. This way, if I update a note in source, the id stays the same, and when I import the generated apkg, the existing note in the anki deck is updated in the way I expect. If I don’t provide my own id, anki generates an id based on the note text. This means when I import an updated note, anki will end up having two notes: the old note (since it hashes to value X) and the new note (since it hashes to value Y).

This works fine for updating, however this does not work for note deletion. If I delete a note and its corresponding id from my source, generate the apkg excluding this note, and import into anki, anki will not perform a “full replacement” and will only add and update the existing cards.

As a user that keeps their notes in source and relies on the import functionality, I would like to be able to maintain the notes in a deck in full such that when I add, update, or remove notes in my source, that is reflected in anki when I import the apkg I generate.

Thanks,
Geoff

Anki rarely gets new features. This must have been discussed before. I’d search the old forum at anki.tenderapp.com and the anki subreddit. Maybe some combination of workarounds or add-ons help?

 

An idea for a manual workaround:

You could give all your notes in your external file a tag that doesn’t change like “from_genanki” an another like “updated_2020-11-12”. Whenever you change any note in the source you change the latter tag in the source for all notes to the current date etc.

Then you import into Anki. For all notes that you import the tag will be updated. For notes that you have deleted in the source the tag will not be updated so deleted notes won’t have this updated tags. You should be able to find them with a search like tag:from_genanki -tag:updated_2020-11-12 and then you can delete them?

Hi ijgnd,

While I would like a solution that doesn’t require these manual steps, this is a great work-around. Thanks for the idea.

Anki doesn’t provide a way for me to perform this tagging solution in an automated way, does it? That is, there’s no “Anki API” that I could use to query by tags and delete matching notes.

Thanks,
Geoff

I’d wait a couple of days if anyone posts a better solution. You should really check the other two places/forums. If nothing comes up I can try to look into this on the weekend.

 

In Anki for Desktop you are not limited to an API. If you know some python you can quickly combine snippets from different add-ons to achieve what you want.

I once collected some resources for Anki add-on development here.

I guess you could wrap the function importFile, https://github.com/ankitects/anki/blob/410987f0a6dacc32d9029e7801a1141b0e6ea434/qt/aqt/importing.py#L336 ?

to iterate over all cards with a certain tag you could try something like for cid in mw.col.find_cards(f'''tag:"{thetag}"''') , see https://addon-docs.ankiweb.net/#/getting-started?id=the-collection

1 Like

Hi ijnd,

Taking your suggestions, I’ve written the following add-on.

from anki.lang import _
from aqt import mw
from aqt.dbcheck import check_db
from aqt.importing import onImport
from aqt.qt import qconnect
# from aqt.utils import showInfo
from PyQt5 import QtWidgets


# This function expects existing cards in the collection and the cards in the file to be imported each to contain a
# "generated_*" tag.  Existing cards with this tag that are *not* updated by the import are deleted.
def put_import():
    generated_tags = set()
    for cid in mw.col.find_cards(""):
        card = mw.col.getCard(cid)
        card_generated_tags = [tag for tag in card.note().tags if tag.startswith("generated_")]
        generated_tags.update(card_generated_tags)
    # showInfo(_("found tags: " + ",".join(generated_tags)))
    onImport(mw)
    query = " or ".join(["tag:" + tag for tag in generated_tags])
    # showInfo(_("query: " + query))
    cards_to_remove = [cid for cid in mw.col.find_cards(query)]
    # showInfo(_("to rem: " + ",".join([str(card) for card in cards_to_remove])))
    mw.col.remove_notes_by_card(cards_to_remove)
    check_db(mw)


actionPutImport = QtWidgets.QAction(mw)
actionPutImport.setText(_("&Put Import"))
actionPutImport.setObjectName("actionPutImport")
qconnect(actionPutImport.triggered, put_import)
mw.form.menuCol.addAction(actionPutImport)

This looks to work as I expect.

I generate my cards in my application built on genanki which adds a tag to every card that looks like generated_<the date>. Then I use this function (rather than the other import possibilities) to import the generated apkg. This function notes the generated_* tags of the current collection, imports the apkg, which overwrites the existing generated_* tags, but does not overwrite those cards which are absent in the importing apkg, ultimately leaving “old” cards with the “old” generated_ tag. The function uses this “old” tag to select and delete those cards. Finally we check the db in order to remove now empty tags.

Let me know if you have any comments or suggestions and thanks for your help.

Cheers,
Geoff

This looks pretty good to me. Though my python skills are very limited and I’ve forgotten a lot about the Anki internals over the last six months … But I guess you’d have noticed if it didn’t work.

Hello geofflittle,
Have you uploaded this add-on to ankiweb? If so what have you called it and if not is it possible for you to share this with me? Thank you!