More create note duplicate options (#269)

* Simplify access to note['options']

* Add checkAllModels option

* Update documentation
This commit is contained in:
toasted-nutbread 2021-07-12 22:46:22 -04:00 committed by GitHub
parent 591ac06aa3
commit 2150940c7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 34 deletions

View File

@ -1957,11 +1957,13 @@ corresponding to when the API was available for use.
Normally duplicate cards can not be added and trigger exception. Normally duplicate cards can not be added and trigger exception.
The `duplicateScope` member inside `options` can be used to specify the scope for which duplicates are checked. The `duplicateScope` member inside `options` can be used to specify the scope for which duplicates are checked.
A value of `"deckName"` will only check for duplicates in the target deck; any other value will check the entire collection. A value of `"deck"` will only check for duplicates in the target deck; any other value will check the entire collection.
The `duplicateScopeOptions` object can be used to specify some additional settings. `duplicateScopeOptions.deckName`
will specify which deck to use for checking duplicates in. If undefined or `null`, the target deck will be used. The `duplicateScopeOptions` object can be used to specify some additional settings:
`duplicateScopeOptions.checkChildren` will change whether or not duplicate cards are checked in child decks;
the default value is `false`. * `duplicateScopeOptions.deckName` will specify which deck to use for checking duplicates in. If undefined or `null`, the target deck will be used.
* `duplicateScopeOptions.checkChildren` will change whether or not duplicate cards are checked in child decks. The default value is `false`.
* `duplicateScopeOptions.checkAllModels` specifies whether duplicate checks are performed across all note types. The default value is `false`.
*Sample request*: *Sample request*:
```json ```json
@ -1981,7 +1983,8 @@ corresponding to when the API was available for use.
"duplicateScope": "deck", "duplicateScope": "deck",
"duplicateScopeOptions": { "duplicateScopeOptions": {
"deckName": "Default", "deckName": "Default",
"checkChildren": false "checkChildren": false,
"checkAllModels": false
} }
}, },
"tags": [ "tags": [

View File

@ -222,22 +222,28 @@ class AnkiConnect:
duplicateScope = None duplicateScope = None
duplicateScopeDeckName = None duplicateScopeDeckName = None
duplicateScopeCheckChildren = False duplicateScopeCheckChildren = False
duplicateScopeCheckAllModels = False
if 'options' in note: if 'options' in note:
if 'allowDuplicate' in note['options']: options = note['options']
allowDuplicate = note['options']['allowDuplicate'] if 'allowDuplicate' in options:
allowDuplicate = options['allowDuplicate']
if type(allowDuplicate) is not bool: if type(allowDuplicate) is not bool:
raise Exception('option parameter "allowDuplicate" must be boolean') raise Exception('option parameter "allowDuplicate" must be boolean')
if 'duplicateScope' in note['options']: if 'duplicateScope' in options:
duplicateScope = note['options']['duplicateScope'] duplicateScope = options['duplicateScope']
if 'duplicateScopeOptions' in note['options']: if 'duplicateScopeOptions' in options:
duplicateScopeOptions = note['options']['duplicateScopeOptions'] duplicateScopeOptions = options['duplicateScopeOptions']
if 'deckName' in duplicateScopeOptions: if 'deckName' in duplicateScopeOptions:
duplicateScopeDeckName = duplicateScopeOptions['deckName'] duplicateScopeDeckName = duplicateScopeOptions['deckName']
if 'checkChildren' in duplicateScopeOptions: if 'checkChildren' in duplicateScopeOptions:
duplicateScopeCheckChildren = duplicateScopeOptions['checkChildren'] duplicateScopeCheckChildren = duplicateScopeOptions['checkChildren']
if type(duplicateScopeCheckChildren) is not bool: if type(duplicateScopeCheckChildren) is not bool:
raise Exception('option parameter "duplicateScopeOptions.checkChildren" must be boolean') raise Exception('option parameter "duplicateScopeOptions.checkChildren" must be boolean')
if 'checkAllModels' in duplicateScopeOptions:
duplicateScopeCheckAllModels = duplicateScopeOptions['checkAllModels']
if type(duplicateScopeCheckAllModels) is not bool:
raise Exception('option parameter "duplicateScopeOptions.checkAllModels" must be boolean')
duplicateOrEmpty = self.isNoteDuplicateOrEmptyInScope( duplicateOrEmpty = self.isNoteDuplicateOrEmptyInScope(
ankiNote, ankiNote,
@ -245,7 +251,8 @@ class AnkiConnect:
collection, collection,
duplicateScope, duplicateScope,
duplicateScopeDeckName, duplicateScopeDeckName,
duplicateScopeCheckChildren duplicateScopeCheckChildren,
duplicateScopeCheckAllModels
) )
if duplicateOrEmpty == 1: if duplicateOrEmpty == 1:
@ -260,17 +267,33 @@ class AnkiConnect:
raise Exception('cannot create note for unknown reason') raise Exception('cannot create note for unknown reason')
def isNoteDuplicateOrEmptyInScope(self, note, deck, collection, duplicateScope, duplicateScopeDeckName, duplicateScopeCheckChildren): def isNoteDuplicateOrEmptyInScope(
# 1 if first is empty; 2 if first is a duplicate, 0 otherwise. self,
if duplicateScope != 'deck': note,
deck,
collection,
duplicateScope,
duplicateScopeDeckName,
duplicateScopeCheckChildren,
duplicateScopeCheckAllModels
):
# Returns: 1 if first is empty, 2 if first is a duplicate, 0 otherwise.
# note.dupeOrEmpty returns if a note is a global duplicate with the specific model.
# This is used as the default check, and the rest of this function is manually
# checking if the note is a duplicate with additional options.
if duplicateScope != 'deck' and not duplicateScopeCheckAllModels:
return note.dupeOrEmpty() or 0 return note.dupeOrEmpty() or 0
# dupeOrEmpty returns if a note is a global duplicate # Primary field for uniqueness
# the rest of the function checks to see if the note is a duplicate in the deck
val = note.fields[0] val = note.fields[0]
if not val.strip(): if not val.strip():
return 1 return 1
csum = anki.utils.fieldChecksum(val)
# Create dictionary of deck ids
dids = None
if duplicateScope == 'deck':
did = deck['id'] did = deck['id']
if duplicateScopeDeckName is not None: if duplicateScopeDeckName is not None:
deck2 = collection.decks.byName(duplicateScopeDeckName) deck2 = collection.decks.byName(duplicateScopeDeckName)
@ -284,12 +307,27 @@ class AnkiConnect:
for kv in collection.decks.children(did): for kv in collection.decks.children(did):
dids[kv[1]] = True dids[kv[1]] = True
csum = anki.utils.fieldChecksum(val) # Build query
for noteId in note.col.db.list('select id from notes where csum = ? and id != ? and mid = ?', csum, note.id or 0, note.mid): query = 'select id from notes where csum=?'
queryArgs = [csum]
if note.id:
query += ' and id!=?'
queryArgs.append(note.id)
if not duplicateScopeCheckAllModels:
query += ' and mid=?'
queryArgs.append(note.mid)
# Search
for noteId in note.col.db.list(query, *queryArgs):
if dids is None:
# Duplicate note exists in the collection
return 2
# Validate that a card exists in one of the specified decks
for cardDeckId in note.col.db.list('select did from cards where nid=?', noteId): for cardDeckId in note.col.db.list('select did from cards where nid=?', noteId):
if cardDeckId in dids: if cardDeckId in dids:
return 2 return 2
# Not a duplicate
return 0 return 0
def getCard(self, card_id: int) -> Card: def getCard(self, card_id: int) -> Card: