From 8a95682172a64059baf3f69612f789522b73eeb4 Mon Sep 17 00:00:00 2001 From: <> Date: Mon, 20 May 2024 15:18:52 +0000 Subject: [PATCH] Update documentation --- .buildinfo | 4 + .doctrees/authentication.doctree | Bin 0 -> 3423 bytes .../check_program_requirements.doctree | Bin 0 -> 7485 bytes .doctrees/commands/configure_instance.doctree | Bin 0 -> 12047 bytes .doctrees/commands/configure_tiers.doctree | Bin 0 -> 21375 bytes .doctrees/commands/create_courseware.doctree | Bin 0 -> 19688 bytes .../commands/create_courseware_page.doctree | Bin 0 -> 5026 bytes .doctrees/commands/create_product.doctree | Bin 0 -> 5832 bytes .doctrees/commands/create_user.doctree | Bin 0 -> 6300 bytes .../commands/generate_discount_code.doctree | Bin 0 -> 14154 bytes .doctrees/commands/import_courserun.doctree | Bin 0 -> 16534 bytes .doctrees/commands/index.doctree | Bin 0 -> 4348 bytes .../commands/refund_fulfilled_order.doctree | Bin 0 -> 6772 bytes .../regenerate_edx_auth_tokens.doctree | Bin 0 -> 5543 bytes .../commands/resolve_pending_order.doctree | Bin 0 -> 5354 bytes .doctrees/configuration/ecommerce.doctree | Bin 0 -> 26358 bytes .doctrees/configuration/hubspot.doctree | Bin 0 -> 3942 bytes .doctrees/configuration/index.doctree | Bin 0 -> 2908 bytes .doctrees/configuration/open_edx.doctree | Bin 0 -> 56058 bytes .doctrees/configuration/quickstart.doctree | Bin 0 -> 27710 bytes .doctrees/configuration/tutor.doctree | Bin 0 -> 72420 bytes .doctrees/configuration/uwsgi_tuning.doctree | Bin 0 -> 14422 bytes .doctrees/ecommerce/flexible_pricing.doctree | Bin 0 -> 17261 bytes .doctrees/ecommerce/index.doctree | Bin 0 -> 2858 bytes .doctrees/ecommerce/overview.doctree | Bin 0 -> 7978 bytes .doctrees/ecommerce/subsystems/basket.doctree | Bin 0 -> 5697 bytes .../ecommerce/subsystems/discount.doctree | Bin 0 -> 13076 bytes .doctrees/ecommerce/subsystems/index.doctree | Bin 0 -> 3006 bytes .doctrees/ecommerce/subsystems/order.doctree | Bin 0 -> 4909 bytes .../ecommerce/subsystems/payment.doctree | Bin 0 -> 3441 bytes .../ecommerce/subsystems/product.doctree | Bin 0 -> 6117 bytes .../ecommerce/subsystems/reporting.doctree | Bin 0 -> 5761 bytes .doctrees/environment.pickle | Bin 0 -> 539752 bytes .doctrees/index.doctree | Bin 0 -> 4817 bytes .nojekyll | 0 _sources/authentication.rst.txt | 4 + .../check_program_requirements.rst.txt | 22 + _sources/commands/configure_instance.rst.txt | 23 + _sources/commands/configure_tiers.rst.txt | 86 ++ _sources/commands/create_courseware.rst.txt | 60 + .../commands/create_courseware_page.rst.txt | 17 + _sources/commands/create_product.rst.txt | 19 + _sources/commands/create_user.rst.txt | 20 + .../commands/generate_discount_code.rst.txt | 51 + _sources/commands/import_courserun.rst.txt | 56 + _sources/commands/index.rst.txt | 22 + .../commands/refund_fulfilled_order.rst.txt | 21 + .../regenerate_edx_auth_tokens.rst.txt | 16 + .../commands/resolve_pending_order.rst.txt | 17 + _sources/configuration/ecommerce.rst.txt | 77 ++ _sources/configuration/hubspot.rst.txt | 9 + _sources/configuration/index.rst.txt | 10 + _sources/configuration/open_edx.rst.txt | 213 +++ _sources/configuration/quickstart.rst.txt | 81 ++ _sources/configuration/tutor.rst.txt | 219 +++ _sources/configuration/uwsgi_tuning.rst.txt | 68 + _sources/ecommerce/flexible_pricing.rst.txt | 52 + _sources/ecommerce/index.rst.txt | 9 + _sources/ecommerce/overview.rst.txt | 31 + _sources/ecommerce/subsystems/basket.rst.txt | 30 + .../ecommerce/subsystems/discount.rst.txt | 101 ++ _sources/ecommerce/subsystems/index.rst.txt | 12 + _sources/ecommerce/subsystems/order.rst.txt | 30 + _sources/ecommerce/subsystems/payment.rst.txt | 4 + _sources/ecommerce/subsystems/product.rst.txt | 28 + .../ecommerce/subsystems/reporting.rst.txt | 17 + _sources/index.rst.txt | 22 + _static/basic.css | 925 +++++++++++++ _static/doctools.js | 156 +++ _static/documentation_options.js | 13 + _static/file.png | Bin 0 -> 286 bytes _static/insipid-sidebar-readthedocs.css | 66 + _static/insipid-sidebar.js | 308 +++++ _static/insipid.css | 1223 +++++++++++++++++ _static/insipid.js | 149 ++ _static/language_data.js | 199 +++ _static/minus.png | Bin 0 -> 90 bytes _static/plus.png | Bin 0 -> 90 bytes _static/pygments.css | 75 + _static/searchtools.js | 619 +++++++++ _static/sphinx_highlight.js | 154 +++ authentication.html | 217 +++ commands/check_program_requirements.html | 217 +++ commands/configure_instance.html | 218 +++ commands/configure_tiers.html | 313 +++++ commands/create_courseware.html | 251 ++++ commands/create_courseware_page.html | 212 +++ commands/create_product.html | 214 +++ commands/create_user.html | 216 +++ commands/generate_discount_code.html | 242 ++++ commands/import_courserun.html | 218 +++ commands/index.html | 260 ++++ commands/refund_fulfilled_order.html | 154 +++ commands/regenerate_edx_auth_tokens.html | 148 ++ commands/resolve_pending_order.html | 149 ++ configuration/ecommerce.html | 264 ++++ configuration/hubspot.html | 142 ++ configuration/index.html | 227 +++ configuration/open_edx.html | 416 ++++++ configuration/quickstart.html | 280 ++++ configuration/tutor.html | 412 ++++++ configuration/uwsgi_tuning.html | 200 +++ ecommerce/flexible_pricing.html | 238 ++++ ecommerce/index.html | 217 +++ ecommerce/overview.html | 233 ++++ ecommerce/subsystems/basket.html | 219 +++ ecommerce/subsystems/discount.html | 284 ++++ ecommerce/subsystems/index.html | 223 +++ ecommerce/subsystems/order.html | 220 +++ ecommerce/subsystems/payment.html | 194 +++ ecommerce/subsystems/product.html | 216 +++ ecommerce/subsystems/reporting.html | 205 +++ genindex.html | 140 ++ index.html | 201 +++ objects.inv | Bin 0 -> 828 bytes search.html | 163 +++ searchindex.js | 1 + 117 files changed, 13062 insertions(+) create mode 100644 .buildinfo create mode 100644 .doctrees/authentication.doctree create mode 100644 .doctrees/commands/check_program_requirements.doctree create mode 100644 .doctrees/commands/configure_instance.doctree create mode 100644 .doctrees/commands/configure_tiers.doctree create mode 100644 .doctrees/commands/create_courseware.doctree create mode 100644 .doctrees/commands/create_courseware_page.doctree create mode 100644 .doctrees/commands/create_product.doctree create mode 100644 .doctrees/commands/create_user.doctree create mode 100644 .doctrees/commands/generate_discount_code.doctree create mode 100644 .doctrees/commands/import_courserun.doctree create mode 100644 .doctrees/commands/index.doctree create mode 100644 .doctrees/commands/refund_fulfilled_order.doctree create mode 100644 .doctrees/commands/regenerate_edx_auth_tokens.doctree create mode 100644 .doctrees/commands/resolve_pending_order.doctree create mode 100644 .doctrees/configuration/ecommerce.doctree create mode 100644 .doctrees/configuration/hubspot.doctree create mode 100644 .doctrees/configuration/index.doctree create mode 100644 .doctrees/configuration/open_edx.doctree create mode 100644 .doctrees/configuration/quickstart.doctree create mode 100644 .doctrees/configuration/tutor.doctree create mode 100644 .doctrees/configuration/uwsgi_tuning.doctree create mode 100644 .doctrees/ecommerce/flexible_pricing.doctree create mode 100644 .doctrees/ecommerce/index.doctree create mode 100644 .doctrees/ecommerce/overview.doctree create mode 100644 .doctrees/ecommerce/subsystems/basket.doctree create mode 100644 .doctrees/ecommerce/subsystems/discount.doctree create mode 100644 .doctrees/ecommerce/subsystems/index.doctree create mode 100644 .doctrees/ecommerce/subsystems/order.doctree create mode 100644 .doctrees/ecommerce/subsystems/payment.doctree create mode 100644 .doctrees/ecommerce/subsystems/product.doctree create mode 100644 .doctrees/ecommerce/subsystems/reporting.doctree create mode 100644 .doctrees/environment.pickle create mode 100644 .doctrees/index.doctree create mode 100644 .nojekyll create mode 100644 _sources/authentication.rst.txt create mode 100644 _sources/commands/check_program_requirements.rst.txt create mode 100644 _sources/commands/configure_instance.rst.txt create mode 100644 _sources/commands/configure_tiers.rst.txt create mode 100644 _sources/commands/create_courseware.rst.txt create mode 100644 _sources/commands/create_courseware_page.rst.txt create mode 100644 _sources/commands/create_product.rst.txt create mode 100644 _sources/commands/create_user.rst.txt create mode 100644 _sources/commands/generate_discount_code.rst.txt create mode 100644 _sources/commands/import_courserun.rst.txt create mode 100644 _sources/commands/index.rst.txt create mode 100644 _sources/commands/refund_fulfilled_order.rst.txt create mode 100644 _sources/commands/regenerate_edx_auth_tokens.rst.txt create mode 100644 _sources/commands/resolve_pending_order.rst.txt create mode 100644 _sources/configuration/ecommerce.rst.txt create mode 100644 _sources/configuration/hubspot.rst.txt create mode 100644 _sources/configuration/index.rst.txt create mode 100644 _sources/configuration/open_edx.rst.txt create mode 100644 _sources/configuration/quickstart.rst.txt create mode 100644 _sources/configuration/tutor.rst.txt create mode 100644 _sources/configuration/uwsgi_tuning.rst.txt create mode 100644 _sources/ecommerce/flexible_pricing.rst.txt create mode 100644 _sources/ecommerce/index.rst.txt create mode 100644 _sources/ecommerce/overview.rst.txt create mode 100644 _sources/ecommerce/subsystems/basket.rst.txt create mode 100644 _sources/ecommerce/subsystems/discount.rst.txt create mode 100644 _sources/ecommerce/subsystems/index.rst.txt create mode 100644 _sources/ecommerce/subsystems/order.rst.txt create mode 100644 _sources/ecommerce/subsystems/payment.rst.txt create mode 100644 _sources/ecommerce/subsystems/product.rst.txt create mode 100644 _sources/ecommerce/subsystems/reporting.rst.txt create mode 100644 _sources/index.rst.txt create mode 100644 _static/basic.css create mode 100644 _static/doctools.js create mode 100644 _static/documentation_options.js create mode 100644 _static/file.png create mode 100644 _static/insipid-sidebar-readthedocs.css create mode 100644 _static/insipid-sidebar.js create mode 100644 _static/insipid.css create mode 100644 _static/insipid.js create mode 100644 _static/language_data.js create mode 100644 _static/minus.png create mode 100644 _static/plus.png create mode 100644 _static/pygments.css create mode 100644 _static/searchtools.js create mode 100644 _static/sphinx_highlight.js create mode 100644 authentication.html create mode 100644 commands/check_program_requirements.html create mode 100644 commands/configure_instance.html create mode 100644 commands/configure_tiers.html create mode 100644 commands/create_courseware.html create mode 100644 commands/create_courseware_page.html create mode 100644 commands/create_product.html create mode 100644 commands/create_user.html create mode 100644 commands/generate_discount_code.html create mode 100644 commands/import_courserun.html create mode 100644 commands/index.html create mode 100644 commands/refund_fulfilled_order.html create mode 100644 commands/regenerate_edx_auth_tokens.html create mode 100644 commands/resolve_pending_order.html create mode 100644 configuration/ecommerce.html create mode 100644 configuration/hubspot.html create mode 100644 configuration/index.html create mode 100644 configuration/open_edx.html create mode 100644 configuration/quickstart.html create mode 100644 configuration/tutor.html create mode 100644 configuration/uwsgi_tuning.html create mode 100644 ecommerce/flexible_pricing.html create mode 100644 ecommerce/index.html create mode 100644 ecommerce/overview.html create mode 100644 ecommerce/subsystems/basket.html create mode 100644 ecommerce/subsystems/discount.html create mode 100644 ecommerce/subsystems/index.html create mode 100644 ecommerce/subsystems/order.html create mode 100644 ecommerce/subsystems/payment.html create mode 100644 ecommerce/subsystems/product.html create mode 100644 ecommerce/subsystems/reporting.html create mode 100644 genindex.html create mode 100644 index.html create mode 100644 objects.inv create mode 100644 search.html create mode 100644 searchindex.js diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 000000000..707e374c1 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 0bd13fe87530f01595e010e46b7ef608 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/authentication.doctree b/.doctrees/authentication.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1e6ba192ad1bbcf84a15c317f7212d40c4612ee0 GIT binary patch literal 3423 zcmai0|7#RS6pzMBa`|#GekldDT`5+j-mNYDL4+!5gA|)w(NO$Fmbu-zn<2Y1>&$Gt z6omE%L1BJ#`~UXyc5gR#iO|52oj32j`MmG%@6NvmTT9iS-?q7sYC2#^qIAM->DLk+ z7a3Qs{9NAsr+i=D@m<5-SzVZzmwpKxv5;wExGLY2zDMj%rgkusU$s1TQma;6bi|5S zeN)~MUEguirF`zn_ns6^;58YuN{Tqac;SFg3Bj5B4`q#MC1*h3O0na+Q43it`;{5e zeSaa9ikJJvi1~HqoROo#!9ucVStgdyuf{2}7DRk^6wB(bwWP>T4kH#H8a^hT!mm0u zp{r7Ci)&(2To=pYhJO&cEDv+0ob6ktl98VFpPT8cC-*}@vtbP>WDBMaj0OB}UYsvR z{%ReDSSx}#$hgUvOn|TXYGTniQ+x|E#7+3>JN$0pcN@REFylguIF_%UFE1@w{Ws!!xZ|=%Kw~|Iwr2?!(}Z;j4onEG#z>%Vl`!b`!fb!?PMD+1P<_xcRyf~n7UVJSs9O!Y*nFq3YY8{OAL`zd@({~~0qIsT<$ zXx}3(u)8+4Jg^w;|Nj93LE)>)o%$4_HvpKcvX%E<2D zr8=BXieMHlf-~=NihQeO0m=V^K8f)e)V1fL+{h!OSlHnI@=JAYHU6oxAaV;?K)_C3 z8$N*#$skG>6e==cY&b$(3DucK60K9{17)alYEINU3h{#&oRq$}hYG$>jCfm+zDIta z>u9Vg(J$Q0ik6hFL+NtePb#Ho3#?VzsA#La>2l(pzk&)!j=1Cd*62_TyYe)HYE-Tf{B94avU~{)(%klSWZiSE-d{<4I#q2hhSKr9Sf7< zwB>XhQ5U9LKQiCM!Ss9$JjdBP0?{H4BB;Y6?$z zMTy=?lw~ss@ZL;dHX8Q*rs26ZE=u_khda;vb-~j1*zr4jD&a-PTwxogj_)LVR7~(( z?;SG=UIYi)W(GKtb38Zey4I)5L zV^{j^+VgW-I+d2v-#B%1N`=tAS1UaM(1>1Nyf`K4FOmUBJS*Uz6b6-kD`jd@unC8| zF@^kjED8QHnwd69lyJU@lxI-pQHi(s~N_2EW^5GmzYkt2fXMLgBI$k99F7 zQ9_9m<%>0c0|}W$ZDqCwL^h`k`Q$I!W98U15}3>>O=qkusiwT&v>BF@=xu?Lws6$t z>RcbARL5-Q+8eQ8z=Oco#Re}|E`;=9k>d&4NvlP3)&o8vdvq+-7&$O&3YVx$*kW{w zO=gt4sb+O0(gt~0B4x4>Pf)(cKp;4Gc;jjeuEU69PfH_g&(w4))H}2N7rB~_>xTBp zFLQN7o2Xx_TM23xnJ85~=p)C>grhZKecJ&jWR}BDjjTy%?|hF|w?M6TQ;?x;+#Xh_ zpJJ^AeU*syw|FWZTi+#uJI;adeJP%Pvf?KGKNg$TZ_UT5lHQ6XB2LdkV@<(G(+yt@ zbDmQ30X$-xjda?Ezhb>|ir8{@7c_R8S1X}Q3;NMojVnEZ!C~ET5=V#4XNU^NGL@n6s`4y OTHCA((C{usgZ}{R7y^C( literal 0 HcmV?d00001 diff --git a/.doctrees/commands/check_program_requirements.doctree b/.doctrees/commands/check_program_requirements.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3d391fbccbe7ad6e058dfeb558d1ee4d68efcb29 GIT binary patch literal 7485 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 literal 0 HcmV?d00001 diff --git a/.doctrees/commands/configure_instance.doctree b/.doctrees/commands/configure_instance.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4958ef3d193036d270a3490a1dcf0a9cc6e9ae4d GIT binary patch literal 12047 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 I|I>E!zhkoLRsaA1 literal 0 HcmV?d00001 diff --git a/.doctrees/commands/configure_tiers.doctree b/.doctrees/commands/configure_tiers.doctree new file mode 100644 index 0000000000000000000000000000000000000000..59d6d8e8325c6b86203415da1cc16c60e500c2cd GIT binary patch literal 21375 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 literal 0 HcmV?d00001 diff --git a/.doctrees/commands/create_courseware.doctree b/.doctrees/commands/create_courseware.doctree new file mode 100644 index 0000000000000000000000000000000000000000..65d20ae85daca2e056d30d2e567dcf44a4edbc4f GIT binary patch literal 19688 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 z z7t!)U1UsW+*wId^|Z0^ aGEai22(?d5w0W08oeQh9p$mR78vF}6K8v~l literal 0 HcmV?d00001 diff --git a/.doctrees/commands/create_user.doctree b/.doctrees/commands/create_user.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0611644360f11012147e60b3ce106e9c531df89f GIT binary patch literal 6300 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 z%0A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 literal 0 HcmV?d00001 diff --git a/.doctrees/commands/import_courserun.doctree b/.doctrees/commands/import_courserun.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4b2f562e1a9e5e0021cc3d5c1743ad1200cb2446 GIT binary patch literal 16534 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 H-fH|ea^~#f literal 0 HcmV?d00001 diff --git a/.doctrees/commands/index.doctree b/.doctrees/commands/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9453064e1c3014a97d3c1be573b76cd38056dd88 GIT binary patch literal 4348 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 C`reKJ literal 0 HcmV?d00001 diff --git a/.doctrees/commands/regenerate_edx_auth_tokens.doctree b/.doctrees/commands/regenerate_edx_auth_tokens.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f3e814d4519558109718408030cd1cd0a45de29e GIT binary patch literal 5543 zcmcgwTZP}bnYWBh4JUDov-rRWz1Ves72zkmsFc=K^1tBJcV90;SSJl^9v6qNAf?0K)sycNp z-#K+oebo8w?>8^Ge`ZZ3T*%pwP%2}}RBje&9ApubI{!3(?brE-`L^k&8 zgcSi7Ax)XgALV9XmunHKVY$DjSfE8L>+QV5yS(=(-{yVO(L#r;HDzAf-%r_;NtP1L zd`4$J$u#$Me88mI-_Pw%Z0v74S9^Fo(yLLj-$`L|5p zuT|v>rdw*bXRd}qvV1Qa6SG9LPQ^IWu#C0Df}qN3-3vmZ6o~llND>uai!@7`!I%UG zDVx|endxaVwNp9Y z7ZyBsapIQw+5g;pWrUp6j-3hAH~B044M6H@1y~68{mLBg2bV8hx<7IL&lF2N8Z#wt zYmbvd084tcwSJD-1`$jE59v;U3eAP`N^pDll(F_mlB98x3Z#sohrM8wvV@4#kAzY} zPW=GsO0g%1b-#caa)~FT(u~ld_nzhXL;z+aRIy`8V+eGF!{ih*ntM#9aTwY}s62oj zfr9jCw=w`#NDm4H1V@9DC{!$C;I?Y?dJ61Q26jbUP!~ihju>1?R4jMwIR!6~);KQ% z&W0Y2wlSduh4oUq?GR~_HpqYJDPw>2(eD~_w=nlX8*?oHubps7h2G^7z1C4JHsdy% z^P&5JgYI1j?jrxc_jBv$(=@nwAuLpSy?iciT1V<<(Kh@)!495QUQ5{1?DI@1>Nn4# z`J>9u-wf!Rd^M-)#qw3Rm9Lhod{xyc>lMLt-#wC=%qp~0!{+EeT--4BPXDY}?owvI zohr?1XVpR8TgW!4tsj0+l|=QO!~f*Upe=Lr^ozf>zlD%LUtD&3@BHz+qNTbEmMcZG zapMfsSA{7_RqwPYt;%Pqa!h%#SSy|3px)bwElX78p?ci7=&u)7)4J%(mWu{Byl+fXU#vK`oc!;LYj2(Wwad`ootM|8mh&WT4E}Zd$OZwG z-`jO=1(|gm{m=98^^Xz~cJbfs8E0)U-NI|Rx%|2-ssGm6?R-3k&R$&NsDWpTbOt^F zkw(RK2eOM^X4e+ayTuKjH=NJvQ%Ku9HW(IKctqiT`XyU;F#HYkoKA^Up)1*a)Jju} zD%Zhubga=vJ-B3+sx^#anAx`DvKa(fYWpLO3@XWMa8s6m4sGtqwf7CX)HC9 zG&kK}kT63#RK1TSf`{u3^_iJ22D9zR6gZUzP4Muuwli}MpFHk);; z1F)w!OcX+=+^mK~PBSuPusFr|3f>OP^`Z*&DMMpOaf|?L_Dna3M2X^cIhLk9l(b%6w7EtSKmc)wr?VS!K zf%XrD652s_&8I?K4>O)Q6TIz>cn+LcZKtn_hT!95!8SOk6d=AG}so`ZA}oM3M$4f|Uadjv1B zkUc=xFPNUVGmsEQTt^`e>uWMb6j9Cd2aDzgFd6xEaMlX=DzOMTHtR)s;BztMA%4K` z)iAcuUr`Z3p#3UCR4W+jx*HcmKaBBOP?+{c!116F!0|=A!OG5r*nN;CIKn9YYOp0K%4@3t!AQO!lMFbx~0?wGm8JhuC9r*4x z_siSdNxe}%ivg?Qq{wN3h)E~ zPe2FUF})v^^iN@!kMnmBEqFph+~N680U*VJb)uOgFV|$}<7FX(FryYzQ|sO(YCGaG JZ4{-=@IOV(IPm}g literal 0 HcmV?d00001 diff --git a/.doctrees/commands/resolve_pending_order.doctree b/.doctrees/commands/resolve_pending_order.doctree new file mode 100644 index 0000000000000000000000000000000000000000..55b486bdaa0f2a7593f3db4a3fccb679ea78c727 GIT binary patch literal 5354 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 Vw3wRM@NR~-7M5kh9{Aa)|6ea};d1~0 literal 0 HcmV?d00001 diff --git a/.doctrees/configuration/ecommerce.doctree b/.doctrees/configuration/ecommerce.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4447822b9c1fd2b85f895cf415535a8fc89a053f GIT binary patch literal 26358 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 literal 0 HcmV?d00001 diff --git a/.doctrees/configuration/open_edx.doctree b/.doctrees/configuration/open_edx.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ba361ae3ca38be443c2746822def3e5a3006a864 GIT binary patch literal 56058 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 KkLcZ;%l>}{o?oN@ literal 0 HcmV?d00001 diff --git a/.doctrees/configuration/quickstart.doctree b/.doctrees/configuration/quickstart.doctree new file mode 100644 index 0000000000000000000000000000000000000000..329e8c71ad172c69d45521802877ab6829b6675b GIT binary patch literal 27710 zcmdsAdyE~|S@&a~Uhl4-Yqz$Oag%h{8}F|3j@^)#?Igx{lh{t;mWF%h-aB_^uIJ9& z%)`5m3TppIYd50`)Qf1<(vU*_paNBh(t?DD;2#hHRZXB!iI@7|B}Eh=KtP4~edo-X zbMDN&vvaREDG8rjQdR3zxZ9d70&`dOLwQ4@mGg`HE z-^We-9(MF5|20+%Tk@i!S1)@;o!W%aXrQmqqqaG19x%6?2hFYKq3F@2pxIh#=}zD; z__|Z8xN8fKdutaSIkp7U{3VXkQq^_p)=KE<#IdCphsE%LcB%cK)L62K0k)skoMHcr9BG49zpp zg=esCeIwAqmIkaP%rn6WCha+MCfboEsvV0Lyj|kFT{drm1i_Y3+X@U%w^gRhbY}wB zrkQA0mP1Mh$JGvW^JeIUc^F!F3;x}Pf3x^^1Tr^oH}4~SfW=Jo+W!p(_h6%uOXY%j z$UF))91@z%_&!kyZQBS+wgn{>PC~Wvae7jMaGJ{F=*UUWfI9flFh7L)L2W%vuU6d< znmK3HwX4wQRandsF0DoGeE|4F%ooh`13{Ae;o?FSa}4K=<%eSkO5 z36!I%AeM~b$w$J#yi?O#E!(OxQ-@7dU9YCW1T~{}se}BZu#TGMw#X&uwtZDwwE~kY z##jSeEoVi8hxLpsn_h_7^se+R8SMAZ;8F0*Ey`eOoMrIKg2B)x_!PJuD3a$JtIQi! z8VExEQ8<~Z9oC3vx_0XHsi(A-=faV~?SaMo1)uZR3mqSxOAlbqLDke&>8Ed6t@t^* zfkUn75L(T+g5L;K!iA;v^;#^5|5Z3jfqk-nfsH{1A1^8}YMd3=>5RZmp1Yv65Sn~u z24pB%5pt1O2wmKHq*?t+%<2t-E}8Ud|4bSO-9KNHNz^#Yr1=X*;8TFCTMojvrP~@t zBQSf4<>Wv-x8+&zqW0A^C%zhUq8|{F+~4e<+-;!XD@Dnr##wUjd!$yQcmTP#Y&M7h zfwd5(3)@O*1#b1!f7+M?t0BJ_*NjvNb2oev$YX(8@V zNcU3`Z0x1JX|gjDrCUk__YH*L4)Z``H{L8N2Wr0~?x|xqbM&O!goTpdVc`}Y%5nlD zCW%K-5f(EldV3!)3oY&I9m%G}f!MSYWY71(CTcI))LUa4n*g-2nIL1zAk-C1PP9g< z>(gf(4U=$-ROBcbJSJ5%l+iU4!;X4rvze9dUyes97%b1)w&oaoUX@Je>X;H?3{>;^ zoC->Hp?}EV{^)1dfh}47#6T?HtuP_Sbc~Ded~%#H#|H?SUU9=99y`!Tj{IZSh)Gl2 zBMFl#G#9;M8LQwKxm8RC15Aq-(-!&TL=v~icL@Q>&gTbW=U!;!2T9KgPFkq_vvE%y zhF&Iy_hcmuUC|4$vlUh_*E5Y8pR^#oav8jZYjBxNlmSaTw#)F^le~WC`<84D24eFJ z*w*TU&D6dtoA0GGM^ne#4)a|~$LO_2=;If-pA3Q6Y=nFOAN$r8GoJOHfxrkKJK)=r zi7yYt#C>4OC;DI_wUs|I2wa4IFnl1!aGXfo{QO$KASqt+~15oKQk zJQ}gs+JFMI@ux%-kI%UY-Nv_ash^967`o9`Kd2$|kG~5rnzoTj_BQ^Ez#M42h5zLR z*k}aA6M$&Hu-kNAzm2sNYSYv$r``B207j#@5nOFyX=N*WcLLYO8f6NtpnZ)!#(2)b zPkI%L11m<*j&90#12jKse1p*D3EcOn%6u+S=E5!l1HxWTB2O_OJwNbVXGM`-G@iH> zp|-=!%_xBWn=oe!DLmPqYpIPNP;13lpLu88m5h671Lz%-XxuNd%8)@4i6(?c6SKt~ z;zqRpDZVnpla`qkv6-9(62tdw>4Sl^aMhSe-e4L3gB^3}BNyF{Wul(pAenUZ@rJ~D zHo>8Q*b~0qq+k!M$D?V(!2%{`+eWR|8ikykAEe1S#l3QwPtj?~ERKPh!RiBqARG@x ztugVUqPGvG2v8!|gaTja{b<~y0^xKDLayf1DpZ5|WHD-7qAgjP#;BO8YJhu!!Oibq ztbtJb`(n(2i{$RDf=L)?@dU0|`N`q3L6CB|yb||h9*UO&<@jKCmwp7dr&AaxTz66s z^Qk7T6Arz(p2n~k*AD}3mBG#LUvQn;cjfw-$6VXSh?LYCNkc4QAe%}TJQ$yh7oD*% zg|JQQPY<~GQ8H?47(RBy1kSr8OVRigF)9|#{$9N(jQ!~pV;gV6S0}FSmIC-N3>tr~ zyw><^j0B8uSC$r~_s^wz?_}#-<2!&_H2D7Ty7t=rj9<4(tg*2go)V`|*zDai9ZE~eOKs5oBmU*yTK8jT~ z%5#;=;wHQTW+-r#de!x*LAi{aLbyghF*_=k9oK<_#%$&cUoWn?&Mf9#G>gXk3v(83 zp3tJ_Pfde!Yl-#*L7$FgOXf6mVc0gylnn zW4Q&H9vPR?tST0lZb6*+eP-nOnkM2BwU=?JcOTIhO%?d;djvIh6$_L1krw9J)>_*f z!R$4x)6Z!0B8$WKkojfiN*a7mGDN!cD8hF)r-;zTvgb&XHCqA8njsb;FGg#2n2>R1b=wT|9E;!-Nhq43JfjA)gEjRM86TNGR^YLD=2Gx9b^2^m^U z@(qJeov9?LOVJWyYYhk^cZi6(YFa3hp=>BRa5P#Bu)~!Vk^UD$%cQi)M4+Wu&9aYm z3+;q;v$YsDr4+9B2%mY)DS_sOGJkg8>TBW+3MrTNK%6-||KSUcuhqTtqGK*n3Qw_V z;#H5~!MYy&r&90c+c(X;gk+*UUG+w~nlxIy-GG{Qz2WFz=TGIMzUu>j&&li$6 z7z%T$4~rhg0MMZXRIVd4$j^}VQ~fbpnZ`5|KFsiBJuhWR7$ikszMOQOnZMtVm47V- zcYR$VOxVEjeX)t>ktDgDCO~S5&;(W5BL6q9a6POv?nNvSGhvYhl!+4?e52_{DrbQp zBIh_=9lJIcI=12aTFvtHiVZi*%13Ff0CS~afdgDfDsao?*xAYu*7y@*WIW3sW{4=# z`(}#EijN*J`|_75i0jLZaA0z~Z$Newqvt>l*Tj%?Uk|=2J^a()^lgD~q~4`jUrg_g zLhs&W49jP%7(7w?ug55Z$x z0V3&8*v3vd<&U2!%Dj|!^alzM_l}pCpdMoT)*DAViRh3e8Kpc&`|EWbqe@ zF~bKE=?02^d@Kb&pRuGLgb~w^=Y@W}GhC9&lIB4%WjO|1uP|KmX%fmp?Yk<=bDhdU zlC^3j8di4<^iYD-Z94!PehaNol_ujEUL{fV(t}@2$w|=z8jf>4?m$iGz^?2=MgCt( z!OiCt$)7M`@_&Hj&w*?G6EDS^Casuw?*J;VFjVqU7UHG$UB&w|o#K_gfTX)>VMm7~ zGgt-1YzGsSiRrWh$)asv@l7fjqq1O(ov?8X^%5Rx%7WzA?NFVj+=sKlnCuebuO zVqifqJC6E8l2I?k_@k5n6~&;qpLTHNve1j2nP-Yxyp@8Q&tp;x!h@*=Ry2P&T$37= z=1DP)x)W6V0iz&4}*caE|D1qKC??L=oMt#D)_@gyg1T z1CAdkg1apRFP|eMIKqGlZn3NN_9jz`Elor*vE7Z%-^)7BM^cE5+IJOOqf>0sjVD7o z;gx7~&mYH|6NKy(@6P7Olrp2r?xyZh!92>0ZR?7W_0Ok?-M!56@sunS*>1=HZaD=% zpN%AI!idTGtd#YQ^F%5_n&M)La1Ss$$uP^OKqvyW@2UuQcPavHVDA{Gk*iHD*<@?> z?-~FIv&*h?^HxemiVjRC_RSF$#yt@mH{De%do2YipI0Pp>YGXXW_29Xg$GJ6X?%-G z@Lu$0_aQ-QFD2N!k46=Fu11ai82Dq3H0tzw1%HrqX~fHYsZ{w<(3~vyMTakfU=eWB zJ2uG1laj~+Ev2S^zAtHr6!ZO{TVM+;>AV%n0V~zFJkyLPBf*M?UALJ>bV|!;)wOA* zC-bo9fe1Jg#NYSj3lWaO5%vM}V9eH~o9r*yn@{dd`xfb5|dxCzT@jceG z@elZ4N`le`CeDL;QZ#Jw5a@Q?lQ<8J0ID(`$;Bbbk`m6F@#Z>N(tdCrg&Wo`>MH`@ z{)#u{{()Ig z#jRz{VXpDrjN;AZE90J1moZA1Ffa`-Y%C2EgmtM?03G)v&Myy)^R)G3_IeM$Q74Mh4wFpSrWlm6SBp4?cox6?2{ z$-*7S?hq2|@l?|Bt%2$I=El-7!s!r*i%W^=_Xo!G?Ty7W*Ca+q2Z`sT7ejcnU_9q~ z(T7RoH$6|B6=T!hO`{z5|a=tdj$#S(-GYW{Sw% zA-RhMay~u7E_!){L*e#PeeNDeFpq*S0&!rRb27u;XE1%Fa}Q6jQpK1UuFeUejmHTK zG^03Lz(cFQ&eHBLwiH8?Exn`DZ~FrWn)gqGX)w-o?5Bidk@Ok z=GD-%+R-fWA#hJn1!^qT?)Z>htWXCB8bqa9EtgjZbsFtI%-Z+H2A~s(QTwhEd+3zq zqhj=`whT4Y=I|T)II#R->UNzirq(SSqq&6hJOhm#-g)E%k9s|n^%F!3SJoQ_Ek$sh zp!PlEc??9q)CZHOeOD&!K!~TACPIA;w))J4wEliUQk`up$@qnV$aoxReZCJesC`#5 z?l|pu*wjX+nEEGJSt1?w*D*Y1gXDO)SuSVx>m2JB0%H_e^<>AJifw-_#SG_Y4whz-OoWHPprh6#G^*P zOwkeR0_>nh78SAOU5$$fm9()jK8{gc`PSyJrH8*sjHfefZ>Q%}<;eDS&3Bs}&QsV$ zo*J3>ctur(`2lS{HC|@zajY0$iXjzw;6_kZCt!(;SF|&u0)l{?In(4=AD%yh`xxQQluSC|G?KkdjsmF23jb(_p~-h>Pbx1g zmlf>Ftyy{6ak^lgs>O2()`yo}3?2lB;wwqTv3~f%4kuVh-eAS*=g_@$7hfdXDQI5{ow@|Jva@>P18{H#uXDx1ITh@XY>QQ<#^r^uDb)Sj zR~NN2Y@-X-?W#JC2d7#LEa3$>lCcHf#5Uz%3(O0l5ppkr3lAAaY<4xgc*%Aq`lXFW zm2b=7)twuJtd!J)3E%rUkl{(_n~+$Cr2c|$+MR`Ph`k+Ke#rAC`ycsU-(=@6v05BU z#5i5!buVmur&Jdk+WbjT$k=gO5gp^?@RDT;EObnf$3}=^m(n~CVyD&*yi;Drg2S9j z2&NaEire|I6o(auUtl}v#njvDyL9T0x%468abZN(+YnkG9Fe2kl#0nW2jNbKeSBa| z5N_+f0aZysajhS&hyajCXh&Kn7079|*^qM;B0`tXfAY^&_*5=k{hh1uNx;fW10{zl zWzSXk3|`%^a}}P3(@I!B-FdEpx(6f8c^OXg=DY-5xUr3}Eyjhyg{_@{u_ zM_Gymo~G!rFO4<9J`0#J#VGp7MvrZ2TlrL#g;{)jqPIgyzN%p!jrL%%#qkm9aCT{l zjY6>jiG5XLIB)}`OwMkKwuv@I4VNK}Pc}zW)eyCePEbm|(6L?IAynJT(HH^+oq3NB zlkB9EN!XW0>Il_1xD#h~jXbN{A%+^_D+JL94hC(7L3`PpUXI3B44kB_2d)Q5<28h0 zP~B|dV#0IX0I0H!zR~8Tb~M3Jz=1)|Wi;3pUxuEItI5EvmguV=^we*&b;f8{9WY3I z%9Cr+Bn1*|DlV;2c`i=vn!-|))k3jelTBU$?Et@H*zBVmeu;)ACGpu2bQZr5e}`l_ z+U^-G*TeS~j4K9sxh2|$3`cS~9PPx10l>mQ4?k%fQhRW` zhU+c(Oii?#Z%r!I3_7O{-B{3>K&Ot^Z6q(}XXqw$&noiP}w_3gt*4)xsleB3Ej(2l?ItD|&$+V;zl&)C5#r1W% z6z|{zAu*Pt!cJgw+d&L{4T2dRfKJBEN_BjAg?+tdhJMk9U<{8Lrs1&tfDrivzUKo` z@yoNDqn%`I&>Uw4ifCK#C+%}PW}~T=(JCdmN+Jfw{I#I%z@6+O+HVFy%RjNSgxmgN z>uO-S&Z6tBz?}9{ONIbimS~hrBVl|y2=^CmJ9antIpx)>De!bhf`7jN56jg_WiMkW zZ5ctG^#O0NWCl$eosFZ)1y;nWPsXA{(8(r>=wstd2q^h2(va)X4pff@`dZ1fR!kfJ zpua;l9a0x<_fe1+lwJs#Q@$4MX4m|(#InDfwTG%EP7A0)eB9o^WyV4(=R-}+b@o*x z_Nk-T4P1xzxK-MjpZJAckb-6f@U?K~I1Gned9CbP`pReLi7>(0gbQ}YV9_+pA?49eZ{dLy%dT1WTmw6aKb32Ddr`K_4 zlu=^PSiB9RNmUUTYLhrvk%uav%kPlAi*}p`>K#&2b{C4%thjcfTkPhZ)Xk*zcn8FB z(JOmtR|k9^i(!a+)3D+v2%qpqNE4FBK&-;TlntptK8TB>EW?ZYp9bS!YM+N?VZ$YC u6yL7$>GK-&DXU$HQ|h=qk=$nbFu}i>LnDK*viLtz9`Bw2 literal 0 HcmV?d00001 diff --git a/.doctrees/configuration/tutor.doctree b/.doctrees/configuration/tutor.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cb7c6cd0f7d85486d71b7c17a6d52de6a504b79c GIT binary patch literal 72420 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 literal 0 HcmV?d00001 diff --git a/.doctrees/ecommerce/overview.doctree b/.doctrees/ecommerce/overview.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cdb141535dc333253f2a62b20a639b9a2e28eec0 GIT binary patch literal 7978 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 PQcQ%+6~Elby6t}eNPtct literal 0 HcmV?d00001 diff --git a/.doctrees/ecommerce/subsystems/index.doctree b/.doctrees/ecommerce/subsystems/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..545690954e88a416e75542ee70f62f9ae10dd2fd GIT binary patch literal 3006 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 rq{5X@cN8$Yr$ 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 literal 0 HcmV?d00001 diff --git a/.doctrees/ecommerce/subsystems/reporting.doctree b/.doctrees/ecommerce/subsystems/reporting.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d6967c8733676d168166f33e6cc340f3c347ddd5 GIT binary patch literal 5761 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 literal 0 HcmV?d00001 diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..15fca8aac1a6698a6891d4afac1ba53e8d75ac87 GIT binary patch literal 4817 zcmds5&yO5O6<&KiKlhh+9UG#=qH{>R4xXKLa1_`JiV_J$t8AoT1#)oOHQhB+?qJZm5Dh=7}ku7I%MK{HWNqy^Nh|m1hAj>qX1{>;3m0PQ4G3NG9BSx7vUisS>Z=t{lZo8=iS% z8S#5wka1>s=#9?$i zy?`Yi3t4JBBcygFJsd5KxZ%;6XOwq_J9&{jmdWIvH_DACji-gmqtIi?0}MFFB3egw0#H#K2HFgS&+BHIlLldXNlqE(FG)uyfnhw5~O~3#4{t)_lSn3UB z67uO@rcGh55iEB7BxWH(uk zBub17T&fp8DUR$7q}NzZ^2~|Kn7D~8?pOqr(-3Zw3=!2c8Mo^)38FmY#2AF0UGbrK zC_WNDe_A(()|YJ2Y>rEv~@fXGiGH7d?+TgWg3p`F`}%HB-SP4ale zGaeR)E4J_AuT>JALAMWHoL|um>s`br^I3rHgVPdu1EU79G}8f21so6Rq;M`B=*2PisDNSLtkx59X+0R#(euLki^ye7 zM7@p!t}Y?!n`PCQr_FvXusiQ1p$xe85JbEQ8%Cl1n&YohOg_}Bzt6Af+g3wM_lyPk5~ zGnx2PtiGtwvu){IeQ|2rcI&;0`R#xI?A!0!Yi<*}s!fQD!~X$o%9o+^TSV&1We7Hv zY#Zwk`$Bwxf=^##)V)Id(RMI0XDQftl}y_;{O8j&l7TeH0-8Q$SyFuA)}c60I7oGV z5z2&LSDA1V$0#m41}Xotq5Si&Qnn4{b8-~o{j(Y3Hp&2M&ayyM;d@%oiod#m{bp(W zyQT3qYKvRn2+2P#35oc_b4BCtm)-1H(fH@{M??H+cJ5{bZXp7HyKL)b1W5TWU!`0} zfRw8U%xS8oo0>IjyUfJGUU}UWrJpw9=kj)e1H$n;H(%AWeJNTZa(;7g^1N1U2}?Vm~`{JQhQe$|4i6@*(do~jIt z%uhJndDZp>i)O=?-R4sXFB)cYT`RS0E99emg6%ema)@JLm$Mj>9zqsyx)PmFCh(ch zlRyy@qw42oJoxrNOMHKMank2mrZQguViBq^vIchxKw(>@=Nnd9&Pri#FU>5Oc=F)N z%8K1A?at3ouYP%PVrj9xPImzIw1A5u*9LDyEScnN!r}1{M0^#!ioJp7hsr`9Jr3#( zwxL$*$hOO#iaa928W5uG`Y+|rMfR!$+&02IPW>4q;aDe-XpypnMPzWxNnAou^yi{f z^E^~O+I47MM@dkc`^}XOC4uozq?RRQxBO6QD*U(zb$(dbsZ#}Mw~otbBuCjH0yUMI zU$=Mm7Jb7ecoa}OhuDy{5FuYfH#Qp{Ua{MhZ$M5m0T3gJ7N>as`W?HO^3@EG?>;pyPj{Ri;p zW;PC%%#&np{DMafGZk6w+x9)#=tlb zk&0%(aKaasgfs2lg2i6Ft6Q|QVBh`5QXq?NF59YR@D>7<1b87W+WKi{u)RqH=Hrbf_QBvcEMb$-eUmIgnmHiheUc6Ka}m>-hTkB Cs6kZ# literal 0 HcmV?d00001 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/_sources/authentication.rst.txt b/_sources/authentication.rst.txt new file mode 100644 index 000000000..d312f39f2 --- /dev/null +++ b/_sources/authentication.rst.txt @@ -0,0 +1,4 @@ +Authentication +============== + +.. mermaid:: assets/authentication-flow.mm diff --git a/_sources/commands/check_program_requirements.rst.txt b/_sources/commands/check_program_requirements.rst.txt new file mode 100644 index 000000000..90405f401 --- /dev/null +++ b/_sources/commands/check_program_requirements.rst.txt @@ -0,0 +1,22 @@ +``check_program_requirements`` +============================== + +Checks programs for a valid requirements tree. A program has a valid requirements tree if it: + +1. Exists +2. Has nodes for all courses assigned to the program + +If the tree does not fit this criteria, an error message is printed to the screen. (This uses the ``check_program_for_orphans`` API call, but it suppresses its error logging so it won't clog up the error log.) + +By default, this will check all programs in the system. Specify individual programs to check with ``--program`` (multiple times if needed) or check only live programs with ``--live``. Note that if you specify both of these together they will be combined: if you specify a check for a specific program that isn't live and then also specify ``--live``, it won't return anything for that program. + +Syntax +------ + +``check_program_requirements [--program ] [--live]`` + +Options +------- + +* ``--program `` - Check this specific program. Can be either the readable ID of the program (e.g. ``program-v1:MITx+DEDP``) or the numeric ID, and can be specified multiple times to check multiple programs. +* ``--live`` - Check only live programs. diff --git a/_sources/commands/configure_instance.rst.txt b/_sources/commands/configure_instance.rst.txt new file mode 100644 index 000000000..03d308182 --- /dev/null +++ b/_sources/commands/configure_instance.rst.txt @@ -0,0 +1,23 @@ +``configure_instance`` +====================== + +Configures a fresh MITx Online instance. For more information, see :doc:`MITx Online Quick Start<../configuration/quickstart>` and :doc:`Local Open edX Tutor and MITx Online Deployment<../configuration/tutor>`. + +For Tutor deployments, this will use ``local.edly.io`` for URLs for the edX platform. If you're running a dev deployment, or are using Tutor Nightly, ``--tutor-dev`` will additionally add the proper ports (as Caddy is disabled in these cases). In either case, the two demo courses will still be created but only the Demonstration Course (``course-v1:edX+DemoX+Demo_Course``) will exist in edX, and then only if you import the demo course using the relevant Tutor command. + +Syntax +------ + +``configure_instance [--dont-enroll|-D] [--dont-create-superuser|-S] [--edx-oauth-client ] [--edx-oauth-secret ] [--gateway ] [--tutor|-T] [--tutor-dev]`` + +Options +------- + +* ```` - One of ``macos``, ``linux``, or ``none``. Specifying ``none`` will additionaly stop creation of the OAuth2 application record for edX. Defaults to ``none``. +* ``--dont-enroll|-D`` - Don't enroll the test learner account in any courses. (Defaults to enrolling the account in ``course-v1:edX+DemoX+Demo_Course``.) +* ``--dont-create-superuser|-S`` - Don't create a superuser account. +* ``--gateway `` - The Docker gateway IP. Required on Linux. See :doc:`Configure Open edX<../configuration/open_edx>` for more info. +* ``--edx-oauth-client `` - Use the specified client ID for the edX OAuth2 client. (Useful if you're redoing your MITx Online instance and you've already created the corresponding record in edX, since you're not allowed to edit it there.) +* ``--edx-oauth-secret `` - Use the specified client secret for the edX OAuth2 client. (Useful if you're redoing your MITx Online instance and you've already created the corresponding record in edX, since you're not allowed to edit it there.) +* ``--tutor|-T`` - Configure the instance for use with a Tutor edX deployment. +* ``--tutor-dev`` - Configure the instnace for use with Tutor dev or nightly. diff --git a/_sources/commands/configure_tiers.rst.txt b/_sources/commands/configure_tiers.rst.txt new file mode 100644 index 000000000..4c852f340 --- /dev/null +++ b/_sources/commands/configure_tiers.rst.txt @@ -0,0 +1,86 @@ +``configure_tiers`` +=================== + +Creates financial assistance tiers and discounts for a course or program. + +This operates in two modes: creating tiers for a program and creating tiers for a course. + +*In the tables below, represents the current year.* + +**Configuring tiers for a course** + +The command will use the readable ID of the course as part of the financial aid discounts. They will default to this: + +=========================== ============= ====== +Code Type Amount +=========================== ============= ====== +-fa-tier1- percent-off .75 +-fa-tier2- percent-off .50 +-fa-tier3- percent-off .25 +-fa-tier4- percent-off 0 +=========================== ============= ====== + +Note that configuring course tiers requires the course to exist. Use ``create_courseware`` (or any of the other methods) to create the course before you run this command. + +**Configuring tiers for a program** + +The default discounts will be: + +==================== =========== ====== +Code Type Amount +==================== =========== ====== +DEDP-fa-tier1- dollars-off 750 +DEDP-fa-tier2- dollars-off 650 +DEDP-fa-tier3- dollars-off 500 +DEDP-fa-tier4- percent-off 0 +==================== =========== ====== + +Specify changes using ``--program`` and/or ``--program-abbrev``. + +**Tiers** + +The actual tiers that will be created are: + +========= ======================== +Threshold Discount +========= ======================== +$0 -fa-tier1- +$25,000 -fa-tier2- +$50,000 -fa-tier3- +$75,000 -fa-tier4- +========= ======================== + +These can be overridden by providing a CSV file. The CSV file should have the following fields and should not have a header row:: + + threshold amount,discount type,discount amount + +If you specify tier information, you must provide all the tiers you want to create - the specified information will override the default. In addition, you must supply a zero income tier. This is a requirement and the command will quit if you don't have one set up, as that tier is used as the starting point for financial assistance. (In other words, learners will see errors if there's not a zero-income threshold tier set up.) + +**Reuse** + +The command will try to reuse any discounts and tiers that match ones the command would have created, so you can safely run this for a course or program that may have already had financial assistance tiers set up. + +Syntax +------ + +Configuring tiers for a program: +``configure_tiers [--program ] [--program-abbrev ] [--tier-info ]`` + +Configuring tiers for a course: +``configure_tiers [--course ] [--tier-info ]`` + +Options +------- + +Program options: + +* ``--program `` - Program ID to use or create. +* ``--program-abbrev `` - Abbreviation to use for tiers and discounts. + +Course options: + +* ``--course `` - Course ID to use. This won't create a course; use ``create_courseware`` for that. + +Common options: + +* ``--tier-info `` - Tier info in CSV format. diff --git a/_sources/commands/create_courseware.rst.txt b/_sources/commands/create_courseware.rst.txt new file mode 100644 index 000000000..feb9d525d --- /dev/null +++ b/_sources/commands/create_courseware.rst.txt @@ -0,0 +1,60 @@ +``create_courseware`` +===================== + +Creates a new courseware object. + +**For programs**, this creates the basic program record. +**For courses**, this creates the course, and then optionally adds it to the specified program (and can add it as an elective or required course). It will also optionally create an initial course run for the course. Finally, it will also add the course to the program's requirements or electives list. +**For courseruns**, this creates the course run and associates it with the specified course. + +This will not run ``sync_course_run`` for you, so for best results, ensure the course run is set up on the edX side, use this command, then run ``sync_course_run`` to pull dates and other information from edX. + +Syntax +------ + +``create_courseware [--live] [--self-paced] [--create-run [create_run]] [--run-url [RUN_URL]] [--program [PROGRAM]] [--run-tag [run-tag]] [--required] [--elective] [--force] [--start <date>] [--end <date>] [--enrollment-start <date>] [--enrollment-end <date>] [--upgrade <date>] [--dept <department_name>] [--create-depts]`` + +Checks +------ + +The command performs the following checks: +* It checks to see if ``readable_id`` contains ``course`` or ``program`` at the front - if it doesn't, it will assume you've swapped the title and readable ID mistakenly and stop. +* It checks to see if the course will be live before adding it to the requirements tree. If ``--live`` isn't specified, it will ignore your request. This only applies to course creation. +* If creating a course or program, ``--depts`` must be specified and the department names must exist. + +Both of these checks can be overridden with the ``--force`` flag. + +Options +------- + +* ``object`` - One of ``program``, ``course``, or ``courserun`` +* ``readable id`` - The readable ID of the object. Note: do not specify the run tag for course runs. +* ``title`` - The title of the object. +* ``--live`` - Makes the object live (default is not). +* ``--force|-f`` - Force the creation of the object. (See "Checks" section for details.) +* ``--create-depts`` - If specified, any departments specified that do not currently exist will be created. + +Programs can take the following options: +* ``--depts`` - The departments to associate the program with. + +Courses can take the following options: + +* ``--program <PROGRAM>`` - The program to assign the course to. +* ``--create-run <run tag>`` - Create a course run for this course with the specified run tag. +* ``--run-url <url>`` - The courseware URL for the course run. (Only if ``--create-run`` is specified.) +* ``--self-paced`` - The course run is self-paced. (Only if ``--create-run`` is specified.) +* ``--required`` - The course is a requirement for the program. +* ``--elective`` - The course is an elective for the program. +* ``--depts`` - The departments to associate the course with. + +Course runs can take the following options: + +* ``--program <PROGRAM>`` - The program to assign the course to. **Required.** +* ``--run-tag <run tag>`` - The run tag to use. **Required.** +* ``--run-url <url>`` - The courseware URL for the course run. +* ``--self-paced`` - The course run is self-paced. +* ``--start <date>`` - The date the course run should start. +* ``--end <date>`` - The date the course run should end. +* ``--enrollment-start <date>`` - The date the course run enrollment should start. +* ``--enrollment-end <date>`` - The date the course run enrollment should end. +* ``--upgrade <date>`` - The date after which course run enrollments should not be possible. diff --git a/_sources/commands/create_courseware_page.rst.txt b/_sources/commands/create_courseware_page.rst.txt new file mode 100644 index 000000000..54170f58f --- /dev/null +++ b/_sources/commands/create_courseware_page.rst.txt @@ -0,0 +1,17 @@ +``create_courseware_page`` +========================== + +Creates a very basic About page in the CMS for the given courseware object. + +The about page will only have the handful of fields that are required to be there, and will be linked to the specified courseware object. If the courseware object is a course, it will also be added to the Featured Products section on the homepage. By default, the CMS page will be saved in a draft state. + +Syntax +------ + +``create_courseware_page <courseware id> [--live]`` + +Options +------- + +* ``courseware id`` - The courseware object to make a CMS page for. +* ``--live`` - Makes the resulting page live. diff --git a/_sources/commands/create_product.rst.txt b/_sources/commands/create_product.rst.txt new file mode 100644 index 000000000..d4bd44fff --- /dev/null +++ b/_sources/commands/create_product.rst.txt @@ -0,0 +1,19 @@ +``create_product`` +================== + +Creates a product for the given courseware ID. (For now, this only works with course runs.) + +By default, the product description will be the courseware ID. This is the recommended setting for this to make it easy to determine which products are for what courseware objects. + +Syntax +------ + +``create_product <courserun> <price> [--description|-d <description>] [--inactive]`` + +Options +------- + +* ``courserun`` - The course run to use. +* ``price`` - The price (numbers only) of the product. +* ``--description <description>`` (or ``-d``) - Optionally specify the product description. (Defaults to the courseware ID.) +* ``--inactive`` - Makes the product inactive. (Defaults to active.) diff --git a/_sources/commands/create_user.rst.txt b/_sources/commands/create_user.rst.txt new file mode 100644 index 000000000..72e37e8f6 --- /dev/null +++ b/_sources/commands/create_user.rst.txt @@ -0,0 +1,20 @@ +``create_user`` +=============== + +Creates a learner account in the system. You will be prompted for the account password. + +Syntax +------ + +``create_user username email firstname lastname displayname countrycode [--enroll <courseid>]`` + +Options +------- + +* ``username`` - Username for the learner to create. +* ``email`` - Email address of the learner to create. +* ``firstname`` - The learner's first name. +* ``lastname`` - The learner's last name. +* ``displayname`` - The learner's display name. +* ``countrycode`` - The country code to use. (Default US) +* ``--enroll <courseid>`` - Optionally enroll the user in the specified course run. The enrollment will be an audit enrollment. diff --git a/_sources/commands/generate_discount_code.rst.txt b/_sources/commands/generate_discount_code.rst.txt new file mode 100644 index 000000000..345246b16 --- /dev/null +++ b/_sources/commands/generate_discount_code.rst.txt @@ -0,0 +1,51 @@ +``generate_discount_code`` +========================== + +Creates discount code(s). + +This can create a single code, a batch of explicitly defined codes, or a batch of automatically generated codes (with an optional prefix). + +Syntax +------ + +``generate_discount_code <code> [<code>...] --amount <amount> [-activates <date>] [--expires <date>] [--discount-type <discount type>] [--one-time] [--once-per-user] [--count <count>] [--prefix <prefix>]`` + +Batch Generating Codes +---------------------- + +You can create a batch of explicitly named codes by simply passing multiple discount codes to the command. All of the codes will be created (assuming they don't already exist) with the options you've specified. + +Alternatively, you can created a number of codes using the ``--count`` and ``-prefix`` option. Using these options will generate the number of codes specified by ``--count`` and will prefix the code with ``-prefix`` if it is specified. The code will be generated using a UUID - if you've supplied a prefix, the code will be in the format ``<prefix><uuid>``. Note that the command won't insert any punctuation between the prefix and the UUID, so you will need to add that yourself if you want, for example, a dash separating the two. UUIDs are 37 characters in length so prefixes need to be a total of 13 characters or less. + +Output +------ + +Generated codes will be written to a ``generated-codes.csv`` file, with the following information: + +* Discount code +* Code type +* Amount +* Expiration date + +The file is overwritten if it exists. + +Options +------- + +General options: + +* ``--amount <amount>`` - The discount's amount. For percent off discounts, this should be on a scale of 0-100. This is required. +* ``--discount-type <discount type>`` - One of ``percent-off``, ``dollars-off``, or ``fixed-price``; the type of discount code to make. Defaults to ``percent-off``. +* ``--activates <date>`` - The date the code should become active (in ISO-8601 format). +* ``--expires <date>`` - The date the code should stop being active (in ISO-8601 format). +* ``--one-time`` - Set the discount to be redeemable only once. +* ``--once-per-user`` - Set the discount to be redeemable only once per learner. + +For explicitly named codes: + +* ``code`` - The code to generate. (You can specify any number of these.) Max length 50 characters. + +For automatically generated codes: + +* ``--count <count>`` - The number of codes to create. +* ``--prefix <prefix>`` - The prefix to append to the code. Max length 13 characters. diff --git a/_sources/commands/import_courserun.rst.txt b/_sources/commands/import_courserun.rst.txt new file mode 100644 index 000000000..b145dd4a7 --- /dev/null +++ b/_sources/commands/import_courserun.rst.txt @@ -0,0 +1,56 @@ +``import_courserun`` +==================== + +Creates courserun(s) in the system based on edX course data. + +You can specify either a specific courserun to create, or you can specify a run tag (e.g. ``1T2023``) and a program (e.g. ``program-v1:MITx+DEDP``), and the command will create courseruns for the courses that it finds in edX. + +You can also optionally have it create a CMS page for the course, if one doesn't already exist. + +Furthermore, you can specify a price, and it will create (or update) product(s) for the courserun(s) with the specified price. Created products will use the readable ID for the courserun as the product description and will be made active depending on the courserun. This command will not update existing courseruns; only new ones that it creates will get products. + +If the course does not exist, it will be created with the same data as the edX course. Any specified courserun that doesn't exist in edX will be skipped - it won't make empty course runs for you (use Django Admin or ``create_courseware`` if you want to do that, since you'll need to specify a few things that you can't here.) Similarly, any courserun that already exists will be skipped - ``sync_courserun``, which runs on a regular basis, will handle syncing the pertinent data for it. + +New courseruns will be created with the following data synced from the edX course_details API call: +* Start and end dates +* Enrollment start and end dates +* Title +* Pacing (self-paced or instructor-led) +* Courseware URL (depends on the ``OPENEDX_API_BASE_URL`` configuration setting) + +You may want to adjust these after they're created. + +Syntax +------ + +To create an individual courserun: + +``import_courserun [--courserun <courserun>] [--program <program>] [--live] [--create-cms-page] [--price <price>]`` + +To walk a program: + +``import_courserun [--program <program>] [--run-tag <run tag>] [--live] [--create-cms-page] [--price <price>]`` + +Options +------- + +* ``--courserun <courserun>`` - The courserun to check for. Takes precedence over ``--program``. +* ``--program <program>`` + * _If walking a program:_ The program to walk through. Requires ``--run_tag``. Specify either the numeric database ID or the readable ID for the program. + * _If creating a single courserun:_ The program the course should belong to, if any. +* ``--run-tag <run tag>`` - The run tag to use for the new courseruns. Required for ``--program``; don't use otherwise. +* ``--live`` - Make the course live. (Default is to set the flag to false.) +* ``--create-cms-page`` - Attempt to create a basic CMS page for the course, in a similar fashion to ``create_courseware_page``. If this fails (for instance, if the course already has a CMS page), this step will be skipped. +* ``--price <price>`` - Create (or update) a product for the courserun with the specified price. If the command creates multiple courseruns, this will create a product for each. +* ``--dept <department_name>`` - Specify department(s) assigned to the course object. If program is specified, all courses associated with the program and imported will have the same department. + +Example +------- + +The use case for this was creating a batch of course runs for an upcoming semester of DEDP courses; these courses existed in edX but not in MITx Online. Since in that case the semester was 1T2023, this command would have created all the applicable courseruns all at once: + +``manage.py import_courserun --program program-v1:MITx+DEDP --run-tag 1T2023 --live --dept Economics`` + +Or, the same but with the standard DEDP pricing applied: + +``manage.py import_courserun --program program-v1:MITx+DEDP --run-tag 1T2023 --live --price 1000`` diff --git a/_sources/commands/index.rst.txt b/_sources/commands/index.rst.txt new file mode 100644 index 000000000..3ba2d4299 --- /dev/null +++ b/_sources/commands/index.rst.txt @@ -0,0 +1,22 @@ +MITx Online Commands +==================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + check_program_requirements + configure_instance + configure_tiers + create_courseware + create_courseware_page + create_product + create_user + generate_discount_code + import_courserun + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/_sources/commands/refund_fulfilled_order.rst.txt b/_sources/commands/refund_fulfilled_order.rst.txt new file mode 100644 index 000000000..b88313c68 --- /dev/null +++ b/_sources/commands/refund_fulfilled_order.rst.txt @@ -0,0 +1,21 @@ +``refund_fulfilled_order`` +========================== + +Looks up a fulfilled order in the system, sets it to Refunded, and then adjusts the enrollments accordingly. + +- If --unenroll is specified, the learner will be unenrolled from the course run associated with the order. +- If --audit is specified, the learner will keep their unenrollments, but they will be set to "audit" instead of "verified". + +This does not make any sort of call to CyberSource or any other payment gateway to perform a refund - you're expected to have refunded the learner's money manually already. (At time of writing, PayPal transactions can't be refunded using the normal means, so they get refunded manually via CyberSource and then this command comes in to clean up afterwards.) + +Syntax +------ + +``refund_fulfilled_order <reference number> [--audit] [--unenroll]`` + +Options +------- + +* ``<reference number>`` - The reference number for the order to refund. +* ``--audit`` - Change the learner's enrollment status to ``audit``. +* ``--unenroll`` - Unenroll the learner. diff --git a/_sources/commands/regenerate_edx_auth_tokens.rst.txt b/_sources/commands/regenerate_edx_auth_tokens.rst.txt new file mode 100644 index 000000000..c5b7632de --- /dev/null +++ b/_sources/commands/regenerate_edx_auth_tokens.rst.txt @@ -0,0 +1,16 @@ +``regenerate_edx_auth_tokens`` +============================== + +Regenerates the authentication tokens for a specified learner. In essence, deletes the ``OpenEdxApiAuth`` record and then makes a call to edX to generate a new refresh and access token. + +If the user doesn't have an ``OpenEdxUser`` record either, then this command is not appropriate. Use ``repair_missing_courseware_records`` instead. This will also not do anything with enrollments or grades. The main usecase is if the learner's ``OpenEdxApiAuth`` record gets deleted for some reason, or if their refresh tokens on the edX side are revoked. + +Syntax +------ + +``regenerate_edx_auth_tokens <username>`` + +Options +------- + +* ``username`` - the learner's ID, username, or email address. diff --git a/_sources/commands/resolve_pending_order.rst.txt b/_sources/commands/resolve_pending_order.rst.txt new file mode 100644 index 000000000..e0d5c549c --- /dev/null +++ b/_sources/commands/resolve_pending_order.rst.txt @@ -0,0 +1,17 @@ +``resolve_pending_order`` +========================= + +Looks up the specified pending order in CyberSource and resolves it. This can mean either fulfilling the order or cancelling it, depending on the status of the payment in CyberSource: if the order is found and the result code is 100, it will be fulfilled; otherwise, it will be cancelled. + +This only works on pending orders and won't accept a reference number for an order that's not in the Pending state. + +Syntax +------ + +``resolve_pending_order [--all] [--order <reference number>]`` + +Options +------- + +* ``--all`` - Process all pending orders. +* ``--order <reference number>`` - Process a specific order specified by reference number (e.g. ``mitxonline-prod-1``). diff --git a/_sources/configuration/ecommerce.rst.txt b/_sources/configuration/ecommerce.rst.txt new file mode 100644 index 000000000..3aa6743e3 --- /dev/null +++ b/_sources/configuration/ecommerce.rst.txt @@ -0,0 +1,77 @@ +Configure eCommerce +=================== + +To use the eCommerce subsystem, some configuration is required. These instructions will also set up a course in your MITx Online environment that you can use for enrollment. + +You'll need a working MITx Online setup and a working devstack setup to begin, and superuser accounts for each. + +Set Up MITx Online eCommerce Config +################################### + +The CyberSource configuration for the app can be lifted out of Heroku. **Make sure you use values from RC - otherwise, you will actually be charged for purchases (and test credit card numbers will fail).** For best results, you should also have an account for the test Enterprise Business Center (``https://ebc2test.cybersource.com/ebc2/``). + +The ``.env`` settings you need to copy over are: + +- ``MITOL_PAYMENT_GATEWAY_CYBERSOURCE_ACCESS_KEY`` +- ``MITOL_PAYMENT_GATEWAY_CYBERSOURCE_PROFILE_ID`` +- ``MITOL_PAYMENT_GATEWAY_CYBERSOURCE_SECURITY_KEY`` +- ``MITOL_PAYMENT_GATEWAY_CYBERSOURCE_SECURE_ACCEPTANCE_URL`` + +Alternatively, you can set up your own CyberSource developer account and generate a set of API keys there: `Evaluation Account Setup <https://ebc2.cybersource.com/ebc2/registration/external>`_ If you set up your own developer account, you will need to properly configure it for Secure Acceptance with the credit card types you wish to test, and you will need to generate your own API keys and supply them in the ``.env`` file. + +You may also set ``ECOMMERCE_DEFAULT_PAYMENT_GATEWAY`` to ``CyberSource`` - this sets it to the default value, but setting it now will prevent issues if the Payment Gateway ol-django library adds in new payment gateways. + +Set Up a Course +############### + +The devstack environment comes with a couple of test courses set up. If you want a different course, you will need to set that up in Open edX before starting here. Bootstrapping a course in edX is beyond the scope of this document. + +In Open edX +----------- + +1. Log in to the Django Admin interface. +2. Find a course from the `Course Overviews <http://edx.odl.local:18000/admin/course_overviews/courseoverview/>`_ page. +3. Note the *Display name* and *Id* fields. + +In MITx Online +-------------- + +1. Log into the Django Admin interface. +2. Under Courses, open Programs and add a Program. (The specifics here aren't important - there just needs to be a Program.) +3. Under Courses, open Courses and add a Course. The *Title* and *Readable Id* fields should be set to the *Display name* and *Id* fields from the edX course you plan to use. Make sure Live is set and the Program is set to the program you created in step 2. +4. Under Course Runs, add a Course Run. The *Title* and *Courseware Id* fields should be set to the Id and Display Name fields from the edX course. The Courseware url path should be set to the URL where the course lives in edX (ex. ``https://courses-qa.mitxonline.mit.edu/learn/course/course-v1:MITx+14.750x+3T2022/home``). The dates will be overwritten when the system is synced with Open edX, but for testing it's good to put Start Date, End Date, Enrollment Start, and Enrollment End. A good starting point for these is today plus/minus one year for each. +5. You now need to add the course to the CMS. Navigate to the Wagtail CMS admin, at /cms. +6. Open the Courses folder under Home Page. +7. Select Add Child Page. +8. Fill out the form. The course you added in steps 1-4 should appear. (If not, double-check your settings in Django Admin.) Publish the page when ready. +9. Open the Home Page, and select Edit. +10. Under the Featured Products section, select Add. You will be given a button to choose a page, and the page chooser there should list the page you created. +11. Publish the Home Page when ready. + +You should now be able to see the course under the hero image on the MITx Online homepage, and navigating into the course should give you the option to Enroll. (At this point, you won't have a Product set up, so enrolling now should just enroll you in the course.) + +Setting Up a Product +#################### + +1. Log into MITx Online Django Admin. +2. Under Ecommerce, open Products and create a new Product. Set *Content type* to Course Run and *Object Id* to the ID of the course run you created earlier (it's probably 1 if you're working from a new install). Price should ideally be set to a non-zero value, that is less than $999, in RC/Sandbox environments. Description needs to be filled in but can be anything - for clarity, it's recommended to use the course name. Make sure Is active is checked. + +You should now be able to enroll in the upgraded course. + +* If you've enrolled in the course already, you should now see the upsell card on the course listing page. +* If you haven't enrolled, enrolling should pop the upgrade modal. +* In either case, enrolling in the paid version of the course should bring you to the cart, and you should then be able to check out. + +Testing Checkout +---------------- + +The test CyberSource credentials won't actually process a charge that has been run through the system. However, you should avoid using a valid credit card number when testing. Any card number that is both invalid but passes the checks should work. Here are some examples: + +- Visa: 4111111111111111 +- Visa: 4242424242424242 +- MasterCard: 5555555555554444 +- MasterCard: 5105105105105100 +- American Express: 378282246310005 +- Discover: 6011111111111117 + +Supply any expiration date in the future. The CVN code should be any three-digit (not AmEx) or 4 digit (AmEx) number that is fairly unique (not like 123, 111). What card types are allowed and whether or not the CVN code is required depends on the settings in the CyberSource account - currently, the MIT test account does require an expiration date and CVN code and supports the four card types listed above. Transactions are logged and can be found in the test EBC. You can additionally adjust the settings in the EBC to email the payment data to you while you're testing - but you should ask around before doing this in case someone else is testing eCommerce elsewhere. diff --git a/_sources/configuration/hubspot.rst.txt b/_sources/configuration/hubspot.rst.txt new file mode 100644 index 000000000..6672c1de5 --- /dev/null +++ b/_sources/configuration/hubspot.rst.txt @@ -0,0 +1,9 @@ +Configure HubSpot +=================== +In order to connect your local instance of MITx Online with HubSpot, you will need to define the following envrionment variables in your `.env` file. + +``` +MITOL_HUBSPOT_API_PRIVATE_TOKEN=<ask a developer to add you to the hubspot account> +MITOL_HUBSPOT_API_ID_PREFIX=<your_initials>-mitxonline-dev +HUBSPOT_PIPELINE_ID=19817792 +``` diff --git a/_sources/configuration/index.rst.txt b/_sources/configuration/index.rst.txt new file mode 100644 index 000000000..f5024eaaf --- /dev/null +++ b/_sources/configuration/index.rst.txt @@ -0,0 +1,10 @@ +Configuration +============= + +.. toctree:: + :maxdepth: 2 + + open_edx + tutor + ecommerce + quickstart diff --git a/_sources/configuration/open_edx.rst.txt b/_sources/configuration/open_edx.rst.txt new file mode 100644 index 000000000..280862692 --- /dev/null +++ b/_sources/configuration/open_edx.rst.txt @@ -0,0 +1,213 @@ +Configure Open edX +================== + +In order to create user accounts in Open edX and permit authentication from MITx Online to Open edX, you need to configure MITx Online as an OAuth2 provider for Open edX. + +Setup Open edX Devstack +####################### + +Following steps are inspired by `edx-devstack <https://github.com/edx/devstack>`_. + +Add ``/etc/hosts`` alias for Open edX +------------------------------------- + +If one doesn't already exist, add an alias to ``/etc/hosts`` for Open edX. We have standardized this alias +to ``edx.odl.local``. Your ``/etc/hosts`` entry should look like this:: + + 127.0.0.1 edx.odl.local + +Clone edx/devstack +------------------ + +.. code-block:: shell + + git clone https://github.com/edx/devstack + cd devstack + make requirements + make dev.clone + +Pull latest images and run provision +------------------------------------ + +.. code-block:: shell + + make pull + make dev.provision + + +Start your servers +------------------ + +.. code-block:: shell + + make dev.up + +Stop your servers +----------------- + +.. code-block:: shell + + make stop + +Setup social auth +################# + +Install ``social-auth-mitxpro`` in LMS +-------------------------------------- + +There are two options for this: + +Install via pip +^^^^^^^^^^^^^^^ + +.. code-block:: shell + + pip install social-auth-mitxpro + +Install from local Build +^^^^^^^^^^^^^^^^^^^^^^^^ + +* Checkout the `social-auth-mitxpro <https://github.com/mitodl/social-auth-mitxpro>`_ project and build the package per the project instructions +* Copy the ``social-auth-mitxpro-$VERSION.tar.gz`` file into devstack's ``edx-platform`` directory +* In devstack, run ``make lms-shell`` and within that shell ``pip install social-auth-mitxpro-$VERSION.tar.gz`` + + * To update to a new development version without having to actually bump the package version, simply ``pip uninstall social-auth-mitxpro``, then install again + +Install ``mitxpro-openedx-extensions`` in LMS +--------------------------------------------- + +There are two options for this: + +Install via pip +^^^^^^^^^^^^^^^ + +.. code-block:: shell + + pip install mitxpro-openedx-extensions + +Install from local Build +^^^^^^^^^^^^^^^^^^^^^^^^ + +* Checkout the `mitxpro-openedx-extensions <https://github.com/mitodl/mitxpro-openedx-extensions>`_ project and build the package per the project instructions +* Copy the ``mitxpro-openedx-extensions-$VERSION.tar.gz`` file into devstack's ``edx-platform`` directory +* In devstack, run ``make lms-shell`` and within that shell ``pip install mitxpro-openedx-extensions-$VERSION.tar.gz`` + + * To update to a new development version without having to actually bump the package version, simply ``pip uninstall -y mitxpro-openedx-extensions``, then install again + +Configure MITx Online as a OAuth provider for Open edX +###################################################### + +In MITx Online: + +* go to ``/admin/oauth2_provider/application/`` and create a new application with these settings selected: + + * ``Redirect uris``: ``http://<EDX_HOSTNAME>:18000/auth/complete/mitxpro-oauth2/`` + + * **[macOS users]** You will need redirect uris for both the local edX host alias and for ``host.docker.internal``. This value should be:: + + http://edx.odl.local:18000/auth/complete/mitxpro-oauth2/ + http://host.docker.internal:18000/auth/complete/mitxpro-oauth2/ + + * **[Linux users]** You will need redirect uris for both the local edX host alias and for the gateway IP of the docker-compose networking setup for MITx Online as found via ``docker network inspect mitx-online_default``:: + + http://edx.odl.local:18000/auth/complete/mitxpro-oauth2/ + http://<GATEWAY_IP>:18000/auth/complete/mitxpro-oauth2/ + + * **[WSL 2 users]**: Use the URLs for macOS. You will also have to set ``OPENEDX_IP`` to ``host-gateway`` in your ``.env`` file to make this work. (Networking with WSL 2 works very differently, and the defaults won't work.) + + NOTE: ``GATEWAY_IP`` should be something like ``172.19.0.1``. + + * ``Client type``: "Confidential" + * ``Authorization grant type``: "Authorization code" + * ``Skip authorization``: checked + * Other values are arbitrary but be sure to fill them all out. Save the client id and secret for later + +In Open edX (derived from instructions `here <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/latest/configuration/tpa/tpa_integrate_open/tpa_oauth.html#additional-oauth2-providers-advanced>`_): + +* ``make lms-shell`` into the LMS container and ensure the following settings are set in ``/edx/etc/lms.yml`` if you are using Juniper or a more recent Open edX release, otherwise they should be in ``/edx/app/edxapp/cms.env.json``: + .. code-block:: yaml + + FEATURES: + ALLOW_PUBLIC_ACCOUNT_CREATION: true + ENABLE_COMBINED_LOGIN_REGISTRATION: true + ENABLE_THIRD_PARTY_AUTH: true + ENABLE_OAUTH2_PROVIDER: true + SKIP_EMAIL_VALIDATION: true + REGISTRATION_EXTRA_FIELDS: + country: hidden + THIRD_PARTY_AUTH_BACKENDS: + - social_auth_mitxpro.backends.MITxProOAuth2 + +* ``make lms-restart`` to pick up the configuration changes +* Login to django-admin (default username and password can be found `here <https://github.com/openedx/devstack#usernames-and-passwords>`_), go to ``http://<EDX_HOSTNAME>:18000/admin/third_party_auth/oauth2providerconfig/``, and create a new config: + + * Select the default example site + * The slug field **MUST** match the the backend's name, which for us is ``mitxpro-oauth2`` + * Client Id should be the client id from the MITx Online Django Oauth Toolkit Application + * Check the following checkboxes: + + * Enabled + * Skip hinted login dialog + * Skip registration form + * Sync learner profile data + * Enable SSO id verification + * Set Backend name to: ``mitxpro-oauth2`` + + * In "Other settings", put: + + .. code-block:: json + + { + "AUTHORIZATION_URL": "http://<LOCAL_MITX_ONLINE_ALIAS>:8013/oauth2/authorize/", + "ACCESS_TOKEN_URL": "http://<EXTERNAL_MITX_ONLINE_HOST>:8013/oauth2/token/", + "API_ROOT": "http://<EXTERNAL_MITX_ONLINE_HOST>:8013/" + } + + * ``LOCAL_MITX_ONLINE_ALIAS`` should be your ``/etc/hosts`` alias for the mitxonline app + * ``EXTERNAL_MITX_ONLINE_HOST`` will depend on your OS, but it needs to be resolvable within the edx container + + * Linux users: The gateway IP of the docker-compose networking setup for mitxonline as found via ``docker network inspect mitx-online_default`` + * OSX users: Use ``host.docker.internal`` + + * Save the configuration. + + + +Configure Open edX to support OAuth2 authentication from MITx Online +#################################################################### + +* In Open edX: + + * go to ``/admin/oauth2_provider/application/`` and verify that an application named 'edx-oauth-app' exists with these settings: + + * ``Redirect uris``: ``http://mitxonline.odl.local:8013/login/_private/complete`` + * ``Client type``: "Confidential" + * ``Authorization grant type``: "Authorization code" + * ``Skip authorization``: checked + * Other values are arbitrary but be sure to fill them all out. Save the client id and secret for later + +* In MITx Online: + + * Set ``OPENEDX_API_CLIENT_ID`` to the client id + * Set ``OPENEDX_API_CLIENT_SECRET`` to the client secret + * Set ``OPENEDX_API_BASE_URL`` to ``http://host.docker.internal:18000`` when running ``devstack`` and ``mitxonline`` locally through Docker + +Configure Logout +################ + +* In Open edX, configure ``settings.IDA_LOGOUT_URI_LIST`` to be a list including the full url to ``<protocol>://<hostname>[:<port>]/logout`` in MITx Online + + * For devstack, this means modifying the value in ``edx-platform/lms/envs/devstack.py`` to include ``http://mitxonline.odl.local:8013/logout`` + * For production, this setting can go in ``lms.env.json`` under the key ``IDA_LOGOUT_URI_LIST`` as a JSON array of with that string in it + +* MITx Online: + + * Set ``LOGOUT_REDIRECT_URL`` to the full path to the edx ``/logout`` view. + +For local development this will be ``http://<EDX_HOSTNAME>:18000/logout`` + + +Configure Open edX user and token for use with MITx Online management commands +############################################################################## + +* In Open edX, create a staff user and then under ``/admin/oauth2_provider/accesstoken/`` add access token. The value of said token needs to match the value set for the ``OPENEDX_SERVICE_WORKER_API_TOKEN`` key in the MITx Online app. diff --git a/_sources/configuration/quickstart.rst.txt b/_sources/configuration/quickstart.rst.txt new file mode 100644 index 000000000..e261019b1 --- /dev/null +++ b/_sources/configuration/quickstart.rst.txt @@ -0,0 +1,81 @@ +MITx Online Quick Start +======================= + +You can use the ``configure_instance`` management command to perform a quick-start of a fresh MITx Online instance. This command takes care of a lot of the boilerplate required to set up an instance. It: + +* Creates a superuser account (if you want) +* Creates the OAuth2 application record for edX (if you want, and optionally with an existing secret) +* Creates a set of courseware objects, including a DEDP program and courses with runs that match what ships with a standard devstack instance +* Creates a set of CMS pages for the courseware objects that it creates +* Sets up financial assistance appropriately +* Adds a couple of products in for the courses it creates +* Creates a learner account for the system + +It does not: + +* Run migrations +* Completely set up integration with devstack + +In addition, there are a handful of tasks that you'll need to perform afterwards: + +* The CMS pages (course about pages and the financial assistance form) need to be reviewed for content. +* The financial assistance form will need to be published, and linked into the appropriate course. +* You may want to adjust the products that are created. + +The ``configure_instance`` command has a few flags you can use to customize how it works. For more details on this, either run it with ``--help`` or read the :doc:`configure_instance<../commands/configure_instance>` command documentation. (Do this especially if you're using the command to **reset** your MITx Online instance - you can provide an existing OAuth client ID and secret.) + +Performing a Quick Start +------------------------ + +To quick-start your MITx Online instance: + +1. Run the ``migrate`` command. +2. Run the ``createsuperuser`` command. +3. Follow the steps in the :doc:`Configure Open edX<open_edx>` documentation +4. Run ``configure_instance <platform>``, where ``platform`` is ``macos``, ``linux``, or ``none``. (If you don't want it to create OAuth2 records, set this to ``none`` or leave it blank. The default is ``none``.) + +``configure_instance`` will prompt you to enter a password for the test learner account and will prompt you to enter account information for the superuser account. At the end, you'll see your edX OAuth2 application credentials, which can then be plugged into Open edX (if you haven't specified ``none`` for your platform). + +Results +------- + +Running ``configure_instance`` will peform these tasks in order: + +1. Runs ``createsuperuser`` to create the superuser account (unless disabled with ``--dont-create-superuser``). +2. Creates the OAuth2 application record. (This is the one part of this that doesn't rely on an existing management command.) +3. Runs ``configure_wagtail`` to set up the CMS. +4. Runs ``configure_tiers`` to add the DEDP program and configure financial assistance tiers and discounts. +5. Runs ``create_courseware_page`` to add a basic about page for the DEDP program (required for the financial assistance form). +6. Runs ``create_finaid_form`` to create a financial assistance form for the DEDP program. +7. Runs ``create_courseware`` twice to create two courses, each with a course run, that correspond to the demo courses in devstack. (Details below.) +8. Runs ``sync_course_run`` to sync the courses with the devstack instance. +9. Runs ``create_product`` twice to create two products for the courses. +10. Runs ``create_courseware_page`` twice to add course pages for the two courses. (These are marked as live.) +11. Runs ``create_user`` to create the learner account. + +The courses that are created are: + ++----------------------+-----------------------+-------------+-------------+ +| Course | Readable ID | Run Tag | Price | ++======================+=======================+=============+=============+ +| Demonstration Course | course-v1:edX+DemoX | Demo_Course | $999 | ++----------------------+-----------------------+-------------+-------------+ +| E2E Test Course | course-v1:edX+E2E-101 | course | $999 | ++----------------------+-----------------------+-------------+-------------+ + +The learner account that is created is: + +* Username: testlearner +* Email: testlearner@mitxonline.odl.local +* Display Name (split in half for first/last names): Test learner +* Country Code: US +* Enrollments: ``course-v1:edX+DemoX+Demo_Course`` + +The program that gets created is the standard DEDP program (``program-v1:MITx+DEDP``). The *Demonstration Course* is added to the DEDP program; *E2E Test Course* is not. + +Notes +----- + +The steps that involve communication with edX may not work if your environment isn't set up properly. In these cases, the attempts will be queued to be run later. + +If you've set your platform to ``macos`` or ``linux``, the command will do the first part of the *Configure MITx Online as an OAuth provider for Open edX* section in the :doc:`Configure Open edX<open_edx>` documentation. diff --git a/_sources/configuration/tutor.rst.txt b/_sources/configuration/tutor.rst.txt new file mode 100644 index 000000000..552b2ccc0 --- /dev/null +++ b/_sources/configuration/tutor.rst.txt @@ -0,0 +1,219 @@ + +Local Open edX Tutor and MITx Online Deployment +=============================================== + +These instructions describe setting up MITx Online and Tutor from scratch on Linux. These instructions should largely apply to macOS users, and they should also apply to users converting from a devstack-based deployment to Tutor. + +.. + + These instructions should work for a Tutor Dev or Tutor Nightly deployment as well. Specify ``--tutor-dev`` instead of ``--tutor`` when running ``configure_instance`` so the URLs have a port on them. + + +At the end of this guide, you should have: + + +* A fully working MITx Online deployment. +* A working Tutor deployment of edX. +* SSO should work from edX to MITx Online. +* MITx Online should have a service worker set up and should be able to perform tasks using the edX api. +* Tutor's included AuthN MFE should be disabled in favor of the MITx Online authentication system. + +Preliminary Steps +----------------- + +``pyenv`` (and ``pyenv-virtualenv``\ ) are highly recommended for managing local Python versions. `Use the instructions on their GitHub page to install if you haven't already installed it. <https://github.com/pyenv/pyenv>`_ + +You'll want to create at least a virtualenv for Tutor. As of this writing, Tutor uses Python 3.8.12 (in the LMS container at least); I have also successfully used 3.9.16. 3.11 has *not* worked for me. You can optionally create a virtualenv for MITx Online too but that's not strictly necessary. (I have one so I can run black/isort/etc. without having to jump into a container to do it.) + +Tutor Setup, Part One +--------------------- + +.. + + Note that no hosts file changes are needed if you use the default ``local.edly.io`` domain - that's a real domain with a wildcard subdomain cname that points to 127.0.0.1. + + +To begin, you need to follow the `One-Click Installer <https://docs.tutor.overhang.io/quickstart.html>`_ instructions provided by Tutor. Do this with your Tutor virtualenv activated. + +.. + + Mac/Arm users should instead follow these instructions: `Running Tutor on ARM-based systems <https://docs.tutor.overhang.io/tutorials/arm64.html>`_ It's mostly the same steps that the quickstart does internally, with some changes to rebuild some of the images and flip some dependencies to use compatible images for Arm. + + +Once Tutor has bootstrapped itself and is available, create a superuser account: + +.. code-block:: + + tutor local do createuser --staff --superuser edx edx@example.org + +Supply a password (the one used by devstack is ``edx`` so use that if you want to be consistent with it). Then, create a service worker account for MITx Online: + +.. code-block:: + + tutor local do createuser --staff mitx_online_serviceworker service@mitxonline.odl.local + +Supply a password (this one doesn't matter for a local deployment, you won't ever actually use the account). + +For best results, create two new courses within edX. The MITx Online ``configure_instance`` command expects a couple of courses to exist in edX (because they come with the devstack package): + +.. list-table:: + :header-rows: 1 + + * - Course ID + - Course Title + * - course-v1:edX+DemoX+Demo_Course + - Demonstration Course + * - course-v1:edX+E2E-101+course + - E2E Test Course + + +If you have a devstack instance handy, you can export these and import them into Tutor. Otherwise, just create them and make sure to set dates for the courses (they default to 2030 otherwise). + +Finally, go here to create an access token for the service worker user: https://local.edly.io/admin/oauth2_provider/accesstoken/add/ The token can be anything, and the expiration date should just be today plus 10 years. + +MITx Online Setup +----------------- + +To set up MITx Online: + + +#. Get the gateway IP for the ``tutor_local_default`` network: ``docker network inspect tutor_local_default | grep Gateway`` + + * Mac users should instead use the host.docker.internal IP. Specify this by setting ``OPENEDX_IP=host-gateway`` in your MITx Online ``.env`` file. + +#. Set up your ``/etc/hosts`` file to point ``mitxonline.odl.local`` to localhost. +#. Clone the repository somewhere. +#. Set up your ``.env`` file. These settings need particular attention: + + * ``OPENEDX_IP``\ : set to the gateway IP from the first step + * ``OPENEDX_API_BASE_URL``\ : set to http://local.edly.io + * ``OPENEDX_SERVICE_WORKER_USERNAME``\ : set to ``mitx_online_serviceworker`` (unless you changed this) + * ``OPENEDX_SERVICE_WORKER_API_TOKEN``\ : set to the token you generated + +#. Build the app: ``docker compose build`` +#. Run migrations and configure Wagtail:: + + docker compose run --rm web ./manage.py migrate + docker compose run --rm web ./manage.py configure_wagtail + +#. Run the ``configure_instance`` command:: + + docker compose run --rm web ./manage.py configure_instance linux --gateway <ip> --tutor + + where ``<ip>`` is the IP from the first step. (On macOS, specify ``macos`` instead of ``linux``. You can also skip ``--gateway``.) You will need to supply passwords for the MITx Online superuser and test learner accounts. Make a note of the client ID and secret that it will print out at the end. + + +Tutor Setup, Part Two +--------------------- + +Note that some of these steps require editing the main configuration files for the production instance (which is also used for a local deployment). Most of the settings that need to be adjusted to get integration working are overridden by the default Tutor configuration, so you can't update them by setting ``config.yml``. If you're using the development Tutor build, you'll likely need to edit ``development.py`` rather than ``production.py`` as necessary. + +These steps will also disable the AuthN SSO MFE, so from here on you'll get normal edX authentication screens (if you're not being bounced to MITx Online). + + +#. Get the gateway IP of the ``mitxonline_default`` Docker network:: + + docker network inspect mitxonline_default | grep Gateway + +#. Log into to edX using your superuser account, and make sure your session stays open. Sessions are pretty long-lived so this just means not closing the browser that you started the session in. (Part of this process will involve mostly breaking authentication so it's important that you are able to get into the admin.) +#. Stop Tutor: ``tutor local stop`` +#. Change into the configuration root for Tutor:: + + cd "$(tutor config printroot)" + +#. Create a ``env/build/openedx/requirements/private.txt`` with the required extensions:: + + social-auth-mitxpro + mitxpro-openedx-extensions + +#. Edit the ``env/apps/openedx/config/lms.env.yml`` file and add:: + + FEATURES: + SKIP_EMAIL_VALIDATION: true + + to the ``FEATURES`` block (should be at the top). +#. Edit the ``env/apps/openedx/settings/lms/production.py`` and/or ``env/apps/openedx/settings/lms/development.py`` settings file. (The former is used by a local instance, where the latter is used by both dev and nightly instances.) + + * Add to the end of the file: + + * ``THIRD_PARTY_AUTH_BACKENDS = ['social_auth_mitxpro.backends.MITxProOAuth2']`` + * ``AUTHENTICATION_BACKENDS.append('social_auth_mitxpro.backends.MITxProOAuth2')`` + * ``IDA_LOGOUT_URI_LIST.append('http://mitxonline.odl.local:8013/logout/')`` - there's an existing one of these around like 300 in production.py too. + + * Find and update: + + * ``FEATURES['ENABLE_AUTHN_MICROFRONTEND'] = False`` (defaults to True) + * ``REGISTRATION_EXTRA_FIELDS["terms_of_service"] = "hidden"`` (defaults to required) + +#. Build a new ``openedx`` image: ``tutor images build openedx`` (this will take a long time) +#. Run a Docker Compse rebuild: ``tutor local dc build`` (this should be pretty quick - it's likely not required, just doing it here for safety) +#. Restart Tutor: ``tutor local start -d`` (omit ``-d`` if you want to watch the logs) +#. Check your settings. There's a ``print_setting`` command that you can use to verify everything is set properly: + + * ``tutor local run lms ./manage.py lms print_setting REGISTRATION_EXTRA_FIELDS`` + * ``tutor local run lms ./manage.py lms print_setting AUTHENTICATION_BACKENDS`` + * ``tutor local run lms ./manage.py lms print_setting FEATURES`` - will print a lot of stuff + * ``tutor local run lms ./manage.py lms print_setting THIRD_PARTY_AUTH_BACKENDS`` + * If you do have weird errors or settings not showing properly, make sure you edited the right yaml files *and* that they're using the right whitespace (i.e. don't use tabs). + +#. In a separate browser session of some kind (incognito/private browsing/other browser entirely), try to navigate to http://local.edly.io . It should load but it should give you an error message. In the LMS logs, you should see an error message for "Can't fetch settings for disabled provider." This is proper operation - the OAuth2 settings aren't in place yet. +#. In the superuser session you have open, go to http://local.edly.io/admin . This should work. If you've been logged out, you should still be able to get in. If you can't (for instance, if you're getting 500 errors), you will need to turn off ``ENABLE_THIRD_PARTY_AUTH`` in ``FEATURES``\ , restart Tutor using ``tutor local stop`` and ``start``\ , not using ``reboot``\ , then try again. +#. Go to http://local.edly.io/admin/third_party_auth/oauth2providerconfig/add/ and add a provider configuration: + + * Enabled is checked. + * Name: ``mitxonline`` + * Slug: ``mitxpro-oauth2`` + * Site: ``local.edly.io`` + * Skip hinted login dialog is checked. + * Skip registration form is checked. + * Skip email verification is checked. + * Sync learner profile data is checked. + * Enable sso id verification is checked. + * Backend name: ``mitxpro-oauth2`` + * Client ID and Client Secret: from record created by ``configure_instance`` when you set up MITx Online. + * Other settings: + + { + "AUTHORIZATION_URL": "\http://mitxonline.odl.local:8013/oauth2/authorize/", + "ACCESS_TOKEN_URL": "\http://<MITXONLINE_GATEWAY_IP>:8013/oauth2/token/", + "API_ROOT": "\http://<MITXONLINE_GATEWAY_IP>:8013/" + } + + where MITXONLINE_GATEWAY_IP is the IP from the ``mitxonline_default`` network from the first step. **Mac users**, use ``host.docker.internal`` for MITXONLINE_GATEWAY_IP. + +#. Configure Tutor for OAuth2 authentication from MITx Online. + + * Go to http://local.edly.io/admin/oauth2_provider/application/ and either add or edit the ``edx-oauth-app`` entry. + * Ensure these settings are set: + + * Name: ``edx-oauth-app`` + * Redirect uris: ``http://mitxonline.odl.local:8013/login/_private/complete`` + * Client type: ``Confidential`` + * Authorization grant type: ``Authorization code`` + * Skip authorization is checked. + + * Save ``Client id`` and ``Client secret``. + +#. Update your MITx Online ``.env`` file. Set ``OPENEDX_API_CLIENT_ID`` and ``OPENEDX_API_CLIENT_SECRET`` to the values from the record you created or updated in the last step. +#. You should now be able to run some MITx Online management commands to ensure the service worker is set up properly: + + * ``sync_courserun --all ALL`` should sync the two test courses (if you made them). + * ``repair_missing_courseware_records`` should also work. + +#. In the separate browser session from step 12, attempt to log in again. This time, you should be able to log in through MITx Online, and you should be able to get to the edX LMS dashboard. If not, then double-check your provider configuration settings and try again. + + * Unlike devstack, the Tutor instance has an Update button for the provider configuration, so you can just update the record you put in. + * If you are still getting "Can't fetch settings" errors, *make sure* your Site is set properly - there are three options by default and only one works. (This was typically the problem I had.) + +#. Optionally, log into the LMS Django Admin and make your MITx Online superuser account a superuser there too. + +Other Notes +----------- + +**Trying to set configuration settings via ``tutor config`` will undo the specialty configuration above.** If you need to make changes to the configuration, either manually edit the ``env/apps/openedx/config/lms.env.yml`` file or the ``env/apps/openedx/settings/lms/production.py`` file and restart your Tutor instance. + +**Make sure your service worker account is active.** It's an easy checkbox to miss. + +**Restarting** If you want to rebuild from scratch, make sure you ``docker image prune``. It's also recommended to remove the Tutor project root folder - ``tutor config printroot`` will tell you where that is. + +**Running Multiple Tutor Instances** If you want to run more than one Tutor instance, it's pretty important to specify the project root explicitly or you may end up with one instance trying to use config files from another and things getting confused from there. `See the Tutor documentation for this. <https://docs.tutor.overhang.io/local.html#tutor-root>`_ (A suggestion: configure aliases to the ``tutor`` command that run ``tutor --root=<whatever>`` so you don't have to rely on environment variables, especially if you keep multiple terminal sessions going.) diff --git a/_sources/configuration/uwsgi_tuning.rst.txt b/_sources/configuration/uwsgi_tuning.rst.txt new file mode 100644 index 000000000..605e1fff5 --- /dev/null +++ b/_sources/configuration/uwsgi_tuning.rst.txt @@ -0,0 +1,68 @@ +======================================= +Setting up uWsgi tuning for MITx Online +======================================= + +This setup satisfies the testing to help with tuning as mentioned in this `Discusssion Post <https://github.com/mitodl/hq/discussions/393>`_ + +Largely borrowed from work on OCW studio: + +| `Adding uWSGI stats <https://github.com/mitodl/ocw-studio/pull/1898/>`_ +| `Tuning the App <https://github.com/mitodl/ocw-studio/pull/1886/>`_ + + +****************** +To set up locally: +****************** + +Set up uwsgitop +--------------- +1. Install uwsgitop: ``docker compose run --rm web poetry add uwsgitop`` +2. Set UWSGI_RELOAD_ON_RSS in your .env to a high value (e.g. 500) +3. Set UWSGI_MAX_REQUESTS in your .env to a high value (e.g. 10000) +4. ``docker compose build`` +5. ``docker compose up`` +6. In a new terminal window/tab, ``docker compose exec web uwsgitop /tmp/uwsgi-stats.sock`` +7. You should see your application's memory usage without usage. Ready to go. + + +Set up Locust +------------- +1. Install Locust: ``docker compose run --rm web poetry add locust`` +2. Add locust to your docker-compose.yml locally, under services: + +.. code-block:: shell + + locust: + image: locustio/locust + ports: + - "8089:8089" + volumes: + - ./:/src + command: > + -f /src/locustfile.py + +3. Add the following to the web block, at the level of, and directly after, ``build``: + +.. code-block:: shell + + deploy: + resources: + limits: + cpus: "2" + memory: "1g" + +4. Add locustfile.py. There is an example file at ``locustfile.py.example`` in the root of the repo. ``cp locustfile.py.example locustfile.py`` will copy it over as is. Change variables and/or add tests as needed. + +Put it all together +------------------- + +1. Run ``docker-compose build`` +2. Run ``docker-compose up`` +3. You can use locust from ``http://0.0.0.0:8089/`` in a browser +4. You can use uwsgitop in a terminal with ``docker compose exec web uwsgitop /tmp/uwsgi-stats.sock`` + +****************** +To test: +****************** + +Coming soon! diff --git a/_sources/ecommerce/flexible_pricing.rst.txt b/_sources/ecommerce/flexible_pricing.rst.txt new file mode 100644 index 000000000..3721af188 --- /dev/null +++ b/_sources/ecommerce/flexible_pricing.rst.txt @@ -0,0 +1,52 @@ +Flexible Pricing +================ + +The Flexible Pricing system allows learners to request alternative pricing for MITx Online courses based on their location and annual income. These requests can be made through a customizable form in the Wagtail CMS system. + +When the learner accesses the Flexible Pricing request form, they will see one of the following: + +* If they aren't logged in, they'll see a message saying so. Learners must be logged in to request flexibile pricing. +* If they haven't submitted a request, they'll see the flexible pricing form and will be able to submit a request. On submission, the learner will see a message saying their request has been approved (if it can be approved automatically), or will see a request for more information to be submitted via DocuSign. +* If they have submitted a request, they'll be presented with a status page for their request. The text for approved, denied, and in progress states can be customized. + +Form Creation +************* + +*To manually create a Flexible Pricing request form,* follow these steps: + +1. Navigate to the course page for the course. (Do not click the pencil button to edit, simply select the page itself.) +2. Click the Add Child Page button in the header. +3. You will be presented with the New Flexible Pricing Request Form page. Fill out the form. + + 1. The Intro field text is displayed on the form regardless of the state of the request. + 2. The Guest text is displayed if the learner isn't logged in yet. + 3. The Application Processing text is displayed if the learner navigates to the form again and their application is still being processed. + 4. The Application Approved text is displayed if the learner navigates to the form and their application has been approved. + 5. The Application Approved No Discount text is displayed if the learner navigates to the form and their application has been approved, but they've been approved for a zero-value tier. (In other words, the learner has exceeded the upper limit of flexible pricing.) + 6. The Application Denied text is displayed if the learner navigates to the form and their application has been denied. + 7. The Form Fields are the data that the learner must provide to be considered for flexible pricing. Leave this alone - the system will automatically add the proper fields when the form is published. + +4. Publish the form when you are ready. +5. Navigate back to the course page for the course, and edit the page. Add a link to the flexible pricing form you created in the page content. +6. Publish the course page when you are ready. + +To add the Flexible Pricing form to the Price card on a course page, first get the link to the live form. This can be done on the edit page for the flexible pricing form (as well as in a few other locations within the CMS). Then, add that link to the Link field in the Price card for the course. + +*To generate a Flexible Pricing request form with some reasonable defaults for your courseware object,* use the ``create_finaid_form`` management command: + +.. code-block:: bash + + $ manage.py create_finaid_form [--force] [--slug <slug name here>] [--title <title here>] courseware-readable-id + +This command will create an appropriate flexible pricing form for the courseware object: + +* If you've specified a Program, the form will be located under the program's page in the CMS. +* If you've specified a standalone Course, the form will be located under the course's page in the CMS. +* If you've specified a Course that is in a Program, the form will be located under the program's page in the CMS, *unless* the ``--force`` option is specified. In that case, the form will be under the course page. + +You can customize the title and slug using their respective options. By default, the system will use the object's title to generate a form title and slug. + +Processing Submitted Request +**************************** + +TBD diff --git a/_sources/ecommerce/index.rst.txt b/_sources/ecommerce/index.rst.txt new file mode 100644 index 000000000..fb0009238 --- /dev/null +++ b/_sources/ecommerce/index.rst.txt @@ -0,0 +1,9 @@ +Ecommerce +========= + +.. toctree:: + :maxdepth: 2 + + overview + flexible_pricing + subsystems/index diff --git a/_sources/ecommerce/overview.rst.txt b/_sources/ecommerce/overview.rst.txt new file mode 100644 index 000000000..916ec6647 --- /dev/null +++ b/_sources/ecommerce/overview.rst.txt @@ -0,0 +1,31 @@ +Overview +========= + +Goals +***** + +We will be creating a robust ecommerce implementation, incorporating learnings from the last several years to implement it in a scalable and reusable way. A good reference point is this guide on Pythonic SOLID Principle. We should also strongly strive towards keeping coupling between the subsystems proposed here to a minimum or at least limited in surface area. +Core + +The core of the ecommerce system should be simple enough to configure and operate but support enough functionality to serve a majority of our use cases. Users should be able to see programs or course runs, select them for purchase, and make a payment. + +Prior Art +********* + +We have a few implementations of ecommerce we’ve created over the years: + +MicroMasters +^^^^^^^^^^^^ +The MicroMasters implementation is highly specialized, particularly around financial aid programs where each learner gets custom pricing. Incorporating this level of complexity into the core of the ecommerce system is not something we want to do, but we should carve out some options to extend the system in the future without implementing it in the core system. + +xPro +^^^^ + +xPro ecommerce was implemented based on our experiences implementing ecommerce in MicroMasters. A good amount of planning went into this implementation, although it also has some specializations we wouldn’t be using in MITx Online such as a vouchers system. We will probably borrow heavily from the core designs that were proved out here. + +Core Systems +************ + +Ecommerce is actually a combination of 3 discernable subsystems that often get muddled together: products, orders, and payment. See the high-level diagram below to understand the pieces of data and operations that happen. + +.. mermaid:: assets/ecommerce-architecture.mm diff --git a/_sources/ecommerce/subsystems/basket.rst.txt b/_sources/ecommerce/subsystems/basket.rst.txt new file mode 100644 index 000000000..a63398f79 --- /dev/null +++ b/_sources/ecommerce/subsystems/basket.rst.txt @@ -0,0 +1,30 @@ +Basket Subsystem +================ + +This tracks products intended to be purchased, often providing some additional state such as which runs under a program a user is purchasing. + +A simple schema for this would be: + +.. code-block:: python + + class Basket(TimestampedModel): + """Represents a User's basket.""" + + user = models.OneToOneField(settings.AUTH_USER_MODEL) + + class BasketItem(TimestampedModel): + """Represents one or more products in a user's basket.""" + + product = models.ForeignKey(Product) + basket = models.ForeignKey(Basket) + quantity = models.PositiveIntegerField() + +APIs +^^^^ + +- ``GET /api/v0/basket/`` -> get the current basket state +- ``POST /api/v0/basket/`` -> update the basket state + +Notes +^^^^^ +The implementation of this would use the discount subsystem to calculate the discounted prices, those values would be returned in the API for the frontend to use for display purposes. diff --git a/_sources/ecommerce/subsystems/discount.rst.txt b/_sources/ecommerce/subsystems/discount.rst.txt new file mode 100644 index 000000000..3b0f8b09e --- /dev/null +++ b/_sources/ecommerce/subsystems/discount.rst.txt @@ -0,0 +1,101 @@ +Discount Subsystem +================== + +Discounts will need to be provided on occasion, these give the user a reduced price for some or all products. Treating this as a discount system and not necessarily a coupon system (e.g. a coupon is a kind of discount) is a good way to frame this approach. + +The discount system would support discounts of multiple types. We’ve done discounts a lot of different ways before so we need to balance out flexibility against keeping complexity down. Each discount would be associated with a certain Product. + +A discount may optionally be pre-associated with a User so that it can be automatically applied on checkout. + +Discounts should only be computed on the backend, some of our ecommerce implementations have computed the discount on the frontend and we want to avoid this going forward. + +Discount Types +^^^^^^^^^^^^^^ +Discount types would track how the discounted price is computed, some examples/ideas: +``percent-off``: a percentage off the original price +``dollars-off``: a fixed dollar reduction in price (e.g. $30 off) +``fixed-price``: the price is discounted to the fixed price (e.g. a product would cost $100 regardless of what the original price was) + +Redemption Types +^^^^^^^^^^^^^^^^ +There may be a few different ways we want to track discount usage, for example: + +``one-time``: the discount can only be used once by anyone +``one-time-per-user``: the discount can be used once per user +``unlimited``: the discount can be used any number of times + +Data Models +^^^^^^^^^^^ +.. code-block:: python + + class Discount(TimestampedModel): + """Discount model""" + amount = models.DecimalField( + decimal_places=5, + max_digits=20, + ) + automatic = models.BooleanField(default=False) + discount_type = models.CharField( + choices=DISCOUNT_TYPES, max_length=30 + ) + redemption_type = models.CharField( + choices=REDEMPTION_TYPES, max_length=30 + ) + max_redemptions = models.PositiveIntegerField(null=True) + + class UserDiscount(TimestampedModel): + """pre-assignment for a discount to a user""" + discount = models.ForeignKey(Discount) + user = models.ForeignKey(User) + +Implementation Proposal +^^^^^^^^^^^^^^^^^^^^^^^ + +Rather than codifying the discount logic in a complicated computation function, discount types can be implemented by abstracting the logic around discounts into a registry-driven discount factory like this: + +.. code-block:: python + + import abc + from dataclasses import dataclass + + + @dataclass + class DiscountType(abc.ABC): + _CLASSES = {} + + discount: Discount + + # see https://www.python.org/dev/peps/pep-0487/ + def __init_subclass__(cls, *, discount_type, **kwargs): + super().__init_subclass__(**kwargs) + + if discount_type in _CLASSES: + raise TypeError(f"{discount_type} already defined for DiscountType") + + cls.discount_type = discount_type + cls._CLASSES[discount_type] = cls + + @classmethod + def for_discount(cls, discount: Discount): + DiscountTypeCls = cls._CLASSES[discount.discount_type] + + return DiscountTypeCls(discount) + + def get_product_price(self, product: Product): + return self.get_product_version_price(product.latest_version) + + @abc.abstractmethod + def get_product_version_price(self, product_version: ProductVersion): + ... + + class PercentDiscount(DiscountType, discount_type=Discount.PERCENT_DISCOUNT): + + def get_product_version_price(self, product_version: ProductVersion): + return product_version.price * self.discount.amount + + class FixedPriceDiscount(DiscountType, discount_type=Discount.PERCENT_DISCOUNT): + + def get_product_version_price(self, product_version: ProductVersion): + return self.discount.amount # the amount here is the fixed price + +With this implementation, prices before ordering would use get_product_price, whereas the receipt service would use get_product_version_price on the purchased versions. This makes it far more scalable to introduce new discount types without having to refactor existing code. diff --git a/_sources/ecommerce/subsystems/index.rst.txt b/_sources/ecommerce/subsystems/index.rst.txt new file mode 100644 index 000000000..fd4f0d3a3 --- /dev/null +++ b/_sources/ecommerce/subsystems/index.rst.txt @@ -0,0 +1,12 @@ +Subsystems +========== + +.. toctree:: + :maxdepth: 2 + + product + basket + order + discount + payment + reporting diff --git a/_sources/ecommerce/subsystems/order.rst.txt b/_sources/ecommerce/subsystems/order.rst.txt new file mode 100644 index 000000000..0e8690df6 --- /dev/null +++ b/_sources/ecommerce/subsystems/order.rst.txt @@ -0,0 +1,30 @@ +Order Subsystem +=============== + +Orders represent a payment for some kind of product(s), these products will typically be either Programs or Course Runs. An order is marked as unfulfilled initially and then marked as fulfilled once a payment is completed. An order can fail or be refunded. + +Data Model +^^^^^^^^^^ +.. code-block:: python + + class Order(TimestampedModel): + """An order containing information for a purchase.""" + status = models.CharField() + purchaser = models.ForeignKey(settings.AUTH_USER_MODEL) + total_price_paid = models.DecimalField() + + class Line(TimestampedModel): + """A line in an Order.""" + + order = models.ForeignKey(Order) + product_version = models.ForeignKey(ProductVersion) + quantity = models.PositiveIntegerField() + + class Transaction(TimestampedModel): + """A transaction on an order, generally a payment but can also cover refunds""" + order = models.ForeignKey(Order) + amount = models.DecimalField( + decimal_places=5, + max_digits=20, + ) + data = models.JSONField() diff --git a/_sources/ecommerce/subsystems/payment.rst.txt b/_sources/ecommerce/subsystems/payment.rst.txt new file mode 100644 index 000000000..ef6b97a9f --- /dev/null +++ b/_sources/ecommerce/subsystems/payment.rst.txt @@ -0,0 +1,4 @@ +Payment Subsystem +================= + +The payment subsystem takes unfulfilled orders, takes the user through payment completion, and finally marks the order as fulfilled. We historically and for the foreseeable future use CyberSource, but this should be strongly decoupled from the rest of the ecommerce system and made pluggable for future flexibility. This system would also be responsible for any webhooks/callbacks that the payment processor makes to us. diff --git a/_sources/ecommerce/subsystems/product.rst.txt b/_sources/ecommerce/subsystems/product.rst.txt new file mode 100644 index 000000000..5f4b7d157 --- /dev/null +++ b/_sources/ecommerce/subsystems/product.rst.txt @@ -0,0 +1,28 @@ +Product Subsystem +================= + +The product subsystem is responsible for tracking all product-related data. Purchasable products are typically Programs and Course Runs. Pricing information is tracked as immutable data for the sake of historically accurate pricing for orders. + +Data Models +^^^^^^^^^^^ + +.. code-block:: python + + @reversion.register(exclude=("content_type", "object_id", "created_on", "updated_on")) + class Product(TimestampedModel): + """ + Representation of a purchasable product. There is a GenericForeignKey to a Course or Program. + """ + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + +This will utilize `django-reversion` to version product data. + +APIs +^^^^ + +The API that would be primarily needed would be one to read back product data. It is presumed that data entry is done through django-admin: + +- ``GET /api/v0/products/`` -> returns a paginated list of products +- ``GET /api/v0/products/1/`` -> returns a single product diff --git a/_sources/ecommerce/subsystems/reporting.rst.txt b/_sources/ecommerce/subsystems/reporting.rst.txt new file mode 100644 index 000000000..9aa05dc62 --- /dev/null +++ b/_sources/ecommerce/subsystems/reporting.rst.txt @@ -0,0 +1,17 @@ +Reporting Subsystem +=================== + +OL marketing and finance team need to include financial data in their reporting, so they can evaluate and plan campaigns, reconcile accounts, and payout royalties. As a guiding principle, we want to be able to report on any data that is collected by the ecommerce system, but avoid supplementing the data with external considerations (such as marketing costs, or payout rates). + +Use Cases +********* + +MicroMasters +^^^^^^^^^^^^ + +Most of MicroMasters programs and courses function the same as any other MITx course, they wouldn’t otherwise be treated any differently except for marketing purposes, which are out of scope for this document if not MITx Online altogether. + +DEDP +^^^^ + +The DEDP program as it is implemented currently is a special case when it comes to ecommerce - DEDP currently supports financial aid. This essentially amounts to a discount to a lower fixed rate. There are a few tiers of financial aid discounts available, but this is easily handled with a couple of preconfigured discounts. We’d need a UI somewhere that enables staff to assign a discount to a particular learner. That discount would then automatically apply to courses within that program, the same as any other program-scoped discount. diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 000000000..a7fd8f7d3 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,22 @@ +.. MITx Online Documentation documentation master file, created by + sphinx-quickstart on Tue Dec 7 10:41:39 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to MITx Online Documentation's documentation! +===================================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + authentication + configuration/index + ecommerce/index + commands/index + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 000000000..59043d557 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 19rem; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: unset; + max-width: 45rem; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 000000000..4d67807d1 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 000000000..f38abe9a8 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: true, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..a858a410e4faa62ce324d814e4b816fff83a6fb3 GIT binary patch literal 286 zcmV+(0pb3MP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0002xNkl<Zcmb`G zgHi?o6ovOGdxdP*AltSE*&JruJwUGI3!FN?xxO>s`hMrGg#P~ix$^RISR_I47Y|r1 z_CyJOe}D1){SET-^Amu_i71Lt6eYfZjRyw@I6OQAIXXHD<M{a4P!N^sPbQKi=?mBx zoos%BSoiGXjr-;%$QixXMOVNSUNp6L0a1Oz&cgu)wqE?07u5I7qrQIu4Fij)Y3c&0 z@0u_#NH6I?Mk(n;dT}d~^J<WkTLqp|RW-hV56tKpXqu)k@V{?amI+5DOlEU@funz+ kySsbM>fiX^GbOlHe=Ae4>0m)d(f|Me07*qoM6N<$f}vM^LjV8( literal 0 HcmV?d00001 diff --git a/_static/insipid-sidebar-readthedocs.css b/_static/insipid-sidebar-readthedocs.css new file mode 100644 index 000000000..ad79916e6 --- /dev/null +++ b/_static/insipid-sidebar-readthedocs.css @@ -0,0 +1,66 @@ + + +#ethical-ad-placement { + margin-top: auto; + padding-top: 3em; + min-height: 200px; +} + +#readthedocs-embed-flyout, #duplicated-readthedocs-versions { + margin-left: -10px; + margin-right: -10px; +} + +#duplicated-readthedocs-versions { + margin-bottom: -10px; +} + +#duplicated-readthedocs-versions { + position: sticky; + width: auto; +} + +#readthedocs-embed-flyout .rst-versions.rst-badge { + position: static; + max-width: none; +} + + +#readthedocs-embed-flyout .rst-versions.shift-up { + max-height: none; +} + +/* We have duplicated this section, so we don't need the second instance: */ +#readthedocs-embed-flyout .rst-versions.rst-badge .rst-current-version { + display: none; +} + +#duplicated-readthedocs-versions .rst-current-version { + font-size: 0.9rem; +} + +#readthedocs-embed-flyout .rst-versions .rst-other-versions { + display: block; + font-size: 0.9rem; +} + +#flyout-search-form input { + width: 100%; +} + +#sidebar-checkbox:not(:checked) ~ .ethical-fixedfooter { + left: 0; + right: 0; + width: unset; +} + +#sidebar-checkbox:checked ~ .ethical-fixedfooter { + left: var(--sidebar-width); + right: 0; + width: unset; +} + +body:not(.sidebar-resizing) .ethical-fixedfooter { + transition: left 0.3s ease-out; +} + diff --git a/_static/insipid-sidebar.js b/_static/insipid-sidebar.js new file mode 100644 index 000000000..4ec001690 --- /dev/null +++ b/_static/insipid-sidebar.js @@ -0,0 +1,308 @@ +(dom_loaded => { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', dom_loaded); + } else { + dom_loaded(); + } +})(() => { + 'use strict'; + + const sidebar = document.querySelector('.sphinxsidebar'); + const sidebar_tabbable = sidebar.querySelectorAll('input, textarea, select, button, a[href], area[href], iframe'); + const sidebar_button = document.getElementById('sidebar-button'); + const sidebar_checkbox = document.getElementById('sidebar-checkbox'); + const topbar = document.getElementById('topbar'); + const overlay = document.getElementById('overlay'); + const root = document.documentElement; + + sidebar.setAttribute('id', 'sphinxsidebar'); // for aria-controls + + Element.prototype.css = function (name, ...value) { + if (value.length) { + this.style.setProperty(name, ...value); + } else { + return window.getComputedStyle(this).getPropertyValue(name); + } + } + + function updateSidebarAttributesVisible() { + sidebar_button.setAttribute('title', "Collapse sidebar"); + sidebar_button.setAttribute('aria-label', "Collapse sidebar"); + sidebar_button.setAttribute('aria-expanded', true); + sidebar.setAttribute('aria-hidden', false); + sidebar_tabbable.forEach(el => el.setAttribute('tabindex', 0)); + } + + function updateSidebarAttributesHidden() { + sidebar_button.setAttribute('title', "Expand sidebar"); + sidebar_button.setAttribute('aria-label', "Expand sidebar"); + sidebar_button.setAttribute('aria-expanded', false); + sidebar.setAttribute('aria-hidden', true); + sidebar_tabbable.forEach(el => el.setAttribute('tabindex', -1)); + } + + sidebar.setAttribute('tabindex', -1); + + function store(key, value) { + try { + localStorage.setItem(key, value); + } catch (e) { + } + } + + sidebar_checkbox.addEventListener('change', event => { + if (event.target.checked) { + updateSidebarAttributesVisible(); + store('sphinx-sidebar', 'visible'); + document.body.classList.remove('topbar-folded'); + sidebar.focus({preventScroll: true}); + sidebar.blur(); + } else { + updateSidebarAttributesHidden(); + store('sphinx-sidebar', 'hidden'); + if (document.scrollingElement.scrollTop < topbar.offsetHeight) { + document.body.classList.remove('topbar-folded'); + } else { + document.body.classList.add('topbar-folded'); + } + document.scrollingElement.focus({preventScroll: true}); + document.scrollingElement.blur(); + } + }); + + if (sidebar_checkbox.checked) { + updateSidebarAttributesVisible(); + } else { + updateSidebarAttributesHidden(); + } + + function show() { + sidebar_checkbox.checked = true; + sidebar_checkbox.dispatchEvent(new Event('change')); + } + + function hide() { + sidebar_checkbox.checked = false; + sidebar_checkbox.dispatchEvent(new Event('change')); + } + + sidebar_button.addEventListener('keydown', event => { + if (event.code === 'Enter' || event.code === 'Space') { + sidebar_button.click(); + event.preventDefault(); + } + }); + + let touchstart; + + document.addEventListener('touchstart', event => { + if (event.touches.length > 1) { return; } + const touch = event.touches[0]; + if (sidebar_checkbox.checked) { + if (touch.clientX > sidebar.offsetWidth) { + return; + } + } else { + if (touch.clientX > 20) { + return; + } + } + touchstart = { + x: touch.clientX, + y: touch.clientY, + t: Date.now(), + }; + }); + + document.addEventListener('touchend', event => { + if (!touchstart) { return; } + if (event.touches.length > 0 || event.changedTouches.length > 1) { + touchstart = null; + return; + } + const touch = event.changedTouches[0]; + const x = touch.clientX; + const y = touch.clientY; + const x_diff = x - touchstart.x; + const y_diff = y - touchstart.y; + const t_diff = Date.now() - touchstart.t; + if (t_diff < 400 && Math.abs(x_diff) > Math.max(100, Math.abs(y_diff))) { + if (x_diff > 0) { + if (!sidebar_checkbox.checked) { + show(); + } + } else { + if (sidebar_checkbox.checked) { + hide(); + } + } + } + touchstart = null; + }); + + const sidebar_resize_handles = document.querySelectorAll('.sidebar-resize-handle'); + sidebar_resize_handles.forEach(el => { + el.addEventListener('mousedown', event => { + window.addEventListener('mousemove', resize_mouse); + window.addEventListener('mouseup', stop_resize_mouse); + document.body.classList.add('sidebar-resizing'); + event.preventDefault(); // Prevent unwanted text selection while resizing + }); + el.addEventListener('touchstart', event => { + if (event.touches.length > 1) { return; } + window.addEventListener('touchmove', resize_touch); + window.addEventListener('touchend', stop_resize_touch); + document.body.classList.add('sidebar-resizing'); + event.preventDefault(); // Prevent unwanted text selection while resizing + }); + }); + + let ignore_resize = false; + + function resize_base(event) { + if (ignore_resize) { return; } + const window_width = window.innerWidth; + const width = event.clientX; + if (width > window_width) { + root.css('--sidebar-width', window_width + 'px'); + } else if (width > 10) { + root.css('--sidebar-width', width + 'px'); + } else { + ignore_resize = true; + hide(); + } + } + + function resize_mouse(event) { + resize_base(event); + } + + function resize_touch(event) { + if (event.touches.length > 1) { return; } + resize_base(event.touches[0]); + } + + function stop_resize_base() { + if (ignore_resize) { + root.css('--sidebar-width', '19rem'); + ignore_resize = false; + } + store('sphinx-sidebar-width', root.css('--sidebar-width')); + document.body.classList.remove('sidebar-resizing'); + } + + function stop_resize_mouse(event) { + window.removeEventListener('mousemove', resize_mouse); + window.removeEventListener('mouseup', stop_resize_mouse); + stop_resize_base(); + } + + function stop_resize_touch(event) { + if (event.touches.length > 0 || event.changedTouches.length > 1) { + return; + } + window.removeEventListener('touchmove', resize_touch); + window.removeEventListener('touchend', stop_resize_touch); + stop_resize_base(); + } + + window.addEventListener('resize', event => { + const window_width = window.innerWidth; + if (window_width < sidebar.offsetWidth) { + root.css('--sidebar-width', window_width + 'px'); + } + }); + + // This is part of the sidebar code because it only affects the sidebar + if (window.ResizeObserver) { + const resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + let height; + if (entry.borderBoxSize && entry.borderBoxSize.length > 0) { + height = entry.borderBoxSize[0].blockSize; + } else { + height = entry.contentRect.height; + } + root.css('--topbar-height', height + 'px'); + } + }); + resizeObserver.observe(topbar); + } + + let current = []; + let links = []; + + document.querySelectorAll('.sphinxsidebar *').forEach(el => { + let link = el.querySelector(':scope > a[href^="#"]'); + if (link) { + el.classList.add('current-page'); + current.push(el); + links.push(link); + } + }); + const small_screen = window.matchMedia('(max-width: 39rem)'); + + if (current.length === 1 && current[0].childElementCount === 1 && small_screen.matches) { + hide(); + } + const bottom_space = 0; + + if (current.length) { + const top = current[0].getBoundingClientRect().top; + const bottom = current[current.length - 1].getBoundingClientRect().bottom; + if (top < topbar.offsetHeight || bottom > (sidebar.offsetHeight - bottom_space)) { + current[0].scrollIntoView(true); + } + } + + let sections = new Map(); + + const intersection_callback = (entries, observer) => { + entries.forEach(entry => { + let link = sections.get(entry.target); + if (entry.isIntersecting) { + link.classList.add('in-view'); + } else { + link.classList.remove('in-view'); + } + }); + }; + + const intersection_observer = new IntersectionObserver(intersection_callback, { + root: null, + // NB: This uses the initial topbar height, later changes are ignored: + rootMargin: -topbar.offsetHeight + 'px 0px 0px 0px', + threshold: 0.0, + }); + + links.forEach(link => { + let section; + let id = link.hash; + if (id) { + id = id.slice(1); + section = document.getElementById(decodeURI(id)); + // Detect API doc headers: + let single_definition_term = ( + section.nodeName == 'DT' && + section.nextElementSibling.nodeName == 'DD' && + !section.nextElementSibling.nextElementSibling && + section.parentElement.nodeName == 'DL'); + if (single_definition_term) { + // The <dl> contains only a single <dt> + <dd>, + // therefore we can observe the whole <dl>. + section = section.parentElement; + } + } else { + // NB: The first section has no hash, so we don't know its ID: + section = document.querySelector('div.body .section, div.body section'); + } + sections.set(section, link); + intersection_observer.observe(section); + link.addEventListener('click', event => { + if (small_screen.matches) { + hide(); + } + }); + }); +}); + diff --git a/_static/insipid.css b/_static/insipid.css new file mode 100644 index 000000000..7834022d3 --- /dev/null +++ b/_static/insipid.css @@ -0,0 +1,1223 @@ +/* -- variables ------------------------------------------------------------- */ + +:root { + --sidebar-width: 19rem; + --topbar-height: 3rem; +} + +/* -- page layout ----------------------------------------------------------- */ + +body { + position: relative; /* reference for sidebar-resize-handle */ + margin: 0; + font-family: 'Open Sans', sans-serif; + min-width: unset; + color: #000; + background-color: #fff; + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +/* keep the footer at the bottom (on very short pages) */ +html { height: 100%; } +body { min-height: 100%; display: flex; flex-direction: column; } +div.document { flex-grow: 1; } + +div.body { + padding: 0 20px; + min-width: unset; /* body_min_width is applied to <body> */ + overflow-x: auto; + + margin-left: auto; + margin-right: auto; + + line-height: 1.618; +} + +* { + scroll-margin-top: var(--topbar-height); + scroll-snap-margin-top: var(--topbar-height); /* Safari */ +} + +/* -- scrollbars ------------------------------------------------------------ */ + +html::-webkit-scrollbar, div.sphinxsidebar::-webkit-scrollbar { + width: 12px; + background-color: #fff; +} + +html::-webkit-scrollbar-thumb, div.sphinxsidebar::-webkit-scrollbar-thumb { + background-color: #ccc; + border-radius: 6px; +} + +html, div.sphinxsidebar { + scrollbar-color: #ccc transparent; +} + +body { + scrollbar-color: initial; +} + +div.sphinxsidebar::-webkit-scrollbar { + width: 7px; +} + +div.sphinxsidebar::-webkit-scrollbar-thumb { + border-radius: 3.5px; +} + +div.sphinxsidebar { + scrollbar-width: thin; +} + +/* -- body styles ----------------------------------------------------------- */ + +strong { + font-weight: 600; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6, +.toctree-wrapper .caption, +p.rubric { + margin-top: 1.3em; + margin-bottom: 1rem; + font-weight: 600; + line-height: 1.35; + overflow-wrap: break-word; +} + +div.body h5, +div.body h6 { + font-weight: bold; +} + +div.body h1 strong, +div.body h2 strong, +div.body h3 strong, +div.body h4 strong, +div.body h5 strong, +div.body h6 strong { + font-weight: 800; +} + +div.body h1 { font-size: 2em; } +div.body h2 { font-size: 1.7em; } +div.body h3 { font-size: 1.4em; } +div.body h4 { font-size: 1.2em; } +div.body h5 { font-size: 1em; } +div.body h6 { font-size: 0.9em } + +.toctree-wrapper .caption, +p.rubric { + font-size: 1.1em; + font-weight: bold; +} + +a.headerlink { + padding: 0 4px; + font-size: 1rem; + font-weight: normal; +} + +blockquote { + clear: inline-start; + margin: 10px 0; + border: 1px solid #0002; + border-left-width: 7px; + padding: 0 1em; +} + +.section > blockquote, +section > blockquote { + margin: 10px -7px; +} + +blockquote.pull-quote p:not(.attribution), +blockquote.epigraph p:not(.attribution) { + font-size: 130%; + font-style: italic; + color: #666; +} + +blockquote:not(.pull-quote):not(.epigraph) p.attribution { + font-style: italic; +} + +blockquote p.attribution { + text-align: end; +} + +div.body a:target, +strong:target { + padding: 3px; + margin: -3px; +} + +div.body dl > dt:target, +div.body div.admonition dl > dt:target, +div.body .topic dl > dt:target, +div.body .sidebar dl > dt:target, +div.body .sidebar .sidebar-title a:target, +div.body a:target, +strong:target { + background-color: #fbe54e; +} + +.footnote:target { + background-color: unset; +} + +.footnote:target .label { + padding: 3px; + margin: -3px; + background-color: #fbe54e; +} + +.citation:target .label { + padding: 3px; + margin-left: -3px; + margin-top: -3px; + margin-bottom: -3px; + margin-right: 5px; + background-color: #fbe54e; +} + +.citation:target .backrefs { + margin-left: -8px; +} + +hr.docutils { + border: 0; + border-top: solid 1px #ccc; +} + +.section > hr.docutils, +section > hr.docutils { + margin-left: -7px; + margin-right: -7px; +} + +.compound:not(.toctree-wrapper) { + margin-top: 1em; + margin-bottom: 1em; +} + +.compound:not(.toctree-wrapper) > * { + margin-top: 0.2em; + margin-bottom: 0.2em; +} + +.compound:not(.toctree-wrapper) > *:first-child { + margin-top: 0; + margin-bottom: 0.2em; +} + +.compound:not(.toctree-wrapper) > *:last-child { + margin-top: 0.2em; + margin-bottom: 0; +} + +section, .section { + clear: inline-start; +} + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #1863b5; + text-decoration: none; + overflow-wrap: break-word; +} + +a:hover { + text-decoration: underline; +} + +a:visited { + color: #004188; +} + +a.external { + text-decoration: underline; +} + +a.external:hover { + text-decoration: none; +} + +a.external:visited { + text-decoration: none; +} + +/* -- code formatting ------------------------------------------------------- */ + +code, pre, kbd, samp { + font-family: 'DejaVu Sans Mono', Menlo, monospace; +} + +pre { + padding: 7px; + line-height: normal; + font-size: 0.875em; +} + +.section > div > div.highlight, +.section > .compound > div > div.highlight, +.section > .literal-block-wrapper > div > div.highlight, +.section > pre.literal-block, +.section > .compound > pre.literal-block, +section > div > div.highlight, +section > .compound > div > div.highlight, +section > .literal-block-wrapper > div > div.highlight, +section > pre.literal-block, +section > .compound > pre.literal-block { + margin-left: -7px; + margin-right: -7px; +} + +code, +code.xref, +a code, +div.body div.admonition dl > dt code, +div.body .topic dl > dt code, +div.body .sidebar dl > dt code, +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code, +div.code-block-caption code { + background-color: rgba(27,31,35,.05); + padding: 0.1em 0.2em; + font-weight: normal; +} + +div.body dl > dt code { + background-color: unset; + padding: unset; +} + +code.xref, +a code { + font-weight: unset; +} + +code.descname { + font-size: unset; +} + +.sig-name { + font-size: unset; +} + +.sig-paren, +.optional { + line-height: 0; +} + +div.code-block-caption { + text-align: center; + font-size: unset; +} + +div.literal-block-wrapper { + clear: both; +} + +td.linenos .linenodiv pre { + color: #666; + background-color: transparent; + padding: 7px 0px; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +span.linenos { + margin-right: 0.7em; +} + +/* highlighted line (:emphasize-lines:) */ +.highlight .hll { + padding: 0 0.5em; + margin: 0 -0.5em; +} + +kbd.docutils:not(.compound) { + padding: 0.15em; + border-radius: 3px; + border: 1px solid #333; +} + +span.pre { + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.topic, aside.topic, +div.sidebar, aside.sidebar, +nav.contents { + border: none; + padding: 7px; +} + +.section > div.admonition, +section > div.admonition, +div.body > div.admonition, +.section > .topic, +section > .topic, +div.body > .topic, +.section > nav.contents, +section > nav.contents, +div.body > nav.contents { + margin: 10px -7px; +} + +div.sidebar, +aside.sidebar { + margin-right: -7px; +} + +div.admonition > p.admonition-title, +.topic > p.topic-title, +nav.contents > p.topic-title, +.sidebar > p.sidebar-title { + margin: -7px -7px 7px -7px; + padding: 4px 7px; + font-weight: normal; + font-size: unset; + color: #333; +} + +.first.admonition-title, +.first.topic-title, +.first.sidebar-title { + /* override basic.css, which uses !important */ + margin-top: -7px !important; +} + +div.admonition > p.admonition-title + *, +.topic > p.topic-title + *, +nav.contents > p.topic-title + *, +.sidebar > p.sidebar-title + * { + margin-top: 0; +} + +div.admonition > :first-child:not(.admonition-title), +div.topic > :first-child:not(.topic-title), +aside.topic > :first-child:not(.topic-title), +nav.contents > :first-child:not(.topic-title) { + margin-top: 0; +} + +nav.contents > :last-child { + margin-bottom: 0; +} + +div.admonition { + background-color: #e4ebf2; +} + +div.admonition > p.admonition-title { + background-color: #cadcea; +} + +div.admonition.attention, +div.admonition.caution, +div.admonition.danger, +div.admonition.error, +div.admonition.warning { + background-color: #fbe7d4; +} + +div.admonition.attention > p.admonition-title, +div.admonition.caution > p.admonition-title, +div.admonition.danger > p.admonition-title, +div.admonition.error > p.admonition-title, +div.admonition.warning > p.admonition-title { + background-color: #f0d5b8; +} + +div.topic, nav.contents, aside.topic { + background-color: #fff0eb; +} + +.topic > p.topic-title, +nav.contents > p.topic-title { + background-color: #f0dbd4; +} + +div.sidebar, +aside.sidebar, +div.admonition.seealso { + background-color: #f4f4f4; +} + +.sidebar > p.sidebar-title, +div.admonition.seealso > p.admonition-title { + background-color: #e4e4e4; +} + +div.admonition.seealso { + clear: both; +} + +p.sidebar-subtitle { + font-style: italic; +} + +/* More specific selectors to override previous defintions */ +div.body div.admonition dl > dt, +div.body .topic dl > dt, +div.body .sidebar dl > dt { + padding: unset; + background-color: unset; +} + +div.body div.admonition dl:not(.glossary) > dt, +div.body .topic dl:not(.glossary) > dt, +div.body .sidebar dl:not(.glossary) > dt { + font-weight: unset; +} + +/* -- tables and lists ------------------------------------------------------ */ + +table.docutils { + overflow-x: auto; + display: block; + border-collapse: separate; + border-spacing: 4px 4px; + margin-top: 1em; + margin-bottom: 1em; +} + +table.docutils th { + font-weight: normal; + background-color: rgba(27,31,35,.05); + border-bottom: 2px solid #333; +} + +table.docutils td { + overflow-wrap: break-word; +} + + + +ol, ul { + padding-left: 1.2em; +} + +ol.simple p, ul.simple p { + margin-bottom: 0.3em; +} + +.toctree-wrapper ul, +.contents ul { + list-style: none; + margin-top: 0.5em; + padding-left: 2em; +} + +.toctree-wrapper li:not(:first-child), +.contents li:not(:first-child) { + margin-top: 0.5em; +} + +.toctree-wrapper > ul, +.contents > ul { + padding-left: unset; +} + +dl.citation > dt { + font-weight: 600; +} + +div.body dl { + margin: 1em 0; +} + +div.body dl > dt, +dl.field-list > dt { + display: table; +} + +div.body dt:not(.label), +dl.field-list > dt { + margin-top: 1em; + padding: 1px 7px; + background-color: #f5f5f5; +} + +.section > dl > dt:not(.label), +.section > dl.field-list > dt, +section > dl > dt:not(.label), +section > dl.field-list > dt { + margin: 1em -7px 0; +} + +div.body dl > dt:first-child { + margin-top: 0; +} + +div.body dl > dt.label:target, +div.body div.admonition dl > dt:target, +div.body .topic dl > dt:target, +div.body .sidebar dl > dt:target { + padding: 3px 7px; + margin-top: -3px; + margin-bottom: -3px; + margin-left: -7px; +} + +dl.glossary > dt { + font-size: unset; + font-weight: 600; +} + +dl.glossary > dt strong { + font-weight: bold; +} + +dl.field-list { + display: unset; +} + +dl.field-list > dt { + font-weight: unset; +} + +dl.field-list > dt::after { + content: unset; +} + +dd, +dl.field-list > dd { + padding-left: 0; + margin-top: 3px; + margin-left: 2em; +} + +dd:not(:last-child), +dl.field-list > dd:not(:last-child) { + margin-bottom: 1em; +} + +div.body dl.class > dt:not(:target), +div.body dl.type > dt:not(:target), +div.body dl.concept > dt:not(:target), +div.body dl.enum > dt:not(:target), +div.body dl.enum-class > dt:not(:target), +div.body dl.union > dt:not(:target) { + background-color: #e4ebf2; +} + +div.body dl.exception > dt:not(:target) { + background-color: #fbe7d4; +} + +/* -- relbar ---------------------------------------------------------------- */ + +.relbar { + padding: 10px 10px 0; +} + +.relbar > a { + box-sizing: border-box; + width: 50%; + padding: 10px; + display: flex; + align-items: center; + color: inherit; + text-decoration: none; + user-select: none; +} + +.relbar > a.previous { + float: left; +} + +.relbar > a.next { + float: right; + text-align: right; +} + +.relbar .icon { + color: #888; +} + +.relbar .previous .icon { + margin-right: 1em; +} + +.relbar .next .icon { + margin-left: 1em; +} + +.relbar .title { + position: relative; + flex: 1; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + font-size: 1.4em; + font-weight: 200; +} + +.relbar .title .text { + line-height: 2.1em; +} + +.relbar .title .text .direction { + position: absolute; + right: 0; + left: 0; + top: 0; + font-size: 60%; + line-height: 0.5em; +} + +.relbar .pre { + white-space: pre; +} + +/* -- breadcrumbs ----------------------------------------------------------- */ + +nav.crumbs { + color: #888; + font-size: 90%; +} + +nav.crumbs ul { + padding: 0; + list-style: none; +} + +nav.crumbs ul li { + display: inline; +} + +nav.crumbs a:visited { + color: #1565c0; +} + +/* -- footer ---------------------------------------------------------------- */ + +footer { + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; + color: #333; +} + +footer, div.articleComments { + border-top: #ccc solid 1px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebar { + position: fixed; + width: var(--sidebar-width); + left: 0; + top: 0; + bottom: 0; + overflow-y: auto; + float: unset; + margin-left: unset; /* override basic.css */ + padding-top: var(--topbar-height); + z-index: 390; + color: #333; + background-color: #f5f5f5; + overscroll-behavior: contain; +} + +#sidebar-checkbox:checked ~ div.document, +#sidebar-checkbox:checked ~ .relbar, +#sidebar-checkbox:checked ~ .articleComments, +#sidebar-checkbox:checked ~ footer { + margin-left: var(--sidebar-width); +} + +body:not(.sidebar-resizing) div.document, +body:not(.sidebar-resizing) .relbar, +body:not(.sidebar-resizing) .articleComments, +body:not(.sidebar-resizing) footer { + transition: margin-left 0.3s ease-out; +} + +body:not(.sidebar-resizing) .sphinxsidebar { + transition: left 0.3s ease-out; +} + +div.sphinxsidebarwrapper { + position: relative; /* reference for sidebar-resize-handle */ + box-sizing: border-box; + padding: 10px; + display: flex; + flex-direction: column; + min-height: 100%; +} + +div.sphinxsidebarwrapper > :first-child { + margin-top: 0; +} + +div.sphinxsidebarwrapper > :last-child { + margin-bottom: 0; +} + +.sidebar-resize-handle { + display: none; + position: absolute; + width: 0; + top: 0; + bottom: 0; +} + +body.js #sidebar-checkbox:checked ~ .sidebar-resize-handle, +body.js #sidebar-checkbox:checked ~ div.document .sidebar-resize-handle { + display: block; + cursor: col-resize; + width: 10px; +} + +.sphinxsidebar .sidebar-resize-handle { + right: 0; +} + +body.js #sidebar-checkbox:checked ~ .sidebar-resize-handle { + z-index: 390; + left: var(--sidebar-width); +} + +#sidebar-checkbox:not(:checked) ~ div.document .sphinxsidebar { + left: calc(0px - var(--sidebar-width)); +} + +div.sphinxsidebar h3 { + font-size: 1.4em; + font-weight: normal; + margin-top: 10px; + margin-bottom: 5px; +} + +div.sphinxsidebar h4 { + font-size: 1.3em; + font-weight: normal; + margin-top: 10px; + margin-bottom: 5px; +} + +div.sphinxsidebar p.caption { + text-transform: uppercase; + margin-top: 10px; + margin-bottom: 5px; +} + +div.sphinxsidebar p.caption + ul { + margin-top: 0; +} + +div.sphinxsidebar p.topless { + margin: 10px; +} + +div.sphinxsidebar p.topless { + margin-top: 0; +} + +div.sphinxsidebar ul { + padding-left: 0; + margin-left: 10px; +} + +div.sphinxsidebar h3 + ul, +div.sphinxsidebar h4 + ul { + margin-top: -5px; +} + +div.sphinxsidebar li { + padding-top: 0.4em; + padding-bottom: 0.4em; +} + +div.sphinxsidebar li li:first-child { + margin-top: 0.4em; +} + +div.sphinxsidebar li li:last-child { + margin-bottom: -0.4em; +} + +div.sphinxsidebar ul ul { + list-style: unset; +} + +.sphinxsidebar a { + color: #004188; +} + +div.sphinxsidebar a.in-view { + font-weight: 600; + letter-spacing: -0.013em; +} + +div.sphinxsidebar code { + background-color: unset; + padding: 0; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4, +div.sphinxsidebar p, +div.sphinxsidebar li { + margin-right: -10px; + padding-right: 10px; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4, +div.sphinxsidebar p, +div.sphinxsidebar li { + margin-left: -20px; + padding-left: 20px; +} + +div.sphinxsidebar li.toctree-l2 { + margin-left: -40px; + padding-left: 40px; +} + +div.sphinxsidebar li.toctree-l3 { + margin-left: -60px; + padding-left: 60px; +} + +div.sphinxsidebar li.toctree-l4 { + margin-left: -80px; + padding-left: 80px; +} + +div.sphinxsidebar li.toctree-l5 { + margin-left: -100px; + padding-left: 100px; +} + +div.sphinxsidebar li.toctree-l6 { + margin-left: -120px; + padding-left: 120px; +} + +div.sphinxsidebar li.toctree-l7 { + margin-left: -140px; + padding-left: 140px; +} + +div.sphinxsidebar li.toctree-l8 { + margin-left: -160px; + padding-left: 160px; +} + +div.sphinxsidebar li.toctree-l9 { + margin-left: -180px; + padding-left: 180px; +} + +div.sphinxsidebar li.toctree-l10 { + margin-left: -200px; + padding-left: 200px; +} + +body:not(.js) div.sphinxsidebar li.current, +div.sphinxsidebar .current-page:not(.logo) { + background-color: #fff; +} + +div.sphinxsidebar .current-page li:not(.current-page) { + background-color: #f5f5f5; +} + +div.sphinxsidebar .current-page { + transition: background-color 0.3s ease-out; +} + +div.sphinxsidebar input { + border-color: #ccc; + font-family: unset; +} + +/* -- topbar ---------------------------------------------------------------- */ + +#topbar-placeholder { + z-index: 500; + position: -webkit-sticky; + position: sticky; + top: 0; +} + +#topbar { + border-bottom: #ccc solid 1px; + background-color: #fff; +} + +#topbar, +#titlebar a, +#titlebar a:visited { + color: #888; +} + +.relbar, +.relbar > a:hover .direction { + color: #ccc; +} + +#topbar button:hover, +#titlebar .buttons > *:hover, +#titlebar a:hover, +.relbar a:hover, +.relbar a:hover .icon { + color: #333; +} + +#titlebar { + display: flex; + align-items: center; + flex-wrap: wrap; + min-height: 3rem; +} + +#titlebar .buttons { + display: flex; + align-items: baseline; + margin: 0 5px; +} + +#titlebar .buttons > *, +#topbar button { + margin: 0; + border: none; + padding: 8px; + color: inherit; + background: none; + font: inherit; + cursor: pointer; +} + +#titlebar .title button { + padding: 0; +} + +#titlebar svg, +.insipid-icon svg, +.relbar svg, +.related svg, +.crumbs svg { + width: 1em; + height: 1em; + vertical-align: middle; + fill: currentColor; +} + +#topbar a { + text-decoration: none; +} + +body.js.topbar-folded #topbar-placeholder:not(.fake-hover) #topbar { + transform: translateY(-100%); +} + +body.js #topbar { + transition: transform 0.3s ease-out; +} + +body:not(.js) #search-button { + display: none; +} + +#titlebar .title { + font-weight: 200; + font-size: 1.7rem; + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#titlebar .top, +body.scrolled #titlebar .parent { + display: none; +} + +body.scrolled #titlebar .top { + display: inline-block; +} + +/* -- search field in topbar ------------------------------------------------ */ + +#topbar #searchbox form { + display: flex; + align-items: stretch; + margin: 5px; +} + +#topbar #searchbox input { + flex-grow: 1; + border: 1px solid #ccc; + padding: 7px; + color: #333; +} + +#topbar #searchbox p.highlight-link { + text-align: center; + margin: 8px; +} + +#searchbox p.highlight-link a { + padding: 0 4px; + background-color: #fbe54e; +} + + + +/* -- navigation icons ------------------------------------------------------ */ + +/* cover nav-icon */ +div.body, .relbar, footer, div.articleComments { + position: relative; + background-color: #fff; +} + +.nav-icon svg { + position: absolute; + top: 50%; + left: 50%; + margin: -1.25em; + width: 2.5em; + height: 2.5em; + fill: currentColor; +} + +.nav-icon { + position: fixed; + right: 0; + margin: -2em 1em; + width: 4em; + height: 4em; + color: #f5f5f5; +} + +.nav-icon:visited { + color: #f5f5f5; +} + +.nav-icon:hover { + color: #fff; + background-color: #f5f5f5; +} + +.nav-icon.previous { + bottom: 50vh; + left: 0; + right: unset; +} + +.nav-icon.next { + top: 50vh; +} +#sidebar-checkbox:checked ~ nav .nav-icon.previous { + left: var(--sidebar-width); +} + +body:not(.sidebar-resizing) .nav-icon.previous { + transition: left 0.3s ease-out; +} + +/* -- sphinx.ext.viewcode --------------------------------------------------- */ + +.viewcode-link { + font-size: 90%; + margin-left: 1em; +} + +div.viewcode-block:target { + margin: -0.5em; + padding: 0.5em; + background-color: #ffffcc; +} + +/* -- media queries --------------------------------------------------------- */ +@media only screen and (max-width: 39rem) { + #sidebar-checkbox:checked ~ div.document, + #sidebar-checkbox:checked ~ .relbar, + #sidebar-checkbox:checked ~ .articleComments, + #sidebar-checkbox:checked ~ footer { + margin-left: 0; + } + + #sidebar-checkbox:checked ~ #topbar-placeholder { + margin-left: 0; + } + + #overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + visibility: hidden; + opacity: 0; + background-color: rgba(100%, 100%, 100%, 80%); + transition: opacity 0.3s ease-out, visibility 0s 0.3s ease-out; + z-index: 380; + } + + #sidebar-checkbox:checked ~ #overlay { + visibility: visible; + opacity: 1; + transition: opacity 0.3s ease-out; + } + + #sidebar-checkbox:checked ~ #topbar-placeholder #titlebar .parent { + display: unset; + } + + #sidebar-checkbox:checked ~ #topbar-placeholder #titlebar #titlebar .top { + display: none; + } +} + +body:not(.js) #fullscreen-button { + display: none; +} + +body.js #fullscreen-button .disable { + display: none; +} + +@media all and (display-mode: fullscreen) { + body.js #fullscreen-button .enable { + display: none; + } + + body.js #fullscreen-button .disable { + display: unset; + } +} + +/* -- fix Firefox buttons --------------------------------------------------- */ + +button::-moz-focus-inner { + border-style: none; + padding: 0; +} + +button:-moz-focusring { + outline: 1px dotted; +} \ No newline at end of file diff --git a/_static/insipid.js b/_static/insipid.js new file mode 100644 index 000000000..0b71b9415 --- /dev/null +++ b/_static/insipid.js @@ -0,0 +1,149 @@ +(dom_loaded => { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', dom_loaded); + } else { + dom_loaded(); + } +})(() => { + 'use strict'; + + // make sure all scripts are re-executed when navigating to cached page + window.onunload = () => {}; + + const topbar = document.getElementById('topbar'); + const topbar_placeholder = document.getElementById('topbar-placeholder'); + + const threshold = 10; + + // auto-hide topbar + function scroll_callback(scroller) { + let ignore_scroll = true; + let initial; + let scroll_timeout; + return event => { + window.clearTimeout(scroll_timeout); + const current = scroller.scrollTop; + if (current <= topbar.offsetHeight || (scroller.scrollHeight - current - scroller.clientHeight) < (scroller.clientHeight / 3)) { + document.body.classList.remove('topbar-folded'); + ignore_scroll = true; + return; + } else if (ignore_scroll) { + // We ignore single jumps + ignore_scroll = false; + initial = current; + } else if (current - initial > threshold) { + document.body.classList.add('topbar-folded'); + ignore_scroll = true; + return; + } else if (current - initial < -threshold) { + document.body.classList.remove('topbar-folded'); + ignore_scroll = true; + return; + } + scroll_timeout = setTimeout(() => { ignore_scroll = true; }, 66); + }; + } + + document.addEventListener('scroll', scroll_callback(document.scrollingElement)); + + const sidebar_scroller = document.querySelector('.sphinxsidebar'); + if (sidebar_scroller) { + sidebar_scroller.addEventListener('scroll', scroll_callback(sidebar_scroller)); + } + + const div_body = document.querySelector('div.body'); + const first_section = document.querySelector('div.body .section, div.body section'); + if (first_section) { + document.addEventListener('scroll', event => { + if (window.pageYOffset >= div_body.offsetTop + first_section.offsetTop) { + document.body.classList.add('scrolled'); + } else { + document.body.classList.remove('scrolled'); + } + }); + document.dispatchEvent(new Event('scroll')); + } + + topbar.querySelector('.top').addEventListener('click', event => { + window.scroll({ top: 0, behavior: 'smooth' }); + event.preventDefault(); + }); + + const search_button = document.getElementById('search-button'); + if (search_button) { + const search_form = document.getElementById('search-form'); + const search_field = search_form.querySelector('input'); + + function show_search() { + try { + // https://readthedocs-sphinx-search.readthedocs.io/ + showSearchModal(); + return; + } catch(e) {} + search_form.style.display = 'flex'; + search_button.setAttribute('aria-expanded', 'true'); + search_field.focus(); + document.body.classList.remove('topbar-folded'); + } + + function hide_search() { + search_form.style.display = 'none'; + search_button.setAttribute('aria-expanded', 'false'); + search_button.blur(); + } + + function toggle_search() { + if (window.getComputedStyle(search_form).display === 'none') { + show_search(); + } else { + hide_search(); + } + } + + search_button.addEventListener('click', toggle_search); + if (Documentation.focusSearchBar) { + // Monkey-patch function provided by Sphinx: + Documentation.focusSearchBar = show_search; + } + + search_field.addEventListener('keydown', event => { + if (event.code === 'Escape') { + hide_search(); + search_field.blur(); + } + }); + } + + const fullscreen_button = document.getElementById('fullscreen-button'); + if (document.fullscreenEnabled) { + fullscreen_button.addEventListener('click', event => { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); + } else { + document.exitFullscreen(); + } + fullscreen_button.blur(); + topbar_placeholder.classList.remove('fake-hover'); + }); + } else { + fullscreen_button.remove(); + } + + topbar_placeholder.addEventListener('mouseenter', event => { + topbar_placeholder.classList.add('fake-hover'); + }); + + topbar_placeholder.addEventListener('mouseleave', event => { + topbar_placeholder.classList.remove('fake-hover'); + }); + + document.addEventListener('touchend', event => { + if (event.touches.length > 1) { return; } + const touch = event.touches[0]; + if (touch.clientY < topbar.offsetHeight) { + topbar_placeholder.classList.add('fake-hover'); + } else { + topbar_placeholder.classList.remove('fake-hover'); + } + }); +}); diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 000000000..367b8ed81 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,199 @@ +/* + * language_data.js + * ~~~~~~~~~~~~~~~~ + * + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000000000000000000000000000000000000..d96755fdaf8bb2214971e0db9c1fd3077d7c419d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu=nj kDsEF_5m^0CR;1wuP-*O&G^0G}KYk!hp00i_>zopr08q^qX#fBK literal 0 HcmV?d00001 diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..7107cec93a979b9a5f64843235a16651d563ce2d GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^+#t*WBp7;*Yy1LIik>cxAr*|t7R?Mi>2?kWtu>-2 m3q%Vub%g%s<8sJhVPMczOq}xhg9DJoz~JfX=d#Wzp$Pyb1r*Kz literal 0 HcmV?d00001 diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 000000000..8054382b5 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: #666666; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: #666666; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f0f0f0; } +.highlight .c { color: #60a0b0; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #40a070 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #40a070 } /* Literal.Number.Bin */ +.highlight .mf { color: #40a070 } /* Literal.Number.Float */ +.highlight .mh { color: #40a070 } /* Literal.Number.Hex */ +.highlight .mi { color: #40a070 } /* Literal.Number.Integer */ +.highlight .mo { color: #40a070 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 000000000..92da3f8b2 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,619 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlinks", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 000000000..8a96c69a1 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '<p class="highlight-link">' + + '<a href="javascript:SphinxHighlight.hideSearchWords()">' + + _("Hide Search Matches") + + "</a></p>" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/authentication.html b/authentication.html new file mode 100644 index 000000000..2750c2873 --- /dev/null +++ b/authentication.html @@ -0,0 +1,217 @@ +<!DOCTYPE html> + +<html lang="en" data-content_root="./"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" /> + + <title>Authentication — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Authentication§

+
+ sequenceDiagram + participant MO as MITx Online + participant OE as Open edX + + par Create Account in Open edx + MO->>OE: POST /user_api/v1/account/registration/ + OE-->>MO: Success + end + + par Create Open edX Access Token + Note right of MO: Create in-memory requests session + par Establish an Open edX session + MO->>OE: GET /auth/login/mitxpro-oauth2/?auth_entry=login + OE->>MO: Redirect to GET /oauth2/authorize + MO->>OE: Redirect to GET /auth/complete/mitxpro-oauth2/ + end + + par Link MITx Online account to Open edX Account + MO->>OE: GET /oauth2/authorize + OE-->>MO: Redirect to GET /login/_private/complete + MO->>OE: POST /oauth2/access_token + OE-->>MO: OAuth access and refresh tokens + end + + end + +
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/check_program_requirements.html b/commands/check_program_requirements.html new file mode 100644 index 000000000..74d2d23f5 --- /dev/null +++ b/commands/check_program_requirements.html @@ -0,0 +1,217 @@ + + + + + + + + check_program_requirements — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

check_program_requirements§

+

Checks programs for a valid requirements tree. A program has a valid requirements tree if it:

+
    +
  1. Exists

  2. +
  3. Has nodes for all courses assigned to the program

  4. +
+

If the tree does not fit this criteria, an error message is printed to the screen. (This uses the check_program_for_orphans API call, but it suppresses its error logging so it won’t clog up the error log.)

+

By default, this will check all programs in the system. Specify individual programs to check with --program (multiple times if needed) or check only live programs with --live. Note that if you specify both of these together they will be combined: if you specify a check for a specific program that isn’t live and then also specify --live, it won’t return anything for that program.

+
+

Syntax§

+

check_program_requirements [--program <readable or numeric id>] [--live]

+
+
+

Options§

+
    +
  • --program <readable or numeric id> - Check this specific program. Can be either the readable ID of the program (e.g. program-v1:MITx+DEDP) or the numeric ID, and can be specified multiple times to check multiple programs.

  • +
  • --live - Check only live programs.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/configure_instance.html b/commands/configure_instance.html new file mode 100644 index 000000000..60e9f3a1a --- /dev/null +++ b/commands/configure_instance.html @@ -0,0 +1,218 @@ + + + + + + + + configure_instance — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

configure_instance§

+

Configures a fresh MITx Online instance. For more information, see MITx Online Quick Start and Local Open edX Tutor and MITx Online Deployment.

+

For Tutor deployments, this will use local.edly.io for URLs for the edX platform. If you’re running a dev deployment, or are using Tutor Nightly, --tutor-dev will additionally add the proper ports (as Caddy is disabled in these cases). In either case, the two demo courses will still be created but only the Demonstration Course (course-v1:edX+DemoX+Demo_Course) will exist in edX, and then only if you import the demo course using the relevant Tutor command.

+
+

Syntax§

+

configure_instance <platform> [--dont-enroll|-D] [--dont-create-superuser|-S] [--edx-oauth-client <client id>] [--edx-oauth-secret <client secret>] [--gateway <ip>] [--tutor|-T] [--tutor-dev]

+
+
+

Options§

+
    +
  • <platform> - One of macos, linux, or none. Specifying none will additionaly stop creation of the OAuth2 application record for edX. Defaults to none.

  • +
  • --dont-enroll|-D - Don’t enroll the test learner account in any courses. (Defaults to enrolling the account in course-v1:edX+DemoX+Demo_Course.)

  • +
  • --dont-create-superuser|-S - Don’t create a superuser account.

  • +
  • --gateway <ip> - The Docker gateway IP. Required on Linux. See Configure Open edX for more info.

  • +
  • --edx-oauth-client <client id> - Use the specified client ID for the edX OAuth2 client. (Useful if you’re redoing your MITx Online instance and you’ve already created the corresponding record in edX, since you’re not allowed to edit it there.)

  • +
  • --edx-oauth-secret <client secret> - Use the specified client secret for the edX OAuth2 client. (Useful if you’re redoing your MITx Online instance and you’ve already created the corresponding record in edX, since you’re not allowed to edit it there.)

  • +
  • --tutor|-T - Configure the instance for use with a Tutor edX deployment.

  • +
  • --tutor-dev - Configure the instnace for use with Tutor dev or nightly.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/configure_tiers.html b/commands/configure_tiers.html new file mode 100644 index 000000000..18b868099 --- /dev/null +++ b/commands/configure_tiers.html @@ -0,0 +1,313 @@ + + + + + + + + configure_tiers — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

configure_tiers§

+

Creates financial assistance tiers and discounts for a course or program.

+

This operates in two modes: creating tiers for a program and creating tiers for a course.

+

In the tables below, <year> represents the current year.

+

Configuring tiers for a course

+

The command will use the readable ID of the course as part of the financial aid discounts. They will default to this:

+ + + + + + + + + + + + + + + + + + + + + + + + + +

Code

Type

Amount

<course id>-fa-tier1-<year>

percent-off

.75

<course id>-fa-tier2-<year>

percent-off

.50

<course id>-fa-tier3-<year>

percent-off

.25

<course id>-fa-tier4-<year>

percent-off

0

+

Note that configuring course tiers requires the course to exist. Use create_courseware (or any of the other methods) to create the course before you run this command.

+

Configuring tiers for a program

+

The default discounts will be:

+ + + + + + + + + + + + + + + + + + + + + + + + + +

Code

Type

Amount

DEDP-fa-tier1-<year>

dollars-off

750

DEDP-fa-tier2-<year>

dollars-off

650

DEDP-fa-tier3-<year>

dollars-off

500

DEDP-fa-tier4-<year>

percent-off

0

+

Specify changes using --program and/or --program-abbrev.

+

Tiers

+

The actual tiers that will be created are:

+ + + + + + + + + + + + + + + + + + + + +

Threshold

Discount

$0

<abbrev>-fa-tier1-<year>

$25,000

<abbrev>-fa-tier2-<year>

$50,000

<abbrev>-fa-tier3-<year>

$75,000

<abbrev>-fa-tier4-<year>

+

These can be overridden by providing a CSV file. The CSV file should have the following fields and should not have a header row:

+
threshold amount,discount type,discount amount
+
+
+

If you specify tier information, you must provide all the tiers you want to create - the specified information will override the default. In addition, you must supply a zero income tier. This is a requirement and the command will quit if you don’t have one set up, as that tier is used as the starting point for financial assistance. (In other words, learners will see errors if there’s not a zero-income threshold tier set up.)

+

Reuse

+

The command will try to reuse any discounts and tiers that match ones the command would have created, so you can safely run this for a course or program that may have already had financial assistance tiers set up.

+
+

Syntax§

+

Configuring tiers for a program: +configure_tiers [--program <readable id>] [--program-abbrev <program abbreviation>] [--tier-info <tier info CSV>]

+

Configuring tiers for a course: +configure_tiers [--course <readable id>] [--tier-info <tier info CSV>]

+
+
+

Options§

+

Program options:

+
    +
  • --program <readable id> - Program ID to use or create.

  • +
  • --program-abbrev <abbreviation> - Abbreviation to use for tiers and discounts.

  • +
+

Course options:

+
    +
  • --course <readable id> - Course ID to use. This won’t create a course; use create_courseware for that.

  • +
+

Common options:

+
    +
  • --tier-info <csv file> - Tier info in CSV format.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/create_courseware.html b/commands/create_courseware.html new file mode 100644 index 000000000..de4770a52 --- /dev/null +++ b/commands/create_courseware.html @@ -0,0 +1,251 @@ + + + + + + + + create_courseware — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

create_courseware§

+

Creates a new courseware object.

+

For programs, this creates the basic program record. +For courses, this creates the course, and then optionally adds it to the specified program (and can add it as an elective or required course). It will also optionally create an initial course run for the course. Finally, it will also add the course to the program’s requirements or electives list. +For courseruns, this creates the course run and associates it with the specified course.

+

This will not run sync_course_run for you, so for best results, ensure the course run is set up on the edX side, use this command, then run sync_course_run to pull dates and other information from edX.

+
+

Syntax§

+

create_courseware <object> <readable id> <title> [--live] [--self-paced] [--create-run [create_run]] [--run-url [RUN_URL]] [--program [PROGRAM]] [--run-tag [run-tag]] [--required] [--elective] [--force] [--start <date>] [--end <date>] [--enrollment-start <date>] [--enrollment-end <date>] [--upgrade <date>] [--dept <department_name>] [--create-depts]

+
+
+

Checks§

+

The command performs the following checks: +* It checks to see if readable_id contains course or program at the front - if it doesn’t, it will assume you’ve swapped the title and readable ID mistakenly and stop. +* It checks to see if the course will be live before adding it to the requirements tree. If --live isn’t specified, it will ignore your request. This only applies to course creation. +* If creating a course or program, --depts must be specified and the department names must exist.

+

Both of these checks can be overridden with the --force flag.

+
+
+

Options§

+
    +
  • object - One of program, course, or courserun

  • +
  • readable id - The readable ID of the object. Note: do not specify the run tag for course runs.

  • +
  • title - The title of the object.

  • +
  • --live - Makes the object live (default is not).

  • +
  • --force|-f - Force the creation of the object. (See “Checks” section for details.)

  • +
  • --create-depts - If specified, any departments specified that do not currently exist will be created.

  • +
+

Programs can take the following options: +* --depts - The departments to associate the program with.

+

Courses can take the following options:

+
    +
  • --program <PROGRAM> - The program to assign the course to.

  • +
  • --create-run <run tag> - Create a course run for this course with the specified run tag.

  • +
  • --run-url <url> - The courseware URL for the course run. (Only if --create-run is specified.)

  • +
  • --self-paced - The course run is self-paced. (Only if --create-run is specified.)

  • +
  • --required - The course is a requirement for the program.

  • +
  • --elective - The course is an elective for the program.

  • +
  • --depts - The departments to associate the course with.

  • +
+

Course runs can take the following options:

+
    +
  • --program <PROGRAM> - The program to assign the course to. Required.

  • +
  • --run-tag <run tag> - The run tag to use. Required.

  • +
  • --run-url <url> - The courseware URL for the course run.

  • +
  • --self-paced - The course run is self-paced.

  • +
  • --start <date> - The date the course run should start.

  • +
  • --end <date> - The date the course run should end.

  • +
  • --enrollment-start <date> - The date the course run enrollment should start.

  • +
  • --enrollment-end <date> - The date the course run enrollment should end.

  • +
  • --upgrade <date> - The date after which course run enrollments should not be possible.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/create_courseware_page.html b/commands/create_courseware_page.html new file mode 100644 index 000000000..a6db06936 --- /dev/null +++ b/commands/create_courseware_page.html @@ -0,0 +1,212 @@ + + + + + + + + create_courseware_page — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

create_courseware_page§

+

Creates a very basic About page in the CMS for the given courseware object.

+

The about page will only have the handful of fields that are required to be there, and will be linked to the specified courseware object. If the courseware object is a course, it will also be added to the Featured Products section on the homepage. By default, the CMS page will be saved in a draft state.

+
+

Syntax§

+

create_courseware_page <courseware id> [--live]

+
+
+

Options§

+
    +
  • courseware id - The courseware object to make a CMS page for.

  • +
  • --live - Makes the resulting page live.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/create_product.html b/commands/create_product.html new file mode 100644 index 000000000..0dcb8bf1e --- /dev/null +++ b/commands/create_product.html @@ -0,0 +1,214 @@ + + + + + + + + create_product — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

create_product§

+

Creates a product for the given courseware ID. (For now, this only works with course runs.)

+

By default, the product description will be the courseware ID. This is the recommended setting for this to make it easy to determine which products are for what courseware objects.

+
+

Syntax§

+

create_product <courserun> <price> [--description|-d <description>] [--inactive]

+
+
+

Options§

+
    +
  • courserun - The course run to use.

  • +
  • price - The price (numbers only) of the product.

  • +
  • --description <description> (or -d) - Optionally specify the product description. (Defaults to the courseware ID.)

  • +
  • --inactive - Makes the product inactive. (Defaults to active.)

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/create_user.html b/commands/create_user.html new file mode 100644 index 000000000..bc6868c71 --- /dev/null +++ b/commands/create_user.html @@ -0,0 +1,216 @@ + + + + + + + + create_user — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

create_user§

+

Creates a learner account in the system. You will be prompted for the account password.

+
+

Syntax§

+

create_user username email firstname lastname displayname countrycode [--enroll <courseid>]

+
+
+

Options§

+
    +
  • username - Username for the learner to create.

  • +
  • email - Email address of the learner to create.

  • +
  • firstname - The learner’s first name.

  • +
  • lastname - The learner’s last name.

  • +
  • displayname - The learner’s display name.

  • +
  • countrycode - The country code to use. (Default US)

  • +
  • --enroll <courseid> - Optionally enroll the user in the specified course run. The enrollment will be an audit enrollment.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/generate_discount_code.html b/commands/generate_discount_code.html new file mode 100644 index 000000000..a95573b82 --- /dev/null +++ b/commands/generate_discount_code.html @@ -0,0 +1,242 @@ + + + + + + + + generate_discount_code — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

generate_discount_code§

+

Creates discount code(s).

+

This can create a single code, a batch of explicitly defined codes, or a batch of automatically generated codes (with an optional prefix).

+
+

Syntax§

+

generate_discount_code <code> [<code>...] --amount <amount> [-activates <date>] [--expires <date>] [--discount-type <discount type>] [--one-time] [--once-per-user] [--count <count>] [--prefix <prefix>]

+
+
+

Batch Generating Codes§

+

You can create a batch of explicitly named codes by simply passing multiple discount codes to the command. All of the codes will be created (assuming they don’t already exist) with the options you’ve specified.

+

Alternatively, you can created a number of codes using the --count and -prefix option. Using these options will generate the number of codes specified by --count and will prefix the code with -prefix if it is specified. The code will be generated using a UUID - if you’ve supplied a prefix, the code will be in the format <prefix><uuid>. Note that the command won’t insert any punctuation between the prefix and the UUID, so you will need to add that yourself if you want, for example, a dash separating the two. UUIDs are 37 characters in length so prefixes need to be a total of 13 characters or less.

+
+
+

Output§

+

Generated codes will be written to a generated-codes.csv file, with the following information:

+
    +
  • Discount code

  • +
  • Code type

  • +
  • Amount

  • +
  • Expiration date

  • +
+

The file is overwritten if it exists.

+
+
+

Options§

+

General options:

+
    +
  • --amount <amount> - The discount’s amount. For percent off discounts, this should be on a scale of 0-100. This is required.

  • +
  • --discount-type <discount type> - One of percent-off, dollars-off, or fixed-price; the type of discount code to make. Defaults to percent-off.

  • +
  • --activates <date> - The date the code should become active (in ISO-8601 format).

  • +
  • --expires <date> - The date the code should stop being active (in ISO-8601 format).

  • +
  • --one-time - Set the discount to be redeemable only once.

  • +
  • --once-per-user - Set the discount to be redeemable only once per learner.

  • +
+

For explicitly named codes:

+
    +
  • code - The code to generate. (You can specify any number of these.) Max length 50 characters.

  • +
+

For automatically generated codes:

+
    +
  • --count <count> - The number of codes to create.

  • +
  • --prefix <prefix> - The prefix to append to the code. Max length 13 characters.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/import_courserun.html b/commands/import_courserun.html new file mode 100644 index 000000000..5ae0c5bd1 --- /dev/null +++ b/commands/import_courserun.html @@ -0,0 +1,218 @@ + + + + + + + + import_courserun — MITx Online documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

import_courserun§

+

Creates courserun(s) in the system based on edX course data.

+

You can specify either a specific courserun to create, or you can specify a run tag (e.g. 1T2023) and a program (e.g. program-v1:MITx+DEDP), and the command will create courseruns for the courses that it finds in edX.

+

You can also optionally have it create a CMS page for the course, if one doesn’t already exist.

+

Furthermore, you can specify a price, and it will create (or update) product(s) for the courserun(s) with the specified price. Created products will use the readable ID for the courserun as the product description and will be made active depending on the courserun. This command will not update existing courseruns; only new ones that it creates will get products.

+

If the course does not exist, it will be created with the same data as the edX course. Any specified courserun that doesn’t exist in edX will be skipped - it won’t make empty course runs for you (use Django Admin or create_courseware if you want to do that, since you’ll need to specify a few things that you can’t here.) Similarly, any courserun that already exists will be skipped - sync_courserun, which runs on a regular basis, will handle syncing the pertinent data for it.

+

New courseruns will be created with the following data synced from the edX course_details API call: +* Start and end dates +* Enrollment start and end dates +* Title +* Pacing (self-paced or instructor-led) +* Courseware URL (depends on the OPENEDX_API_BASE_URL configuration setting)

+

You may want to adjust these after they’re created.

+
+

Syntax§

+

To create an individual courserun:

+

import_courserun [--courserun <courserun>] [--program <program>] [--live] [--create-cms-page] [--price <price>]

+

To walk a program:

+

import_courserun [--program <program>] [--run-tag <run tag>] [--live] [--create-cms-page] [--price <price>]

+
+
+

Options§

+
    +
  • --courserun <courserun> - The courserun to check for. Takes precedence over --program.

  • +
  • +
    --program <program>
      +
    • _If walking a program:_ The program to walk through. Requires --run_tag. Specify either the numeric database ID or the readable ID for the program.

    • +
    • _If creating a single courserun:_ The program the course should belong to, if any.

    • +
    +
    +
    +
  • +
  • --run-tag <run tag> - The run tag to use for the new courseruns. Required for --program; don’t use otherwise.

  • +
  • --live - Make the course live. (Default is to set the flag to false.)

  • +
  • --create-cms-page - Attempt to create a basic CMS page for the course, in a similar fashion to create_courseware_page. If this fails (for instance, if the course already has a CMS page), this step will be skipped.

  • +
  • --price <price> - Create (or update) a product for the courserun with the specified price. If the command creates multiple courseruns, this will create a product for each.

  • +
  • --dept <department_name> - Specify department(s) assigned to the course object. If program is specified, all courses associated with the program and imported will have the same department.

  • +
+
+
+

Example§

+

The use case for this was creating a batch of course runs for an upcoming semester of DEDP courses; these courses existed in edX but not in MITx Online. Since in that case the semester was 1T2023, this command would have created all the applicable courseruns all at once:

+

manage.py import_courserun --program program-v1:MITx+DEDP --run-tag 1T2023 --live --dept Economics

+

Or, the same but with the standard DEDP pricing applied:

+

manage.py import_courserun --program program-v1:MITx+DEDP --run-tag 1T2023 --live --price 1000

+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/index.html b/commands/index.html new file mode 100644 index 000000000..41c231e19 --- /dev/null +++ b/commands/index.html @@ -0,0 +1,260 @@ + + + + + + + + MITx Online Commands — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/commands/refund_fulfilled_order.html b/commands/refund_fulfilled_order.html new file mode 100644 index 000000000..b4133eb92 --- /dev/null +++ b/commands/refund_fulfilled_order.html @@ -0,0 +1,154 @@ + + + + + + + + refund_fulfilled_order — MITx Online documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

refund_fulfilled_order§

+

Looks up a fulfilled order in the system, sets it to Refunded, and then adjusts the enrollments accordingly.

+
    +
  • If –unenroll is specified, the learner will be unenrolled from the course run associated with the order.

  • +
  • If –audit is specified, the learner will keep their unenrollments, but they will be set to “audit” instead of “verified”.

  • +
+

This does not make any sort of call to CyberSource or any other payment gateway to perform a refund - you’re expected to have refunded the learner’s money manually already. (At time of writing, PayPal transactions can’t be refunded using the normal means, so they get refunded manually via CyberSource and then this command comes in to clean up afterwards.)

+
+

Syntax§

+

refund_fulfilled_order <reference number> [--audit] [--unenroll]

+
+
+

Options§

+
    +
  • <reference number> - The reference number for the order to refund.

  • +
  • --audit - Change the learner’s enrollment status to audit.

  • +
  • --unenroll - Unenroll the learner.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/regenerate_edx_auth_tokens.html b/commands/regenerate_edx_auth_tokens.html new file mode 100644 index 000000000..f8b67e2d9 --- /dev/null +++ b/commands/regenerate_edx_auth_tokens.html @@ -0,0 +1,148 @@ + + + + + + + + regenerate_edx_auth_tokens — MITx Online documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

regenerate_edx_auth_tokens§

+

Regenerates the authentication tokens for a specified learner. In essence, deletes the OpenEdxApiAuth record and then makes a call to edX to generate a new refresh and access token.

+

If the user doesn’t have an OpenEdxUser record either, then this command is not appropriate. Use repair_missing_courseware_records instead. This will also not do anything with enrollments or grades. The main usecase is if the learner’s OpenEdxApiAuth record gets deleted for some reason, or if their refresh tokens on the edX side are revoked.

+
+

Syntax§

+

regenerate_edx_auth_tokens <username>

+
+
+

Options§

+
    +
  • username - the learner’s ID, username, or email address.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/commands/resolve_pending_order.html b/commands/resolve_pending_order.html new file mode 100644 index 000000000..aa7edd9ff --- /dev/null +++ b/commands/resolve_pending_order.html @@ -0,0 +1,149 @@ + + + + + + + + resolve_pending_order — MITx Online documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

resolve_pending_order§

+

Looks up the specified pending order in CyberSource and resolves it. This can mean either fulfilling the order or cancelling it, depending on the status of the payment in CyberSource: if the order is found and the result code is 100, it will be fulfilled; otherwise, it will be cancelled.

+

This only works on pending orders and won’t accept a reference number for an order that’s not in the Pending state.

+
+

Syntax§

+

resolve_pending_order [--all] [--order <reference number>]

+
+
+

Options§

+
    +
  • --all - Process all pending orders.

  • +
  • --order <reference number> - Process a specific order specified by reference number (e.g. mitxonline-prod-1).

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/configuration/ecommerce.html b/configuration/ecommerce.html new file mode 100644 index 000000000..5ab613652 --- /dev/null +++ b/configuration/ecommerce.html @@ -0,0 +1,264 @@ + + + + + + + + Configure eCommerce — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Configure eCommerce§

+

To use the eCommerce subsystem, some configuration is required. These instructions will also set up a course in your MITx Online environment that you can use for enrollment.

+

You’ll need a working MITx Online setup and a working devstack setup to begin, and superuser accounts for each.

+
+

Set Up MITx Online eCommerce Config§

+

The CyberSource configuration for the app can be lifted out of Heroku. Make sure you use values from RC - otherwise, you will actually be charged for purchases (and test credit card numbers will fail). For best results, you should also have an account for the test Enterprise Business Center (https://ebc2test.cybersource.com/ebc2/).

+

The .env settings you need to copy over are:

+
    +
  • MITOL_PAYMENT_GATEWAY_CYBERSOURCE_ACCESS_KEY

  • +
  • MITOL_PAYMENT_GATEWAY_CYBERSOURCE_PROFILE_ID

  • +
  • MITOL_PAYMENT_GATEWAY_CYBERSOURCE_SECURITY_KEY

  • +
  • MITOL_PAYMENT_GATEWAY_CYBERSOURCE_SECURE_ACCEPTANCE_URL

  • +
+

Alternatively, you can set up your own CyberSource developer account and generate a set of API keys there: Evaluation Account Setup If you set up your own developer account, you will need to properly configure it for Secure Acceptance with the credit card types you wish to test, and you will need to generate your own API keys and supply them in the .env file.

+

You may also set ECOMMERCE_DEFAULT_PAYMENT_GATEWAY to CyberSource - this sets it to the default value, but setting it now will prevent issues if the Payment Gateway ol-django library adds in new payment gateways.

+
+
+

Set Up a Course§

+

The devstack environment comes with a couple of test courses set up. If you want a different course, you will need to set that up in Open edX before starting here. Bootstrapping a course in edX is beyond the scope of this document.

+
+

In Open edX§

+
    +
  1. Log in to the Django Admin interface.

  2. +
  3. Find a course from the Course Overviews page.

  4. +
  5. Note the Display name and Id fields.

  6. +
+
+
+

In MITx Online§

+
    +
  1. Log into the Django Admin interface.

  2. +
  3. Under Courses, open Programs and add a Program. (The specifics here aren’t important - there just needs to be a Program.)

  4. +
  5. Under Courses, open Courses and add a Course. The Title and Readable Id fields should be set to the Display name and Id fields from the edX course you plan to use. Make sure Live is set and the Program is set to the program you created in step 2.

  6. +
  7. Under Course Runs, add a Course Run. The Title and Courseware Id fields should be set to the Id and Display Name fields from the edX course. The Courseware url path should be set to the URL where the course lives in edX (ex. https://courses-qa.mitxonline.mit.edu/learn/course/course-v1:MITx+14.750x+3T2022/home). The dates will be overwritten when the system is synced with Open edX, but for testing it’s good to put Start Date, End Date, Enrollment Start, and Enrollment End. A good starting point for these is today plus/minus one year for each.

  8. +
  9. You now need to add the course to the CMS. Navigate to the Wagtail CMS admin, at /cms.

  10. +
  11. Open the Courses folder under Home Page.

  12. +
  13. Select Add Child Page.

  14. +
  15. Fill out the form. The course you added in steps 1-4 should appear. (If not, double-check your settings in Django Admin.) Publish the page when ready.

  16. +
  17. Open the Home Page, and select Edit.

  18. +
  19. Under the Featured Products section, select Add. You will be given a button to choose a page, and the page chooser there should list the page you created.

  20. +
  21. Publish the Home Page when ready.

  22. +
+

You should now be able to see the course under the hero image on the MITx Online homepage, and navigating into the course should give you the option to Enroll. (At this point, you won’t have a Product set up, so enrolling now should just enroll you in the course.)

+
+
+
+

Setting Up a Product§

+
    +
  1. Log into MITx Online Django Admin.

  2. +
  3. Under Ecommerce, open Products and create a new Product. Set Content type to Course Run and Object Id to the ID of the course run you created earlier (it’s probably 1 if you’re working from a new install). Price should ideally be set to a non-zero value, that is less than $999, in RC/Sandbox environments. Description needs to be filled in but can be anything - for clarity, it’s recommended to use the course name. Make sure Is active is checked.

  4. +
+

You should now be able to enroll in the upgraded course.

+
    +
  • If you’ve enrolled in the course already, you should now see the upsell card on the course listing page.

  • +
  • If you haven’t enrolled, enrolling should pop the upgrade modal.

  • +
  • In either case, enrolling in the paid version of the course should bring you to the cart, and you should then be able to check out.

  • +
+
+

Testing Checkout§

+

The test CyberSource credentials won’t actually process a charge that has been run through the system. However, you should avoid using a valid credit card number when testing. Any card number that is both invalid but passes the checks should work. Here are some examples:

+
    +
  • Visa: 4111111111111111

  • +
  • Visa: 4242424242424242

  • +
  • MasterCard: 5555555555554444

  • +
  • MasterCard: 5105105105105100

  • +
  • American Express: 378282246310005

  • +
  • Discover: 6011111111111117

  • +
+

Supply any expiration date in the future. The CVN code should be any three-digit (not AmEx) or 4 digit (AmEx) number that is fairly unique (not like 123, 111). What card types are allowed and whether or not the CVN code is required depends on the settings in the CyberSource account - currently, the MIT test account does require an expiration date and CVN code and supports the four card types listed above. Transactions are logged and can be found in the test EBC. You can additionally adjust the settings in the EBC to email the payment data to you while you’re testing - but you should ask around before doing this in case someone else is testing eCommerce elsewhere.

+
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/configuration/hubspot.html b/configuration/hubspot.html new file mode 100644 index 000000000..ea86e1a49 --- /dev/null +++ b/configuration/hubspot.html @@ -0,0 +1,142 @@ + + + + + + + + Configure HubSpot — MITx Online documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Configure HubSpot§

+

In order to connect your local instance of MITx Online with HubSpot, you will need to define the following envrionment variables in your .env file.

+

` +MITOL_HUBSPOT_API_PRIVATE_TOKEN=<ask a developer to add you to the hubspot account> +MITOL_HUBSPOT_API_ID_PREFIX=<your_initials>-mitxonline-dev +HUBSPOT_PIPELINE_ID=19817792 +`

+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/configuration/index.html b/configuration/index.html new file mode 100644 index 000000000..b5d06f932 --- /dev/null +++ b/configuration/index.html @@ -0,0 +1,227 @@ + + + + + + + + Configuration — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/configuration/open_edx.html b/configuration/open_edx.html new file mode 100644 index 000000000..572f2d617 --- /dev/null +++ b/configuration/open_edx.html @@ -0,0 +1,416 @@ + + + + + + + + Configure Open edX — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Configure Open edX§

+

In order to create user accounts in Open edX and permit authentication from MITx Online to Open edX, you need to configure MITx Online as an OAuth2 provider for Open edX.

+
+

Setup Open edX Devstack§

+

Following steps are inspired by edx-devstack.

+
+

Add /etc/hosts alias for Open edX§

+

If one doesn’t already exist, add an alias to /etc/hosts for Open edX. We have standardized this alias +to edx.odl.local. Your /etc/hosts entry should look like this:

+
127.0.0.1       edx.odl.local
+
+
+
+
+

Clone edx/devstack§

+
git clone https://github.com/edx/devstack
+cd devstack
+make requirements
+make dev.clone
+
+
+
+
+

Pull latest images and run provision§

+
make pull
+make dev.provision
+
+
+
+
+

Start your servers§

+
make dev.up
+
+
+
+
+

Stop your servers§

+
make stop
+
+
+
+
+
+

Setup social auth§

+
+

Install social-auth-mitxpro in LMS§

+

There are two options for this:

+
+

Install via pip§

+
pip install social-auth-mitxpro
+
+
+
+
+

Install from local Build§

+
    +
  • Checkout the social-auth-mitxpro project and build the package per the project instructions

  • +
  • Copy the social-auth-mitxpro-$VERSION.tar.gz file into devstack’s edx-platform directory

  • +
  • In devstack, run make lms-shell and within that shell pip install social-auth-mitxpro-$VERSION.tar.gz

    +
      +
    • To update to a new development version without having to actually bump the package version, simply pip uninstall social-auth-mitxpro, then install again

    • +
    +
  • +
+
+
+
+

Install mitxpro-openedx-extensions in LMS§

+

There are two options for this:

+
+

Install via pip§

+
pip install mitxpro-openedx-extensions
+
+
+
+
+

Install from local Build§

+
    +
  • Checkout the mitxpro-openedx-extensions project and build the package per the project instructions

  • +
  • Copy the mitxpro-openedx-extensions-$VERSION.tar.gz file into devstack’s edx-platform directory

  • +
  • In devstack, run make lms-shell and within that shell pip install mitxpro-openedx-extensions-$VERSION.tar.gz

    +
      +
    • To update to a new development version without having to actually bump the package version, simply pip uninstall -y mitxpro-openedx-extensions, then install again

    • +
    +
  • +
+
+
+
+
+

Configure MITx Online as a OAuth provider for Open edX§

+

In MITx Online:

+
    +
  • go to /admin/oauth2_provider/application/ and create a new application with these settings selected:

    +
      +
    • Redirect uris: http://<EDX_HOSTNAME>:18000/auth/complete/mitxpro-oauth2/

      +
        +
      • [macOS users] You will need redirect uris for both the local edX host alias and for host.docker.internal. This value should be:

        +
        http://edx.odl.local:18000/auth/complete/mitxpro-oauth2/
        +http://host.docker.internal:18000/auth/complete/mitxpro-oauth2/
        +
        +
        +
      • +
      • [Linux users] You will need redirect uris for both the local edX host alias and for the gateway IP of the docker-compose networking setup for MITx Online as found via docker network inspect mitx-online_default:

        +
        http://edx.odl.local:18000/auth/complete/mitxpro-oauth2/
        +http://<GATEWAY_IP>:18000/auth/complete/mitxpro-oauth2/
        +
        +
        +
      • +
      • [WSL 2 users]: Use the URLs for macOS. You will also have to set OPENEDX_IP to host-gateway in your .env file to make this work. (Networking with WSL 2 works very differently, and the defaults won’t work.)

      • +
      +

      NOTE: GATEWAY_IP should be something like 172.19.0.1.

      +
    • +
    • Client type: “Confidential”

    • +
    • Authorization grant type: “Authorization code”

    • +
    • Skip authorization: checked

    • +
    • Other values are arbitrary but be sure to fill them all out. Save the client id and secret for later

    • +
    +
  • +
+

In Open edX (derived from instructions here):

+
    +
  • +
    make lms-shell into the LMS container and ensure the following settings are set in /edx/etc/lms.yml if you are using Juniper or a more recent Open edX release, otherwise they should be in /edx/app/edxapp/cms.env.json:
    FEATURES:
    +  ALLOW_PUBLIC_ACCOUNT_CREATION: true
    +  ENABLE_COMBINED_LOGIN_REGISTRATION: true
    +  ENABLE_THIRD_PARTY_AUTH: true
    +  ENABLE_OAUTH2_PROVIDER: true
    +  SKIP_EMAIL_VALIDATION: true
    +REGISTRATION_EXTRA_FIELDS:
    +  country: hidden
    +THIRD_PARTY_AUTH_BACKENDS:
    + - social_auth_mitxpro.backends.MITxProOAuth2
    +
    +
    +
    +
    +
  • +
  • make lms-restart to pick up the configuration changes

  • +
  • Login to django-admin (default username and password can be found here), go to http://<EDX_HOSTNAME>:18000/admin/third_party_auth/oauth2providerconfig/, and create a new config:

    +
      +
    • Select the default example site

    • +
    • The slug field MUST match the the backend’s name, which for us is mitxpro-oauth2

    • +
    • Client Id should be the client id from the MITx Online Django Oauth Toolkit Application

    • +
    • Check the following checkboxes:

      +
        +
      • Enabled

      • +
      • Skip hinted login dialog

      • +
      • Skip registration form

      • +
      • Sync learner profile data

      • +
      • Enable SSO id verification

      • +
      +
    • +
    • Set Backend name to: mitxpro-oauth2

    • +
    • In “Other settings”, put:

      +
      {
      +  "AUTHORIZATION_URL": "http://<LOCAL_MITX_ONLINE_ALIAS>:8013/oauth2/authorize/",
      +  "ACCESS_TOKEN_URL": "http://<EXTERNAL_MITX_ONLINE_HOST>:8013/oauth2/token/",
      +  "API_ROOT": "http://<EXTERNAL_MITX_ONLINE_HOST>:8013/"
      +}
      +
      +
      +
    • +
    • LOCAL_MITX_ONLINE_ALIAS should be your /etc/hosts alias for the mitxonline app

    • +
    • EXTERNAL_MITX_ONLINE_HOST will depend on your OS, but it needs to be resolvable within the edx container

      +
        +
      • Linux users: The gateway IP of the docker-compose networking setup for mitxonline as found via docker network inspect mitx-online_default

      • +
      • OSX users: Use host.docker.internal

      • +
      +
    • +
    • Save the configuration.

    • +
    +
  • +
+
+
+

Configure Open edX to support OAuth2 authentication from MITx Online§

+
    +
  • In Open edX:

    +
      +
    • go to /admin/oauth2_provider/application/ and verify that an application named ‘edx-oauth-app’ exists with these settings:

      +
        +
      • Redirect uris: http://mitxonline.odl.local:8013/login/_private/complete

      • +
      • Client type: “Confidential”

      • +
      • Authorization grant type: “Authorization code”

      • +
      • Skip authorization: checked

      • +
      • Other values are arbitrary but be sure to fill them all out. Save the client id and secret for later

      • +
      +
    • +
    +
  • +
  • In MITx Online:

    +
      +
    • Set OPENEDX_API_CLIENT_ID to the client id

    • +
    • Set OPENEDX_API_CLIENT_SECRET to the client secret

    • +
    • Set OPENEDX_API_BASE_URL to http://host.docker.internal:18000 when running devstack and mitxonline locally through Docker

    • +
    +
  • +
+
+
+

Configure Logout§

+
    +
  • In Open edX, configure settings.IDA_LOGOUT_URI_LIST to be a list including the full url to <protocol>://<hostname>[:<port>]/logout in MITx Online

    +
      +
    • For devstack, this means modifying the value in edx-platform/lms/envs/devstack.py to include http://mitxonline.odl.local:8013/logout

    • +
    • For production, this setting can go in lms.env.json under the key IDA_LOGOUT_URI_LIST as a JSON array of with that string in it

    • +
    +
  • +
  • MITx Online:

    +
      +
    • Set LOGOUT_REDIRECT_URL to the full path to the edx /logout view.

    • +
    +
  • +
+

For local development this will be http://<EDX_HOSTNAME>:18000/logout

+
+
+

Configure Open edX user and token for use with MITx Online management commands§

+
    +
  • In Open edX, create a staff user and then under /admin/oauth2_provider/accesstoken/ add access token. The value of said token needs to match the value set for the OPENEDX_SERVICE_WORKER_API_TOKEN key in the MITx Online app.

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/configuration/quickstart.html b/configuration/quickstart.html new file mode 100644 index 000000000..2ec666312 --- /dev/null +++ b/configuration/quickstart.html @@ -0,0 +1,280 @@ + + + + + + + + MITx Online Quick Start — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

MITx Online Quick Start§

+

You can use the configure_instance management command to perform a quick-start of a fresh MITx Online instance. This command takes care of a lot of the boilerplate required to set up an instance. It:

+
    +
  • Creates a superuser account (if you want)

  • +
  • Creates the OAuth2 application record for edX (if you want, and optionally with an existing secret)

  • +
  • Creates a set of courseware objects, including a DEDP program and courses with runs that match what ships with a standard devstack instance

  • +
  • Creates a set of CMS pages for the courseware objects that it creates

  • +
  • Sets up financial assistance appropriately

  • +
  • Adds a couple of products in for the courses it creates

  • +
  • Creates a learner account for the system

  • +
+

It does not:

+
    +
  • Run migrations

  • +
  • Completely set up integration with devstack

  • +
+

In addition, there are a handful of tasks that you’ll need to perform afterwards:

+
    +
  • The CMS pages (course about pages and the financial assistance form) need to be reviewed for content.

  • +
  • The financial assistance form will need to be published, and linked into the appropriate course.

  • +
  • You may want to adjust the products that are created.

  • +
+

The configure_instance command has a few flags you can use to customize how it works. For more details on this, either run it with --help or read the configure_instance command documentation. (Do this especially if you’re using the command to reset your MITx Online instance - you can provide an existing OAuth client ID and secret.)

+
+

Performing a Quick Start§

+

To quick-start your MITx Online instance:

+
    +
  1. Run the migrate command.

  2. +
  3. Run the createsuperuser command.

  4. +
  5. Follow the steps in the Configure Open edX documentation

  6. +
  7. Run configure_instance <platform>, where platform is macos, linux, or none. (If you don’t want it to create OAuth2 records, set this to none or leave it blank. The default is none.)

  8. +
+

configure_instance will prompt you to enter a password for the test learner account and will prompt you to enter account information for the superuser account. At the end, you’ll see your edX OAuth2 application credentials, which can then be plugged into Open edX (if you haven’t specified none for your platform).

+
+
+

Results§

+

Running configure_instance will peform these tasks in order:

+
    +
  1. Runs createsuperuser to create the superuser account (unless disabled with --dont-create-superuser).

  2. +
  3. Creates the OAuth2 application record. (This is the one part of this that doesn’t rely on an existing management command.)

  4. +
  5. Runs configure_wagtail to set up the CMS.

  6. +
  7. Runs configure_tiers to add the DEDP program and configure financial assistance tiers and discounts.

  8. +
  9. Runs create_courseware_page to add a basic about page for the DEDP program (required for the financial assistance form).

  10. +
  11. Runs create_finaid_form to create a financial assistance form for the DEDP program.

  12. +
  13. Runs create_courseware twice to create two courses, each with a course run, that correspond to the demo courses in devstack. (Details below.)

  14. +
  15. Runs sync_course_run to sync the courses with the devstack instance.

  16. +
  17. Runs create_product twice to create two products for the courses.

  18. +
  19. Runs create_courseware_page twice to add course pages for the two courses. (These are marked as live.)

  20. +
  21. Runs create_user to create the learner account.

  22. +
+

The courses that are created are:

+ + + + + + + + + + + + + + + + + + + + +

Course

Readable ID

Run Tag

Price

Demonstration Course

course-v1:edX+DemoX

Demo_Course

$999

E2E Test Course

course-v1:edX+E2E-101

course

$999

+

The learner account that is created is:

+
    +
  • Username: testlearner

  • +
  • Email: testlearner@mitxonline.odl.local

  • +
  • Display Name (split in half for first/last names): Test learner

  • +
  • Country Code: US

  • +
  • Enrollments: course-v1:edX+DemoX+Demo_Course

  • +
+

The program that gets created is the standard DEDP program (program-v1:MITx+DEDP). The Demonstration Course is added to the DEDP program; E2E Test Course is not.

+
+
+

Notes§

+

The steps that involve communication with edX may not work if your environment isn’t set up properly. In these cases, the attempts will be queued to be run later.

+

If you’ve set your platform to macos or linux, the command will do the first part of the Configure MITx Online as an OAuth provider for Open edX section in the Configure Open edX documentation.

+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/configuration/tutor.html b/configuration/tutor.html new file mode 100644 index 000000000..2c5f2b438 --- /dev/null +++ b/configuration/tutor.html @@ -0,0 +1,412 @@ + + + + + + + + Local Open edX Tutor and MITx Online Deployment — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Local Open edX Tutor and MITx Online Deployment§

+

These instructions describe setting up MITx Online and Tutor from scratch on Linux. These instructions should largely apply to macOS users, and they should also apply to users converting from a devstack-based deployment to Tutor.

+
+

These instructions should work for a Tutor Dev or Tutor Nightly deployment as well. Specify --tutor-dev instead of --tutor when running configure_instance so the URLs have a port on them.

+
+

At the end of this guide, you should have:

+
    +
  • A fully working MITx Online deployment.

  • +
  • A working Tutor deployment of edX.

  • +
  • SSO should work from edX to MITx Online.

  • +
  • MITx Online should have a service worker set up and should be able to perform tasks using the edX api.

  • +
  • Tutor’s included AuthN MFE should be disabled in favor of the MITx Online authentication system.

  • +
+
+

Preliminary Steps§

+

pyenv (and pyenv-virtualenv) are highly recommended for managing local Python versions. Use the instructions on their GitHub page to install if you haven’t already installed it.

+

You’ll want to create at least a virtualenv for Tutor. As of this writing, Tutor uses Python 3.8.12 (in the LMS container at least); I have also successfully used 3.9.16. 3.11 has not worked for me. You can optionally create a virtualenv for MITx Online too but that’s not strictly necessary. (I have one so I can run black/isort/etc. without having to jump into a container to do it.)

+
+
+

Tutor Setup, Part One§

+
+

Note that no hosts file changes are needed if you use the default local.edly.io domain - that’s a real domain with a wildcard subdomain cname that points to 127.0.0.1.

+
+

To begin, you need to follow the One-Click Installer instructions provided by Tutor. Do this with your Tutor virtualenv activated.

+
+

Mac/Arm users should instead follow these instructions: Running Tutor on ARM-based systems It’s mostly the same steps that the quickstart does internally, with some changes to rebuild some of the images and flip some dependencies to use compatible images for Arm.

+
+

Once Tutor has bootstrapped itself and is available, create a superuser account:

+
tutor local do createuser --staff --superuser edx edx@example.org
+
+
+

Supply a password (the one used by devstack is edx so use that if you want to be consistent with it). Then, create a service worker account for MITx Online:

+
tutor local do createuser --staff mitx_online_serviceworker service@mitxonline.odl.local
+
+
+

Supply a password (this one doesn’t matter for a local deployment, you won’t ever actually use the account).

+

For best results, create two new courses within edX. The MITx Online configure_instance command expects a couple of courses to exist in edX (because they come with the devstack package):

+ + + + + + + + + + + + + + +

Course ID

Course Title

course-v1:edX+DemoX+Demo_Course

Demonstration Course

course-v1:edX+E2E-101+course

E2E Test Course

+

If you have a devstack instance handy, you can export these and import them into Tutor. Otherwise, just create them and make sure to set dates for the courses (they default to 2030 otherwise).

+

Finally, go here to create an access token for the service worker user: https://local.edly.io/admin/oauth2_provider/accesstoken/add/ The token can be anything, and the expiration date should just be today plus 10 years.

+
+
+

MITx Online Setup§

+

To set up MITx Online:

+
    +
  1. Get the gateway IP for the tutor_local_default network: docker network inspect tutor_local_default | grep Gateway

    +
      +
    • Mac users should instead use the host.docker.internal IP. Specify this by setting OPENEDX_IP=host-gateway in your MITx Online .env file.

    • +
    +
  2. +
  3. Set up your /etc/hosts file to point mitxonline.odl.local to localhost.

  4. +
  5. Clone the repository somewhere.

  6. +
  7. Set up your .env file. These settings need particular attention:

    +
      +
    • OPENEDX_IP: set to the gateway IP from the first step

    • +
    • OPENEDX_API_BASE_URL: set to http://local.edly.io

    • +
    • OPENEDX_SERVICE_WORKER_USERNAME: set to mitx_online_serviceworker (unless you changed this)

    • +
    • OPENEDX_SERVICE_WORKER_API_TOKEN: set to the token you generated

    • +
    +
  8. +
  9. Build the app: docker compose build

  10. +
  11. Run migrations and configure Wagtail:

    +
    docker compose run --rm web ./manage.py migrate
    +docker compose run --rm web ./manage.py configure_wagtail
    +
    +
    +
  12. +
  13. Run the configure_instance command:

    +
    docker compose run --rm web ./manage.py configure_instance linux --gateway <ip> --tutor
    +
    +
    +

    where <ip> is the IP from the first step. (On macOS, specify macos instead of linux. You can also skip --gateway.) You will need to supply passwords for the MITx Online superuser and test learner accounts. Make a note of the client ID and secret that it will print out at the end.

    +
  14. +
+
+
+

Tutor Setup, Part Two§

+

Note that some of these steps require editing the main configuration files for the production instance (which is also used for a local deployment). Most of the settings that need to be adjusted to get integration working are overridden by the default Tutor configuration, so you can’t update them by setting config.yml. If you’re using the development Tutor build, you’ll likely need to edit development.py rather than production.py as necessary.

+

These steps will also disable the AuthN SSO MFE, so from here on you’ll get normal edX authentication screens (if you’re not being bounced to MITx Online).

+
    +
  1. Get the gateway IP of the mitxonline_default Docker network:

    +
    docker network inspect mitxonline_default | grep Gateway
    +
    +
    +
  2. +
  3. Log into to edX using your superuser account, and make sure your session stays open. Sessions are pretty long-lived so this just means not closing the browser that you started the session in. (Part of this process will involve mostly breaking authentication so it’s important that you are able to get into the admin.)

  4. +
  5. Stop Tutor: tutor local stop

  6. +
  7. Change into the configuration root for Tutor:

    +
    cd "$(tutor config printroot)"
    +
    +
    +
  8. +
  9. Create a env/build/openedx/requirements/private.txt with the required extensions:

    +
    social-auth-mitxpro
    +mitxpro-openedx-extensions
    +
    +
    +
  10. +
  11. Edit the env/apps/openedx/config/lms.env.yml file and add:

    +
    FEATURES:
    +  SKIP_EMAIL_VALIDATION: true
    +
    +
    +

    to the FEATURES block (should be at the top).

    +
  12. +
  13. Edit the env/apps/openedx/settings/lms/production.py and/or env/apps/openedx/settings/lms/development.py settings file. (The former is used by a local instance, where the latter is used by both dev and nightly instances.)

    +
      +
    • Add to the end of the file:

      +
      +
        +
      • THIRD_PARTY_AUTH_BACKENDS = ['social_auth_mitxpro.backends.MITxProOAuth2']

      • +
      • AUTHENTICATION_BACKENDS.append('social_auth_mitxpro.backends.MITxProOAuth2')

      • +
      • IDA_LOGOUT_URI_LIST.append('http://mitxonline.odl.local:8013/logout/') - there’s an existing one of these around like 300 in production.py too.

      • +
      +
      +
    • +
    • Find and update:

      +
        +
      • FEATURES['ENABLE_AUTHN_MICROFRONTEND'] = False (defaults to True)

      • +
      • REGISTRATION_EXTRA_FIELDS["terms_of_service"] = "hidden" (defaults to required)

      • +
      +
    • +
    +
  14. +
  15. Build a new openedx image: tutor images build openedx (this will take a long time)

  16. +
  17. Run a Docker Compse rebuild: tutor local dc build (this should be pretty quick - it’s likely not required, just doing it here for safety)

  18. +
  19. Restart Tutor: tutor local start -d (omit -d if you want to watch the logs)

  20. +
  21. Check your settings. There’s a print_setting command that you can use to verify everything is set properly:

    +
      +
    • tutor local run lms ./manage.py lms print_setting REGISTRATION_EXTRA_FIELDS

    • +
    • tutor local run lms ./manage.py lms print_setting AUTHENTICATION_BACKENDS

    • +
    • tutor local run lms ./manage.py lms print_setting FEATURES - will print a lot of stuff

    • +
    • tutor local run lms ./manage.py lms print_setting THIRD_PARTY_AUTH_BACKENDS

    • +
    • If you do have weird errors or settings not showing properly, make sure you edited the right yaml files and that they’re using the right whitespace (i.e. don’t use tabs).

    • +
    +
  22. +
  23. In a separate browser session of some kind (incognito/private browsing/other browser entirely), try to navigate to http://local.edly.io . It should load but it should give you an error message. In the LMS logs, you should see an error message for “Can’t fetch settings for disabled provider.” This is proper operation - the OAuth2 settings aren’t in place yet.

  24. +
  25. In the superuser session you have open, go to http://local.edly.io/admin . This should work. If you’ve been logged out, you should still be able to get in. If you can’t (for instance, if you’re getting 500 errors), you will need to turn off ENABLE_THIRD_PARTY_AUTH in FEATURES, restart Tutor using tutor local stop and start, not using reboot, then try again.

  26. +
  27. Go to http://local.edly.io/admin/third_party_auth/oauth2providerconfig/add/ and add a provider configuration:

    +
      +
    • Enabled is checked.

    • +
    • Name: mitxonline

    • +
    • Slug: mitxpro-oauth2

    • +
    • Site: local.edly.io

    • +
    • Skip hinted login dialog is checked.

    • +
    • Skip registration form is checked.

    • +
    • Skip email verification is checked.

    • +
    • Sync learner profile data is checked.

    • +
    • Enable sso id verification is checked.

    • +
    • Backend name: mitxpro-oauth2

    • +
    • Client ID and Client Secret: from record created by configure_instance when you set up MITx Online.

    • +
    • Other settings:

      +
      +
      +
      {

      “AUTHORIZATION_URL”: “http://mitxonline.odl.local:8013/oauth2/authorize/”, +“ACCESS_TOKEN_URL”: “http://<MITXONLINE_GATEWAY_IP>:8013/oauth2/token/”, +“API_ROOT”: “http://<MITXONLINE_GATEWAY_IP>:8013/”

      +
      +
      +

      }

      +
      +

      where MITXONLINE_GATEWAY_IP is the IP from the mitxonline_default network from the first step. Mac users, use host.docker.internal for MITXONLINE_GATEWAY_IP.

      +
    • +
    +
  28. +
  29. Configure Tutor for OAuth2 authentication from MITx Online.

    +
      +
    • Go to http://local.edly.io/admin/oauth2_provider/application/ and either add or edit the edx-oauth-app entry.

    • +
    • Ensure these settings are set:

      +
      +
        +
      • Name: edx-oauth-app

      • +
      • Redirect uris: http://mitxonline.odl.local:8013/login/_private/complete

      • +
      • Client type: Confidential

      • +
      • Authorization grant type: Authorization code

      • +
      • Skip authorization is checked.

      • +
      +
      +
    • +
    • Save Client id and Client secret.

    • +
    +
  30. +
  31. Update your MITx Online .env file. Set OPENEDX_API_CLIENT_ID and OPENEDX_API_CLIENT_SECRET to the values from the record you created or updated in the last step.

  32. +
  33. You should now be able to run some MITx Online management commands to ensure the service worker is set up properly:

    +
      +
    • sync_courserun --all ALL should sync the two test courses (if you made them).

    • +
    • repair_missing_courseware_records should also work.

    • +
    +
  34. +
  35. In the separate browser session from step 12, attempt to log in again. This time, you should be able to log in through MITx Online, and you should be able to get to the edX LMS dashboard. If not, then double-check your provider configuration settings and try again.

    +
      +
    • Unlike devstack, the Tutor instance has an Update button for the provider configuration, so you can just update the record you put in.

    • +
    • If you are still getting “Can’t fetch settings” errors, make sure your Site is set properly - there are three options by default and only one works. (This was typically the problem I had.)

    • +
    +
  36. +
  37. Optionally, log into the LMS Django Admin and make your MITx Online superuser account a superuser there too.

  38. +
+
+
+

Other Notes§

+

Trying to set configuration settings via ``tutor config`` will undo the specialty configuration above. If you need to make changes to the configuration, either manually edit the env/apps/openedx/config/lms.env.yml file or the env/apps/openedx/settings/lms/production.py file and restart your Tutor instance.

+

Make sure your service worker account is active. It’s an easy checkbox to miss.

+

Restarting If you want to rebuild from scratch, make sure you docker image prune. It’s also recommended to remove the Tutor project root folder - tutor config printroot will tell you where that is.

+

Running Multiple Tutor Instances If you want to run more than one Tutor instance, it’s pretty important to specify the project root explicitly or you may end up with one instance trying to use config files from another and things getting confused from there. See the Tutor documentation for this. (A suggestion: configure aliases to the tutor command that run tutor --root=<whatever> so you don’t have to rely on environment variables, especially if you keep multiple terminal sessions going.)

+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/configuration/uwsgi_tuning.html b/configuration/uwsgi_tuning.html new file mode 100644 index 000000000..1d46e793b --- /dev/null +++ b/configuration/uwsgi_tuning.html @@ -0,0 +1,200 @@ + + + + + + + + Setting up uWsgi tuning for MITx Online — MITx Online documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Setting up uWsgi tuning for MITx Online§

+

This setup satisfies the testing to help with tuning as mentioned in this Discusssion Post

+

Largely borrowed from work on OCW studio:

+ +
+

To set up locally:§

+
+

Set up uwsgitop§

+
    +
  1. Install uwsgitop: docker compose run --rm web poetry add uwsgitop

  2. +
  3. Set UWSGI_RELOAD_ON_RSS in your .env to a high value (e.g. 500)

  4. +
  5. Set UWSGI_MAX_REQUESTS in your .env to a high value (e.g. 10000)

  6. +
  7. docker compose build

  8. +
  9. docker compose up

  10. +
  11. In a new terminal window/tab, docker compose exec web uwsgitop /tmp/uwsgi-stats.sock

  12. +
  13. You should see your application’s memory usage without usage. Ready to go.

  14. +
+
+
+

Set up Locust§

+
    +
  1. Install Locust: docker compose run --rm web poetry add locust

  2. +
  3. Add locust to your docker-compose.yml locally, under services:

  4. +
+
locust:
+  image: locustio/locust
+  ports:
+    - "8089:8089"
+  volumes:
+    - ./:/src
+  command: >
+    -f /src/locustfile.py
+
+
+
    +
  1. Add the following to the web block, at the level of, and directly after, build:

  2. +
+
deploy:
+  resources:
+    limits:
+      cpus: "2"
+      memory: "1g"
+
+
+
    +
  1. Add locustfile.py. There is an example file at locustfile.py.example in the root of the repo. cp locustfile.py.example locustfile.py will copy it over as is. Change variables and/or add tests as needed.

  2. +
+
+
+

Put it all together§

+
    +
  1. Run docker-compose build

  2. +
  3. Run docker-compose up

  4. +
  5. You can use locust from http://0.0.0.0:8089/ in a browser

  6. +
  7. You can use uwsgitop in a terminal with docker compose exec web uwsgitop /tmp/uwsgi-stats.sock

  8. +
+
+
+
+

To test:§

+

Coming soon!

+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/flexible_pricing.html b/ecommerce/flexible_pricing.html new file mode 100644 index 000000000..f04de4fd1 --- /dev/null +++ b/ecommerce/flexible_pricing.html @@ -0,0 +1,238 @@ + + + + + + + + Flexible Pricing — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Flexible Pricing§

+

The Flexible Pricing system allows learners to request alternative pricing for MITx Online courses based on their location and annual income. These requests can be made through a customizable form in the Wagtail CMS system.

+

When the learner accesses the Flexible Pricing request form, they will see one of the following:

+
    +
  • If they aren’t logged in, they’ll see a message saying so. Learners must be logged in to request flexibile pricing.

  • +
  • If they haven’t submitted a request, they’ll see the flexible pricing form and will be able to submit a request. On submission, the learner will see a message saying their request has been approved (if it can be approved automatically), or will see a request for more information to be submitted via DocuSign.

  • +
  • If they have submitted a request, they’ll be presented with a status page for their request. The text for approved, denied, and in progress states can be customized.

  • +
+
+

Form Creation§

+

To manually create a Flexible Pricing request form, follow these steps:

+
    +
  1. Navigate to the course page for the course. (Do not click the pencil button to edit, simply select the page itself.)

  2. +
  3. Click the Add Child Page button in the header.

  4. +
  5. You will be presented with the New Flexible Pricing Request Form page. Fill out the form.

    +
      +
    1. The Intro field text is displayed on the form regardless of the state of the request.

    2. +
    3. The Guest text is displayed if the learner isn’t logged in yet.

    4. +
    5. The Application Processing text is displayed if the learner navigates to the form again and their application is still being processed.

    6. +
    7. The Application Approved text is displayed if the learner navigates to the form and their application has been approved.

    8. +
    9. The Application Approved No Discount text is displayed if the learner navigates to the form and their application has been approved, but they’ve been approved for a zero-value tier. (In other words, the learner has exceeded the upper limit of flexible pricing.)

    10. +
    11. The Application Denied text is displayed if the learner navigates to the form and their application has been denied.

    12. +
    13. The Form Fields are the data that the learner must provide to be considered for flexible pricing. Leave this alone - the system will automatically add the proper fields when the form is published.

    14. +
    +
  6. +
  7. Publish the form when you are ready.

  8. +
  9. Navigate back to the course page for the course, and edit the page. Add a link to the flexible pricing form you created in the page content.

  10. +
  11. Publish the course page when you are ready.

  12. +
+

To add the Flexible Pricing form to the Price card on a course page, first get the link to the live form. This can be done on the edit page for the flexible pricing form (as well as in a few other locations within the CMS). Then, add that link to the Link field in the Price card for the course.

+

To generate a Flexible Pricing request form with some reasonable defaults for your courseware object, use the create_finaid_form management command:

+
$ manage.py create_finaid_form [--force] [--slug <slug name here>] [--title <title here>] courseware-readable-id
+
+
+

This command will create an appropriate flexible pricing form for the courseware object:

+
    +
  • If you’ve specified a Program, the form will be located under the program’s page in the CMS.

  • +
  • If you’ve specified a standalone Course, the form will be located under the course’s page in the CMS.

  • +
  • If you’ve specified a Course that is in a Program, the form will be located under the program’s page in the CMS, unless the --force option is specified. In that case, the form will be under the course page.

  • +
+

You can customize the title and slug using their respective options. By default, the system will use the object’s title to generate a form title and slug.

+
+
+

Processing Submitted Request§

+

TBD

+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/index.html b/ecommerce/index.html new file mode 100644 index 000000000..1edeb7904 --- /dev/null +++ b/ecommerce/index.html @@ -0,0 +1,217 @@ + + + + + + + + Ecommerce — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/ecommerce/overview.html b/ecommerce/overview.html new file mode 100644 index 000000000..bbcfed437 --- /dev/null +++ b/ecommerce/overview.html @@ -0,0 +1,233 @@ + + + + + + + + Overview — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Overview§

+
+

Goals§

+

We will be creating a robust ecommerce implementation, incorporating learnings from the last several years to implement it in a scalable and reusable way. A good reference point is this guide on Pythonic SOLID Principle. We should also strongly strive towards keeping coupling between the subsystems proposed here to a minimum or at least limited in surface area. +Core

+

The core of the ecommerce system should be simple enough to configure and operate but support enough functionality to serve a majority of our use cases. Users should be able to see programs or course runs, select them for purchase, and make a payment.

+
+
+

Prior Art§

+

We have a few implementations of ecommerce we’ve created over the years:

+
+

MicroMasters§

+

The MicroMasters implementation is highly specialized, particularly around financial aid programs where each learner gets custom pricing. Incorporating this level of complexity into the core of the ecommerce system is not something we want to do, but we should carve out some options to extend the system in the future without implementing it in the core system.

+
+
+

xPro§

+

xPro ecommerce was implemented based on our experiences implementing ecommerce in MicroMasters. A good amount of planning went into this implementation, although it also has some specializations we wouldn’t be using in MITx Online such as a vouchers system. We will probably borrow heavily from the core designs that were proved out here.

+
+
+
+

Core Systems§

+

Ecommerce is actually a combination of 3 discernable subsystems that often get muddled together: products, orders, and payment. See the high-level diagram below to understand the pieces of data and operations that happen.

+
+ flowchart TB + product_subsystem(Product Subsystem) + basket_subsystem(Basket Subsystem) + order_subsystem(Order Subsystem) + discount_subsystem(Discount Subsystem) + payment_subsystem(Payment Subsystem) + + cybersource(CyberSource) + + basket_subsystem --> order_subsystem + discount_subsystem --> order_subsystem + order_subsystem & basket_subsystem & discount_subsystem --> product_subsystem + order_subsystem <--> payment_subsystem + payment_subsystem <--> cybersource + +
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/subsystems/basket.html b/ecommerce/subsystems/basket.html new file mode 100644 index 000000000..60caf57b6 --- /dev/null +++ b/ecommerce/subsystems/basket.html @@ -0,0 +1,219 @@ + + + + + + + + Basket Subsystem — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Basket Subsystem§

+

This tracks products intended to be purchased, often providing some additional state such as which runs under a program a user is purchasing.

+

A simple schema for this would be:

+
class Basket(TimestampedModel):
+    """Represents a User's basket."""
+
+    user = models.OneToOneField(settings.AUTH_USER_MODEL)
+
+class BasketItem(TimestampedModel):
+    """Represents one or more products in a user's basket."""
+
+    product = models.ForeignKey(Product)
+    basket = models.ForeignKey(Basket)
+    quantity = models.PositiveIntegerField()
+
+
+
+

APIs§

+
    +
  • GET  /api/v0/basket/ -> get the current basket state

  • +
  • POST /api/v0/basket/ -> update the basket state

  • +
+
+
+

Notes§

+

The implementation of this would use the discount subsystem to calculate the discounted prices, those values would be returned in the API for the frontend to use for display purposes.

+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/subsystems/discount.html b/ecommerce/subsystems/discount.html new file mode 100644 index 000000000..bd166c2aa --- /dev/null +++ b/ecommerce/subsystems/discount.html @@ -0,0 +1,284 @@ + + + + + + + + Discount Subsystem — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Discount Subsystem§

+

Discounts will need to be provided on occasion, these give the user a reduced price for some or all products. Treating this as a discount system and not necessarily a coupon system (e.g. a coupon is a kind of discount) is a good way to frame this approach.

+

The discount system would support discounts of multiple types. We’ve done discounts a lot of different ways before so we need to balance out flexibility against keeping complexity down. Each discount would be associated with a certain Product.

+

A discount may optionally be pre-associated with a User so that it can be automatically applied on checkout.

+

Discounts should only be computed on the backend, some of our ecommerce implementations have computed the discount on the frontend and we want to avoid this going forward.

+
+

Discount Types§

+

Discount types would track how the discounted price is computed, some examples/ideas: +percent-off: a percentage off the original price +dollars-off: a fixed dollar reduction in price (e.g. $30 off) +fixed-price: the price is discounted to the fixed price (e.g. a product would cost $100 regardless of what the original price was)

+
+
+

Redemption Types§

+

There may be a few different ways we want to track discount usage, for example:

+

one-time: the discount can only be used once by anyone +one-time-per-user: the discount can be used once per user +unlimited: the discount can be used any number of times

+
+
+

Data Models§

+
class Discount(TimestampedModel):
+    """Discount model"""
+    amount = models.DecimalField(
+        decimal_places=5,
+        max_digits=20,
+    )
+    automatic = models.BooleanField(default=False)
+    discount_type = models.CharField(
+        choices=DISCOUNT_TYPES, max_length=30
+    )
+    redemption_type = models.CharField(
+        choices=REDEMPTION_TYPES, max_length=30
+    )
+    max_redemptions = models.PositiveIntegerField(null=True)
+
+class UserDiscount(TimestampedModel):
+    """pre-assignment for a discount to a user"""
+    discount = models.ForeignKey(Discount)
+    user = models.ForeignKey(User)
+
+
+
+
+

Implementation Proposal§

+

Rather than codifying the discount logic in a complicated computation function, discount types can be implemented by abstracting the logic around discounts into a registry-driven discount factory like this:

+
import abc
+from dataclasses import dataclass
+
+
+@dataclass
+class DiscountType(abc.ABC):
+    _CLASSES = {}
+
+    discount: Discount
+
+    # see https://www.python.org/dev/peps/pep-0487/
+    def __init_subclass__(cls, *, discount_type, **kwargs):
+        super().__init_subclass__(**kwargs)
+
+        if discount_type in _CLASSES:
+            raise TypeError(f"{discount_type} already defined for DiscountType")
+
+        cls.discount_type = discount_type
+        cls._CLASSES[discount_type] = cls
+
+    @classmethod
+    def for_discount(cls, discount: Discount):
+        DiscountTypeCls = cls._CLASSES[discount.discount_type]
+
+        return DiscountTypeCls(discount)
+
+    def get_product_price(self, product: Product):
+        return self.get_product_version_price(product.latest_version)
+
+    @abc.abstractmethod
+    def get_product_version_price(self, product_version: ProductVersion):
+        ...
+
+class PercentDiscount(DiscountType, discount_type=Discount.PERCENT_DISCOUNT):
+
+    def get_product_version_price(self, product_version: ProductVersion):
+        return product_version.price * self.discount.amount
+
+class FixedPriceDiscount(DiscountType, discount_type=Discount.PERCENT_DISCOUNT):
+
+    def get_product_version_price(self, product_version: ProductVersion):
+        return self.discount.amount  # the amount here is the fixed price
+
+
+

With this implementation, prices before ordering would use get_product_price, whereas the receipt service would use get_product_version_price on the purchased versions. This makes it far more scalable to introduce new discount types without having to refactor existing code.

+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/subsystems/index.html b/ecommerce/subsystems/index.html new file mode 100644 index 000000000..464b563b8 --- /dev/null +++ b/ecommerce/subsystems/index.html @@ -0,0 +1,223 @@ + + + + + + + + Subsystems — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/ecommerce/subsystems/order.html b/ecommerce/subsystems/order.html new file mode 100644 index 000000000..a40ba73da --- /dev/null +++ b/ecommerce/subsystems/order.html @@ -0,0 +1,220 @@ + + + + + + + + Order Subsystem — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Order Subsystem§

+

Orders represent a payment for some kind of product(s), these products will typically be either Programs or Course Runs. An order is marked as unfulfilled initially and then marked as fulfilled once a payment is completed. An order can fail or be refunded.

+
+

Data Model§

+
class Order(TimestampedModel):
+    """An order containing information for a purchase."""
+    status = models.CharField()
+    purchaser = models.ForeignKey(settings.AUTH_USER_MODEL)
+    total_price_paid = models.DecimalField()
+
+class Line(TimestampedModel):
+    """A line in an Order."""
+
+    order = models.ForeignKey(Order)
+    product_version = models.ForeignKey(ProductVersion)
+    quantity = models.PositiveIntegerField()
+
+class Transaction(TimestampedModel):
+    """A transaction on an order, generally a payment but can also cover refunds"""
+    order = models.ForeignKey(Order)
+    amount = models.DecimalField(
+        decimal_places=5,
+        max_digits=20,
+    )
+    data = models.JSONField()
+
+
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/subsystems/payment.html b/ecommerce/subsystems/payment.html new file mode 100644 index 000000000..4eeae4e14 --- /dev/null +++ b/ecommerce/subsystems/payment.html @@ -0,0 +1,194 @@ + + + + + + + + Payment Subsystem — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Payment Subsystem§

+

The payment subsystem takes unfulfilled orders, takes the user through payment completion, and finally marks the order as fulfilled. We historically and for the foreseeable future use CyberSource, but this should be strongly decoupled from the rest of the ecommerce system and made pluggable for future flexibility. This system would also be responsible for any webhooks/callbacks that the payment processor makes to us.

+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/subsystems/product.html b/ecommerce/subsystems/product.html new file mode 100644 index 000000000..2a1177450 --- /dev/null +++ b/ecommerce/subsystems/product.html @@ -0,0 +1,216 @@ + + + + + + + + Product Subsystem — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Product Subsystem§

+

The product subsystem is responsible for tracking all product-related data. Purchasable products are typically Programs and Course Runs. Pricing information is tracked as immutable data for the sake of historically accurate pricing for orders.

+
+

Data Models§

+
@reversion.register(exclude=("content_type", "object_id", "created_on", "updated_on"))
+class Product(TimestampedModel):
+    """
+    Representation of a purchasable product. There is a GenericForeignKey to a Course or Program.
+    """
+    content_type = models.ForeignKey(ContentType)
+    object_id = models.PositiveIntegerField()
+    content_object = GenericForeignKey("content_type", "object_id")
+
+
+

This will utilize django-reversion to version product data.

+
+
+

APIs§

+

The API that would be primarily needed would be one to read back product data. It is presumed that data entry is done through django-admin:

+
    +
  • GET /api/v0/products/ -> returns a paginated list of products

  • +
  • GET /api/v0/products/1/ -> returns a single product

  • +
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/ecommerce/subsystems/reporting.html b/ecommerce/subsystems/reporting.html new file mode 100644 index 000000000..76dadcc4c --- /dev/null +++ b/ecommerce/subsystems/reporting.html @@ -0,0 +1,205 @@ + + + + + + + + Reporting Subsystem — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ +
+

Reporting Subsystem§

+

OL marketing and finance team need to include financial data in their reporting, so they can evaluate and plan campaigns, reconcile accounts, and payout royalties. As a guiding principle, we want to be able to report on any data that is collected by the ecommerce system, but avoid supplementing the data with external considerations (such as marketing costs, or payout rates).

+
+

Use Cases§

+
+

MicroMasters§

+

Most of MicroMasters programs and courses function the same as any other MITx course, they wouldn’t otherwise be treated any differently except for marketing purposes, which are out of scope for this document if not MITx Online altogether.

+
+
+

DEDP§

+

The DEDP program as it is implemented currently is a special case when it comes to ecommerce - DEDP currently supports financial aid. This essentially amounts to a discount to a lower fixed rate. There are a few tiers of financial aid discounts available, but this is easily handled with a couple of preconfigured discounts. We’d need a UI somewhere that enables staff to assign a discount to a particular learner. That discount would then automatically apply to courses within that program, the same as any other program-scoped discount.

+
+
+
+ +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 000000000..95c413619 --- /dev/null +++ b/genindex.html @@ -0,0 +1,140 @@ + + + + + + + Index — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ + +

Index

+ +
+ +
+ + +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 000000000..b626b7e32 --- /dev/null +++ b/index.html @@ -0,0 +1,201 @@ + + + + + + + + Welcome to MITx Online Documentation’s documentation! — MITx Online documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..2012f17cd650619870f70d1e02cf2e7278bcf17b GIT binary patch literal 828 zcmV-C1H=3yAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkSNmO_s zPi|~!Ze%ZEX>4U6X>%ZB zZ*6dLWpi_7WFU2OX>MmAdTeQ8E(&FG^j0V-nN(#xvY;+pQ=vqgTQSi;J2m%TH};4@eOttQ`VzM&i3@61yqztg;@>x zsFBS_jOsbGq9Ei9xA&ybz8Db&UnHhZ&s!Zkuiav3+i{`%-U!XI z0jbHM(`p1|1kQPqRb2b`ho>`IvxYK2E3e-W0`{7bYOk9;Z5kjI7o@PCd8-S_YjMnV zgMgeXI}|F#Przg$s$>swB|VOmym>L_1{k62FnM7aX9&tji|~N<<7){P1R|XrS&O5t z!UK+A<%NWK)H~U6RVI2v%KN5wTT@PGnR7Cdhomr=V;eV{V7I#r3XXJDYg0!+ekDLN zkL#0}nquNQZ=UgK{$Vw=wPHe6T5(Z^_OjzW@)|eS1)`_E(VU7w&^>ewzt}fNlA~Yc zfmTX0b2iFxvCE}yK`Gm!JEWhDh&*C%!lU5D_1rA?4V>w&v15?dNm|+E(!TmC+PG?j z9i5Csa)2+P7RLxZSP>@1U`?P<4F|_UB@BS0l$WG$tRA;=}R4Yi)O{f7<{3 z`705LEAJL9Cz+=FfjSk%X1qpO7h)ax7jefA&V G^lzmdD3e+M literal 0 HcmV?d00001 diff --git a/search.html b/search.html new file mode 100644 index 000000000..4de604dea --- /dev/null +++ b/search.html @@ -0,0 +1,163 @@ + + + + + + + Search — MITx Online documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+ +
+ +
+
+
+
+ + + + +
+
+
+
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + + +
+ + +
+ + +
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 000000000..c94215d2a --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"APIs": [[24, "apis"], [29, "apis"]], "Add /etc/hosts alias for Open edX": [[17, "add-etc-hosts-alias-for-open-edx"]], "Authentication": [[0, "authentication"]], "Basket Subsystem": [[24, "basket-subsystem"]], "Batch Generating Codes": [[8, "batch-generating-codes"]], "Checks": [[4, "checks"]], "Clone edx/devstack": [[17, "clone-edx-devstack"]], "Configuration": [[16, "configuration"]], "Configure HubSpot": [[15, "configure-hubspot"]], "Configure Logout": [[17, "configure-logout"]], "Configure MITx Online as a OAuth provider for Open edX": [[17, "configure-mitx-online-as-a-oauth-provider-for-open-edx"]], "Configure Open edX": [[17, "configure-open-edx"]], "Configure Open edX to support OAuth2 authentication from MITx Online": [[17, "configure-open-edx-to-support-oauth2-authentication-from-mitx-online"]], "Configure Open edX user and token for use with MITx Online management commands": [[17, "configure-open-edx-user-and-token-for-use-with-mitx-online-management-commands"]], "Configure eCommerce": [[14, "configure-ecommerce"]], "Contents:": [[10, null], [31, null]], "Core Systems": [[23, "core-systems"]], "DEDP": [[30, "dedp"]], "Data Model": [[27, "data-model"]], "Data Models": [[25, "data-models"], [29, "data-models"]], "Discount Subsystem": [[25, "discount-subsystem"]], "Discount Types": [[25, "discount-types"]], "Ecommerce": [[22, "ecommerce"]], "Example": [[9, "example"]], "Flexible Pricing": [[21, "flexible-pricing"]], "Form Creation": [[21, "form-creation"]], "Goals": [[23, "goals"]], "Implementation Proposal": [[25, "implementation-proposal"]], "In MITx Online": [[14, "in-mitx-online"]], "In Open edX": [[14, "in-open-edx"]], "Indices and tables": [[10, "indices-and-tables"], [31, "indices-and-tables"]], "Install from local Build": [[17, "install-from-local-build"], [17, "id2"]], "Install mitxpro-openedx-extensions in LMS": [[17, "install-mitxpro-openedx-extensions-in-lms"]], "Install social-auth-mitxpro in LMS": [[17, "install-social-auth-mitxpro-in-lms"]], "Install via pip": [[17, "install-via-pip"], [17, "id1"]], "Local Open edX Tutor and MITx Online Deployment": [[19, "local-open-edx-tutor-and-mitx-online-deployment"]], "MITx Online Commands": [[10, "mitx-online-commands"]], "MITx Online Quick Start": [[18, "mitx-online-quick-start"]], "MITx Online Setup": [[19, "mitx-online-setup"]], "MicroMasters": [[23, "micromasters"], [30, "micromasters"]], "Notes": [[18, "notes"], [24, "notes"]], "Options": [[1, "options"], [2, "options"], [3, "options"], [4, "options"], [5, "options"], [6, "options"], [7, "options"], [8, "options"], [9, "options"], [11, "options"], [12, "options"], [13, "options"]], "Order Subsystem": [[27, "order-subsystem"]], "Other Notes": [[19, "other-notes"]], "Output": [[8, "output"]], "Overview": [[23, "overview"]], "Payment Subsystem": [[28, "payment-subsystem"]], "Performing a Quick Start": [[18, "performing-a-quick-start"]], "Preliminary Steps": [[19, "preliminary-steps"]], "Prior Art": [[23, "prior-art"]], "Processing Submitted Request": [[21, "processing-submitted-request"]], "Product Subsystem": [[29, "product-subsystem"]], "Pull latest images and run provision": [[17, "pull-latest-images-and-run-provision"]], "Put it all together": [[20, "put-it-all-together"]], "Redemption Types": [[25, "redemption-types"]], "Reporting Subsystem": [[30, "reporting-subsystem"]], "Results": [[18, "results"]], "Set Up MITx Online eCommerce Config": [[14, "set-up-mitx-online-ecommerce-config"]], "Set Up a Course": [[14, "set-up-a-course"]], "Set up Locust": [[20, "set-up-locust"]], "Set up uwsgitop": [[20, "set-up-uwsgitop"]], "Setting Up a Product": [[14, "setting-up-a-product"]], "Setting up uWsgi tuning for MITx Online": [[20, "setting-up-uwsgi-tuning-for-mitx-online"]], "Setup Open edX Devstack": [[17, "setup-open-edx-devstack"]], "Setup social auth": [[17, "setup-social-auth"]], "Start your servers": [[17, "start-your-servers"]], "Stop your servers": [[17, "stop-your-servers"]], "Subsystems": [[26, "subsystems"]], "Syntax": [[1, "syntax"], [2, "syntax"], [3, "syntax"], [4, "syntax"], [5, "syntax"], [6, "syntax"], [7, "syntax"], [8, "syntax"], [9, "syntax"], [11, "syntax"], [12, "syntax"], [13, "syntax"]], "Testing Checkout": [[14, "testing-checkout"]], "To set up locally:": [[20, "to-set-up-locally"]], "To test:": [[20, "to-test"]], "Tutor Setup, Part One": [[19, "tutor-setup-part-one"]], "Tutor Setup, Part Two": [[19, "tutor-setup-part-two"]], "Use Cases": [[30, "use-cases"]], "Welcome to MITx Online Documentation\u2019s documentation!": [[31, "welcome-to-mitx-online-documentation-s-documentation"]], "check_program_requirements": [[1, "check-program-requirements"]], "configure_instance": [[2, "configure-instance"]], "configure_tiers": [[3, "configure-tiers"]], "create_courseware": [[4, "create-courseware"]], "create_courseware_page": [[5, "create-courseware-page"]], "create_product": [[6, "create-product"]], "create_user": [[7, "create-user"]], "generate_discount_code": [[8, "generate-discount-code"]], "import_courserun": [[9, "import-courserun"]], "refund_fulfilled_order": [[11, "refund-fulfilled-order"]], "regenerate_edx_auth_tokens": [[12, "regenerate-edx-auth-tokens"]], "resolve_pending_order": [[13, "resolve-pending-order"]], "xPro": [[23, "xpro"]]}, "docnames": ["authentication", "commands/check_program_requirements", "commands/configure_instance", "commands/configure_tiers", "commands/create_courseware", "commands/create_courseware_page", "commands/create_product", "commands/create_user", "commands/generate_discount_code", "commands/import_courserun", "commands/index", "commands/refund_fulfilled_order", "commands/regenerate_edx_auth_tokens", "commands/resolve_pending_order", "configuration/ecommerce", "configuration/hubspot", "configuration/index", "configuration/open_edx", "configuration/quickstart", "configuration/tutor", "configuration/uwsgi_tuning", "ecommerce/flexible_pricing", "ecommerce/index", "ecommerce/overview", "ecommerce/subsystems/basket", "ecommerce/subsystems/discount", "ecommerce/subsystems/index", "ecommerce/subsystems/order", "ecommerce/subsystems/payment", "ecommerce/subsystems/product", "ecommerce/subsystems/reporting", "index"], "envversion": {"sphinx": 61, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["authentication.rst", "commands/check_program_requirements.rst", "commands/configure_instance.rst", "commands/configure_tiers.rst", "commands/create_courseware.rst", "commands/create_courseware_page.rst", "commands/create_product.rst", "commands/create_user.rst", "commands/generate_discount_code.rst", "commands/import_courserun.rst", "commands/index.rst", "commands/refund_fulfilled_order.rst", "commands/regenerate_edx_auth_tokens.rst", "commands/resolve_pending_order.rst", "configuration/ecommerce.rst", "configuration/hubspot.rst", "configuration/index.rst", "configuration/open_edx.rst", "configuration/quickstart.rst", "configuration/tutor.rst", "configuration/uwsgi_tuning.rst", "ecommerce/flexible_pricing.rst", "ecommerce/index.rst", "ecommerce/overview.rst", "ecommerce/subsystems/basket.rst", "ecommerce/subsystems/discount.rst", "ecommerce/subsystems/index.rst", "ecommerce/subsystems/order.rst", "ecommerce/subsystems/payment.rst", "ecommerce/subsystems/product.rst", "ecommerce/subsystems/reporting.rst", "index.rst"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"": [2, 3, 4, 7, 8, 9, 11, 12, 13, 14, 17, 19, 20, 21, 24, 27], "0": [3, 8, 17, 19, 20], "000": 3, "0487": 25, "1": [13, 14, 17, 19, 29], "10": 19, "100": [8, 13, 25], "1000": 9, "10000": 20, "101": [18, 19], "11": 19, "111": 14, "12": 19, "123": 14, "127": [17, 19], "13": 8, "14": 14, "16": 19, "172": 17, "18000": 17, "19": 17, "19817792": 15, "1g": 20, "1t2023": 9, "2": [14, 17, 20], "20": [25, 27], "2030": 19, "25": 3, "3": [19, 23], "30": 25, "300": 19, "37": 8, "378282246310005": 14, "3t2022": 14, "4": 14, "4111111111111111": 14, "4242424242424242": 14, "5": [25, 27], "50": [3, 8], "500": [3, 19, 20], "5105105105105100": 14, "5555555555554444": 14, "6011111111111117": 14, "650": 3, "75": 3, "750": 3, "750x": 14, "8": 19, "8013": [17, 19], "8089": 20, "8601": 8, "9": 19, "999": [14, 18], "A": [1, 14, 19, 23, 24, 25, 27], "As": [19, 30], "At": [11, 14, 18, 19], "By": [1, 5, 6, 21], "For": [2, 4, 6, 8, 14, 17, 18, 19], "If": [1, 2, 3, 4, 5, 9, 11, 12, 14, 17, 18, 19, 21], "In": [2, 3, 12, 15, 17, 18, 19, 20, 21], "It": [4, 18, 19, 29], "No": 21, "On": [19, 21], "One": [2, 4, 8, 16], "Or": 9, "That": 30, "The": [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14, 17, 18, 19, 21, 23, 24, 25, 28, 29, 30], "Then": [19, 21], "There": [17, 19, 20, 25, 29, 30], "These": [3, 14, 18, 19, 21], "To": [9, 14, 17, 18, 19, 21], "With": 25, "_": 9, "__init_subclass__": 25, "_class": 25, "_if": 9, "_privat": [17, 19], "abbrev": 3, "abbrevi": 3, "abc": 25, "abl": [14, 19, 21, 23, 30], "about": [5, 18], "abov": [14, 19], "abstract": 25, "abstractmethod": 25, "accept": [13, 14], "access": [12, 17, 19, 21], "access_token_url": [17, 19], "accesstoken": [17, 19], "accordingli": 11, "account": [2, 7, 14, 15, 17, 18, 19, 30], "accur": 29, "activ": [6, 8, 9, 14, 19], "actual": [3, 14, 17, 19, 23], "ad": [4, 5, 14, 18, 20], "add": [2, 4, 8, 14, 15, 18, 19, 20, 21], "addit": [3, 18, 24], "addition": [2, 14], "additionali": 2, "address": [7, 12], "adjust": [9, 11, 14, 18, 19], "admin": [9, 14, 17, 19, 29], "after": [4, 9, 20], "afterward": [11, 18], "again": [17, 19, 21], "against": 25, "aid": [3, 23, 30], "alias": 19, "all": [1, 3, 8, 9, 13, 17, 19, 25, 29], "allow": [2, 14, 21], "allow_public_account_cr": 17, "alon": 21, "alreadi": [2, 3, 8, 9, 11, 14, 17, 19, 25], "also": [1, 4, 5, 9, 12, 14, 17, 19, 23, 27, 28], "altern": [8, 14, 21], "although": 23, "altogeth": 30, "american": 14, "amex": 14, "amount": [3, 8, 23, 25, 27, 30], "an": [1, 4, 7, 8, 9, 12, 13, 14, 17, 18, 19, 20, 21, 27], "ani": [2, 3, 4, 8, 9, 11, 14, 25, 28, 30], "annual": 21, "anoth": 19, "anyon": 25, "anyth": [1, 12, 14, 19], "api": [1, 9, 14, 19, 26], "api_root": [17, 19], "app": [14, 17, 19, 20], "appear": 14, "append": [8, 19], "appli": [4, 9, 19, 25, 30], "applic": [2, 9, 17, 18, 19, 20, 21], "approach": 25, "appropri": [12, 18, 21], "approv": 21, "ar": [2, 3, 5, 6, 8, 12, 14, 17, 18, 19, 21, 29, 30], "arbitrari": 17, "area": 23, "aren": [14, 19, 21], "arm": 19, "around": [14, 19, 23, 25], "arrai": 17, "art": 22, "ask": [14, 15], "assign": [1, 4, 9, 25, 30], "assist": [3, 18], "associ": [4, 9, 11, 25], "assum": [4, 8], "attempt": [9, 18, 19], "attent": 19, "audit": [7, 11], "auth": [16, 19], "auth_user_model": [24, 27], "authent": [12, 16, 19, 31], "authentication_backend": 19, "authn": 19, "author": [17, 19], "authorization_url": [17, 19], "automat": [8, 21, 25, 30], "avail": [19, 30], "avoid": [14, 25, 30], "back": [21, 29], "backend": [17, 19, 25], "balanc": 25, "base": [9, 19, 21, 23], "basi": 9, "basic": [4, 5, 9, 18], "basket": [22, 26], "basketitem": 24, "batch": [9, 10], "becaus": 19, "becom": 8, "been": [14, 19, 21], "befor": [3, 4, 14, 25], "begin": [14, 19], "being": [8, 19, 21], "belong": 9, "below": [3, 18, 23], "best": [4, 14, 19], "between": [8, 23], "beyond": 14, "black": 19, "blank": 18, "block": [19, 20], "boilerpl": 18, "booleanfield": 25, "bootstrap": [14, 19], "borrow": [20, 23], "both": [1, 4, 14, 17, 19], "bounc": 19, "break": 19, "bring": 14, "brows": 19, "browser": [19, 20], "build": [19, 20], "bump": 17, "busi": 14, "button": [14, 19, 21], "caddi": 2, "calcul": 24, "call": [1, 9, 11, 12], "callback": 28, "campaign": 30, "can": [1, 3, 4, 8, 9, 11, 13, 14, 17, 18, 19, 20, 21, 25, 27, 30], "cancel": 13, "card": [14, 21], "care": 18, "cart": 14, "carv": 23, "case": [2, 9, 14, 18, 21, 23, 26], "cd": [17, 19], "center": 14, "certain": 25, "chang": [3, 11, 17, 19, 20], "charact": 8, "charfield": [25, 27], "charg": 14, "check": [1, 9, 10, 14, 17, 19], "check_program_for_orphan": 1, "check_program_requir": [10, 31], "checkbox": [17, 19], "checkout": [17, 25], "child": [14, 21], "choic": 25, "choos": 14, "chooser": 14, "cl": 25, "clariti": 14, "class": [24, 25, 27, 29], "classmethod": 25, "clean": 11, "click": [19, 21], "client": [2, 17, 18, 19], "clog": 1, "clone": 19, "close": 19, "cm": [5, 9, 14, 17, 18, 21], "cname": 19, "code": [3, 7, 10, 13, 14, 17, 18, 19, 25], "codifi": 25, "collect": 30, "com": [14, 17], "combin": [1, 23], "come": [11, 14, 19, 20, 30], "command": [2, 3, 4, 8, 9, 11, 12, 16, 18, 19, 20, 21, 31], "common": 3, "commun": 18, "compat": 19, "complet": [17, 18, 19, 27, 28], "complex": [23, 25], "complic": 25, "compos": [17, 19, 20], "comps": 19, "comput": 25, "confidenti": [17, 19], "config": [16, 17, 19], "configur": [2, 3, 9, 18, 19, 23, 31], "configure_inst": [10, 18, 19, 31], "configure_ti": [10, 18, 31], "configure_wagtail": [18, 19], "confus": 19, "connect": 15, "consid": 21, "consider": 30, "consist": 19, "contain": [4, 17, 19, 27], "content": [14, 18, 21], "content_object": 29, "content_typ": 29, "contenttyp": 29, "convert": 19, "copi": [14, 17, 20], "core": 22, "correspond": [2, 18], "cost": [25, 30], "count": 8, "countri": [7, 17, 18], "countrycod": 7, "coupl": [14, 18, 19, 23, 30], "coupon": 25, "cours": [1, 2, 3, 4, 5, 6, 7, 9, 11, 16, 18, 19, 21, 23, 27, 29, 30], "course_detail": 9, "courseid": 7, "courserun": [4, 6, 9], "coursewar": [4, 5, 6, 9, 14, 18, 21], "cover": 27, "cp": 20, "cpu": 20, "creat": [2, 3, 4, 5, 6, 7, 8, 9, 14, 17, 18, 19, 21, 23], "create_coursewar": [3, 9, 10, 18, 31], "create_courseware_pag": [9, 10, 18, 31], "create_finaid_form": [18, 21], "create_product": [10, 18, 31], "create_run": 4, "create_us": [10, 18, 31], "created_on": 29, "createsuperus": 18, "createus": 19, "creation": [2, 4, 22], "credenti": [14, 18], "credit": 14, "criteria": 1, "csv": [3, 8], "current": [3, 4, 14, 24, 30], "custom": [18, 21, 23], "customiz": 21, "cvn": 14, "cybersourc": [11, 13, 14, 28], "d": [2, 6, 19, 30], "dash": 8, "dashboard": 19, "data": [9, 14, 17, 19, 21, 23, 26, 30], "databas": 9, "dataclass": 25, "date": [4, 8, 9, 14, 19], "dc": 19, "decimal_plac": [25, 27], "decimalfield": [25, 27], "decoupl": 28, "dedp": [1, 3, 9, 18], "def": 25, "default": [1, 2, 3, 4, 5, 6, 7, 8, 9, 14, 17, 18, 19, 21, 25], "defin": [8, 15, 25], "delet": 12, "demo": [2, 18], "demo_cours": [2, 18, 19], "demonstr": [2, 18, 19], "demox": [2, 18, 19], "deni": 21, "depart": [4, 9], "department_nam": [4, 9], "depend": [9, 13, 14, 17, 19], "deploi": 20, "deploy": [2, 16, 31], "dept": [4, 9], "deriv": 17, "describ": 19, "descript": [6, 9, 14], "design": 23, "detail": [4, 18], "determin": 6, "dev": [2, 15, 17, 19, 25], "develop": [14, 15, 17, 19], "devstack": [14, 16, 18, 19], "diagram": 23, "dialog": [17, 19], "differ": [14, 17, 25, 30], "digit": 14, "directli": 20, "directori": 17, "disabl": [2, 18, 19], "discern": 23, "discount": [3, 8, 18, 21, 22, 24, 26, 30], "discount_typ": 25, "discounttyp": 25, "discounttypecl": 25, "discov": 14, "discusss": 20, "displai": [7, 14, 18, 21, 24], "displaynam": 7, "django": [9, 14, 17, 19, 29], "do": [4, 9, 12, 14, 18, 19, 21, 23], "docker": [2, 17, 19, 20], "document": [14, 18, 19, 30], "docusign": 21, "doe": [1, 9, 11, 14, 18, 19], "doesn": [4, 9, 12, 17, 18, 19], "dollar": [3, 8, 25], "domain": 19, "don": [2, 3, 8, 9, 18, 19], "done": [21, 25, 29], "dont": [2, 18], "doubl": [14, 19], "down": 25, "draft": 5, "driven": 25, "e": [1, 9, 13, 19, 20, 25], "e2": [18, 19], "each": [9, 14, 18, 23, 25], "earlier": 14, "easi": [6, 19], "easili": 30, "ebc": 14, "ebc2": 14, "ebc2test": 14, "ecommerc": [16, 23, 25, 28, 30, 31], "ecommerce_default_payment_gatewai": 14, "econom": 9, "edit": [2, 14, 19, 21], "edli": [2, 19], "edu": 14, "edx": [2, 4, 9, 12, 16, 18, 31], "edx_hostnam": 17, "edxapp": 17, "either": [1, 2, 9, 12, 13, 14, 18, 19, 27], "elect": 4, "els": 14, "elsewher": 14, "email": [7, 12, 14, 18, 19], "empti": 9, "enabl": [17, 19, 30], "enable_authn_microfrontend": 19, "enable_combined_login_registr": 17, "enable_oauth2_provid": 17, "enable_third_party_auth": [17, 19], "end": [4, 9, 14, 18, 19], "enough": 23, "enrol": [2, 4, 7, 9, 11, 12, 14, 18], "ensur": [4, 17, 19], "enter": 18, "enterpris": 14, "entir": 19, "entri": [17, 19, 29], "env": [14, 15, 17, 19, 20], "environ": [14, 18, 19], "envrion": 15, "error": [1, 3, 19], "especi": [18, 19], "essenc": 12, "essenti": 30, "etc": 19, "evalu": [14, 30], "ever": 19, "everyth": 19, "ex": 14, "exampl": [8, 10, 14, 17, 19, 20, 25], "exceed": 21, "except": 30, "exclud": 29, "exec": 20, "exist": [1, 2, 3, 4, 8, 9, 17, 18, 19, 25], "expect": [11, 19], "experi": 23, "expir": [8, 14, 19], "explicitli": [8, 19], "export": 19, "express": 14, "extend": 23, "extens": 19, "extern": 30, "external_mitx_online_host": 17, "f": [4, 20, 25], "fa": 3, "factori": 25, "fail": [9, 14, 27], "fairli": 14, "fals": [9, 19, 25], "far": 25, "fashion": 9, "favor": 19, "featur": [5, 14, 17, 19], "fetch": 19, "few": [9, 18, 21, 23, 25, 30], "field": [3, 5, 14, 17, 21], "file": [3, 8, 14, 15, 17, 19, 20], "fill": [14, 17, 21], "final": [4, 19, 28], "financ": 30, "financi": [3, 18, 23, 30], "find": [9, 14, 19], "first": [7, 18, 19, 21], "firstnam": 7, "fit": 1, "fix": [8, 25, 30], "fixedpricediscount": 25, "flag": [4, 9, 18], "flexibil": 21, "flexibl": [22, 25, 28, 31], "flip": 19, "folder": [14, 19], "follow": [3, 4, 8, 9, 15, 17, 18, 19, 20, 21], "for_discount": 25, "forc": [4, 21], "foreignkei": [24, 25, 27, 29], "forese": 28, "form": [14, 17, 18, 19, 22], "format": [3, 8], "former": 19, "forward": 25, "found": [13, 14, 17], "four": 14, "frame": 25, "fresh": [2, 18], "from": [4, 9, 11, 14, 16, 19, 20, 23, 25, 28], "front": 4, "frontend": [24, 25], "fulfil": [11, 13, 27, 28], "full": 17, "fulli": 19, "function": [23, 25, 30], "furthermor": 9, "futur": [14, 23, 28], "g": [1, 9, 13, 20, 25], "gatewai": [2, 11, 14, 17, 19], "gateway_ip": 17, "gener": [10, 12, 14, 19, 21, 27], "generate_discount_cod": [10, 31], "genericforeignkei": 29, "get": [9, 11, 12, 18, 19, 21, 23, 24, 29], "get_product_pric": 25, "get_product_version_pric": 25, "git": 17, "github": [17, 19], "give": [14, 19, 25], "given": [5, 6, 14], "go": [17, 19, 20, 25], "goal": 22, "good": [14, 23, 25], "grade": 12, "grant": [17, 19], "grep": 19, "guest": 21, "guid": [19, 23, 30], "gz": 17, "ha": [1, 9, 14, 18, 19, 21, 23], "had": [3, 19], "half": 18, "hand": [5, 18], "handi": 19, "handl": [9, 30], "happen": 23, "have": [3, 5, 9, 11, 12, 14, 17, 19, 21, 23, 25], "haven": [14, 18, 19, 21], "header": [3, 21], "heavili": 23, "help": [18, 20], "here": [9, 14, 17, 19, 21, 23, 25], "hero": 14, "heroku": 14, "hidden": [17, 19], "high": [20, 23], "highli": [19, 23], "hint": [17, 19], "histor": [28, 29], "home": 14, "homepag": [5, 14], "host": 19, "hostnam": 17, "how": [18, 25], "howev": 14, "http": [14, 17, 19, 20, 25], "hubspot_pipeline_id": 15, "i": [1, 2, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 27, 29, 30], "id": [1, 2, 3, 4, 5, 6, 9, 12, 14, 17, 18, 19, 21], "ida_logout_uri_list": [17, 19], "idea": 25, "ideal": 14, "ignor": 4, "imag": [14, 19, 20], "immut": 29, "implement": [23, 24, 26, 30], "import": [2, 9, 14, 19, 25], "import_courserun": [10, 31], "inact": 6, "includ": [17, 18, 19, 30], "incognito": 19, "incom": [3, 21], "incorpor": 23, "index": [10, 31], "individu": [1, 9], "info": [2, 3], "inform": [2, 3, 4, 8, 18, 21, 27, 29], "initi": [4, 27], "insert": 8, "inspect": [17, 19], "inspir": 17, "instal": [14, 19, 20], "instanc": [2, 9, 15, 18, 19], "instead": [11, 12, 19], "instnac": 2, "instruct": [14, 17, 19], "instructor": 9, "integr": [18, 19], "intend": 24, "interfac": 14, "intern": [17, 19], "intro": 21, "introduc": 25, "invalid": 14, "involv": [18, 19], "io": [2, 19], "ip": [2, 17, 19], "isn": [1, 4, 18, 21], "iso": 8, "isort": 19, "issu": 14, "its": 1, "itself": [19, 21], "json": 17, "jsonfield": 27, "jump": 19, "junip": 17, "just": [14, 19], "keep": [11, 19, 23, 25], "kei": [14, 17], "kind": [19, 25, 27], "kwarg": 25, "larg": [19, 20], "last": [7, 18, 19, 23], "lastnam": 7, "later": [17, 18], "latest_vers": 25, "latter": 19, "learn": [14, 23], "learner": [2, 3, 7, 8, 11, 12, 17, 18, 19, 21, 23, 30], "least": [19, 23], "leav": [18, 21], "led": 9, "length": 8, "less": [8, 14], "level": [20, 23], "librari": 14, "lift": 14, "like": [14, 17, 19, 25], "limit": [20, 21, 23], "line": 27, "link": [5, 18, 21], "linux": [2, 17, 18, 19], "list": [4, 14, 17, 29], "live": [1, 4, 5, 9, 14, 18, 19, 21], "ll": [9, 14, 18, 19, 21], "lm": 19, "load": 19, "local": [2, 15, 16, 18, 31], "local_mitx_online_alia": 17, "localhost": 19, "locat": 21, "locustfil": 20, "locustio": 20, "log": [1, 14, 19, 21], "logic": 25, "login": [17, 19], "logout": [16, 19], "logout_redirect_url": 17, "long": 19, "look": [11, 13, 17], "lot": [18, 19, 25], "lower": 30, "mac": 19, "maco": [2, 17, 18, 19], "made": [9, 19, 21, 28], "mai": [3, 9, 14, 18, 19, 25], "main": [12, 19], "major": 23, "make": [4, 5, 6, 8, 9, 11, 12, 14, 17, 19, 23, 25, 28], "manag": [9, 16, 18, 19, 21], "manual": [11, 19, 21], "mark": [18, 27, 28], "market": 30, "mastercard": 14, "match": [3, 17, 18], "matter": 19, "max": 8, "max_digit": [25, 27], "max_length": 25, "max_redempt": 25, "me": 19, "mean": [11, 13, 17, 19], "memori": 20, "mention": 20, "messag": [1, 19, 21], "method": 3, "mfe": 19, "migrat": [18, 19], "minimum": 23, "minu": 14, "miss": 19, "mistakenli": 4, "mit": 14, "mitol_hubspot_api_id_prefix": 15, "mitol_hubspot_api_private_token": 15, "mitol_payment_gateway_cybersource_access_kei": 14, "mitol_payment_gateway_cybersource_profile_id": 14, "mitol_payment_gateway_cybersource_secure_acceptance_url": 14, "mitol_payment_gateway_cybersource_security_kei": 14, "mitx": [1, 2, 9, 15, 16, 21, 23, 30], "mitx_online_servicework": 19, "mitxonlin": [13, 14, 15, 17, 18, 19], "mitxonline_default": 19, "mitxonline_gateway_ip": 19, "mitxpro": 19, "mitxprooauth2": [17, 19], "modal": 14, "mode": 3, "model": [24, 26], "modifi": 17, "monei": 11, "more": [2, 17, 18, 19, 21, 24, 25], "most": [19, 30], "mostli": 19, "muddl": 23, "multipl": [1, 8, 9, 19, 25], "must": [3, 4, 17, 21], "name": [4, 7, 8, 14, 17, 18, 19, 21], "navig": [14, 19, 21], "necessari": 19, "necessarili": 25, "need": [1, 8, 9, 14, 15, 17, 18, 19, 20, 25, 29, 30], "network": [17, 19], "new": [4, 9, 12, 14, 17, 19, 20, 21, 25], "nightli": [2, 19], "node": 1, "non": 14, "none": [2, 18], "normal": [11, 19], "note": [1, 3, 4, 8, 14, 16, 17, 26], "now": [6, 14, 19], "null": 25, "number": [6, 8, 11, 13, 14, 25], "numer": [1, 9], "o": 17, "oauth": [2, 16, 18, 19], "oauth2": [2, 16, 18, 19], "oauth2_provid": [17, 19], "oauth2providerconfig": [17, 19], "object": [4, 5, 6, 9, 14, 18, 21], "object_id": 29, "occas": 25, "ocw": 20, "odl": [17, 18, 19], "off": [3, 8, 19, 25], "often": [23, 24], "ol": [14, 30], "omit": 19, "onc": [8, 9, 19, 25, 27], "one": [3, 8, 9, 14, 17, 18, 19, 21, 24, 25, 29], "ones": [3, 9], "onetoonefield": 24, "onli": [1, 2, 4, 5, 6, 8, 9, 13, 19, 25], "onlin": [2, 9, 15, 16, 21, 23, 30], "online_default": 17, "open": [2, 16, 18, 31], "openedx": 19, "openedx_api_base_url": [9, 17, 19], "openedx_api_client_id": [17, 19], "openedx_api_client_secret": [17, 19], "openedx_ip": [17, 19], "openedx_service_worker_api_token": [17, 19], "openedx_service_worker_usernam": 19, "openedxapiauth": 12, "openedxus": 12, "oper": [3, 19, 23], "option": [10, 14, 17, 18, 19, 21, 23, 25], "order": [11, 13, 15, 17, 18, 22, 23, 25, 26, 28, 29], "org": [19, 25], "origin": 25, "osx": 17, "other": [3, 4, 11, 16, 17, 21, 30], "otherwis": [9, 13, 14, 17, 19, 30], "our": [23, 25], "out": [14, 17, 19, 21, 23, 25, 30], "output": 10, "over": [9, 14, 20, 23], "overrid": 3, "overridden": [3, 4, 19], "overview": [14, 22, 31], "overwritten": [8, 14], "own": 14, "pace": [4, 9], "packag": [17, 19], "page": [5, 9, 10, 14, 18, 19, 21, 31], "pagin": 29, "paid": 14, "part": [3, 16, 18], "particular": [19, 30], "particularli": 23, "pass": [8, 14], "password": [7, 17, 18, 19], "path": [14, 17], "payment": [11, 13, 14, 22, 23, 26, 27], "payout": 30, "paypal": 11, "peform": 18, "pencil": 21, "pend": 13, "pep": 25, "per": [8, 17, 25], "percent": [3, 8, 25], "percent_discount": 25, "percentag": 25, "percentdiscount": 25, "perform": [4, 11, 16, 19], "permit": 17, "pertin": 9, "pick": 17, "piec": 23, "place": 19, "plan": [14, 23, 30], "platform": [2, 17, 18], "plu": [14, 19], "plug": 18, "pluggabl": 28, "poetri": 20, "point": [3, 14, 19, 23], "pop": 14, "port": [2, 17, 19, 20], "positiveintegerfield": [24, 25, 27, 29], "possibl": 4, "post": [20, 24], "pre": 25, "preced": 9, "preconfigur": 30, "prefix": 8, "preliminari": 16, "present": 21, "presum": 29, "pretti": 19, "prevent": 14, "price": [6, 8, 9, 14, 18, 22, 23, 24, 25, 29, 31], "primarili": 29, "principl": [23, 30], "print": [1, 19], "print_set": 19, "printroot": 19, "prior": 22, "privat": 19, "probabl": [14, 23], "problem": 19, "process": [13, 14, 19, 22], "processor": 28, "prod": 13, "product": [5, 6, 9, 16, 17, 18, 19, 22, 23, 24, 25, 26, 27], "product_vers": [25, 27], "productvers": [25, 27], "profil": [17, 19], "program": [1, 3, 4, 9, 14, 18, 21, 23, 24, 27, 29, 30], "progress": 21, "project": [17, 19], "prompt": [7, 18], "proper": [2, 19, 21], "properli": [14, 18, 19], "propos": [23, 26], "protocol": 17, "prove": 23, "provid": [3, 16, 18, 19, 21, 24, 25], "prune": 19, "publish": [14, 18, 21], "pull": 4, "punctuat": 8, "purchas": [14, 23, 24, 25, 27, 29], "purpos": [24, 30], "put": [14, 17, 19], "py": [9, 17, 19, 20, 21], "pyenv": 19, "python": [19, 23, 25], "qa": 14, "quantiti": [24, 27], "queu": 18, "quick": [2, 16, 19, 31], "quickstart": 19, "quit": 3, "rais": 25, "rate": 30, "rather": [19, 25], "rc": 14, "re": [2, 9, 11, 14, 18, 19], "read": [18, 29], "readabl": [1, 3, 4, 9, 14, 18, 21], "readable_id": 4, "readi": [14, 20, 21], "real": 19, "reason": [12, 21], "reboot": 19, "rebuild": 19, "receipt": 25, "recent": 17, "recommend": [6, 14, 19], "reconcil": 30, "record": [2, 4, 12, 18, 19], "redeem": 8, "redempt": 26, "redemption_typ": 25, "redirect": [17, 19], "redo": 2, "reduc": 25, "reduct": 25, "refactor": 25, "refer": [11, 13, 23], "refresh": 12, "refund": [11, 27], "regardless": [21, 25], "regener": 12, "regist": 29, "registr": [17, 19], "registration_extra_field": [17, 19], "registri": 25, "regular": 9, "relat": 29, "releas": 17, "relev": 2, "reli": [18, 19], "remov": 19, "repair_missing_courseware_record": [12, 19], "repo": 20, "report": [22, 26], "repositori": 19, "repres": [3, 24, 27], "represent": 29, "request": [4, 22], "requir": [1, 2, 3, 4, 5, 8, 9, 14, 17, 18, 19], "reset": 18, "resolv": [13, 17], "resourc": 20, "respect": 21, "respons": [28, 29], "rest": 28, "restart": [17, 19], "result": [4, 5, 13, 14, 16, 19], "return": [1, 24, 25, 29], "reus": 3, "reusabl": 23, "revers": 29, "review": 18, "revok": 12, "right": 19, "rm": [19, 20], "robust": 23, "root": [19, 20], "row": 3, "royalti": 30, "run": [2, 3, 4, 6, 7, 9, 11, 14, 18, 19, 20, 23, 24, 27, 29], "run_tag": 9, "run_url": 4, "safe": 3, "safeti": 19, "sai": 21, "said": 17, "sake": 29, "same": [9, 19, 30], "sandbox": 14, "satisfi": 20, "save": [5, 17, 19], "scalabl": [23, 25], "scale": 8, "schema": 24, "scope": [14, 30], "scratch": 19, "screen": [1, 19], "search": [10, 31], "secret": [2, 17, 18, 19], "section": [4, 5, 14, 18], "secur": 14, "see": [2, 3, 4, 14, 18, 19, 20, 21, 23, 25], "select": [14, 17, 21, 23], "self": [4, 9, 25], "semest": 9, "separ": [8, 19], "serv": 23, "servic": [19, 20, 25], "session": 19, "set": [3, 4, 6, 8, 9, 11, 16, 17, 18, 19, 24, 27], "setup": [14, 16, 20], "sever": 23, "shell": 17, "ship": 18, "should": [3, 4, 8, 9, 14, 17, 19, 20, 23, 25, 28], "show": 19, "side": [4, 12], "similar": 9, "similarli": 9, "simpl": [23, 24], "simpli": [8, 17, 21], "sinc": [2, 9], "singl": [8, 9, 29], "site": [17, 19], "skip": [9, 17, 19], "skip_email_valid": [17, 19], "slug": [17, 19, 21], "so": [1, 3, 4, 8, 11, 14, 19, 21, 25, 30], "social": [16, 19], "social_auth_mitxpro": [17, 19], "sock": 20, "solid": 23, "some": [12, 14, 19, 21, 23, 24, 25, 27], "someon": 14, "someth": [17, 23], "somewher": [19, 30], "soon": 20, "sort": 11, "special": [23, 30], "specialti": 19, "specif": [1, 9, 13, 14], "specifi": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 18, 19, 21], "split": 18, "src": 20, "sso": [17, 19], "staff": [17, 19, 30], "stai": 19, "standalon": 21, "standard": [9, 17, 18], "start": [2, 3, 4, 9, 14, 16, 19, 31], "stat": 20, "state": [5, 13, 21, 24], "statu": [11, 13, 21, 27], "step": [9, 14, 16, 17, 18, 21], "still": [2, 19, 21], "stop": [2, 4, 8, 19], "strictli": 19, "string": 17, "strive": 23, "strongli": [23, 28], "studio": 20, "stuff": 19, "subdomain": 19, "submiss": 21, "submit": 22, "subsystem": [14, 22, 23, 31], "successfulli": 19, "suggest": 19, "super": 25, "superus": [2, 14, 18, 19], "supplement": 30, "suppli": [3, 8, 14, 19], "support": [14, 16, 23, 25, 30], "suppress": 1, "sure": [14, 17, 19], "surfac": 23, "swap": 4, "sync": [9, 14, 17, 18, 19], "sync_course_run": [4, 18], "sync_courserun": [9, 19], "syntax": 10, "system": [1, 7, 9, 11, 14, 18, 19, 21, 22, 25, 28, 30], "t": [1, 2, 3, 4, 8, 9, 11, 12, 13, 14, 17, 18, 19, 21, 23, 30], "tab": [19, 20], "tabl": 3, "tag": [4, 9, 18], "take": [4, 9, 18, 19, 28], "tar": 17, "task": [18, 19], "tbd": 21, "team": 30, "tell": 19, "termin": [19, 20], "terms_of_servic": 19, "test": [2, 18, 19], "testlearn": 18, "text": 21, "than": [14, 19, 25], "thei": [1, 3, 8, 9, 11, 17, 19, 21, 30], "them": [14, 17, 19, 23], "thi": [1, 2, 3, 4, 6, 8, 9, 11, 12, 13, 14, 17, 18, 19, 20, 21, 23, 24, 25, 28, 29, 30], "thing": [9, 19], "third_party_auth": [17, 19], "third_party_auth_backend": [17, 19], "those": 24, "three": [14, 19], "threshold": 3, "through": [9, 14, 17, 19, 21, 28, 29], "tier": [3, 18, 21, 30], "tier1": 3, "tier2": 3, "tier3": 3, "tier4": 3, "time": [1, 8, 11, 19, 25], "timestampedmodel": [24, 25, 27, 29], "titl": [4, 9, 14, 19, 21], "tmp": 20, "todai": [14, 19], "togeth": [1, 23], "token": [12, 16, 19], "too": 19, "toolkit": 17, "top": 19, "total": 8, "total_price_paid": 27, "toward": 23, "track": [24, 25, 29], "transact": [11, 14, 27], "treat": [25, 30], "tree": [1, 4], "true": [17, 19, 25], "try": [3, 19], "turn": 19, "tutor": [2, 16, 31], "tutor_local_default": 19, "twice": 18, "two": [2, 3, 8, 16, 17, 18], "txt": 19, "type": [3, 8, 14, 17, 19, 26], "typeerror": 25, "typic": [19, 27, 29], "u": [7, 17, 18, 28], "ui": 30, "under": [14, 17, 20, 21, 24], "understand": 23, "undo": 19, "unenrol": 11, "unfulfil": [27, 28], "uninstal": 17, "uniqu": 14, "unless": [18, 19, 21], "unlik": 19, "unlimit": 25, "up": [1, 3, 4, 11, 13, 16, 17, 18, 19], "upcom": 9, "updat": [9, 17, 19, 24], "updated_on": 29, "upgrad": [4, 14], "upper": 21, "upsel": 14, "uri": [17, 19], "url": [2, 4, 9, 14, 17, 19], "us": [1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 14, 16, 18, 19, 20, 21, 23, 24, 25, 26, 28], "usag": [20, 25], "usecas": 12, "user": [7, 8, 12, 16, 19, 23, 24, 25, 28], "userdiscount": 25, "usernam": [7, 12, 17, 18], "util": 29, "uuid": 8, "uwsgi_max_request": 20, "uwsgi_reload_on_rss": 20, "v0": [24, 29], "v1": [1, 2, 9, 14, 18, 19], "valid": [1, 14], "valu": [14, 17, 19, 20, 21, 24], "variabl": [15, 19, 20], "ve": [2, 4, 8, 14, 18, 19, 21, 23, 25], "veri": [5, 17], "verif": [17, 19], "verifi": [11, 17, 19], "version": [14, 17, 19, 25, 29], "via": [11, 19, 21], "view": 17, "virtualenv": 19, "visa": 14, "volum": 20, "voucher": 23, "wa": [9, 19, 23, 25], "wagtail": [14, 19, 21], "wai": [23, 25], "walk": 9, "want": [3, 8, 9, 14, 18, 19, 23, 25, 30], "watch": 19, "we": [17, 23, 25, 28, 30], "web": [19, 20], "webhook": 28, "weird": 19, "well": [19, 21], "went": 23, "were": 23, "what": [6, 14, 18, 25], "whatev": 19, "when": [14, 17, 19, 21, 30], "where": [14, 18, 19, 23], "wherea": 25, "whether": 14, "which": [4, 6, 9, 17, 18, 19, 24, 30], "while": 14, "whitespac": 19, "wildcard": 19, "window": 20, "wish": 14, "within": [17, 19, 21, 30], "without": [17, 19, 20, 23, 25], "won": [1, 3, 8, 9, 13, 14, 17, 19], "word": [3, 21], "work": [6, 13, 14, 17, 18, 19, 20], "worker": 19, "would": [3, 9, 24, 25, 28, 29, 30], "wouldn": [23, 30], "write": [11, 19], "written": 8, "wsl": 17, "www": 25, "y": 17, "yaml": 19, "year": [3, 14, 19, 23], "yet": [19, 21], "yml": [17, 19, 20], "you": [1, 2, 3, 4, 7, 8, 9, 11, 14, 15, 17, 18, 19, 20, 21], "your": [2, 4, 14, 15, 18, 19, 20, 21], "your_initi": 15, "yourself": 8, "zero": [3, 14, 21]}, "titles": ["Authentication", "check_program_requirements", "configure_instance", "configure_tiers", "create_courseware", "create_courseware_page", "create_product", "create_user", "generate_discount_code", "import_courserun", "MITx Online Commands", "refund_fulfilled_order", "regenerate_edx_auth_tokens", "resolve_pending_order", "Configure eCommerce", "Configure HubSpot", "Configuration", "Configure Open edX", "MITx Online Quick Start", "Local Open edX Tutor and MITx Online Deployment", "Setting up uWsgi tuning for MITx Online", "Flexible Pricing", "Ecommerce", "Overview", "Basket Subsystem", "Discount Subsystem", "Subsystems", "Order Subsystem", "Payment Subsystem", "Product Subsystem", "Reporting Subsystem", "Welcome to MITx Online Documentation\u2019s documentation!"], "titleterms": {"": 31, "In": 14, "One": 19, "To": 20, "add": 17, "alia": 17, "all": 20, "api": [24, 29], "art": 23, "auth": 17, "authent": [0, 17], "basket": 24, "batch": 8, "build": 17, "case": 30, "check": 4, "check_program_requir": 1, "checkout": 14, "clone": 17, "code": 8, "command": [10, 17], "config": 14, "configur": [14, 15, 16, 17], "configure_inst": 2, "configure_ti": 3, "content": [10, 31], "core": 23, "cours": 14, "create_coursewar": 4, "create_courseware_pag": 5, "create_product": 6, "create_us": 7, "creation": 21, "data": [25, 27, 29], "dedp": 30, "deploy": 19, "devstack": 17, "discount": 25, "document": 31, "ecommerc": [14, 22], "edx": [14, 17, 19], "etc": 17, "exampl": 9, "extens": 17, "flexibl": 21, "form": 21, "from": 17, "gener": 8, "generate_discount_cod": 8, "goal": 23, "host": 17, "hubspot": 15, "imag": 17, "implement": 25, "import_courserun": 9, "indic": [10, 31], "instal": 17, "latest": 17, "lm": 17, "local": [17, 19, 20], "locust": 20, "logout": 17, "manag": 17, "micromast": [23, 30], "mitx": [10, 14, 17, 18, 19, 20, 31], "mitxpro": 17, "model": [25, 27, 29], "note": [18, 19, 24], "oauth": 17, "oauth2": 17, "onlin": [10, 14, 17, 18, 19, 20, 31], "open": [14, 17, 19], "openedx": 17, "option": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13], "order": 27, "other": 19, "output": 8, "overview": 23, "part": 19, "payment": 28, "perform": 18, "pip": 17, "preliminari": 19, "price": 21, "prior": 23, "process": 21, "product": [14, 29], "propos": 25, "provid": 17, "provis": 17, "pull": 17, "put": 20, "quick": 18, "redempt": 25, "refund_fulfilled_ord": 11, "regenerate_edx_auth_token": 12, "report": 30, "request": 21, "resolve_pending_ord": 13, "result": 18, "run": 17, "server": 17, "set": [14, 20], "setup": [17, 19], "social": 17, "start": [17, 18], "step": 19, "stop": 17, "submit": 21, "subsystem": [24, 25, 26, 27, 28, 29, 30], "support": 17, "syntax": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13], "system": 23, "tabl": [10, 31], "test": [14, 20], "togeth": 20, "token": 17, "tune": 20, "tutor": 19, "two": 19, "type": 25, "up": [14, 20], "us": [17, 30], "user": 17, "uwsgi": 20, "uwsgitop": 20, "via": 17, "welcom": 31, "xpro": 23, "your": 17}}) \ No newline at end of file