django-whiteneuron 0.2.47__tar.gz → 0.2.48__tar.gz

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.
Files changed (184) hide show
  1. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/PKG-INFO +36 -3
  2. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/README.md +35 -2
  3. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/django_whiteneuron.egg-info/PKG-INFO +36 -3
  4. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/django_whiteneuron.egg-info/SOURCES.txt +2 -0
  5. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/pyproject.toml +1 -1
  6. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/modeladmin.py +36 -59
  7. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/models.py +29 -6
  8. django_whiteneuron-0.2.48/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
  9. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/locale/vi/LC_MESSAGES/django.po +647 -673
  10. django_whiteneuron-0.2.48/whiteneuron/static/base/css/styles.css +2 -0
  11. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/base/image_change_form.html +1 -1
  12. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/base/userprofile_change_form.html +1 -1
  13. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/change_list.html +0 -1
  14. django_whiteneuron-0.2.48/whiteneuron/templates/admin/wn_change_form.html +12 -0
  15. django_whiteneuron-0.2.48/whiteneuron/templates/admin/wn_history_panel.html +113 -0
  16. django_whiteneuron-0.2.47/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
  17. django_whiteneuron-0.2.47/whiteneuron/static/base/css/styles.css +0 -2
  18. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/LICENSE +0 -0
  19. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/django_whiteneuron.egg-info/dependency_links.txt +0 -0
  20. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/django_whiteneuron.egg-info/requires.txt +0 -0
  21. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/django_whiteneuron.egg-info/top_level.txt +0 -0
  22. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/setup.cfg +0 -0
  23. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/__init__.py +0 -0
  24. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/__init__.py +0 -0
  25. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/admin.py +0 -0
  26. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/apps.py +0 -0
  27. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/asgi.py +0 -0
  28. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/ckeditor.py +0 -0
  29. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/filters.py +0 -0
  30. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/forms.py +0 -0
  31. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/management/__init__.py +0 -0
  32. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/management/commands/__init__.py +0 -0
  33. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/management/commands/init_admin.py +0 -0
  34. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/management/commands/init_groupuser.py +0 -0
  35. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/management/commands/init_guest.py +0 -0
  36. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/middleware.py +0 -0
  37. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0001_initial.py +0 -0
  38. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0002_imagemodel_userprofile_user_avatar_user_biography_and_more.py +0 -0
  39. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0003_notification_obj_link.py +0 -0
  40. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0004_delete_notification.py +0 -0
  41. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0005_user_show_softdelete.py +0 -0
  42. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0006_user_is_bot.py +0 -0
  43. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0007_app.py +0 -0
  44. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0008_app_created_at_app_created_by_app_deleted_at_and_more.py +0 -0
  45. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0009_alter_app_table.py +0 -0
  46. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0010_app_category.py +0 -0
  47. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0011_alter_app_options.py +0 -0
  48. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0012_app_permission.py +0 -0
  49. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0013_app_thumbnail_url.py +0 -0
  50. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0014_alter_app_created_at_alter_app_updated_at_and_more.py +0 -0
  51. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0015_ipblacklist.py +0 -0
  52. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0016_uablacklist.py +0 -0
  53. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/0017_alter_uablacklist_pattern.py +0 -0
  54. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/migrations/__init__.py +0 -0
  55. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/resources.py +0 -0
  56. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/settings.py +0 -0
  57. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/sites.py +0 -0
  58. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/tasks.py +0 -0
  59. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/templatetags/__init__.py +0 -0
  60. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/templatetags/unfold_list_custom.py +0 -0
  61. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/templatetags/wn_filters.py +0 -0
  62. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/tests.py +0 -0
  63. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/thread_local.py +0 -0
  64. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/urls.py +0 -0
  65. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/utils.py +0 -0
  66. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/views.py +0 -0
  67. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/base/widgets.py +0 -0
  68. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/__init__.py +0 -0
  69. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_import_export/__init__.py +0 -0
  70. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_import_export/admin.py +0 -0
  71. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_import_export/constants.py +0 -0
  72. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_import_export/mixins.py +0 -0
  73. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_import_export/tasks.py +0 -0
  74. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_import_export/utils.py +0 -0
  75. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_modeltranslation/__init__.py +0 -0
  76. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_modeltranslation/admin.py +0 -0
  77. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/contrib/wn_modeltranslation/utils.py +0 -0
  78. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/dashboard/__init__.py +0 -0
  79. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/dashboard/admin.py +0 -0
  80. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/dashboard/apps.py +0 -0
  81. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/dashboard/migrations/__init__.py +0 -0
  82. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/dashboard/models.py +0 -0
  83. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/dashboard/tests.py +0 -0
  84. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/dashboard/views.py +0 -0
  85. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/__init__.py +0 -0
  86. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/admin.py +0 -0
  87. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/apps.py +0 -0
  88. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/migrations/0001_initial.py +0 -0
  89. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/migrations/0002_feedbackdata_created_by_feedbackdata_deleted_at_and_more.py +0 -0
  90. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/migrations/0003_feedbackdata_note.py +0 -0
  91. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/migrations/0004_alter_feedbackdata_object_id.py +0 -0
  92. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/migrations/0005_feedbackdata_field.py +0 -0
  93. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/migrations/0006_alter_feedbackdata_created_at_and_more.py +0 -0
  94. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/migrations/__init__.py +0 -0
  95. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/models.py +0 -0
  96. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/tests.py +0 -0
  97. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/urls.py +0 -0
  98. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/utils.py +0 -0
  99. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/feedbacks/views.py +0 -0
  100. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/__init__.py +0 -0
  101. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/admin.py +0 -0
  102. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/apps.py +0 -0
  103. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/migrations/0001_initial.py +0 -0
  104. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/migrations/0002_alter_excelfile_options_alter_pdffile_options_and_more.py +0 -0
  105. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/migrations/0003_alter_excelfile_created_at_and_more.py +0 -0
  106. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/migrations/__init__.py +0 -0
  107. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/models.py +0 -0
  108. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/tests.py +0 -0
  109. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/utils.py +0 -0
  110. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/file_management/views.py +0 -0
  111. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/manage.py +0 -0
  112. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/__init__.py +0 -0
  113. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/admin.py +0 -0
  114. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/apps.py +0 -0
  115. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/consumers.py +0 -0
  116. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/management/__init__.py +0 -0
  117. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/management/commands/__init__.py +0 -0
  118. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/management/commands/call_demo_noti.py +0 -0
  119. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0001_initial.py +0 -0
  120. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0002_alter_notificationconfig_event_type.py +0 -0
  121. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0003_notification.py +0 -0
  122. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0004_alter_notification_flag.py +0 -0
  123. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0005_notification_keywords.py +0 -0
  124. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0006_remove_notification_keywords.py +0 -0
  125. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0007_notification_changed_data.py +0 -0
  126. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0008_alter_notification_changed_data.py +0 -0
  127. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0009_notification_action_by_alter_notification_user.py +0 -0
  128. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0010_alter_notification_user.py +0 -0
  129. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0011_alter_notification_user.py +0 -0
  130. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/0012_alter_notification_action_and_more.py +0 -0
  131. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/migrations/__init__.py +0 -0
  132. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/models.py +0 -0
  133. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/routing.py +0 -0
  134. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/tests.py +0 -0
  135. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/utils.py +0 -0
  136. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/notification/views.py +0 -0
  137. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/css/btn-styles.css +0 -0
  138. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/css/ckeditor5.css +0 -0
  139. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/css/loading.css +0 -0
  140. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/forms/css/html-editor.css +0 -0
  141. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/forms/js/format.html.js +0 -0
  142. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/forms/js/trix.config.js +0 -0
  143. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/images/delete.svg +0 -0
  144. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/images/edit.svg +0 -0
  145. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/images/icon-viewlink.svg +0 -0
  146. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/images/loading.gif +0 -0
  147. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/images/login-bg.jpg +0 -0
  148. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/images/login_bg.jpg +0 -0
  149. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/images/logo/WhiteNeuron.png +0 -0
  150. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/js/chart.min.js +0 -0
  151. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/js/d3.v7.min.js +0 -0
  152. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/js/loading.js +0 -0
  153. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/static/base/js/whiteneuron.js +0 -0
  154. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/synchronization/__init__.py +0 -0
  155. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/synchronization/admin.py +0 -0
  156. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/synchronization/apps.py +0 -0
  157. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/synchronization/migrations/__init__.py +0 -0
  158. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/synchronization/models.py +0 -0
  159. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/synchronization/tests.py +0 -0
  160. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/synchronization/views.py +0 -0
  161. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/400.html +0 -0
  162. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/403.html +0 -0
  163. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/404.html +0 -0
  164. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/429.html +0 -0
  165. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/500.html +0 -0
  166. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/auth/user/add_form.html +0 -0
  167. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/base/app_change_list.html +0 -0
  168. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/base.html +0 -0
  169. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/feedbacks/feedback_change_form.html +0 -0
  170. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/grid_view_results.html +0 -0
  171. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/header.html +0 -0
  172. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/index.html +0 -0
  173. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/login.html +0 -0
  174. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/login_after.html +0 -0
  175. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/search_form.html +0 -0
  176. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/admin/submit_line.html +0 -0
  177. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/base/forms/helpers/toolbar.html +0 -0
  178. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/base/forms/wysiwyg.html +0 -0
  179. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/base.html +0 -0
  180. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/unfold/helpers/account_links.html +0 -0
  181. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/unfold/helpers/app_list_badge.html +0 -0
  182. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/unfold/helpers/field_password.html +0 -0
  183. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/unfold/helpers/navigation_user.html +0 -0
  184. {django_whiteneuron-0.2.47 → django_whiteneuron-0.2.48}/whiteneuron/templates/unfold/helpers/pagination.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-whiteneuron
3
- Version: 0.2.47
3
+ Version: 0.2.48
4
4
  Summary: A modern Django Admin extension with enhanced UI/UX, dashboard, feedback, file management, rate limiting, IP blacklisting, and advanced admin integrations built on django-unfold.
5
5
  Author-email: White Neuron <anhnt@whiteneuron.ai>
6
6
  License: MIT License
@@ -95,7 +95,7 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
95
95
 
96
96
  ## Current Version
97
97
 
98
- - 0.2.45
98
+ - 0.2.48
99
99
 
100
100
  ## Compatibility
101
101
 
@@ -106,7 +106,40 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
106
106
 
107
107
  ## Changelog
108
108
 
109
- ### v0.2.45 (2026-04-03) — latest
109
+ ### v0.2.48 (2026-04-06) — latest
110
+ **Feature: Change history panel — modal UI, M2M diff, permission gate**
111
+ - **Added**: Change history panel shown on every change-form — floating "History" button (bottom-right, `fixed position`) opens a scrollable modal listing the 50 most recent `Notification` records for the object.
112
+ - **Added**: Each history entry displays: action badge (Create/Update/Delete/Restore), actor avatar + username, timestamp, and an **old → new diff** for every changed field using `changed_data` from the `Notification` model.
113
+ - **Added**: `has_view_history_permission()` on `ModelAdmin` — panel is shown only to superusers or users with `change` permission; read-only staff cannot see history.
114
+ - **Added**: `show_history = True` class attribute on `ModelAdmin` — subclasses can opt out per model by setting `show_history = False`.
115
+ - **Fixed**: DaisyUI v5 `ui-` prefix applied correctly to `ui-modal`, `ui-modal-box`, `ui-modal-backdrop`, `ui-btn` — resolves invisible/broken modal.
116
+ - **Fixed**: Trigger `<button type="button">` — prevents accidental form submission when clicking the History button.
117
+ - **Fixed**: Close button and backdrop use `dialog.close()` via JS — avoids nested `<form method="dialog">` inside the admin form (HTML spec violation that caused the button to escape DOM).
118
+ - **Fixed**: UNFOLD's built-in "Lịch sử" button hidden via `{% block object-tools-items %}{% endblock %}` — eliminates duplicate history link in the toolbar.
119
+ - **Improved**: `import ast` and `reverse` moved to module-level imports in `modeladmin.py`.
120
+ - **Improved**: All panel strings wrapped in `{% trans %}` for full i18n support.
121
+ - **Improved**: `change_list.html` — pagination state (`p=...`) now preserved in `sessionStorage` alongside filters; navigating back from a change form restores the correct page instead of jumping to page 1.
122
+
123
+ ### v0.2.47 (2026-04-05)
124
+ **Feature: M2M change tracking, zero-DB App dashboard, dual-view UI**
125
+ - **Added**: `m2m_changed` signal handler for all concrete `BaseModel` subclasses — `.add()`, `.remove()`, `.set()`, `.clear()` on any ManyToMany field now correctly updates `updated_at`/`updated_by` and sends a notification to superusers with the same `changed_data` format as `save()`.
126
+ - **Added**: App dashboard — two toggle-able view modes: **grid** (2-level category → app cards) and **list** (flat grouped), persisted via `localStorage`.
127
+ - **Removed**: `init_app_db()` DB-write mechanism — App dashboard now reads directly from `settings.UNFOLD['SIDEBAR']` via a new `_parse_sidebar_apps()` helper; zero DB writes on page load.
128
+ - **Fixed**: `app_badge_callback()` counted from stale DB; now counts from `settings.UNFOLD['SIDEBAR']` — badge is always in sync with config.
129
+ - **Fixed**: `has_permission()` in `AppAdmin` now handles callable UNFOLD permissions (lambdas) in addition to dotted-string imports.
130
+ - **Fixed**: Protocol-relative URLs (`//cdn…`) in app icons/thumbnails were incorrectly routed to `{% static %}`; added `|slice:":2" == "//"` check across all render paths.
131
+ - **Fixed**: Mosaic `+N` counter (categories with >4 apps) correctly shows 3 icons + overflow count.
132
+ - **Improved**: Pagination and filter bars hidden on App dashboard via `{% block pagination %}` and `{% block filters %}` override.
133
+ - **Improved**: `pyproject.toml` — `package-data` reduced from 14 lines to 3; removed unused `pyasn1` dep, dead `[tool.uv.workspace]`, empty `[tool.setuptools]`; fixed `Framework :: Django :: 5.2` classifier.
134
+
135
+ ### v0.2.46 (2026-04-04)
136
+ **Fix: Email template — logo URL, company name, and contact details**
137
+ - **Fixed**: Logo URL updated from `api.logo.com` to stable Google-hosted URL.
138
+ - **Fixed**: Company name updated to `White Neuron Co., Ltd` (official name).
139
+ - **Fixed**: Title updated to `Founder & CTO`.
140
+ - **Fixed**: Website URL in signature changed to full `https://whiteneuron.ai`.
141
+
142
+ ### v0.2.45 (2026-04-03)
110
143
  **Security: 11 CVE fixes across django, pillow, cryptography, pyasn1**
111
144
  - **Security**: `django` lower bound raised from `>=5.1.6` to `>=5.2.12` — patches 7 CVEs including SQL Injection (CVE-2026-1207), DoS ×5, race condition in file-system storage (CVE-2026-25674), and URLField vulnerability (CVE-2026-25673).
112
145
  - **Security**: `pillow` lower bound raised from `>=11.0.0` to `>=12.1.1` — patches heap buffer overflow (CVE-2026-25990).
@@ -11,7 +11,7 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
11
11
 
12
12
  ## Current Version
13
13
 
14
- - 0.2.45
14
+ - 0.2.48
15
15
 
16
16
  ## Compatibility
17
17
 
@@ -22,7 +22,40 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
22
22
 
23
23
  ## Changelog
24
24
 
25
- ### v0.2.45 (2026-04-03) — latest
25
+ ### v0.2.48 (2026-04-06) — latest
26
+ **Feature: Change history panel — modal UI, M2M diff, permission gate**
27
+ - **Added**: Change history panel shown on every change-form — floating "History" button (bottom-right, `fixed position`) opens a scrollable modal listing the 50 most recent `Notification` records for the object.
28
+ - **Added**: Each history entry displays: action badge (Create/Update/Delete/Restore), actor avatar + username, timestamp, and an **old → new diff** for every changed field using `changed_data` from the `Notification` model.
29
+ - **Added**: `has_view_history_permission()` on `ModelAdmin` — panel is shown only to superusers or users with `change` permission; read-only staff cannot see history.
30
+ - **Added**: `show_history = True` class attribute on `ModelAdmin` — subclasses can opt out per model by setting `show_history = False`.
31
+ - **Fixed**: DaisyUI v5 `ui-` prefix applied correctly to `ui-modal`, `ui-modal-box`, `ui-modal-backdrop`, `ui-btn` — resolves invisible/broken modal.
32
+ - **Fixed**: Trigger `<button type="button">` — prevents accidental form submission when clicking the History button.
33
+ - **Fixed**: Close button and backdrop use `dialog.close()` via JS — avoids nested `<form method="dialog">` inside the admin form (HTML spec violation that caused the button to escape DOM).
34
+ - **Fixed**: UNFOLD's built-in "Lịch sử" button hidden via `{% block object-tools-items %}{% endblock %}` — eliminates duplicate history link in the toolbar.
35
+ - **Improved**: `import ast` and `reverse` moved to module-level imports in `modeladmin.py`.
36
+ - **Improved**: All panel strings wrapped in `{% trans %}` for full i18n support.
37
+ - **Improved**: `change_list.html` — pagination state (`p=...`) now preserved in `sessionStorage` alongside filters; navigating back from a change form restores the correct page instead of jumping to page 1.
38
+
39
+ ### v0.2.47 (2026-04-05)
40
+ **Feature: M2M change tracking, zero-DB App dashboard, dual-view UI**
41
+ - **Added**: `m2m_changed` signal handler for all concrete `BaseModel` subclasses — `.add()`, `.remove()`, `.set()`, `.clear()` on any ManyToMany field now correctly updates `updated_at`/`updated_by` and sends a notification to superusers with the same `changed_data` format as `save()`.
42
+ - **Added**: App dashboard — two toggle-able view modes: **grid** (2-level category → app cards) and **list** (flat grouped), persisted via `localStorage`.
43
+ - **Removed**: `init_app_db()` DB-write mechanism — App dashboard now reads directly from `settings.UNFOLD['SIDEBAR']` via a new `_parse_sidebar_apps()` helper; zero DB writes on page load.
44
+ - **Fixed**: `app_badge_callback()` counted from stale DB; now counts from `settings.UNFOLD['SIDEBAR']` — badge is always in sync with config.
45
+ - **Fixed**: `has_permission()` in `AppAdmin` now handles callable UNFOLD permissions (lambdas) in addition to dotted-string imports.
46
+ - **Fixed**: Protocol-relative URLs (`//cdn…`) in app icons/thumbnails were incorrectly routed to `{% static %}`; added `|slice:":2" == "//"` check across all render paths.
47
+ - **Fixed**: Mosaic `+N` counter (categories with >4 apps) correctly shows 3 icons + overflow count.
48
+ - **Improved**: Pagination and filter bars hidden on App dashboard via `{% block pagination %}` and `{% block filters %}` override.
49
+ - **Improved**: `pyproject.toml` — `package-data` reduced from 14 lines to 3; removed unused `pyasn1` dep, dead `[tool.uv.workspace]`, empty `[tool.setuptools]`; fixed `Framework :: Django :: 5.2` classifier.
50
+
51
+ ### v0.2.46 (2026-04-04)
52
+ **Fix: Email template — logo URL, company name, and contact details**
53
+ - **Fixed**: Logo URL updated from `api.logo.com` to stable Google-hosted URL.
54
+ - **Fixed**: Company name updated to `White Neuron Co., Ltd` (official name).
55
+ - **Fixed**: Title updated to `Founder & CTO`.
56
+ - **Fixed**: Website URL in signature changed to full `https://whiteneuron.ai`.
57
+
58
+ ### v0.2.45 (2026-04-03)
26
59
  **Security: 11 CVE fixes across django, pillow, cryptography, pyasn1**
27
60
  - **Security**: `django` lower bound raised from `>=5.1.6` to `>=5.2.12` — patches 7 CVEs including SQL Injection (CVE-2026-1207), DoS ×5, race condition in file-system storage (CVE-2026-25674), and URLField vulnerability (CVE-2026-25673).
28
61
  - **Security**: `pillow` lower bound raised from `>=11.0.0` to `>=12.1.1` — patches heap buffer overflow (CVE-2026-25990).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-whiteneuron
3
- Version: 0.2.47
3
+ Version: 0.2.48
4
4
  Summary: A modern Django Admin extension with enhanced UI/UX, dashboard, feedback, file management, rate limiting, IP blacklisting, and advanced admin integrations built on django-unfold.
5
5
  Author-email: White Neuron <anhnt@whiteneuron.ai>
6
6
  License: MIT License
@@ -95,7 +95,7 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
95
95
 
96
96
  ## Current Version
97
97
 
98
- - 0.2.45
98
+ - 0.2.48
99
99
 
100
100
  ## Compatibility
101
101
 
@@ -106,7 +106,40 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
106
106
 
107
107
  ## Changelog
108
108
 
109
- ### v0.2.45 (2026-04-03) — latest
109
+ ### v0.2.48 (2026-04-06) — latest
110
+ **Feature: Change history panel — modal UI, M2M diff, permission gate**
111
+ - **Added**: Change history panel shown on every change-form — floating "History" button (bottom-right, `fixed position`) opens a scrollable modal listing the 50 most recent `Notification` records for the object.
112
+ - **Added**: Each history entry displays: action badge (Create/Update/Delete/Restore), actor avatar + username, timestamp, and an **old → new diff** for every changed field using `changed_data` from the `Notification` model.
113
+ - **Added**: `has_view_history_permission()` on `ModelAdmin` — panel is shown only to superusers or users with `change` permission; read-only staff cannot see history.
114
+ - **Added**: `show_history = True` class attribute on `ModelAdmin` — subclasses can opt out per model by setting `show_history = False`.
115
+ - **Fixed**: DaisyUI v5 `ui-` prefix applied correctly to `ui-modal`, `ui-modal-box`, `ui-modal-backdrop`, `ui-btn` — resolves invisible/broken modal.
116
+ - **Fixed**: Trigger `<button type="button">` — prevents accidental form submission when clicking the History button.
117
+ - **Fixed**: Close button and backdrop use `dialog.close()` via JS — avoids nested `<form method="dialog">` inside the admin form (HTML spec violation that caused the button to escape DOM).
118
+ - **Fixed**: UNFOLD's built-in "Lịch sử" button hidden via `{% block object-tools-items %}{% endblock %}` — eliminates duplicate history link in the toolbar.
119
+ - **Improved**: `import ast` and `reverse` moved to module-level imports in `modeladmin.py`.
120
+ - **Improved**: All panel strings wrapped in `{% trans %}` for full i18n support.
121
+ - **Improved**: `change_list.html` — pagination state (`p=...`) now preserved in `sessionStorage` alongside filters; navigating back from a change form restores the correct page instead of jumping to page 1.
122
+
123
+ ### v0.2.47 (2026-04-05)
124
+ **Feature: M2M change tracking, zero-DB App dashboard, dual-view UI**
125
+ - **Added**: `m2m_changed` signal handler for all concrete `BaseModel` subclasses — `.add()`, `.remove()`, `.set()`, `.clear()` on any ManyToMany field now correctly updates `updated_at`/`updated_by` and sends a notification to superusers with the same `changed_data` format as `save()`.
126
+ - **Added**: App dashboard — two toggle-able view modes: **grid** (2-level category → app cards) and **list** (flat grouped), persisted via `localStorage`.
127
+ - **Removed**: `init_app_db()` DB-write mechanism — App dashboard now reads directly from `settings.UNFOLD['SIDEBAR']` via a new `_parse_sidebar_apps()` helper; zero DB writes on page load.
128
+ - **Fixed**: `app_badge_callback()` counted from stale DB; now counts from `settings.UNFOLD['SIDEBAR']` — badge is always in sync with config.
129
+ - **Fixed**: `has_permission()` in `AppAdmin` now handles callable UNFOLD permissions (lambdas) in addition to dotted-string imports.
130
+ - **Fixed**: Protocol-relative URLs (`//cdn…`) in app icons/thumbnails were incorrectly routed to `{% static %}`; added `|slice:":2" == "//"` check across all render paths.
131
+ - **Fixed**: Mosaic `+N` counter (categories with >4 apps) correctly shows 3 icons + overflow count.
132
+ - **Improved**: Pagination and filter bars hidden on App dashboard via `{% block pagination %}` and `{% block filters %}` override.
133
+ - **Improved**: `pyproject.toml` — `package-data` reduced from 14 lines to 3; removed unused `pyasn1` dep, dead `[tool.uv.workspace]`, empty `[tool.setuptools]`; fixed `Framework :: Django :: 5.2` classifier.
134
+
135
+ ### v0.2.46 (2026-04-04)
136
+ **Fix: Email template — logo URL, company name, and contact details**
137
+ - **Fixed**: Logo URL updated from `api.logo.com` to stable Google-hosted URL.
138
+ - **Fixed**: Company name updated to `White Neuron Co., Ltd` (official name).
139
+ - **Fixed**: Title updated to `Founder & CTO`.
140
+ - **Fixed**: Website URL in signature changed to full `https://whiteneuron.ai`.
141
+
142
+ ### v0.2.45 (2026-04-03)
110
143
  **Security: 11 CVE fixes across django, pillow, cryptography, pyasn1**
111
144
  - **Security**: `django` lower bound raised from `>=5.1.6` to `>=5.2.12` — patches 7 CVEs including SQL Injection (CVE-2026-1207), DoS ×5, race condition in file-system storage (CVE-2026-25674), and URLField vulnerability (CVE-2026-25673).
112
145
  - **Security**: `pillow` lower bound raised from `>=11.0.0` to `>=12.1.1` — patches heap buffer overflow (CVE-2026-25990).
@@ -164,6 +164,8 @@ whiteneuron/templates/admin/login.html
164
164
  whiteneuron/templates/admin/login_after.html
165
165
  whiteneuron/templates/admin/search_form.html
166
166
  whiteneuron/templates/admin/submit_line.html
167
+ whiteneuron/templates/admin/wn_change_form.html
168
+ whiteneuron/templates/admin/wn_history_panel.html
167
169
  whiteneuron/templates/admin/auth/user/add_form.html
168
170
  whiteneuron/templates/admin/base/app_change_list.html
169
171
  whiteneuron/templates/admin/base/image_change_form.html
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-whiteneuron"
3
- version = "0.2.47"
3
+ version = "0.2.48"
4
4
  description = "A modern Django Admin extension with enhanced UI/UX, dashboard, feedback, file management, rate limiting, IP blacklisting, and advanced admin integrations built on django-unfold."
5
5
  authors = [
6
6
  {name = "White Neuron", email = "anhnt@whiteneuron.ai"}
@@ -9,7 +9,8 @@ from django.contrib.admin.views.main import ChangeList
9
9
  from collections import OrderedDict
10
10
  from typing import Any, Sequence
11
11
 
12
- from django.urls import reverse_lazy
12
+ import ast
13
+ from django.urls import reverse, reverse_lazy
13
14
 
14
15
  from unfold.contrib.forms.widgets import WysiwygWidget as UnfoldWysiwygWidget
15
16
  from whiteneuron.base.widgets import WysiwygWidget, CKEditor5Widget
@@ -49,6 +50,9 @@ def get_verbose_name_field(model, field):
49
50
 
50
51
  class ModelAdmin(UnfoldAdmin):
51
52
 
53
+ show_history = True # Show history panel in change form, data from Notification model
54
+ change_form_template = 'admin/wn_change_form.html'
55
+
52
56
  # MAX OBJECTS PER PAGE
53
57
  list_per_page = 50
54
58
 
@@ -205,63 +209,6 @@ class ModelAdmin(UnfoldAdmin):
205
209
  flag= 'danger',
206
210
  action= 'delete',
207
211
  content= content_html)
208
-
209
- # def save_model(self, request, obj, form, change):
210
- # save created_by and updated_by when create or update
211
- # title= ''
212
- # content_html= ''
213
- # action= ''
214
- # user= request.user
215
- # obj_link= f"/admin/{obj._meta.app_label}/{obj._meta.model_name}/{obj.id}/change/"
216
- # if not change: # check if the object is being created
217
- # obj.updated_by= user
218
- # obj.created_by= user
219
- # action= 'create'
220
- # title= f"New {obj._meta.verbose_name} \"{obj}\" has been created by user \"{obj.created_by}\""
221
- # content_html= f"New {obj._meta.verbose_name} <a href='{obj_link}'>{obj}</a> has been created by user \"{obj.created_by}\""
222
-
223
- # # check if the object is being updated with a new version
224
- # else:
225
- # fields_changed= []
226
- # isUpdate= False
227
- # if hasattr(obj.__class__, 'objects_all'):
228
- # old= obj.__class__.objects_all.get(pk= obj.pk)
229
- # else:
230
- # old= obj.__class__.objects.get(pk= obj.pk)
231
-
232
- # for attr in obj.__dict__:
233
- # if attr in ['_state', 'created_at', 'created_by', 'updated_at', 'updated_by']:
234
- # continue
235
- # # kiểm tra attr có phải là field không
236
- # if not hasattr(obj.__class__, attr):
237
- # continue
238
- # # print(attr)
239
- # try:
240
- # if getattr(obj, attr) != getattr(old, attr):
241
- # isUpdate= True
242
- # fields_changed.append((attr, getattr(old, attr), getattr(obj, attr)))
243
- # except Exception as e:
244
- # print(e)
245
- # pass
246
- # if isUpdate:
247
- # obj.updated_by= user
248
- # action= 'update'
249
- # title= f"{obj._meta.verbose_name} \"{obj}\" has been updated by user \"{obj.updated_by}\""
250
- # content_html= f"{obj._meta.verbose_name} \"{obj}\" has been updated by user \"{obj.updated_by}\" with the following changes: <ul>"
251
- # for field in fields_changed:
252
- # content_html+= f"<li>{field[0].verbose_name if hasattr(field[0], 'verbose_name') else field[0]}: {field[1]} -> {field[2]}</li>"
253
- # content_html+= "</ul>"
254
- # super().save_model(request, obj, form, change)
255
- # send notification to superuser when create or update successfully
256
- # if title:
257
- # for user in User.objects.filter(is_superuser= True):
258
- # obj= Notification.objects.create(user= user, title= title,
259
- # action_by= obj.updated_by if action == 'update' else obj.created_by,
260
- # flag= 'info',
261
- # action= action,
262
- # obj_link= obj_link,
263
- # content= content_html)
264
- # obj.alert(request) # send alert to user
265
212
 
266
213
 
267
214
 
@@ -504,7 +451,12 @@ class ModelAdmin(UnfoldAdmin):
504
451
  res = super().changelist_view(request, extra_context)
505
452
  return res
506
453
 
507
- def changeform_view(self, request, object_id = None, form_url = "", extra_context = None):
454
+ def has_view_history_permission(self, request, obj=None):
455
+ if request.user.is_superuser:
456
+ return True
457
+ return self.has_change_permission(request, obj)
458
+
459
+ def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
508
460
  extra_context = extra_context or {}
509
461
  extra_context["sidebar_default"] = self.default_toggle_sidebar
510
462
  extra_context["sidebar_model_key"] = (
@@ -512,6 +464,31 @@ class ModelAdmin(UnfoldAdmin):
512
464
  )
513
465
  if self.default_toggle_sidebar is not None:
514
466
  request.session["toggle_sidebar"] = self.default_toggle_sidebar
467
+
468
+ can_view_history = self.show_history and self.has_view_history_permission(request)
469
+ extra_context['show_history'] = can_view_history
470
+ if can_view_history and object_id:
471
+ try:
472
+ obj_link = reverse(
473
+ 'admin:%s_%s_change' % (self.model._meta.app_label, self.model._meta.model_name),
474
+ args=[object_id],
475
+ )
476
+ notifications = (
477
+ Notification.objects
478
+ .filter(obj_link=obj_link)
479
+ .select_related('action_by')
480
+ .order_by('-created_at')[:50]
481
+ )
482
+ # Parse changed_data string → list of dicts for each notification
483
+ for notif in notifications:
484
+ try:
485
+ notif.parsed_changed_data = ast.literal_eval(notif.changed_data) if notif.changed_data else []
486
+ except Exception:
487
+ notif.parsed_changed_data = []
488
+ extra_context['history_notifications'] = notifications
489
+ except Exception:
490
+ extra_context['history_notifications'] = []
491
+
515
492
  return super().changeform_view(request, object_id, form_url, extra_context)
516
493
 
517
494
  # GridItemHeader
@@ -504,14 +504,37 @@ def _base_model_m2m_changed(sender, instance, action, pk_set, model, **kwargs):
504
504
 
505
505
  # ── Build change description (same format as save()) ──
506
506
  pk_list = list(pk_set or [])
507
+
508
+ def _labels_from_qs(qs):
509
+ """Return list of '__str__ (pk)' for a queryset."""
510
+ return [f"{o} ({o.pk})" for o in qs]
511
+
512
+ def _resolve_labels(pks):
513
+ """Return list of '__str__ (pk)' for pk list, fallback to pk on error."""
514
+ try:
515
+ objs = {o.pk: o for o in model.objects.filter(pk__in=pks)}
516
+ return [f"{objs[pk]} ({pk})" if pk in objs else str(pk) for pk in pks]
517
+ except Exception:
518
+ return [str(pk) for pk in pks]
519
+
520
+ # current state (post-signal, so add already happened / remove already happened)
521
+ try:
522
+ current_qs = getattr(instance, field_name).all()
523
+ current_labels = _labels_from_qs(current_qs)
524
+ except Exception:
525
+ current_labels = []
526
+
507
527
  if action == 'post_add':
508
- old_value = []
509
- new_value = pk_list
510
- desc = f"{_('added')}: {pk_list}"
528
+ added_labels = _resolve_labels(pk_list)
529
+ added_set = set(pk_list)
530
+ old_value = [lbl for lbl in current_labels if not any(f"({pk})" in lbl for pk in added_set)]
531
+ new_value = current_labels
532
+ desc = f"{_('added')}: {added_labels}"
511
533
  elif action == 'post_remove':
512
- old_value = pk_list
513
- new_value = []
514
- desc = f"{_('removed')}: {pk_list}"
534
+ removed_labels = _resolve_labels(pk_list)
535
+ old_value = current_labels + removed_labels
536
+ new_value = current_labels
537
+ desc = f"{_('removed')}: {removed_labels}"
515
538
  else: # post_clear
516
539
  old_value = '(all)'
517
540
  new_value = []