fnschool 20250109.80531.837__py3-none-any.whl → 20251011.80531.840__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fnschool might be problematic. Click here for more details.

Files changed (276) hide show
  1. fnschoo1/__init__.py +52 -0
  2. fnschoo1/canteen/admin.py +3 -0
  3. fnschoo1/canteen/apps.py +6 -0
  4. fnschoo1/canteen/forms.py +84 -0
  5. fnschoo1/canteen/migrations/0001_initial.py +119 -0
  6. fnschoo1/canteen/migrations/0002_ingredient_is_disabled.py +20 -0
  7. fnschoo1/canteen/migrations/0003_consumption_is_disabled_alter_ingredient_is_disabled.py +23 -0
  8. fnschoo1/canteen/migrations/0004_alter_ingredient_name_category_and_more.py +66 -0
  9. fnschoo1/canteen/migrations/0005_alter_category_created_at_alter_category_name_and_more.py +41 -0
  10. fnschoo1/canteen/migrations/0006_category_is_disabled_alter_category_user_and_more.py +49 -0
  11. fnschoo1/canteen/migrations/0007_alter_consumption_amount_used_and_more.py +30 -0
  12. fnschoo1/canteen/migrations/0008_category_abbreviation_mealtype.py +67 -0
  13. fnschoo1/canteen/migrations/0009_alter_category_abbreviation_and_more.py +55 -0
  14. fnschoo1/canteen/migrations/0010_alter_consumption_options_alter_ingredient_options_and_more.py +215 -0
  15. fnschoo1/canteen/migrations/0011_category_pin_to_consumptions_top.py +23 -0
  16. fnschoo1/canteen/migrations/0012_alter_ingredient_storage_date.py +18 -0
  17. fnschoo1/canteen/models.py +179 -0
  18. fnschoo1/canteen/templates/canteen/category/create.html +17 -0
  19. fnschoo1/canteen/templates/canteen/category/delete.html +61 -0
  20. fnschoo1/canteen/templates/canteen/category/list.html +63 -0
  21. fnschoo1/canteen/templates/canteen/category/update.html +23 -0
  22. fnschoo1/canteen/templates/canteen/close.html +11 -0
  23. fnschoo1/canteen/templates/canteen/consumption/_create.html +19 -0
  24. fnschoo1/canteen/templates/canteen/consumption/create.html +456 -0
  25. fnschoo1/canteen/templates/canteen/ingredient/close.html +11 -0
  26. fnschoo1/canteen/templates/canteen/ingredient/create.html +19 -0
  27. fnschoo1/canteen/templates/canteen/ingredient/create_one.html +17 -0
  28. fnschoo1/canteen/templates/canteen/ingredient/delete.html +41 -0
  29. fnschoo1/canteen/templates/canteen/ingredient/list.html +128 -0
  30. fnschoo1/canteen/templates/canteen/ingredient/update.html +23 -0
  31. fnschoo1/canteen/templates/canteen/meal_type/create.html +17 -0
  32. fnschoo1/canteen/templates/canteen/meal_type/delete.html +56 -0
  33. fnschoo1/canteen/templates/canteen/meal_type/list.html +59 -0
  34. fnschoo1/canteen/templates/canteen/meal_type/update.html +23 -0
  35. fnschoo1/canteen/tests.py +3 -0
  36. fnschoo1/canteen/urls.py +116 -0
  37. fnschoo1/canteen/views.py +814 -0
  38. fnschoo1/canteen/workbook/generate.py +2098 -0
  39. fnschoo1/db.sqlite3 +0 -0
  40. fnschoo1/fnschool/__init__.py +13 -0
  41. fnschoo1/fnschool/asgi.py +16 -0
  42. fnschoo1/fnschool/settings.py +167 -0
  43. fnschoo1/fnschool/templatetags/fnschool_tags.py +27 -0
  44. fnschoo1/fnschool/urls.py +30 -0
  45. fnschoo1/fnschool/views.py +9 -0
  46. fnschoo1/fnschool/wsgi.py +16 -0
  47. fnschoo1/locale/en/LC_MESSAGES/django.mo +0 -0
  48. fnschoo1/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  49. fnschoo1/manage.py +25 -0
  50. fnschoo1/profiles/admin.py +27 -0
  51. fnschoo1/profiles/apps.py +12 -0
  52. fnschoo1/profiles/forms.py +67 -0
  53. fnschoo1/profiles/migrations/0001_initial.py +192 -0
  54. fnschoo1/profiles/migrations/0002_alter_profile_bio.py +20 -0
  55. fnschoo1/profiles/migrations/0003_alter_profile_options_alter_profile_address_and_more.py +67 -0
  56. fnschoo1/profiles/migrations/0004_profile_gender.py +26 -0
  57. fnschoo1/profiles/migrations/0005_alter_profile_gender.py +23 -0
  58. fnschoo1/profiles/models.py +60 -0
  59. fnschoo1/profiles/signals.py +20 -0
  60. fnschoo1/profiles/templates/profiles/create.html +16 -0
  61. fnschoo1/profiles/templates/profiles/detail.html +14 -0
  62. fnschoo1/profiles/templates/profiles/edit.html +12 -0
  63. fnschoo1/profiles/templates/profiles/log_in.html +20 -0
  64. fnschoo1/profiles/templates/profiles/log_out.html +12 -0
  65. fnschoo1/profiles/tests.py +3 -0
  66. fnschoo1/profiles/urls.py +15 -0
  67. fnschoo1/profiles/views.py +63 -0
  68. fnschoo1/static/css/bootstrap.min.css +11776 -0
  69. fnschoo1/static/css/fnschool.css +26 -0
  70. fnschoo1/static/js/bootstrap.bundle.min.js +4223 -0
  71. fnschoo1/static/js/bootstrap.min.js +2919 -0
  72. fnschoo1/static/js/fnschool.js +84 -0
  73. fnschoo1/static/js/jquery.min.js +5413 -0
  74. fnschoo1/static/js/jquery.slim.min.js +4331 -0
  75. fnschoo1/static/js/popper.min.js +1306 -0
  76. fnschoo1/static_collected/admin/css/autocomplete.css +377 -0
  77. fnschoo1/static_collected/admin/css/base.css +1224 -0
  78. fnschoo1/static_collected/admin/css/changelists.css +334 -0
  79. fnschoo1/static_collected/admin/css/dark_mode.css +136 -0
  80. fnschoo1/static_collected/admin/css/dashboard.css +30 -0
  81. fnschoo1/static_collected/admin/css/forms.css +546 -0
  82. fnschoo1/static_collected/admin/css/login.css +62 -0
  83. fnschoo1/static_collected/admin/css/nav_sidebar.css +145 -0
  84. fnschoo1/static_collected/admin/css/responsive.css +1043 -0
  85. fnschoo1/static_collected/admin/css/responsive_rtl.css +84 -0
  86. fnschoo1/static_collected/admin/css/rtl.css +311 -0
  87. fnschoo1/static_collected/admin/css/vendor/select2/select2.css +708 -0
  88. fnschoo1/static_collected/admin/css/vendor/select2/select2.min.css +1 -0
  89. fnschoo1/static_collected/admin/css/widgets.css +639 -0
  90. fnschoo1/static_collected/admin/js/SelectBox.js +128 -0
  91. fnschoo1/static_collected/admin/js/SelectFilter2.js +503 -0
  92. fnschoo1/static_collected/admin/js/actions.js +232 -0
  93. fnschoo1/static_collected/admin/js/admin/DateTimeShortcuts.js +496 -0
  94. fnschoo1/static_collected/admin/js/admin/RelatedObjectLookups.js +276 -0
  95. fnschoo1/static_collected/admin/js/autocomplete.js +33 -0
  96. fnschoo1/static_collected/admin/js/calendar.js +251 -0
  97. fnschoo1/static_collected/admin/js/cancel.js +29 -0
  98. fnschoo1/static_collected/admin/js/change_form.js +21 -0
  99. fnschoo1/static_collected/admin/js/collapse.js +43 -0
  100. fnschoo1/static_collected/admin/js/core.js +174 -0
  101. fnschoo1/static_collected/admin/js/filters.js +37 -0
  102. fnschoo1/static_collected/admin/js/inlines.js +439 -0
  103. fnschoo1/static_collected/admin/js/jquery.init.js +8 -0
  104. fnschoo1/static_collected/admin/js/nav_sidebar.js +81 -0
  105. fnschoo1/static_collected/admin/js/popup_response.js +24 -0
  106. fnschoo1/static_collected/admin/js/prepopulate.js +43 -0
  107. fnschoo1/static_collected/admin/js/prepopulate_init.js +20 -0
  108. fnschoo1/static_collected/admin/js/theme.js +57 -0
  109. fnschoo1/static_collected/admin/js/urlify.js +529 -0
  110. fnschoo1/static_collected/admin/js/vendor/jquery/jquery.js +10913 -0
  111. fnschoo1/static_collected/admin/js/vendor/jquery/jquery.min.js +2 -0
  112. fnschoo1/static_collected/admin/js/vendor/select2/i18n/af.js +43 -0
  113. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ar.js +36 -0
  114. fnschoo1/static_collected/admin/js/vendor/select2/i18n/az.js +33 -0
  115. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bg.js +38 -0
  116. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bn.js +39 -0
  117. fnschoo1/static_collected/admin/js/vendor/select2/i18n/bs.js +48 -0
  118. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ca.js +41 -0
  119. fnschoo1/static_collected/admin/js/vendor/select2/i18n/cs.js +62 -0
  120. fnschoo1/static_collected/admin/js/vendor/select2/i18n/da.js +37 -0
  121. fnschoo1/static_collected/admin/js/vendor/select2/i18n/de.js +41 -0
  122. fnschoo1/static_collected/admin/js/vendor/select2/i18n/dsb.js +51 -0
  123. fnschoo1/static_collected/admin/js/vendor/select2/i18n/el.js +43 -0
  124. fnschoo1/static_collected/admin/js/vendor/select2/i18n/en.js +41 -0
  125. fnschoo1/static_collected/admin/js/vendor/select2/i18n/es.js +41 -0
  126. fnschoo1/static_collected/admin/js/vendor/select2/i18n/et.js +38 -0
  127. fnschoo1/static_collected/admin/js/vendor/select2/i18n/eu.js +45 -0
  128. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fa.js +42 -0
  129. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fi.js +42 -0
  130. fnschoo1/static_collected/admin/js/vendor/select2/i18n/fr.js +43 -0
  131. fnschoo1/static_collected/admin/js/vendor/select2/i18n/gl.js +40 -0
  132. fnschoo1/static_collected/admin/js/vendor/select2/i18n/he.js +44 -0
  133. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hi.js +40 -0
  134. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hr.js +45 -0
  135. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hsb.js +51 -0
  136. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hu.js +44 -0
  137. fnschoo1/static_collected/admin/js/vendor/select2/i18n/hy.js +40 -0
  138. fnschoo1/static_collected/admin/js/vendor/select2/i18n/id.js +36 -0
  139. fnschoo1/static_collected/admin/js/vendor/select2/i18n/is.js +37 -0
  140. fnschoo1/static_collected/admin/js/vendor/select2/i18n/it.js +43 -0
  141. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ja.js +40 -0
  142. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ka.js +42 -0
  143. fnschoo1/static_collected/admin/js/vendor/select2/i18n/km.js +38 -0
  144. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ko.js +42 -0
  145. fnschoo1/static_collected/admin/js/vendor/select2/i18n/lt.js +45 -0
  146. fnschoo1/static_collected/admin/js/vendor/select2/i18n/lv.js +41 -0
  147. fnschoo1/static_collected/admin/js/vendor/select2/i18n/mk.js +42 -0
  148. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ms.js +38 -0
  149. fnschoo1/static_collected/admin/js/vendor/select2/i18n/nb.js +38 -0
  150. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ne.js +44 -0
  151. fnschoo1/static_collected/admin/js/vendor/select2/i18n/nl.js +46 -0
  152. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pl.js +43 -0
  153. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ps.js +41 -0
  154. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pt-BR.js +39 -0
  155. fnschoo1/static_collected/admin/js/vendor/select2/i18n/pt.js +41 -0
  156. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ro.js +43 -0
  157. fnschoo1/static_collected/admin/js/vendor/select2/i18n/ru.js +48 -0
  158. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sk.js +61 -0
  159. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sl.js +41 -0
  160. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sq.js +43 -0
  161. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sr-Cyrl.js +48 -0
  162. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sr.js +48 -0
  163. fnschoo1/static_collected/admin/js/vendor/select2/i18n/sv.js +40 -0
  164. fnschoo1/static_collected/admin/js/vendor/select2/i18n/th.js +36 -0
  165. fnschoo1/static_collected/admin/js/vendor/select2/i18n/tk.js +36 -0
  166. fnschoo1/static_collected/admin/js/vendor/select2/i18n/tr.js +40 -0
  167. fnschoo1/static_collected/admin/js/vendor/select2/i18n/uk.js +59 -0
  168. fnschoo1/static_collected/admin/js/vendor/select2/i18n/vi.js +37 -0
  169. fnschoo1/static_collected/admin/js/vendor/select2/i18n/zh-CN.js +36 -0
  170. fnschoo1/static_collected/admin/js/vendor/select2/i18n/zh-TW.js +33 -0
  171. fnschoo1/static_collected/admin/js/vendor/select2/select2.full.js +7115 -0
  172. fnschoo1/static_collected/admin/js/vendor/select2/select2.full.min.js +2 -0
  173. fnschoo1/static_collected/admin/js/vendor/xregexp/xregexp.js +4993 -0
  174. fnschoo1/static_collected/admin/js/vendor/xregexp/xregexp.min.js +160 -0
  175. fnschoo1/static_collected/css/bootstrap.min.css +11776 -0
  176. fnschoo1/static_collected/css/fnschool.css +26 -0
  177. fnschoo1/static_collected/js/bootstrap.bundle.min.js +4223 -0
  178. fnschoo1/static_collected/js/bootstrap.min.js +2919 -0
  179. fnschoo1/static_collected/js/fnschool.js +84 -0
  180. fnschoo1/static_collected/js/jquery.min.js +5413 -0
  181. fnschoo1/static_collected/js/jquery.slim.min.js +4331 -0
  182. fnschoo1/static_collected/js/popper.min.js +1306 -0
  183. fnschoo1/templates/base/_css.html +1 -0
  184. fnschoo1/templates/base/_js.html +15 -0
  185. fnschoo1/templates/base/content.html +30 -0
  186. fnschoo1/templates/base/header_content_footer.html +35 -0
  187. fnschoo1/templates/close.html +11 -0
  188. fnschoo1/templates/home.html +51 -0
  189. fnschoo1/templates/includes/_footer.html +39 -0
  190. fnschoo1/templates/includes/_header.html +77 -0
  191. fnschoo1/templates/includes/_navigation.html +0 -0
  192. fnschoo1/templates/includes/_paginator.html +27 -0
  193. fnschoo1/templates/registration/logged_out.html +0 -0
  194. fnschoo1/templates/registration/login.html +0 -0
  195. fnschool-20251011.80531.840.dist-info/METADATA +179 -0
  196. fnschool-20251011.80531.840.dist-info/RECORD +207 -0
  197. {fnschool-20250109.80531.837.dist-info → fnschool-20251011.80531.840.dist-info}/WHEEL +1 -1
  198. fnschool-20251011.80531.840.dist-info/entry_points.txt +2 -0
  199. fnschool-20251011.80531.840.dist-info/top_level.txt +1 -0
  200. fnschool/__init__.py +0 -35
  201. fnschool/__main__.py +0 -16
  202. fnschool/app.py +0 -103
  203. fnschool/canteen/__init__.py +0 -3
  204. fnschool/canteen/__main__.py +0 -3
  205. fnschool/canteen/bill.py +0 -253
  206. fnschool/canteen/canteen.py +0 -1
  207. fnschool/canteen/canteen.toml +0 -61
  208. fnschool/canteen/config.py +0 -10
  209. fnschool/canteen/consuming.py +0 -53
  210. fnschool/canteen/currency.py +0 -17
  211. fnschool/canteen/data/bill.i18n.xlsx +0 -0
  212. fnschool/canteen/data/bill.xlsx +0 -0
  213. fnschool/canteen/data/consuming.xlsx +0 -0
  214. fnschool/canteen/data/purchase_list.xlsx +0 -0
  215. fnschool/canteen/entry.py +0 -40
  216. fnschool/canteen/food.py +0 -206
  217. fnschool/canteen/food_classes.py +0 -33
  218. fnschool/canteen/food_classes.toml +0 -64
  219. fnschool/canteen/operator.py +0 -91
  220. fnschool/canteen/path.py +0 -28
  221. fnschool/canteen/spreadsheet/base.py +0 -213
  222. fnschool/canteen/spreadsheet/consuming.py +0 -310
  223. fnschool/canteen/spreadsheet/consumingsum.py +0 -76
  224. fnschool/canteen/spreadsheet/cover.py +0 -64
  225. fnschool/canteen/spreadsheet/ctspreadsheet.py +0 -351
  226. fnschool/canteen/spreadsheet/food.py +0 -680
  227. fnschool/canteen/spreadsheet/inventory.py +0 -375
  228. fnschool/canteen/spreadsheet/merging.py +0 -340
  229. fnschool/canteen/spreadsheet/preconsuming.py +0 -329
  230. fnschool/canteen/spreadsheet/purchasing.py +0 -885
  231. fnschool/canteen/spreadsheet/purchasingsum.py +0 -110
  232. fnschool/canteen/spreadsheet/spreadsheet.py +0 -363
  233. fnschool/canteen/spreadsheet/translating.py +0 -12
  234. fnschool/canteen/spreadsheet/unwarehousing.py +0 -178
  235. fnschool/canteen/spreadsheet/unwarehousingsum.py +0 -11
  236. fnschool/canteen/spreadsheet/warehousing.py +0 -360
  237. fnschool/canteen/spreadsheet/workbook.py +0 -17
  238. fnschool/canteen/test.py +0 -97
  239. fnschool/config.py +0 -48
  240. fnschool/entry.py +0 -67
  241. fnschool/exam/__init__.py +0 -8
  242. fnschool/exam/data/parental_emails.xlsx +0 -0
  243. fnschool/exam/data/score.xlsx +0 -0
  244. fnschool/exam/email.py +0 -349
  245. fnschool/exam/entry.py +0 -36
  246. fnschool/exam/language.py +0 -19
  247. fnschool/exam/path.py +0 -24
  248. fnschool/exam/score.py +0 -1191
  249. fnschool/exam/subject.py +0 -20
  250. fnschool/exam/teacher.py +0 -54
  251. fnschool/external.py +0 -89
  252. fnschool/games/__init__.py +0 -1
  253. fnschool/games/__main__.py +0 -1
  254. fnschool/games/games.py +0 -1
  255. fnschool/inoutput.py +0 -97
  256. fnschool/language.py +0 -40
  257. fnschool/locales/en_US/LC_MESSAGES/fnschool.mo +0 -0
  258. fnschool/locales/zh_CN/LC_MESSAGES/fnschool.mo +0 -0
  259. fnschool/locales/zh_HK/LC_MESSAGES/fnschool.mo +0 -0
  260. fnschool/locales/zh_SG/LC_MESSAGES/fnschool.mo +0 -0
  261. fnschool/locales/zh_TW/LC_MESSAGES/fnschool.mo +0 -0
  262. fnschool/path.py +0 -45
  263. fnschool/test.py +0 -24
  264. fnschool/user.py +0 -262
  265. fnschool-20250109.80531.837.dist-info/METADATA +0 -342
  266. fnschool-20250109.80531.837.dist-info/RECORD +0 -78
  267. fnschool-20250109.80531.837.dist-info/entry_points.txt +0 -5
  268. fnschool-20250109.80531.837.dist-info/top_level.txt +0 -1
  269. /fnschool/canteen/consume.py → /fnschoo1/canteen/__init__.py +0 -0
  270. /fnschool/canteen/food_recount.toml → /fnschoo1/canteen/migrations/__init__.py +0 -0
  271. {fnschool/canteen/spreadsheet → fnschoo1/canteen/workbook}/__init__.py +0 -0
  272. /fnschool/exam/__main__.py → /fnschoo1/fnschool/templatetags/__init__.py +0 -0
  273. /fnschool/canteen/food_recounts.toml → /fnschoo1/profiles/__init__.py +0 -0
  274. /fnschool/canteen/warehouse.py → /fnschoo1/profiles/migrations/__init__.py +0 -0
  275. /fnschool/canteen/workbook.toml → /fnschoo1/templates/base/_content.html +0 -0
  276. {fnschool-20250109.80531.837.dist-info → fnschool-20251011.80531.840.dist-info/licenses}/LICENSE +0 -0
fnschool/exam/score.py DELETED
@@ -1,1191 +0,0 @@
1
- import os
2
- import sys
3
- import time
4
- from fnschool import *
5
- from fnschool.exam import *
6
- from fnschool.exam.path import *
7
- from fnschool.exam.teacher import *
8
- from fnschool.exam.email import Email as FnEmail
9
-
10
-
11
- class Score:
12
- def __init__(
13
- self,
14
- ):
15
- self._full_name = None
16
- self._teacher = None
17
- self.fpath0 = score_fpath0
18
- self._sclass = None
19
- self._subject = None
20
- self._short_name = None
21
- self._short_name_m2 = None
22
- self._test_t = None
23
- self._fpath = None
24
- self._dpath = None
25
- self._fpath_m2 = None
26
- self._fpaths = None
27
- self._scores_m1 = None
28
- self._wb = None
29
- self._sheet0 = None
30
- self._total_points = None
31
- self._scores = None
32
- self.p_path_key = _("scores_parent_directory")
33
- self._question_titles = None
34
- self.fext = ".xlsx"
35
- self.no_test_m2_s = _("No recent tests")
36
- self.name_index0 = 4
37
- self.question_index0 = 4
38
- self.points_index0 = self.question_index0 - 1
39
- self._test_names = None
40
- self._astudent_names = None
41
- self._student_names = None
42
- self._src_dpath = None
43
- self.average_points_s = _("Average")
44
- self.plot_alpha0 = 0.16
45
- self.discipline_s = _("Discipline Points")
46
- self.name_s = _("Student Name")
47
- self._scores_m1_img_afpaths = None
48
- self._scores_img_afpaths = None
49
- self._scores_img_fpaths = None
50
- self._email = None
51
- self._sclass_dpath = None
52
-
53
- pass
54
-
55
- @property
56
- def email(self):
57
- if not self._email:
58
- email = FnEmail(self)
59
- self._email = email
60
- return self._email
61
-
62
- @property
63
- def test_names(self):
64
- if not self._test_names:
65
- scores = self.scores
66
- test_names = scores.columns.to_list()
67
- self._test_names = test_names
68
- return self._test_names
69
-
70
- @property
71
- def astudent_names(self):
72
- if not self._astudent_names:
73
- scores = self.scores
74
- astudent_names = scores.index.to_list()
75
- self._astudent_names = astudent_names
76
- return self._astudent_names
77
-
78
- @property
79
- def student_names(self):
80
- if not self._student_names:
81
- names = self.astudent_names
82
- if self.average_points_s in names:
83
- names.remove(self.average_points_s)
84
- self._student_names = names
85
- return self._student_names
86
-
87
- @property
88
- def src_dpath(self):
89
- return self.get_src_dpath()
90
-
91
- def get_src_dpath(self, fpath=None):
92
- if fpath or not self._src_dpath:
93
- src_dpath = fpath or self.fpath
94
- if isinstance(src_dpath, Path):
95
- src_dpath = src_dpath.as_posix()
96
- src_dpath = Path(os.path.splitext(src_dpath)[0])
97
- if fpath:
98
- return src_dpath
99
- self._src_dpath = src_dpath
100
- return self._src_dpath
101
-
102
- def get_rotation(self, xticks):
103
- locs, labels = xticks
104
-
105
- boxes = [l.get_window_extent().get_points() for l in labels]
106
- x0, x1 = boxes[0][0][0], boxes[-1][0][0]
107
- label_w = (x1 - x0) / (len(labels))
108
- label_hx = max([b[-1][1] - b[0][1] for b in boxes])
109
- rotation = math.degrees(math.sin(label_hx / label_w))
110
- return rotation
111
-
112
- def plot(self):
113
- print_info(
114
- (
115
- _(
116
- "Generate the scores and scoring "
117
- + 'rate plots now ? (Yes: "Y","y")'
118
- )
119
- if len(self.scores.columns) > 1
120
- else _(
121
- "Generate the scoring " + 'rate plots now ? (Yes: "Y","y")'
122
- )
123
- )
124
- )
125
- p_input = get_input()
126
- if p_input and p_input in "Yy":
127
- if len(self.scores.columns) > 1:
128
- self.plot_scores()
129
- self.plot_scores_m1()
130
-
131
- def get_scores_img_fpath(self, student_name=None):
132
-
133
- img_path = (
134
- self.src_dpath
135
- / (
136
- (
137
- _("scores_of_all_students")
138
- if not student_name
139
- else (
140
- _("scores_of_{0}").format(student_name)
141
- if student_name != self.average_points_s
142
- else _("average_scores")
143
- )
144
- )
145
- + ".png"
146
- )
147
- ).as_posix()
148
- return img_path
149
-
150
- def print_summary(self):
151
- scores_m1 = self.scores_m1.copy()
152
- scores_m1_t = scores_m1[scores_m1.columns[0]]
153
- scores_m1_index = scores_m1.index.to_list()
154
- scores_m1_index_lenx = max([len(i) for i in scores_m1_index])
155
- if scores_m1_index_lenx == 3:
156
- scores_m1_index = [
157
- (i if len(i) > 2 else i[0] + " " + i[1])
158
- for i in scores_m1_index
159
- ]
160
- scores_m1_t = pd.Series(scores_m1_t.values, index=scores_m1_index)
161
- scores_m1_t = scores_m1_t.sort_values(ascending=False)
162
- scores_m1_t_len2 = len(str(scores_m1_t.size))
163
- scores_m1_t_s = sqr_slist(
164
- [
165
- f"{i+1:>{scores_m1_t_len2}}. {s_name} ({s_point:.2f})"
166
- for i, (s_name, s_point) in enumerate(scores_m1_t.items())
167
- ]
168
- )
169
- sep = get_random_sep_char() * max(
170
- [get_len(s) for s in scores_m1_t_s.split("\n")]
171
- )
172
- summary_s = (
173
- "\n"
174
- + _("Summary of {0}:").format(self.full_name)
175
- + "\n"
176
- + sep
177
- + "\n"
178
- + _("Scores:")
179
- + "\n"
180
- + scores_m1_t_s
181
- + "\n"
182
- + sep
183
- )
184
- print_info(summary_s)
185
-
186
- pass
187
-
188
- @property
189
- def scores_img_fpaths(self):
190
- if not self._scores_img_fpaths:
191
- fpaths = []
192
- for name in self.student_names:
193
- fpaths.append(
194
- [
195
- name,
196
- self.get_scores_m1_img_fpath(name),
197
- self.get_scores_img_fpath(name),
198
- ]
199
- )
200
- self._scores_img_fpaths = fpaths
201
- return self._scores_img_fpaths
202
-
203
- @property
204
- def scores_img_afpaths(self):
205
- if not self._scores_img_afpaths:
206
- self._scores_img_afpaths = [
207
- [name, self.get_scores_img_fpath(name)]
208
- for name in self.astudent_names
209
- ]
210
- return self._scores_img_afpaths
211
-
212
- def plot_scores(self, max_test_num=None):
213
- scores = self.scores
214
-
215
- scores_m1 = self.scores_m1.copy()
216
- scores_m1_d = scores_m1.loc[:, scores_m1.columns[1]]
217
- max_test_num = max_test_num or scores.columns.size
218
- test_names = self.test_names
219
- img_saved_s = _('[{0}] "{1}" {2}has been saved.')
220
- img_fpaths = [f for __, f in self.scores_img_afpaths] + [
221
- self.get_scores_img_fpath()
222
- ]
223
-
224
- img_paths_lenx = max(get_len(f) for f in img_fpaths)
225
-
226
- i_index = 0
227
- img_fpaths_len = len(img_fpaths)
228
- img_fpaths_len2 = len(str(img_fpaths_len))
229
- labelrotation = None
230
- for student_name, s_scores in scores.iterrows():
231
- comment = self.get_comment(student_name)
232
- discipline_points = scores_m1_d.loc[student_name]
233
- student_name0 = (
234
- student_name
235
- if len(student_name) > 2
236
- else (student_name[0] + " " + student_name[1])
237
- )
238
-
239
- img_fpath = self.get_scores_img_fpath(student_name)
240
- img_fpath_len_diff = (
241
- img_paths_lenx - get_zh_CN_chars_len(img_fpath) - len(img_fpath)
242
- )
243
-
244
- s_scores = s_scores[:max_test_num]
245
- plt.plot(range(s_scores.size), s_scores)
246
- plt.title(
247
- _("The scores of Student {0}").format(student_name0)
248
- if not student_name == self.average_points_s
249
- else _("The average scores")
250
- )
251
- xticks = plt.xticks(range(s_scores.size), self.test_names)
252
-
253
- if not labelrotation:
254
- labelrotation = self.get_rotation(xticks)
255
- plt.tick_params(axis="x", labelrotation=labelrotation)
256
-
257
- plt.xlabel(_("Examination names of {0}").format(self.subject))
258
- plt.ylabel(
259
- (
260
- _("Examination Points " + "({0} points in total)")
261
- if self.total_points != 1.0
262
- else _("Examination Points " + "({0} point in total)")
263
- ).format(self.total_points)
264
- )
265
- for test_name, point in s_scores.items():
266
- plt.text(
267
- *(test_names.index(test_name), point),
268
- round(point, 2),
269
- va="bottom",
270
- bbox=dict(
271
- facecolor="red",
272
- alpha=self.plot_alpha0,
273
- boxstyle="round",
274
- ),
275
- )
276
-
277
- comment_value = ""
278
-
279
- if comment:
280
- comment_value += comment
281
-
282
- if not discipline_points == 0.0:
283
- comment_value += ("\n" if comment else "") + (
284
- _("Discipline point: {0}.")
285
- if discipline_points == 1.0
286
- else _("Discipline points: {0}.")
287
- ).format(discipline_points)
288
-
289
- if comment_value:
290
- plt.text(
291
- *(0, s_scores.max()),
292
- comment_value,
293
- ha="left",
294
- va="top",
295
- bbox=dict(
296
- facecolor="white",
297
- alpha=0.5,
298
- boxstyle="round",
299
- edgecolor="red",
300
- ),
301
- )
302
-
303
- plt.savefig(img_fpath, bbox_inches="tight")
304
- print_info(
305
- img_saved_s.format(
306
- f"{i_index+1:>{img_fpaths_len2}}/{img_fpaths_len}",
307
- img_fpath,
308
- " " * img_fpath_len_diff,
309
- )
310
- )
311
- plt.cla()
312
- i_index += 1
313
-
314
- plt.cla()
315
- student_scores_img_fpath = img_fpaths[-1]
316
- scores0 = scores.T
317
- scores0.columns.name = self.name_s
318
- img_fpath_len_diff = (
319
- img_paths_lenx
320
- - get_zh_CN_chars_len(student_scores_img_fpath)
321
- - len(student_scores_img_fpath)
322
- )
323
-
324
- scores0.plot()
325
- plt.title(_("The scores of all students"))
326
- xticks = plt.xticks(range(s_scores.size), self.test_names)
327
-
328
- if not labelrotation:
329
- labelrotation = self.get_rotation(xticks)
330
- plt.tick_params(axis="x", labelrotation=labelrotation)
331
-
332
- plt.xlabel(_("Examination names of {0}").format(self.subject))
333
- plt.ylabel(
334
- (
335
- _("Examination Points " + "({0} points in total)")
336
- if self.total_points != 1.0
337
- else _("Examination Points " + "({0} point in total)")
338
- ).format(self.total_points)
339
- )
340
- plt.savefig(student_scores_img_fpath, bbox_inches="tight")
341
- print_info(
342
- img_saved_s.format(
343
- f"{i_index+1:>{img_fpaths_len2}}/{img_fpaths_len}",
344
- student_scores_img_fpath,
345
- " " * img_fpath_len_diff,
346
- )
347
- )
348
- plt.cla()
349
-
350
- def get_scores_m1_img_fpath(self, student_name):
351
- img_fpath = (
352
- self.src_dpath
353
- / (
354
- (
355
- _("scoring_rate_of_{0}").format(student_name)
356
- if not student_name == self.average_points_s
357
- else _("average_scoring_rate")
358
- )
359
- + ".png"
360
- )
361
- ).as_posix()
362
-
363
- return img_fpath
364
-
365
- @property
366
- def scores_m1_img_afpaths(self):
367
- if not self._scores_m1_img_afpaths:
368
- fpaths = [
369
- [v, self.get_scores_m1_img_fpath(v)]
370
- for v in self.astudent_names
371
- ]
372
- self._scores_m1_img_afpaths = fpaths
373
- return self._scores_m1_img_afpaths
374
-
375
- def plot_scores_m1(self):
376
- scores_m1 = self.scores_m1
377
- scores_m1.loc[self.average_points_s] = scores_m1.mean(axis=0)
378
- scores_m1_t = scores_m1.loc[:, scores_m1.columns[0]]
379
- scores_m1_d = scores_m1.loc[:, scores_m1.columns[1]]
380
- scores_m1_q = scores_m1.loc[:, scores_m1.columns[2:]]
381
- scores_m1_q = scores_m1_q.astype(float)
382
- img_saved_s = _('[{0}] "{1}" {2}has been saved.')
383
-
384
- for student_name, q_points in scores_m1_q.iterrows():
385
- for q_title, q_point in q_points.items():
386
- q_point_t = self.get_points(q_title)
387
- scores_m1_q.loc[student_name, q_title] = round(
388
- q_point * 100 / q_point_t, 1
389
- )
390
- img_fpaths = [f for __, f in self.scores_m1_img_afpaths]
391
- img_fpaths_lenx = max(get_len(f) for f in img_fpaths)
392
-
393
- labelrotation = None
394
- i_index = 0
395
- img_fpaths_len = len(img_fpaths)
396
- img_fpaths_len2 = len(str(img_fpaths_len))
397
- for student_name, q_point_rates in scores_m1_q.iterrows():
398
-
399
- s_total_points = scores_m1_t.loc[student_name]
400
-
401
- student_name0 = (
402
- student_name
403
- if len(student_name) > 2
404
- else (student_name[0] + " " + student_name[1])
405
- )
406
-
407
- img_fpath = self.get_scores_m1_img_fpath(student_name)
408
- img_fpath_len_diff = (
409
- img_fpaths_lenx
410
- - get_zh_CN_chars_len(img_fpath)
411
- - len(img_fpath)
412
- )
413
-
414
- img = plt.bar(range(q_point_rates.size), q_point_rates)
415
- plt.title(
416
- (
417
- _("The scoring rate of Student {0} ({1})").format(
418
- student_name0, f"{s_total_points}/{self.total_points}"
419
- )
420
- if student_name != self.average_points_s
421
- else _("The average scoring rate ({0})").format(
422
- f"{s_total_points:.2f}/{self.total_points}"
423
- )
424
- )
425
- )
426
- xticks = plt.xticks(range(q_point_rates.size), self.question_titles)
427
-
428
- if not labelrotation:
429
- labelrotation = self.get_rotation(xticks)
430
- plt.tick_params(axis="x", labelrotation=labelrotation)
431
-
432
- showed_test_name = f"{self.subject}/{self.short_name}"
433
- plt.xlabel(
434
- _("Question titles of {0} ({1})").format(
435
- showed_test_name,
436
- (
437
- _("{0} point in total, ")
438
- if self.total_points == 1
439
- else _("{0} points in total, ")
440
- ).format(self.total_points)
441
- + (
442
- _("{0} point got")
443
- if s_total_points == 1
444
- else _("{0} points got")
445
- ).format(s_total_points),
446
- )
447
- )
448
-
449
- plt.ylabel(_("Scoring rate(%)"))
450
- for q_title, s_rate in q_point_rates.items():
451
- plt.text(
452
- *(self.question_titles.index(q_title), s_rate),
453
- f"{s_rate}%",
454
- va="bottom",
455
- ha="center",
456
- bbox=dict(
457
- facecolor="red",
458
- alpha=self.plot_alpha0,
459
- boxstyle="round",
460
- ),
461
- )
462
-
463
- plt.savefig(img_fpath, bbox_inches="tight")
464
- print_info(
465
- img_saved_s.format(
466
- f"{i_index+1:>{img_fpaths_len2}}/{img_fpaths_len}",
467
- img_fpath,
468
- " " * img_fpath_len_diff,
469
- )
470
- )
471
- plt.cla()
472
- i_index += 1
473
- i_index = 0
474
-
475
- def get_comment(self, student_name):
476
- comment = _("comment:") + "\n"
477
- for row in self.sheet0.iter_rows():
478
- if student_name == row[0].value:
479
- if not row[0].comment:
480
- return None
481
- comment += row[0].comment.text
482
- return comment
483
-
484
- return None
485
-
486
- @property
487
- def dpath(self):
488
- if not self._dpath:
489
- self._dpath = self.fpath.parent
490
- print(self._dpath)
491
- return self._dpath
492
-
493
- @property
494
- def config(self):
495
- return self.teacher.config
496
-
497
- @property
498
- def teacher(self):
499
- if not self._teacher:
500
- self._teacher = Teacher()
501
- return self._teacher
502
-
503
- def enter(self):
504
- __ = self.scores
505
- self.plot()
506
- self.print_summary()
507
- self.email.send_scores()
508
- pass
509
-
510
- def read(self):
511
-
512
- p_dpath = self.config.get(self.p_path_key)
513
- if p_dpath == ".":
514
- p_dpath = None
515
- initialdir = (
516
- p_dpath
517
- if (p_dpath and Path(p_dpath).exists())
518
- else self.teacher.exam_dpath
519
- )
520
-
521
- filetypes = ((_("Spreadsheet Files"), "*.xlsx"),)
522
-
523
- tkroot = tk.Tk()
524
- tkroot.withdraw()
525
-
526
- filename = filedialog.askopenfilename(
527
- title=_("Please select the scores file"),
528
- initialdir=initialdir,
529
- filetypes=filetypes,
530
- )
531
-
532
- if (
533
- filename is None
534
- or filename == ()
535
- or filename == "."
536
- or filename == ""
537
- ):
538
- print_warning(_("No file was selected, exit."))
539
- exit()
540
-
541
- print_info(_('Scores file "{0}" has been selected.').format(filename))
542
- self.config.save(self.p_path_key, Path(filename).parent.as_posix())
543
-
544
- self.full_name = filename
545
-
546
- self.plot()
547
-
548
- def get_scores(self, fpath):
549
- if not Path(fpath).exists():
550
- return None
551
-
552
- discipline_s = "考试纪律"
553
- scores = pd.read_excel(fpath, skiprows=[0, 2])
554
- scores.rename(columns={scores.columns[0]: self.name_s}, inplace=True)
555
- scores.dropna(subset=[self.name_s], inplace=True)
556
- scores.set_index(self.name_s, inplace=True)
557
- scores[discipline_s] = scores[discipline_s].fillna(0)
558
- point_cols = scores.columns[self.points_index0 :].to_list()
559
- scores["总分"] = (
560
- scores.loc[:, point_cols].sum(axis=1) + scores[discipline_s]
561
- )
562
- scores.drop([scores.columns[1]], axis=1, inplace=True)
563
- scores = scores.loc[:, ~scores.columns.str.contains("^Unnamed")]
564
-
565
- return scores
566
-
567
- def edit_test_m1(self):
568
- fpaths = self.get_fpaths()
569
- fpath_m1, __ = fpaths[-1]
570
- print_warning(
571
- _(
572
- "Would you like to edit \"{0}\" ? (\"Yes\": 'Y' or 'y'. "
573
- + "Default: No)"
574
- ).format(fpath_m1)
575
- )
576
- edit_yn = get_input()
577
- if edit_yn and edit_yn in "Yy":
578
- open_path(fpath_m1)
579
- print_info(
580
- _(
581
- "Ok, I have edited and closed it? (Enter any key "
582
- + "to continue)"
583
- )
584
- )
585
- get_input()
586
- pass
587
- pass
588
-
589
- @property
590
- def scores(self):
591
- if self._scores is None:
592
- if not self.fpath == self.fpaths[-1][0]:
593
- self.edit_test_m1()
594
- fpaths = self.fpaths[::-1]
595
- if len(fpaths) < 1:
596
- return None
597
-
598
- scores_cols = ["Name"]
599
- scores_rows = None
600
-
601
- for fi, (f, __) in enumerate(fpaths):
602
- name = Path(f).stem
603
- scores_cols.append(name)
604
- f_scores = self.get_scores(f)
605
- s_index = f_scores.index.to_list()
606
- if not scores_rows:
607
- scores_rows = [[n] for n in s_index]
608
-
609
- for i in range(len(scores_rows)):
610
- r = scores_rows[i]
611
- s_name = r[0]
612
-
613
- if s_name in s_index:
614
- s_points = f_scores.loc[s_name, f_scores.columns[0]]
615
- s_points = 0 if pd.isna(s_points) else s_points
616
- r.append(s_points)
617
- scores_rows[i] = r
618
- else:
619
- r.append(0)
620
-
621
- scores = pd.DataFrame(scores_rows, columns=scores_cols)
622
-
623
- scores.set_index("Name", inplace=True)
624
- scores.loc[self.average_points_s] = scores.mean(axis=0)
625
-
626
- self._scores = scores[scores.columns[::-1]]
627
-
628
- return self._scores
629
-
630
- @scores.deleter
631
- def scores(self):
632
- self._scores = None
633
- del self.total_points
634
- del self.scores_m1
635
-
636
- @property
637
- def fpaths(self):
638
- fpaths = self.get_fpaths()
639
- return fpaths
640
- pass
641
-
642
- @fpaths.setter
643
- def fpaths(self, paths):
644
- self._fpaths = paths
645
- pass
646
-
647
- def get_fpaths(self, dpath=None):
648
- fpath_time = None
649
- if not self._fpaths:
650
- dpath = dpath or self.fpath.parent.as_posix()
651
- fpaths = []
652
- fpath_time = None
653
- for f in os.listdir(dpath):
654
- if f.endswith(self.fext):
655
- fpath = (Path(dpath) / f).as_posix()
656
- wb = load_workbook(fpath, read_only=True)
657
- sheet = wb.active
658
- test_t1 = self.get_test_t(sheet)
659
- if test_t1:
660
- test_t1 = test_t1[1]
661
- else:
662
- test_t1 = get_file_ctime(fpath)
663
-
664
- if fpath == self.fpath.as_posix():
665
- fpath_time = test_t1
666
-
667
- fpaths.append([fpath, test_t1])
668
- self._fpaths = fpaths
669
- if len(self._fpaths) < 1:
670
- return None
671
-
672
- self._fpaths = sorted(self._fpaths, key=lambda f: (f[1], f[0]))
673
- if fpath_time:
674
- self._fpaths = [[f, t] for f, t in fpaths if t <= fpath_time]
675
- return self._fpaths
676
-
677
- @property
678
- def sclass(self):
679
- if not self._sclass:
680
- value = self.full_name.split("/")
681
- if len(value) > 2:
682
- value = value[0]
683
- else:
684
- return None
685
- self._sclass = value
686
- return self._sclass
687
-
688
- @property
689
- def sclass_dpath(self):
690
- if not self._sclass_dpath:
691
- sclass_dpath = self.teacher.exam_dpath / self.sclass
692
- if not sclass_dpath.exists():
693
- os.makedirs(sclass_dpath, exist_ok=True)
694
- self._sclass_dpath = sclass_dpath
695
- return self._sclass_dpath
696
-
697
- @property
698
- def subject(self):
699
- value = self.full_name.split("/")
700
- if len(value) > 2:
701
- value = value[1]
702
- else:
703
- return None
704
- return value
705
-
706
- @property
707
- def short_name_m2(self):
708
- if not self._short_name_m2:
709
- if len(self.fpaths) > 1:
710
- name, __ = self.fpaths[-2]
711
- self._short_name_m2 = Path(name).stem
712
- else:
713
- return None
714
-
715
- return self._short_name_m2
716
-
717
- @property
718
- def test_t(self):
719
- if not self._test_t:
720
- self._test_t = self.get_test_t(self.sheet0)
721
-
722
- return self._test_t
723
-
724
- def get_cell_time(self, value):
725
- value = str(value)
726
- value = re.sub(r"\s+", " ", value)
727
- time = (
728
- (
729
- datetime.strptime(value, "%Y/%m/%d %H:%M")
730
- if ":" in value
731
- else (
732
- datetime.strptime(value, "%Y/%m/%d %H:%M")
733
- if ":" in value
734
- else datetime.strptime(value, "%Y/%m/%d %H%M")
735
- )
736
- )
737
- if "/" in value
738
- else (
739
- (
740
- datetime.strptime(value, "%Y%m%d %H:%M")
741
- if ":" in value
742
- else (
743
- datetime.strptime(value, "%Y%m%d %H:%M")
744
- if ":" in value
745
- else datetime.strptime(value, "%Y%m%d %H%M")
746
- )
747
- )
748
- if " " in value
749
- else datetime.strptime(value, "%Y%m%d%H%M")
750
- )
751
- )
752
-
753
- return time
754
-
755
- def get_test_t(self, sheet, interval=90):
756
-
757
- test_t = None
758
- test_t0 = sheet.cell(1, 2).value
759
- test_t1 = sheet.cell(1, 5).value
760
- time_from_cell_value = self.get_cell_time
761
- if test_t0:
762
- try:
763
- test_t0 = time_from_cell_value(test_t0)
764
- except:
765
- print_error(
766
- _(
767
- "Failed to get examination " + 'start time from "{0}".'
768
- ).format(test_t0)
769
- )
770
- return None
771
- else:
772
- return None
773
-
774
- if test_t1:
775
- try:
776
- test_t1 = time_from_cell_value(test_t1)
777
- except:
778
- print_error(
779
- _(
780
- "Failed to get examination " + 'end time from "{0}".'
781
- ).format(test_t1)
782
- )
783
- else:
784
- pass
785
-
786
- if test_t0 and not test_t1:
787
- test_t1 = test_t0 + timedelta(minutes=interval)
788
- print_warning(
789
- _('The examination end time is set to "{0}".').format(
790
- test_t1.strftime("%Y/%m/%d %H%M")
791
- )
792
- )
793
-
794
- test_t = [test_t0, test_t1]
795
-
796
- return test_t
797
-
798
- @property
799
- def short_name(self):
800
- if not self._short_name:
801
- self._short_name = Path(self.fpath).stem
802
-
803
- return self._short_name
804
-
805
- @property
806
- def wb(self):
807
- if not self._wb:
808
- self._wb = load_workbook(self.fpath)
809
- return self._wb
810
-
811
- @property
812
- def sheet0(self):
813
-
814
- if not self._sheet0:
815
- self._sheet0 = self.wb[self.wb.sheetnames[0]]
816
- return self._sheet0
817
-
818
- @property
819
- def scores_m1(self):
820
-
821
- if self._scores_m1 is None:
822
-
823
- fpath = self.fpath
824
- scores = self.get_scores(fpath)
825
- scores_m1 = scores
826
- scores_m1.fillna(0, axis=1, inplace=True)
827
- scores_m1.loc[self.average_points_s] = scores_m1.mean(axis=0)
828
- self._scores_m1 = scores_m1
829
-
830
- return self._scores_m1
831
-
832
- @scores_m1.deleter
833
- def scores_m1(self):
834
- self._scores_m1 = None
835
-
836
- def get_points(self, question):
837
- points = question
838
- if "(" in points:
839
- points = points.split("(")[-1]
840
- if "(" in points:
841
- points = points.split("(")[-1]
842
- if "分" in points:
843
- points = points.split("分")[-2]
844
- if ")" in points:
845
- points = points.split(")")[-2]
846
- if ")" in points:
847
- points = points.split(")")[-2]
848
- points = points.strip()
849
-
850
- if str.isnumeric(points.replace(".", "")):
851
- return float(points)
852
-
853
- return 0
854
-
855
- @property
856
- def total_points(self):
857
- if not self._total_points:
858
- total_points = sum(
859
- [self.get_points(q) for q in self.question_titles]
860
- )
861
- self._total_points = total_points
862
-
863
- return self._total_points
864
-
865
- @total_points.deleter
866
- def total_points(self):
867
- self._total_points = None
868
-
869
- @property
870
- def question_titles(self):
871
- if not self._question_titles:
872
- self._question_titles = self.get_question_titles(self.scores_m1)
873
-
874
- return self._question_titles
875
-
876
- def get_question_titles(self, scores):
877
- question_titles = scores.columns.to_list()
878
- question_titles = [
879
- q
880
- for q in question_titles
881
- if (
882
- ("(" in q or "(" in q)
883
- and bool(
884
- re.search(
885
- r"\d",
886
- q.split("(")[-1]
887
- .split("(")[-1]
888
- .split(")")[0]
889
- .split(")")[0],
890
- )
891
- )
892
- )
893
- ]
894
- return question_titles
895
-
896
- @property
897
- def names_fpath(self):
898
- fpath = self.teacher.dpath / (_("exam_names") + ".txt")
899
- if not fpath.exists():
900
- with open(fpath, "w", encoding="utf-8") as f:
901
- f.write("")
902
- return fpath
903
-
904
- @property
905
- def fpath_m2(self):
906
- if not self._fpath_m2:
907
- path = self.fpaths[-2][0] if len(self.fpaths) > 1 else None
908
- self._fpath_m2 = path
909
- return self._fpath_m2
910
-
911
- @property
912
- def fpath(self):
913
- if not self._fpath:
914
- fpath = self.teacher.exam_dpath / (
915
- Path(self.full_name).as_posix() + self.fext
916
- )
917
- if not fpath.parent.exists():
918
- os.makedirs(fpath.parent.as_posix(), exist_ok=True)
919
-
920
- if not fpath.exists():
921
- dpath = fpath.parent.as_posix()
922
- self.fpath = fpath
923
- fpaths = self.get_fpaths(dpath)
924
- self.fpath = None
925
- scores = None
926
- if fpaths:
927
- self.fpath, __ = fpaths[-1]
928
- scores = self.scores
929
- self.fpath = None
930
- self.fpaths = None
931
- shutil.copy(self.fpath0, fpath)
932
-
933
- print_info(
934
- _(
935
- 'Scores spreadsheet "{0}" doesn\'t '
936
- + 'exist, spreadsheet "{1}" was '
937
- + 'copied to "{0}".'
938
- ).format(fpath, self.fpath0)
939
- )
940
-
941
- name_m2 = self.short_name_m2 or self.no_test_m2_s
942
-
943
- scores_m2 = (
944
- scores[scores.columns[-2]]
945
- if not scores is None and (len(scores.columns) > 1)
946
- else None
947
- )
948
-
949
- if not scores_m2 is None:
950
- scores_m2 = scores_m2.copy()
951
- scores_m2.drop(
952
- [self.average_points_s], axis=0, inplace=True
953
- )
954
-
955
- wb = load_workbook(fpath)
956
- sheet = wb[wb.sheetnames[0]]
957
- sheet.cell(2, 3, name_m2)
958
-
959
- if not scores_m2 is None:
960
- r_index = 0
961
- for i, (s_name, s_score) in enumerate(scores_m2.items()):
962
- sheet.cell(self.name_index0 + i, 1, s_name)
963
- sheet.cell(self.name_index0 + i, 3, s_score)
964
- r_index = self.name_index0 + i
965
-
966
- for row_index in range(r_index + 1, sheet.max_row + 1):
967
- sheet.cell(row_index, 1, "")
968
- sheet.cell(row_index, 3, "")
969
-
970
- print_info(
971
- _(
972
- "The recent examination scores ({0}) "
973
- + 'have been added to "{1}".'
974
- ).format(name_m2, fpath)
975
- if not scores_m2 is None
976
- else _("There is no recent tests.")
977
- )
978
-
979
- wb.save(fpath)
980
-
981
- print_info(_('Spreadsheet "{0}" has been saved.').format(fpath))
982
- wb.close()
983
- sheet = None
984
- print_info(
985
- _(
986
- "Please update the question titles "
987
- + ", student names "
988
- + "and scores "
989
- + 'of "{0}" '
990
- + "according to the comments. "
991
- + "(Ok, open it for me [Press any "
992
- + "key to open file])"
993
- ).format(fpath)
994
- )
995
- get_input()
996
-
997
- y_input_n = 64
998
- for i in range(y_input_n):
999
- open_path(fpath)
1000
- print_warning(
1001
- _(
1002
- "Ok, I have updated the question"
1003
- + " titles, student names and scores."
1004
- + " (Press any key to continue)"
1005
- )
1006
- )
1007
- get_input()
1008
- wb = load_workbook(fpath)
1009
- sheet = wb[wb.sheetnames[0]]
1010
-
1011
- total_points = sum(
1012
- [
1013
- self.get_points(q)
1014
- for q in [
1015
- sheet.cell(2, c).value
1016
- for c in range(1, sheet.max_column + 1)
1017
- ]
1018
- if (q and ("(" in q or "(" in q))
1019
- ]
1020
- )
1021
- print_warning(
1022
- (
1023
- _("Is the total score {0} points?")
1024
- if total_points != 1.0
1025
- else _("Is the total score {0} point?")
1026
- ).format(self.total_points)
1027
- + _(' (Yes: "Y","y")')
1028
- )
1029
- y_input = get_input()
1030
- if y_input and y_input in "Yy":
1031
- del self.scores
1032
- break
1033
- else:
1034
- wb.close()
1035
- sheet = None
1036
- if i >= y_input_n:
1037
- exit()
1038
-
1039
- self._fpath = fpath
1040
-
1041
- src_dpath = Path(self.get_src_dpath(self._fpath))
1042
- if not src_dpath.exists():
1043
- os.makedirs(src_dpath, exist_ok=True)
1044
-
1045
- return self._fpath
1046
-
1047
- @fpath.setter
1048
- def fpath(self, path):
1049
- self._fpath = path
1050
-
1051
- @property
1052
- def full_name(self):
1053
- if not self._full_name:
1054
- names = None
1055
- with open(self.names_fpath, "r", encoding="utf-8") as f:
1056
- names = f.read().replace(" ", "").strip().split("\n")
1057
- names = [n for n in names if (len(n) > 0)]
1058
- names_len = len(names)
1059
-
1060
- name_writed_s = lambda name=None: (
1061
- _(
1062
- 'The examination name "{0}" ' + 'has been saved to "{1}".'
1063
- ).format(name, self.names_fpath)
1064
- if name
1065
- else _('The examination name has been saved to "{0}".').format(
1066
- self.names_fpath
1067
- )
1068
- )
1069
-
1070
- if names_len > 0:
1071
- name0 = (
1072
- names[0]
1073
- if not any([n.startswith(">") for n in names])
1074
- else [n for n in names if n.startswith(">")][0].replace(
1075
- ">", ""
1076
- )
1077
- )
1078
- print_error(
1079
- (
1080
- _("The saved examination names are as follow:")
1081
- if names_len > 1
1082
- else _("The saved examination name is as follow:")
1083
- )
1084
- )
1085
-
1086
- names_len2 = len(str(names_len))
1087
- print_warning(
1088
- sqr_slist(
1089
- [
1090
- f"{i+1:>{names_len2}} {n}"
1091
- for i, n in enumerate(names)
1092
- ]
1093
- )
1094
- )
1095
- names = [n.replace(">", "") for n in names]
1096
- print_info(
1097
- _(
1098
- "Select the examination name "
1099
- + "you entered (index), "
1100
- + "or enter new examination "
1101
- + "name, please! (default: {0})"
1102
- ).format(name0)
1103
- )
1104
- name_i = None
1105
- for i in range(0, 3):
1106
- n_input = get_input().replace(" ", "")
1107
- if len(n_input) > 0:
1108
- if n_input.isnumeric():
1109
- n_input = int(n_input) - 1
1110
- if n_input >= 0 and n_input <= names_len:
1111
- name_i = names[n_input]
1112
- break
1113
- break
1114
- else:
1115
- name_i = n_input
1116
- break
1117
- else:
1118
- name_i = name0
1119
- break
1120
-
1121
- if i > 2:
1122
- print_error(_("Unexpected value was got. Exit."))
1123
- exit()
1124
- else:
1125
- print_error(_("Unexpected value was got."))
1126
-
1127
- if name_i != name0:
1128
- if name_i in names:
1129
- names.remove(name_i)
1130
- with open(self.names_fpath, "w", encoding="utf-8") as f:
1131
- f.write("\n".join([">" + name_i] + names))
1132
- name0 = name_i
1133
- print_info(name_writed_s(name0))
1134
-
1135
- self._full_name = name0
1136
-
1137
- else:
1138
- print_info(
1139
- _(
1140
- "Hello~ tell {0} the examination"
1141
- + " name, please!"
1142
- + " (e.g: Class 5(1)/English Volume "
1143
- + "1/Unit 1 Testing)"
1144
- ).format(app_name)
1145
- )
1146
- for i in range(0, 3):
1147
- name0 = get_input().replace(" ", "")
1148
- if len(name0) > 0:
1149
- with open(self.names_fpath, "w", encoding="utf-8") as f:
1150
- f.write(">" + name0)
1151
- print_info(name_writed_s(name0))
1152
- self._full_name = name0
1153
- break
1154
- else:
1155
- print_error(_("Unexpected value was got."))
1156
- if i > 2:
1157
- print_error(_("Unexpected value was got." + " Exit."))
1158
- exit()
1159
-
1160
- if self._full_name.startswith("/"):
1161
- self._full_name = re.sub(r"^/+", "", self._full_name)
1162
- if self._full_name.startswith("\\"):
1163
- self._full_name = re.sub(r"^\\+", "", self._full_name)
1164
- if ".." in self._full_name:
1165
- self._full_name = re.sub("..", "", self._full_name)
1166
-
1167
- if "/" in self._full_name:
1168
- dpath = (self.teacher.exam_dpath / Path(self._full_name)).parent
1169
- if not dpath.exists():
1170
- os.makedirs(dpath, exist_ok=True)
1171
-
1172
- return self._full_name
1173
-
1174
- @full_name.setter
1175
- def full_name(self, value):
1176
- exam_dpath = self.teacher.exam_dpath.as_posix()
1177
- value = Path(value).as_posix()
1178
- if exam_dpath in value:
1179
- value = value.replace(exam_dpath, "")
1180
- if value.startswith("/"):
1181
- value = re.sub(r"^/+", "", value)
1182
- if value.startswith("\\"):
1183
- value = re.sub(r"^\\+", "", value)
1184
- if ".." in value:
1185
- value = re.sub("..", "", value)
1186
-
1187
- value = os.path.splitext(value)[0]
1188
- self._full_name = value
1189
-
1190
-
1191
- # The end.