Skip to content

Commit

Permalink
Add built-in models
Browse files Browse the repository at this point in the history
Add the following genanki.Model instances:
  - genanki.BASIC_MODEL
  - genanki.BASIC_AND_REVERSED_CARD_MODEL
  - genanki.BASIC_OPTIONAL_REVERSED_CARD_MODEL
  - genanki.BASIC_TYPE_IN_THE_ANSWER_MODEL
  - genanki.CLOZE_MODEL

These can be used in the same way as Anki's built-in models and are
identical to them except for their names, which are different due to a
technical issue with how Anki handles built-in model IDs.

This also updates some fields we set on each model when we serialize a
deck to more closely match Anki's output.
  • Loading branch information
kerrickstaley committed Oct 29, 2020
1 parent 7ac1ec8 commit ec5847e
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 2 deletions.
6 changes: 6 additions & 0 deletions genanki/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@
from .package import Package

from .util import guid_for

from .builtin_models import BASIC_MODEL
from .builtin_models import BASIC_AND_REVERSED_CARD_MODEL
from .builtin_models import BASIC_OPTIONAL_REVERSED_CARD_MODEL
from .builtin_models import BASIC_TYPE_IN_THE_ANSWER_MODEL
from .builtin_models import CLOZE_MODEL
141 changes: 141 additions & 0 deletions genanki/builtin_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""
Models that behave the same as Anki's built-in models ("Basic", "Basic (and reversed card)", "Cloze", etc.).
Note: Anki does not assign consistent IDs to its built-in models (see
https://github.com/kerrickstaley/genanki/issues/55#issuecomment-717687667 and
https://forums.ankiweb.net/t/exported-basic-cards-create-duplicate-card-types-when-imported-by-other-users/959 ).
Because of this, we cannot simply call these models "Basic" etc. If we did, then when importing a genanki-generated
deck, Anki would see a model called "Basic" which has a different model ID than its internal "Basic" model, and it
would rename the imported model to something like "Basic-123abc". Instead, we name the models "Basic (genanki)"
etc., which is less confusing.
"""

from .model import Model


BASIC_MODEL = Model(
1559383000,
'Basic (genanki)',
fields=[
{
'name': 'Front',
'font': 'Arial',
},
{
'name': 'Back',
'font': 'Arial',
},
],
templates=[
{
'name': 'Card 1',
'qfmt': '{{Front}}',
'afmt': '{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}',
},
],
css='.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n',
)

BASIC_AND_REVERSED_CARD_MODEL = Model(
1485830179,
'Basic (and reversed card) (genanki)',
fields=[
{
'name': 'Front',
'font': 'Arial',
},
{
'name': 'Back',
'font': 'Arial',
},
],
templates=[
{
'name': 'Card 1',
'qfmt': '{{Front}}',
'afmt': '{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}',
},
{
'name': 'Card 2',
'qfmt': '{{Back}}',
'afmt': '{{FrontSide}}\n\n<hr id=answer>\n\n{{Front}}',
},
],
css='.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n',
)

BASIC_OPTIONAL_REVERSED_CARD_MODEL = Model(
1382232460,
'Basic (optional reversed card) (genanki)',
fields=[
{
'name': 'Front',
'font': 'Arial',
},
{
'name': 'Back',
'font': 'Arial',
},
{
'name': 'Add Reverse',
'font': 'Arial',
},
],
templates=[
{
'name': 'Card 1',
'qfmt': '{{Front}}',
'afmt': '{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}',
},
{
'name': 'Card 2',
'qfmt': '{{#Add Reverse}}{{Back}}{{/Add Reverse}}',
'afmt': '{{FrontSide}}\n\n<hr id=answer>\n\n{{Front}}',
},
],
css='.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n',
)

BASIC_TYPE_IN_THE_ANSWER_MODEL = Model(
1305534440,
'Basic (type in the answer) (genanki)',
fields=[
{
'name': 'Front',
'font': 'Arial',
},
{
'name': 'Back',
'font': 'Arial',
},
],
templates=[
{
'name': 'Card 1',
'qfmt': '{{Front}}\n\n{{type:Back}}',
'afmt': '{{Front}}\n\n<hr id=answer>\n\n{{type:Back}}',
},
],
css='.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n',
)

CLOZE_MODEL = Model(
1122529321,
'Cloze (genanki)',
model_type=Model.CLOZE,
fields=[
{
'name': 'Text',
'font': 'Arial',
},
],
templates=[
{
'name': 'Cloze',
'qfmt': '{{cloze:Text}}',
'afmt': '{{cloze:Text}}',
},
],
css='.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n\n'
'.cloze {\n font-weight: bold;\n color: blue;\n}\n.nightMode .cloze {\n color: lightblue;\n}',
)
8 changes: 6 additions & 2 deletions genanki/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ def to_json(self, now_ts, deck_id):
tmpl['ord'] = ord_
tmpl.setdefault('bafmt', '')
tmpl.setdefault('bqfmt', '')
tmpl.setdefault('bfont', '')
tmpl.setdefault('bsize', 0)
tmpl.setdefault('did', None) # TODO None works just fine here, but should it be deck_id?

for ord_, field in enumerate(self.fields):
Expand All @@ -103,8 +105,10 @@ def to_json(self, now_ts, deck_id):
"flds": self.fields,
"id": str(self.model_id),
"latexPost": "\\end{document}",
"latexPre": "\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage{amssymb,amsmath}\n"
"\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n\\begin{document}\n",
"latexPre": "\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n"
"\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n"
"\\begin{document}\n",
"latexsvg": False,
"mod": now_ts,
"name": self.name,
"req": self._req,
Expand Down
36 changes: 36 additions & 0 deletions tests/test_builtin_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import genanki
import os
import tempfile


def test_builtin_models():
my_deck = genanki.Deck(
1598559905,
'Country Capitals')

my_deck.add_note(genanki.Note(
model=genanki.BASIC_MODEL,
fields=['Capital of Argentina', 'Buenos Aires']))

my_deck.add_note(genanki.Note(
model=genanki.BASIC_AND_REVERSED_CARD_MODEL,
fields=['Costa Rica', 'San José']))

my_deck.add_note(genanki.Note(
model=genanki.BASIC_OPTIONAL_REVERSED_CARD_MODEL,
fields=['France', 'Paris', 'y']))

my_deck.add_note(genanki.Note(
model=genanki.BASIC_TYPE_IN_THE_ANSWER_MODEL,
fields=['Taiwan', 'Taipei']))

my_deck.add_note(genanki.Note(
model=genanki.CLOZE_MODEL,
fields=['{{c1::Rome}} is the capital of {{c2::Italy}}']))

# Just try writing the note to a .apkg file; if there is no Exception, we assume things are good.
fnode, fpath = tempfile.mkstemp()
os.close(fnode)
my_deck.write_to_file(fpath)

os.unlink(fpath)

0 comments on commit ec5847e

Please sign in to comment.