More create note duplicate options (#269)
* Simplify access to note['options'] * Add checkAllModels option * Update documentation
This commit is contained in:
parent
591ac06aa3
commit
2150940c7e
15
README.md
15
README.md
@ -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": [
|
||||||
|
@ -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,36 +267,67 @@ 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
|
||||||
|
|
||||||
did = deck['id']
|
|
||||||
if duplicateScopeDeckName is not None:
|
|
||||||
deck2 = collection.decks.byName(duplicateScopeDeckName)
|
|
||||||
if deck2 is None:
|
|
||||||
# Invalid deck, so cannot be duplicate
|
|
||||||
return 0
|
|
||||||
did = deck2['id']
|
|
||||||
|
|
||||||
dids = {did: True}
|
|
||||||
if duplicateScopeCheckChildren:
|
|
||||||
for kv in collection.decks.children(did):
|
|
||||||
dids[kv[1]] = True
|
|
||||||
|
|
||||||
csum = anki.utils.fieldChecksum(val)
|
csum = anki.utils.fieldChecksum(val)
|
||||||
for noteId in note.col.db.list('select id from notes where csum = ? and id != ? and mid = ?', csum, note.id or 0, note.mid):
|
|
||||||
for cardDeckId in note.col.db.list('select did from cards where nid = ?', noteId):
|
# Create dictionary of deck ids
|
||||||
|
dids = None
|
||||||
|
if duplicateScope == 'deck':
|
||||||
|
did = deck['id']
|
||||||
|
if duplicateScopeDeckName is not None:
|
||||||
|
deck2 = collection.decks.byName(duplicateScopeDeckName)
|
||||||
|
if deck2 is None:
|
||||||
|
# Invalid deck, so cannot be duplicate
|
||||||
|
return 0
|
||||||
|
did = deck2['id']
|
||||||
|
|
||||||
|
dids = {did: True}
|
||||||
|
if duplicateScopeCheckChildren:
|
||||||
|
for kv in collection.decks.children(did):
|
||||||
|
dids[kv[1]] = True
|
||||||
|
|
||||||
|
# Build query
|
||||||
|
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):
|
||||||
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:
|
||||||
|
Loading…
Reference in New Issue
Block a user