Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deck Movement Options #4

Merged
merged 3 commits into from
Nov 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions crowd_anki/config/config_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def accept(self):
super().accept()

def ui_initial_setup(self):
self.setup_snapshot_options()
self.setup_export_options()
self.setup_import_options()

def setup_snapshot_options(self):
self.form.textedit_snapshot_path.setText(self.config.snapshot_path)
self.form.textedit_snapshot_path.textChanged.connect(self.changed_textedit_snapshot_path)

Expand All @@ -43,6 +48,7 @@ def ui_initial_setup(self):
)
self.form.textedit_snapshot_root_decks.textChanged.connect(self.changed_textedit_snapshot_root_decks)

def setup_export_options(self):
self.form.cb_reverse_sort.setChecked(self.config.export_notes_reverse_order)
self.form.cb_reverse_sort.stateChanged.connect(self.toggle_reverse_sort)

Expand All @@ -51,12 +57,19 @@ def ui_initial_setup(self):
)
self.form.textedit_deck_sort_methods.textChanged.connect(self.changed_textedit_deck_sort_methods)

def setup_import_options(self):
self.form.cb_ignore_move_cards.setChecked(self.config.import_notes_ignore_deck_movement)
self.form.cb_ignore_move_cards.stateChanged.connect(self.toggle_ignore_move_cards)

def toggle_automated_snapshot(self):
self.config.automated_snapshot = not self.config.automated_snapshot

def toggle_reverse_sort(self):
self.config.export_notes_reverse_order = not self.config.export_notes_reverse_order

def toggle_ignore_move_cards(self):
self.config.import_notes_ignore_deck_movement = not self.config.import_notes_ignore_deck_movement

def changed_textedit_deck_sort_methods(self):
self.config.export_note_sort_methods = self.string_cs_to_list(
self.form.textedit_deck_sort_methods.toPlainText()
Expand Down
2 changes: 2 additions & 0 deletions crowd_anki/config/config_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class ConfigSettings:
snapshot_root_decks: list
export_notes_reverse_order: bool
export_note_sort_methods: list
import_notes_ignore_deck_movement: bool

@property
def formatted_export_note_sort_methods(self) -> list:
Expand All @@ -44,6 +45,7 @@ class Properties(Enum):
SNAPSHOT_ROOT_DECKS = ConfigEntry("snapshot_root_decks", [])
EXPORT_NOTE_SORT_METHODS = ConfigEntry("export_note_sort_methods", [NoteSortingMethods.NO_SORTING.value])
EXPORT_NOTES_REVERSE_ORDER = ConfigEntry("export_notes_reverse_order", False)
IMPORT_NOTES_IGNORE_DECK_MOVEMENT = ConfigEntry("import_notes_ignore_deck_movement", False)

def __init__(self, addon_manager=None, init_values=None):
self.addon_manager = addon_manager or mw.addonManager
Expand Down
21 changes: 15 additions & 6 deletions crowd_anki/config/config_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(843, 749)
Dialog.resize(825, 726)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
Expand Down Expand Up @@ -43,6 +43,14 @@ def setupUi(self, Dialog):
self.textedit_snapshot_root_decks.setObjectName("textedit_snapshot_root_decks")
self.verticalLayout_3.addWidget(self.textedit_snapshot_root_decks)
self.verticalLayout_2.addWidget(self.group_snapshot)
self.group_deck_import = QtWidgets.QGroupBox(Dialog)
self.group_deck_import.setObjectName("group_deck_import")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.group_deck_import)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.cb_ignore_move_cards = QtWidgets.QCheckBox(self.group_deck_import)
self.cb_ignore_move_cards.setObjectName("cb_ignore_move_cards")
self.verticalLayout_5.addWidget(self.cb_ignore_move_cards)
self.verticalLayout_2.addWidget(self.group_deck_import)
self.group_deck_export = QtWidgets.QGroupBox(Dialog)
self.group_deck_export.setObjectName("group_deck_export")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.group_deck_export)
Expand All @@ -58,12 +66,8 @@ def setupUi(self, Dialog):
self.verticalLayout_4.addWidget(self.cb_reverse_sort)
self.verticalLayout_2.addWidget(self.group_deck_export)
self.horizontalLayout.addLayout(self.verticalLayout_2)
self.explanatory_text = QtWidgets.QLabel(Dialog)
self.explanatory_text.setText("")
self.explanatory_text.setWordWrap(True)
self.explanatory_text.setObjectName("explanatory_text")
self.horizontalLayout.addWidget(self.explanatory_text)
self.tb_instructions = QtWidgets.QTextBrowser(Dialog)
self.tb_instructions.setLineWidth(1)
self.tb_instructions.setObjectName("tb_instructions")
self.horizontalLayout.addWidget(self.tb_instructions)
self.verticalLayout.addLayout(self.horizontalLayout)
Expand All @@ -85,6 +89,8 @@ def retranslateUi(self, Dialog):
self.lbl_snapshot_path.setText(_translate("Dialog", "Snapshot Path:"))
self.cb_automated_snapshot.setText(_translate("Dialog", "Automated Snapshot"))
self.lbl_snapshot.setText(_translate("Dialog", "Snapshot Root Decks (separated by comma)"))
self.group_deck_import.setTitle(_translate("Dialog", "Import"))
self.cb_ignore_move_cards.setText(_translate("Dialog", "Do Not Move Existing Cards"))
self.group_deck_export.setTitle(_translate("Dialog", "Export"))
self.lbl_deck_sort.setText(_translate("Dialog", "Deck Sort Method(s) (separated by comma)"))
self.cb_reverse_sort.setText(_translate("Dialog", "Reverse Sort Order"))
Expand All @@ -103,6 +109,9 @@ def retranslateUi(self, Dialog):
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" text-decoration: underline;\">Example</span>: Let\'s assume that you have the following decks in your collection: `a` (with sub-decks `b` and `c`), and `d`. By default CrowdAnki is going to create 3 separate repositories - `a::b`, `a::c` and `d`. If you are to add `a` to `snapshot_root_decks` then CrowdAnki would create 2 repositories instead - `a` and `d`. The information for sub-decks `b` and `c` would be stored within repository `a` in this case.</p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:16pt;\">Import</span></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-weight:600;\">Do Not Move Existing Cards</span>: By default on import of a CrowdAnki deck file, when a note already exists in Anki itself, all notes will be updated and placed in the deck set in the deck file. Tick this box if you wish only to have the notes updated, but left in their current deck. See <a href=\"https://github.com/Stvad/CrowdAnki/issues/23\"><span style=\" text-decoration: underline; color:#2980b9;\">this Issue</span></a> on the CrowdAnki Github Repo for more info.</p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:16pt;\">Export</span></p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n"
Expand Down
12 changes: 8 additions & 4 deletions crowd_anki/representation/note.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import anki
import anki.utils
from anki.notes import Note as AnkiNote

from ..config.config_settings import ConfigSettings
from .json_serializable import JsonSerializableAnkiObject
from .note_model import NoteModel
from ..anki.overrides.change_model_dialog import ChangeModelDialog
Expand All @@ -13,12 +15,14 @@ class Note(JsonSerializableAnkiObject):
"_fmap", # Generated data
"_model", # Card model. Would be handled by deck.
"mid", # -> uuid
"scm" # todo: clarify
"scm", # todo: clarify
"config"
}

def __init__(self, anki_note=None):
def __init__(self, anki_note=None, config: ConfigSettings = None):
super(Note, self).__init__(anki_note)
self.note_model_uuid = None
self.config = config or ConfigSettings.get_instance()

@staticmethod
def get_notes_from_collection(collection, deck_id, note_models):
Expand All @@ -28,7 +32,7 @@ def get_notes_from_collection(collection, deck_id, note_models):
@classmethod
def from_collection(cls, collection, note_id, note_models):
anki_note = AnkiNote(collection, id=note_id)
note = Note(anki_note)
note = Note(anki_note=anki_note)

note_model = NoteModel.from_collection(collection, note.anki_object.mid)
note_models.setdefault(note_model.get_uuid(), note_model)
Expand Down Expand Up @@ -117,5 +121,5 @@ def save_to_collection(self, collection, deck, model_map_cache):

if new_note:
collection.addNote(self.anki_object)
else:
elif not self.config.import_notes_ignore_deck_movement:
self.move_cards_to_deck(deck.anki_dict["id"])
6 changes: 4 additions & 2 deletions test/config/config_settings_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,17 @@
"automated_snapshot": False,
"snapshot_root_decks": [],
"export_notes_reverse_order": False,
"export_note_sort_methods": []
"export_note_sort_methods": [],
"import_notes_ignore_deck_movement": False
}

new_settings = {
"snapshot_path": "testing",
"automated_snapshot": True,
"snapshot_root_decks": ["TestDeck1", "Other"],
"export_notes_reverse_order": True,
"export_note_sort_methods": ["notemodel", "guid"]
"export_note_sort_methods": ["notemodel", "guid"],
"import_notes_ignore_deck_movement": True
}

config = ConfigSettings(addon_manager=addon_manager_mock, init_values=old_settings)
Expand Down
10 changes: 9 additions & 1 deletion test/utils/filesystem/name_sanitizer_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
invalid_filename_chars
from test_utils.matchers import contain_any

size_limit = 255


def byte_length_size(sample):
return len(bytes(sample, "utf-8")) <= size_limit


with description("AnkiDeckNameSanitizer"):
with it("should remove all bad characters from the string"):
expect(sanitize_anki_deck_name(invalid_filename_chars)) \
.not_to(contain_any(*invalid_filename_chars))

with it("should be possible to create a file name from a random sanitized string"):
@given(text(characters(min_codepoint=1, max_codepoint=800), max_size=255, min_size=1))
@given(text(characters(min_codepoint=1, max_codepoint=800), max_size=size_limit, min_size=1)
.filter(byte_length_size))
@example("line\n another one")
def can_create(potential_name):
assume(potential_name not in ('.', '..'))
Expand Down
36 changes: 24 additions & 12 deletions ui_files/config.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>843</width>
<height>749</height>
<width>825</width>
<height>726</height>
</rect>
</property>
<property name="windowTitle">
Expand Down Expand Up @@ -58,6 +58,22 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="group_deck_import">
<property name="title">
<string>Import</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="cb_ignore_move_cards">
<property name="text">
<string>Do Not Move Existing Cards</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="group_deck_export">
<property name="title">
Expand Down Expand Up @@ -86,18 +102,11 @@
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="explanatory_text">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="tb_instructions">
<property name="lineWidth">
<number>1</number>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
Expand All @@ -114,6 +123,9 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; text-decoration: underline;&quot;&gt;Example&lt;/span&gt;: Let's assume that you have the following decks in your collection: `a` (with sub-decks `b` and `c`), and `d`. By default CrowdAnki is going to create 3 separate repositories - `a::b`, `a::c` and `d`. If you are to add `a` to `snapshot_root_decks` then CrowdAnki would create 2 repositories instead - `a` and `d`. The information for sub-decks `b` and `c` would be stored within repository `a` in this case.&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;Import&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Do Not Move Existing Cards&lt;/span&gt;: By default on import of a CrowdAnki deck file, when a note already exists in Anki itself, all notes will be updated and placed in the deck set in the deck file. Tick this box if you wish only to have the notes updated, but left in their current deck. See &lt;a href=&quot;https://github.com/Stvad/CrowdAnki/issues/23&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#2980b9;&quot;&gt;this Issue&lt;/span&gt;&lt;/a&gt; on the CrowdAnki Github Repo for more info.&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;Export&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
Expand Down