django-whiteneuron 0.2.42__tar.gz → 0.2.44__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 (187) hide show
  1. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/PKG-INFO +26 -3
  2. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/README.md +25 -2
  3. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/PKG-INFO +26 -3
  4. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/pyproject.toml +1 -1
  5. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/modeladmin.py +23 -16
  6. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/utils.py +6 -2
  7. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/admin.py +39 -26
  8. django_whiteneuron-0.2.44/whiteneuron/feedbacks/urls.py +81 -0
  9. django_whiteneuron-0.2.44/whiteneuron/feedbacks/utils.py +10 -0
  10. django_whiteneuron-0.2.44/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
  11. django_whiteneuron-0.2.44/whiteneuron/locale/vi/LC_MESSAGES/django.po +1423 -0
  12. django_whiteneuron-0.2.44/whiteneuron/static/base/css/styles.css +2 -0
  13. django_whiteneuron-0.2.44/whiteneuron/templates/admin/feedbacks/feedback_change_form.html +132 -0
  14. django_whiteneuron-0.2.44/whiteneuron/templates/admin/submit_line.html +157 -0
  15. django_whiteneuron-0.2.42/whiteneuron/feedbacks/urls.py +0 -69
  16. django_whiteneuron-0.2.42/whiteneuron/feedbacks/utils.py +0 -5
  17. django_whiteneuron-0.2.42/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
  18. django_whiteneuron-0.2.42/whiteneuron/locale/vi/LC_MESSAGES/django.po +0 -1182
  19. django_whiteneuron-0.2.42/whiteneuron/static/base/css/styles.css +0 -2
  20. django_whiteneuron-0.2.42/whiteneuron/templates/admin/feedbacks/feedback_change_form.html +0 -41
  21. django_whiteneuron-0.2.42/whiteneuron/templates/admin/submit_line.html +0 -67
  22. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/LICENSE +0 -0
  23. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/SOURCES.txt +0 -0
  24. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/dependency_links.txt +0 -0
  25. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/requires.txt +0 -0
  26. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/top_level.txt +0 -0
  27. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/setup.cfg +0 -0
  28. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/__init__.py +0 -0
  29. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/__init__.py +0 -0
  30. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/admin.py +0 -0
  31. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/apps.py +0 -0
  32. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/asgi.py +0 -0
  33. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/ckeditor.py +0 -0
  34. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/filters.py +0 -0
  35. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/forms.py +0 -0
  36. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/__init__.py +0 -0
  37. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/__init__.py +0 -0
  38. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/init_admin.py +0 -0
  39. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/init_groupuser.py +0 -0
  40. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/init_guest.py +0 -0
  41. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/middleware.py +0 -0
  42. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0001_initial.py +0 -0
  43. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0002_imagemodel_userprofile_user_avatar_user_biography_and_more.py +0 -0
  44. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0003_notification_obj_link.py +0 -0
  45. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0004_delete_notification.py +0 -0
  46. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0005_user_show_softdelete.py +0 -0
  47. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0006_user_is_bot.py +0 -0
  48. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0007_app.py +0 -0
  49. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0008_app_created_at_app_created_by_app_deleted_at_and_more.py +0 -0
  50. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0009_alter_app_table.py +0 -0
  51. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0010_app_category.py +0 -0
  52. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0011_alter_app_options.py +0 -0
  53. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0012_app_permission.py +0 -0
  54. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0013_app_thumbnail_url.py +0 -0
  55. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0014_alter_app_created_at_alter_app_updated_at_and_more.py +0 -0
  56. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0015_ipblacklist.py +0 -0
  57. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0016_uablacklist.py +0 -0
  58. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0017_alter_uablacklist_pattern.py +0 -0
  59. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/__init__.py +0 -0
  60. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/models.py +0 -0
  61. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/resources.py +0 -0
  62. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/settings.py +0 -0
  63. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/sites.py +0 -0
  64. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/tasks.py +0 -0
  65. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/templatetags/__init__.py +0 -0
  66. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/templatetags/unfold_list_custom.py +0 -0
  67. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/templatetags/wn_filters.py +0 -0
  68. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/tests.py +0 -0
  69. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/thread_local.py +0 -0
  70. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/urls.py +0 -0
  71. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/views.py +0 -0
  72. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/widgets.py +0 -0
  73. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/__init__.py +0 -0
  74. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/__init__.py +0 -0
  75. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/admin.py +0 -0
  76. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/constants.py +0 -0
  77. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/mixins.py +0 -0
  78. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/tasks.py +0 -0
  79. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/utils.py +0 -0
  80. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_modeltranslation/__init__.py +0 -0
  81. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_modeltranslation/admin.py +0 -0
  82. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_modeltranslation/utils.py +0 -0
  83. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/__init__.py +0 -0
  84. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/admin.py +0 -0
  85. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/apps.py +0 -0
  86. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/migrations/__init__.py +0 -0
  87. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/models.py +0 -0
  88. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/tests.py +0 -0
  89. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/views.py +0 -0
  90. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/__init__.py +0 -0
  91. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/apps.py +0 -0
  92. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0001_initial.py +0 -0
  93. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0002_feedbackdata_created_by_feedbackdata_deleted_at_and_more.py +0 -0
  94. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0003_feedbackdata_note.py +0 -0
  95. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0004_alter_feedbackdata_object_id.py +0 -0
  96. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0005_feedbackdata_field.py +0 -0
  97. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0006_alter_feedbackdata_created_at_and_more.py +0 -0
  98. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/__init__.py +0 -0
  99. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/models.py +0 -0
  100. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/tests.py +0 -0
  101. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/views.py +0 -0
  102. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/__init__.py +0 -0
  103. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/admin.py +0 -0
  104. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/apps.py +0 -0
  105. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/migrations/0001_initial.py +0 -0
  106. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/migrations/0002_alter_excelfile_options_alter_pdffile_options_and_more.py +0 -0
  107. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/migrations/0003_alter_excelfile_created_at_and_more.py +0 -0
  108. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/migrations/__init__.py +0 -0
  109. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/models.py +0 -0
  110. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/tests.py +0 -0
  111. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/utils.py +0 -0
  112. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/views.py +0 -0
  113. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/manage.py +0 -0
  114. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/__init__.py +0 -0
  115. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/admin.py +0 -0
  116. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/apps.py +0 -0
  117. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/consumers.py +0 -0
  118. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/management/__init__.py +0 -0
  119. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/management/commands/__init__.py +0 -0
  120. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/management/commands/call_demo_noti.py +0 -0
  121. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0001_initial.py +0 -0
  122. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0002_alter_notificationconfig_event_type.py +0 -0
  123. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0003_notification.py +0 -0
  124. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0004_alter_notification_flag.py +0 -0
  125. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0005_notification_keywords.py +0 -0
  126. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0006_remove_notification_keywords.py +0 -0
  127. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0007_notification_changed_data.py +0 -0
  128. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0008_alter_notification_changed_data.py +0 -0
  129. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0009_notification_action_by_alter_notification_user.py +0 -0
  130. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0010_alter_notification_user.py +0 -0
  131. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0011_alter_notification_user.py +0 -0
  132. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0012_alter_notification_action_and_more.py +0 -0
  133. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/__init__.py +0 -0
  134. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/models.py +0 -0
  135. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/routing.py +0 -0
  136. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/tests.py +0 -0
  137. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/utils.py +0 -0
  138. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/views.py +0 -0
  139. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/css/btn-styles.css +0 -0
  140. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/css/ckeditor5.css +0 -0
  141. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/css/loading.css +0 -0
  142. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/forms/css/html-editor.css +0 -0
  143. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/forms/js/format.html.js +0 -0
  144. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/forms/js/trix.config.js +0 -0
  145. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/delete.svg +0 -0
  146. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/edit.svg +0 -0
  147. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/icon-viewlink.svg +0 -0
  148. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/loading.gif +0 -0
  149. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/login-bg.jpg +0 -0
  150. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/login_bg.jpg +0 -0
  151. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/logo/WhiteNeuron.png +0 -0
  152. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/chart.min.js +0 -0
  153. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/d3.v7.min.js +0 -0
  154. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/loading.js +0 -0
  155. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/whiteneuron.js +0 -0
  156. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/__init__.py +0 -0
  157. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/admin.py +0 -0
  158. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/apps.py +0 -0
  159. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/migrations/__init__.py +0 -0
  160. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/models.py +0 -0
  161. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/tests.py +0 -0
  162. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/views.py +0 -0
  163. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/400.html +0 -0
  164. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/403.html +0 -0
  165. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/404.html +0 -0
  166. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/429.html +0 -0
  167. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/500.html +0 -0
  168. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/auth/user/add_form.html +0 -0
  169. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base/app_change_list.html +0 -0
  170. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base/image_change_form.html +0 -0
  171. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base/userprofile_change_form.html +0 -0
  172. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base.html +0 -0
  173. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/change_list.html +0 -0
  174. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/grid_view_results.html +0 -0
  175. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/header.html +0 -0
  176. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/index.html +0 -0
  177. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/login.html +0 -0
  178. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/login_after.html +0 -0
  179. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/search_form.html +0 -0
  180. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/base/forms/helpers/toolbar.html +0 -0
  181. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/base/forms/wysiwyg.html +0 -0
  182. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/base.html +0 -0
  183. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/account_links.html +0 -0
  184. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/app_list_badge.html +0 -0
  185. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/field_password.html +0 -0
  186. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/navigation_user.html +0 -0
  187. {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/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.42
3
+ Version: 0.2.44
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.com>
6
6
  License: MIT License
@@ -94,7 +94,7 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
94
94
 
95
95
  ## Current Version
96
96
 
97
- - 0.2.42
97
+ - 0.2.44
98
98
 
99
99
  ## Compatibility
100
100
 
@@ -105,7 +105,30 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
105
105
 
106
106
  ## Changelog
107
107
 
108
- ### v0.2.42 (2026-03-31) — latest
108
+ ### v0.2.44 (2026-04-02) — latest
109
+ **Fixes & Improvements: Feedback System — Error handling, input limits, i18n lazy evaluation**
110
+ - **Fixed**: `changeform_view` now returns `JsonResponse({"success": True})` after a successful resolve instead of falling through to `super()` with a POST request — prevents a spurious HTTP 403 caused by `has_change_permission = False`.
111
+ - **Fixed**: `note` is read from `request.POST` instead of `request.GET` — prevents HTTP 414 (URI Too Long) when the note is lengthy.
112
+ - **Fixed**: JS `fetch` upgraded to POST + `FormData` with CSRF token; error handling now maps status codes (403/404/500/network) to specific user-facing messages.
113
+ - **Added**: Two-sided character limits — `maxlength` attribute + live counter on client; server-side validation: `note ≤ 500`, `feedback_message ≤ 2000`.
114
+ - **Added**: `get_short_message()` in `FeedbackDataAdmin.list_display` — truncates to 200 words via Django's `Truncator`.
115
+ - **Fixed**: `search_help_text` moved from `__init__` to a `@property` using `format_lazy` — now re-evaluates per request and responds correctly to language switches instead of being frozen at server startup.
116
+
117
+ ### v0.2.43 (2026-04-01)
118
+ **Feature: Feedback System — DaisyUI modals, anti-spam cooldown, i18n, security hardening**
119
+ - **Added**: DaisyUI `<dialog>` modals replace native `prompt()`/`alert()` in both `feedback_change_form.html` (Mark as Resolved flow) and `submit_line.html` (Feedback submission) — smooth animations, no browser dialogs.
120
+ - **Added**: Server-side anti-spam cooldown — 60s per user globally; HTTP 429 with dynamically calculated remaining seconds (`remaining = 60 - elapsed`).
121
+ - **Security**: Replaced `@csrf_exempt` with `@require_POST` in feedback endpoint — CSRF protection fully enforced via `X-CSRFToken` header.
122
+ - **Security**: Auth check moved before body parse; removed redundant `User.objects.get()` in favor of `request.user`.
123
+ - **Fixed**: `FeedbackDataAdmin.changeform_view()` — null guard added after `get_object()` to prevent `AttributeError` when object does not exist.
124
+ - **Fixed**: OK button in result modal now wrapped in `<form method="dialog">` — dialog closes correctly, `close` event fires `location.reload()`.
125
+ - **Fixed**: Buttons missing `type="button"` were inadvertently submitting the admin form — fixed across all modals.
126
+ - **Fixed**: Modals moved to `document.body` via JS — eliminates content flash at page bottom during closing animation.
127
+ - **Fixed**: Import cleanup in `feedbacks/admin.py` — removed unused `display` import, moved `FEEDBACK_COOLDOWN_SECONDS` import to top.
128
+ - **Added**: Full i18n (`{% trans %}`, `{% blocktrans %}`, `gettext_lazy`) across feedback templates and `modeladmin.py` (12 strings); Vietnamese translations updated in `locale/vi`.
129
+ - **Added**: `feedback_cooldown_ms` passed from server via `FeedbackBaseAdmin.render_change_form()` — no more hardcoded 60000ms in template.
130
+
131
+ ### v0.2.42 (2026-03-31)
109
132
  **Improve: persist filter/search state in admin changelist + fix Django duplicate filter bug**
110
133
  - **Added**: Filter/search state is now persisted via `sessionStorage` across navigation — selecting a filter, navigating to a detail page, then returning restores the exact filter state automatically.
111
134
  - **Fixed**: Django bug — `add_preserved_filters()` uses `dict(parse_qsl())` which drops duplicate filter params (e.g. `?chapter__id__exact=29&chapter__id__exact=31` → only `=31` kept after save). Fixed server-side in `ModelAdmin._fix_preserved_filters()` — rebuilds the redirect URL with `parse_qsl()` (preserves all values) when duplicates are detected.
@@ -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.42
14
+ - 0.2.44
15
15
 
16
16
  ## Compatibility
17
17
 
@@ -22,7 +22,30 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
22
22
 
23
23
  ## Changelog
24
24
 
25
- ### v0.2.42 (2026-03-31) — latest
25
+ ### v0.2.44 (2026-04-02) — latest
26
+ **Fixes & Improvements: Feedback System — Error handling, input limits, i18n lazy evaluation**
27
+ - **Fixed**: `changeform_view` now returns `JsonResponse({"success": True})` after a successful resolve instead of falling through to `super()` with a POST request — prevents a spurious HTTP 403 caused by `has_change_permission = False`.
28
+ - **Fixed**: `note` is read from `request.POST` instead of `request.GET` — prevents HTTP 414 (URI Too Long) when the note is lengthy.
29
+ - **Fixed**: JS `fetch` upgraded to POST + `FormData` with CSRF token; error handling now maps status codes (403/404/500/network) to specific user-facing messages.
30
+ - **Added**: Two-sided character limits — `maxlength` attribute + live counter on client; server-side validation: `note ≤ 500`, `feedback_message ≤ 2000`.
31
+ - **Added**: `get_short_message()` in `FeedbackDataAdmin.list_display` — truncates to 200 words via Django's `Truncator`.
32
+ - **Fixed**: `search_help_text` moved from `__init__` to a `@property` using `format_lazy` — now re-evaluates per request and responds correctly to language switches instead of being frozen at server startup.
33
+
34
+ ### v0.2.43 (2026-04-01)
35
+ **Feature: Feedback System — DaisyUI modals, anti-spam cooldown, i18n, security hardening**
36
+ - **Added**: DaisyUI `<dialog>` modals replace native `prompt()`/`alert()` in both `feedback_change_form.html` (Mark as Resolved flow) and `submit_line.html` (Feedback submission) — smooth animations, no browser dialogs.
37
+ - **Added**: Server-side anti-spam cooldown — 60s per user globally; HTTP 429 with dynamically calculated remaining seconds (`remaining = 60 - elapsed`).
38
+ - **Security**: Replaced `@csrf_exempt` with `@require_POST` in feedback endpoint — CSRF protection fully enforced via `X-CSRFToken` header.
39
+ - **Security**: Auth check moved before body parse; removed redundant `User.objects.get()` in favor of `request.user`.
40
+ - **Fixed**: `FeedbackDataAdmin.changeform_view()` — null guard added after `get_object()` to prevent `AttributeError` when object does not exist.
41
+ - **Fixed**: OK button in result modal now wrapped in `<form method="dialog">` — dialog closes correctly, `close` event fires `location.reload()`.
42
+ - **Fixed**: Buttons missing `type="button"` were inadvertently submitting the admin form — fixed across all modals.
43
+ - **Fixed**: Modals moved to `document.body` via JS — eliminates content flash at page bottom during closing animation.
44
+ - **Fixed**: Import cleanup in `feedbacks/admin.py` — removed unused `display` import, moved `FEEDBACK_COOLDOWN_SECONDS` import to top.
45
+ - **Added**: Full i18n (`{% trans %}`, `{% blocktrans %}`, `gettext_lazy`) across feedback templates and `modeladmin.py` (12 strings); Vietnamese translations updated in `locale/vi`.
46
+ - **Added**: `feedback_cooldown_ms` passed from server via `FeedbackBaseAdmin.render_change_form()` — no more hardcoded 60000ms in template.
47
+
48
+ ### v0.2.42 (2026-03-31)
26
49
  **Improve: persist filter/search state in admin changelist + fix Django duplicate filter bug**
27
50
  - **Added**: Filter/search state is now persisted via `sessionStorage` across navigation — selecting a filter, navigating to a detail page, then returning restores the exact filter state automatically.
28
51
  - **Fixed**: Django bug — `add_preserved_filters()` uses `dict(parse_qsl())` which drops duplicate filter params (e.g. `?chapter__id__exact=29&chapter__id__exact=31` → only `=31` kept after save). Fixed server-side in `ModelAdmin._fix_preserved_filters()` — rebuilds the redirect URL with `parse_qsl()` (preserves all values) when duplicates are detected.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-whiteneuron
3
- Version: 0.2.42
3
+ Version: 0.2.44
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.com>
6
6
  License: MIT License
@@ -94,7 +94,7 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
94
94
 
95
95
  ## Current Version
96
96
 
97
- - 0.2.42
97
+ - 0.2.44
98
98
 
99
99
  ## Compatibility
100
100
 
@@ -105,7 +105,30 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
105
105
 
106
106
  ## Changelog
107
107
 
108
- ### v0.2.42 (2026-03-31) — latest
108
+ ### v0.2.44 (2026-04-02) — latest
109
+ **Fixes & Improvements: Feedback System — Error handling, input limits, i18n lazy evaluation**
110
+ - **Fixed**: `changeform_view` now returns `JsonResponse({"success": True})` after a successful resolve instead of falling through to `super()` with a POST request — prevents a spurious HTTP 403 caused by `has_change_permission = False`.
111
+ - **Fixed**: `note` is read from `request.POST` instead of `request.GET` — prevents HTTP 414 (URI Too Long) when the note is lengthy.
112
+ - **Fixed**: JS `fetch` upgraded to POST + `FormData` with CSRF token; error handling now maps status codes (403/404/500/network) to specific user-facing messages.
113
+ - **Added**: Two-sided character limits — `maxlength` attribute + live counter on client; server-side validation: `note ≤ 500`, `feedback_message ≤ 2000`.
114
+ - **Added**: `get_short_message()` in `FeedbackDataAdmin.list_display` — truncates to 200 words via Django's `Truncator`.
115
+ - **Fixed**: `search_help_text` moved from `__init__` to a `@property` using `format_lazy` — now re-evaluates per request and responds correctly to language switches instead of being frozen at server startup.
116
+
117
+ ### v0.2.43 (2026-04-01)
118
+ **Feature: Feedback System — DaisyUI modals, anti-spam cooldown, i18n, security hardening**
119
+ - **Added**: DaisyUI `<dialog>` modals replace native `prompt()`/`alert()` in both `feedback_change_form.html` (Mark as Resolved flow) and `submit_line.html` (Feedback submission) — smooth animations, no browser dialogs.
120
+ - **Added**: Server-side anti-spam cooldown — 60s per user globally; HTTP 429 with dynamically calculated remaining seconds (`remaining = 60 - elapsed`).
121
+ - **Security**: Replaced `@csrf_exempt` with `@require_POST` in feedback endpoint — CSRF protection fully enforced via `X-CSRFToken` header.
122
+ - **Security**: Auth check moved before body parse; removed redundant `User.objects.get()` in favor of `request.user`.
123
+ - **Fixed**: `FeedbackDataAdmin.changeform_view()` — null guard added after `get_object()` to prevent `AttributeError` when object does not exist.
124
+ - **Fixed**: OK button in result modal now wrapped in `<form method="dialog">` — dialog closes correctly, `close` event fires `location.reload()`.
125
+ - **Fixed**: Buttons missing `type="button"` were inadvertently submitting the admin form — fixed across all modals.
126
+ - **Fixed**: Modals moved to `document.body` via JS — eliminates content flash at page bottom during closing animation.
127
+ - **Fixed**: Import cleanup in `feedbacks/admin.py` — removed unused `display` import, moved `FEEDBACK_COOLDOWN_SECONDS` import to top.
128
+ - **Added**: Full i18n (`{% trans %}`, `{% blocktrans %}`, `gettext_lazy`) across feedback templates and `modeladmin.py` (12 strings); Vietnamese translations updated in `locale/vi`.
129
+ - **Added**: `feedback_cooldown_ms` passed from server via `FeedbackBaseAdmin.render_change_form()` — no more hardcoded 60000ms in template.
130
+
131
+ ### v0.2.42 (2026-03-31)
109
132
  **Improve: persist filter/search state in admin changelist + fix Django duplicate filter bug**
110
133
  - **Added**: Filter/search state is now persisted via `sessionStorage` across navigation — selecting a filter, navigating to a detail page, then returning restores the exact filter state automatically.
111
134
  - **Fixed**: Django bug — `add_preserved_filters()` uses `dict(parse_qsl())` which drops duplicate filter params (e.g. `?chapter__id__exact=29&chapter__id__exact=31` → only `=31` kept after save). Fixed server-side in `ModelAdmin._fix_preserved_filters()` — rebuilds the redirect URL with `parse_qsl()` (preserves all values) when duplicates are detected.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "django-whiteneuron"
3
- version = "0.2.42"
3
+ version = "0.2.44"
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.com"}
@@ -24,6 +24,9 @@ from .filters import FieldSelectionFilter
24
24
  from django.urls import path
25
25
  from django.shortcuts import render
26
26
 
27
+ from django.utils.translation import gettext_lazy as _
28
+ from django.utils.text import format_lazy
29
+
27
30
  from unfold.contrib.filters.admin import (
28
31
  AutocompleteSelectFilter,
29
32
  AutocompleteSelectMultipleFilter,
@@ -78,9 +81,13 @@ class ModelAdmin(UnfoldAdmin):
78
81
 
79
82
  default_toggle_sidebar= True
80
83
 
84
+ @property
85
+ def search_help_text(self):
86
+ fields = ', '.join([str(get_verbose_name_field(self.model, f)) for f in self.search_fields])
87
+ return format_lazy(_('Search by {fields}'), fields=fields)
88
+
81
89
  def __init__(self, model, admin_site):
82
90
  super().__init__(model, admin_site)
83
- self.search_help_text= f'Search by {", ".join([get_verbose_name_field(model, f) for f in self.search_fields])}'
84
91
 
85
92
  if self.text_field_widget == 'ckeditor':
86
93
  self.formfield_overrides[models.TextField]["widget"]= CKEditor5Widget(
@@ -138,8 +145,8 @@ class ModelAdmin(UnfoldAdmin):
138
145
 
139
146
  def soft_delete(self, cl, request, queryset):
140
147
  for obj in queryset:
141
- title= f"{obj._meta.verbose_name} \"{obj}\" has been soft-deleted by user \"{request.user}\""
142
- content_html= f"{obj._meta.verbose_name} \"{obj}\" has been soft-deleted by user \"{request.user}\""
148
+ title= _('%(name)s "%(obj)s" has been soft-deleted by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
149
+ content_html= _('%(name)s "%(obj)s" has been soft-deleted by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
143
150
  obj.delete()
144
151
  # send notification to superuser when delete successfully
145
152
  for user in User.objects.filter(is_superuser= True):
@@ -148,12 +155,12 @@ class ModelAdmin(UnfoldAdmin):
148
155
  flag= 'danger',
149
156
  action= 'delete',
150
157
  content= content_html)
151
- soft_delete.short_description = 'Delete selected records'
158
+ soft_delete.short_description = _('Delete selected records')
152
159
 
153
160
  def hard_delete(self, cl, request, queryset):
154
161
  for obj in queryset:
155
- title= f"{obj._meta.verbose_name} \"{obj}\" has been hard-deleted by user \"{request.user}\""
156
- content_html= f"{obj._meta.verbose_name} \"{obj}\" has been hard-deleted by user \"{request.user}\""
162
+ title= _('%(name)s "%(obj)s" has been hard-deleted by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
163
+ content_html= _('%(name)s "%(obj)s" has been hard-deleted by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
157
164
  if hasattr(obj, 'hard_delete'):
158
165
  obj.hard_delete()
159
166
  else:
@@ -165,12 +172,12 @@ class ModelAdmin(UnfoldAdmin):
165
172
  flag= 'danger',
166
173
  action= 'delete',
167
174
  content= content_html)
168
- hard_delete.short_description = 'Hard delete selected records'
175
+ hard_delete.short_description = _('Hard delete selected records')
169
176
 
170
177
  def restore(self, cl, request, queryset):
171
178
  for obj in queryset:
172
- title= f"{obj._meta.verbose_name} \"{obj}\" has been restored by user \"{request.user}\""
173
- content_html= f"{obj._meta.verbose_name} \"{obj}\" has been restored by user \"{request.user}\""
179
+ title= _('%(name)s "%(obj)s" has been restored by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
180
+ content_html= _('%(name)s "%(obj)s" has been restored by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
174
181
  obj.restore(request)
175
182
  # send notification to superuser when delete successfully
176
183
  for user in User.objects.filter(is_superuser= True):
@@ -179,7 +186,7 @@ class ModelAdmin(UnfoldAdmin):
179
186
  flag= 'info',
180
187
  action= 'restore',
181
188
  content= content_html)
182
- restore.short_description = 'Restore selected records'
189
+ restore.short_description = _('Restore selected records')
183
190
 
184
191
  def get_queryset(self, request: HttpRequest) -> QuerySet[Any]:
185
192
  if request.user.is_superuser and request.user.show_softdelete:
@@ -188,8 +195,8 @@ class ModelAdmin(UnfoldAdmin):
188
195
  return self.model.objects.all()
189
196
 
190
197
  def delete_model(self, request, obj):
191
- title= f"{obj._meta.verbose_name} \"{obj}\" has been soft-deleted by user \"{request.user}\""
192
- content_html= f"{obj._meta.verbose_name} \"{obj}\" has been soft-deleted by user \"{request.user}\""
198
+ title= _('%(name)s "%(obj)s" has been soft-deleted by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
199
+ content_html= _('%(name)s "%(obj)s" has been soft-deleted by user "%(user)s"') % {'name': obj._meta.verbose_name, 'obj': obj, 'user': request.user}
193
200
  super().delete_model(request, obj)
194
201
  # send notification to superuser when delete successfully
195
202
  for user in User.objects.filter(is_superuser= True):
@@ -394,7 +401,7 @@ class ModelAdmin(UnfoldAdmin):
394
401
  meta_fields.remove('updated_at')
395
402
  meta_fields.remove('updated_by')
396
403
  meta_fields.append(('updated_at', 'updated_by'))
397
- fieldsets.append(('Meta', {'fields': meta_fields, 'classes': ["tab"]}))
404
+ fieldsets.append((_('Meta'), {'fields': meta_fields, 'classes': ["collapse"]}))
398
405
  return fieldsets
399
406
 
400
407
  def buttons(self, obj):
@@ -414,15 +421,15 @@ class ModelAdmin(UnfoldAdmin):
414
421
  path_delete= reverse_lazy(path + '_delete', args=[obj.pk])
415
422
  c= 0
416
423
  if self.has_change_permission(self.request, obj):
417
- button_edit = f'<a class="btn-edit col-span-1" href="{path_change}"> <img src="{svg_url_edit}" alt="Sửa" style="width: {width}px; height: {height}px;"></a>'
424
+ button_edit = f'<a class="btn-edit col-span-1" href="{path_change}"> <img src="{svg_url_edit}" alt="{_("Edit")}" style="width: {width}px; height: {height}px;"></a>'
418
425
  c+= 1
419
426
  else:
420
- button_view = f'<a class="btn-view col-span-1" href="{path_change}"> <img src="{svg_url_view}" alt="Xem" style="width: {width}px; height: {height}px;"></a>'
427
+ button_view = f'<a class="btn-view col-span-1" href="{path_change}"> <img src="{svg_url_view}" alt="{_("View")}" style="width: {width}px; height: {height}px;"></a>'
421
428
  c+= 1
422
429
 
423
430
  if self.has_delete_permission(self.request, obj):
424
431
  c+= 1
425
- button_delete = f'<a class="btn-delete btn-danger col-span-1" href="{path_delete}"> <img src="{svg_url_delete}" alt="Xóa" style="width: {width}px; height: {height}px;"></a>'
432
+ button_delete = f'<a class="btn-delete btn-danger col-span-1" href="{path_delete}"> <img src="{svg_url_delete}" alt="{_("Delete")}" style="width: {width}px; height: {height}px;"></a>'
426
433
 
427
434
  return mark_safe(f'''
428
435
  <div id="action_buttoms_{obj.pk}" class="action_buttoms grid gap-1 grid-cols-{c}" style="width: {33*c}px;">{button_view} {button_edit} {button_delete} </div>
@@ -15,7 +15,7 @@ def timeit(func):
15
15
  from django.utils import timezone
16
16
  from django.core.exceptions import FieldDoesNotExist
17
17
 
18
- def base_badge_callback(request, model):
18
+ def base_badge_callback(request, model, filter_kwargs=None):
19
19
  today = timezone.localtime().replace(hour=0, minute=0, second=0, microsecond=0)
20
20
 
21
21
  # try:
@@ -42,7 +42,11 @@ def base_badge_callback(request, model):
42
42
 
43
43
  c = getattr(model, 'objects_all', model.objects).filter(
44
44
  updated_at__gte=today,
45
- ).count()
45
+ )
46
+ if filter_kwargs:
47
+ c = c.filter(**filter_kwargs).count()
48
+ else:
49
+ c = c.count()
46
50
  return c
47
51
 
48
52
 
@@ -1,11 +1,13 @@
1
1
  from django.contrib import admin, messages
2
+ from django.http import JsonResponse
3
+ from django.urls import reverse
4
+ from django.utils.html import format_html
5
+ from django.utils.text import Truncator
2
6
  from django.utils.translation import gettext_lazy as _
3
- from unfold.decorators import display
4
7
 
5
8
  from whiteneuron.base.admin import base_admin_site, ModelAdmin
6
9
  from .models import FeedbackData
7
-
8
- from django.urls import reverse
10
+ from .urls import FEEDBACK_COOLDOWN_SECONDS
9
11
 
10
12
  from whiteneuron.notification.models import Notification
11
13
 
@@ -18,7 +20,7 @@ class FeedbackDataAdmin(ModelAdmin):
18
20
  'user',
19
21
  'content_type',
20
22
  'get_related_object_link', # Hiển thị link đến object gốc
21
- 'message',
23
+ 'get_short_message',
22
24
  'is_resolved',
23
25
  'created_at',
24
26
  )
@@ -26,26 +28,39 @@ class FeedbackDataAdmin(ModelAdmin):
26
28
  search_fields = ('message', 'user__username', 'content_type__model')
27
29
 
28
30
  fieldsets = (
29
- ('Feedback Data', {
30
- 'fields': ('user', ('content_type', 'object_id'), ('get_related_object_link', 'field'), 'message')
31
+ (_('Feedback Data'), {
32
+ # 'fields': ('user', ('content_type', 'object_id'), ('get_related_object_link', 'field'), 'message')
33
+ 'fields': ('user', ('content_type', 'object_id'), 'get_related_object_link', 'message')
31
34
  }),
32
- ('Status', {
35
+ (_('Status'), {
33
36
  'fields': ('is_resolved', 'note')
34
37
  })
35
38
  )
36
39
 
37
40
  readonly_fields = ('get_related_object_link',) # Chỉ hiển thị link, không cho sửa
38
41
 
42
+ def get_queryset(self, request):
43
+ qs = super().get_queryset(request)
44
+ if request.user.is_superuser:
45
+ return qs
46
+
47
+ # Người liên quan có thể xem feedback
48
+ return qs.filter(user=request.user)
49
+
50
+ def get_short_message(self, obj):
51
+ return Truncator(obj.message).words(200)
52
+ get_short_message.short_description = _('Message')
53
+
39
54
  def get_related_object_link(self, obj):
40
55
  """Hiển thị link đến object gốc"""
41
56
  if obj.content_object:
42
57
  admin_url = reverse(
43
- f'admin:{obj.content_type.app_label}_{obj.content_type.model}_change',
58
+ f'admin:{obj.content_type.app_label}_{obj.content_type.model}_change',
44
59
  args=[obj.object_id]
45
60
  )
46
- return format_html(f'<a href="{admin_url}" target="_blank">{obj.content_object}</a>')
61
+ return format_html('<a href="{}" target="_blank">{}</a>', admin_url, obj.content_object)
47
62
  return "-"
48
- get_related_object_link.short_description = "Related Object" # Tiêu đề trong Admin
63
+ get_related_object_link.short_description = _("Related Object") # Tiêu đề trong Admin
49
64
 
50
65
  # Định nghĩa action để đánh dấu đã xử lý
51
66
  actions = ['mark_as_resolved']
@@ -83,13 +98,16 @@ class FeedbackDataAdmin(ModelAdmin):
83
98
  if request.GET.get('resolved') == '1' and object_id:
84
99
  # chỉ cho phép superuser thực hiện
85
100
  if not request.user.is_superuser:
86
- self.message_user(request, _("Only superuser can mark feedback as resolved"), messages.ERROR)
87
- return super().changeform_view(request, object_id, form_url, extra_context)
101
+ return JsonResponse({"success": False, "message": str(_("Only superuser can mark feedback as resolved"))}, status=403)
88
102
 
89
103
  # note
90
- note= request.GET.get('note', '')
104
+ note= request.POST.get('note', '')
105
+ if len(note) > 500:
106
+ return JsonResponse({"success": False, "message": str(_("Note must not exceed 500 characters."))}, status=400)
91
107
 
92
108
  obj = self.get_object(request, object_id)
109
+ if obj is None:
110
+ return JsonResponse({"success": False, "message": str(_("Feedback not found."))}, status=404)
93
111
  obj.is_resolved = True
94
112
  obj.note = note
95
113
  obj.save()
@@ -98,30 +116,25 @@ class FeedbackDataAdmin(ModelAdmin):
98
116
  noti= Notification(
99
117
  user= obj.user,
100
118
  title= _("Feedback resolved"),
101
- content= f"""
102
- <p>Your feedback has been resolved.</p>
103
- <p>Object: {self.get_related_object_link(obj)}</p>
104
- <p>Note: {note}</p>
105
- """,
119
+ content= format_html(
120
+ '<p>{}</p><p>{}: {}</p><p>{}: {}</p>',
121
+ _("Your feedback has been resolved."),
122
+ _("Object"), self.get_related_object_link(obj),
123
+ _("Note"), note,
124
+ ),
106
125
  action='update',
107
126
  flag= 'success',
108
127
  )
109
128
  noti.save()
110
129
 
111
- # reload và thông báo
112
- self.message_user(request, _("Feedback marked as resolved"), messages.SUCCESS)
130
+ return JsonResponse({"success": True})
113
131
 
114
132
  return super().changeform_view(request, object_id, form_url, extra_context)
115
133
 
116
134
  # FeedBack base admin
117
135
  # Cho phép các model khác kế thừa để gọi
118
- from django.utils.html import format_html
119
136
  class FeedbackBaseAdmin(ModelAdmin):
120
137
  def render_change_form(self, request, context, *args, **kwargs):
121
- # Thêm show_feedback = True vào context để hiển thị feedbacks
122
138
  context['show_feedback'] = True
139
+ context['feedback_cooldown_ms'] = FEEDBACK_COOLDOWN_SECONDS * 1000
123
140
  return super().render_change_form(request, context, *args, **kwargs)
124
-
125
-
126
-
127
-
@@ -0,0 +1,81 @@
1
+ import json
2
+ import logging
3
+ from django.urls import path
4
+ from django.http import JsonResponse
5
+ from django.views.decorators.http import require_POST
6
+ from django.contrib.contenttypes.models import ContentType
7
+ from django.utils import timezone
8
+ from django.utils.translation import gettext as _
9
+ from datetime import timedelta
10
+ from whiteneuron.feedbacks.models import FeedbackData
11
+
12
+ FEEDBACK_COOLDOWN_SECONDS = 60
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ @require_POST
17
+ def receive_feedback(request):
18
+ if not request.user.is_authenticated:
19
+ return JsonResponse({"success": False, "message": _("User is not authenticated")}, status=403)
20
+
21
+ try:
22
+ # Đọc dữ liệu từ request
23
+ body_unicode = request.body.decode("utf-8")
24
+
25
+ data = json.loads(body_unicode)
26
+ object_id = data.get("object_id")
27
+ field = data.get("field")
28
+ feedback_message = data.get("feedback_message")
29
+ model_name = data.get("model_name")
30
+ app_label = data.get("app_label")
31
+
32
+ # Kiểm tra dữ liệu bắt buộc
33
+ if not object_id or not feedback_message or not model_name or not app_label:
34
+ return JsonResponse({"success": False, "message": _("Missing required fields")}, status=400)
35
+
36
+ if len(feedback_message) > 2000:
37
+ return JsonResponse({"success": False, "message": _("Feedback message must not exceed 2000 characters.")}, status=400)
38
+
39
+ # Lấy ContentType
40
+ try:
41
+ content_type = ContentType.objects.get(model=model_name, app_label=app_label)
42
+ except ContentType.DoesNotExist:
43
+ return JsonResponse({"success": False, "message": _("Invalid model or app_label")}, status=400)
44
+
45
+ # Kiểm tra cooldown chống spam — per user, không phân biệt đối tượng
46
+ cooldown_since = timezone.now() - timedelta(seconds=FEEDBACK_COOLDOWN_SECONDS)
47
+ last_feedback = FeedbackData.objects.filter(
48
+ user=request.user,
49
+ created_at__gte=cooldown_since,
50
+ ).order_by('-created_at').first()
51
+ if last_feedback:
52
+ remaining = FEEDBACK_COOLDOWN_SECONDS - int((timezone.now() - last_feedback.created_at).total_seconds())
53
+ return JsonResponse(
54
+ {"success": False, "message": _("Please wait %(seconds)d seconds before submitting another feedback.") % {"seconds": max(remaining, 1)}},
55
+ status=429,
56
+ )
57
+
58
+ # Tạo feedback mới
59
+ feedback = FeedbackData(
60
+ user=request.user,
61
+ content_type=content_type,
62
+ object_id=object_id,
63
+ field=field,
64
+ message=feedback_message,
65
+ )
66
+
67
+ feedback.save()
68
+
69
+ logger.info("Feedback saved: %s", feedback)
70
+ return JsonResponse({"success": True, "message": _("Feedback saved successfully")})
71
+
72
+ except json.JSONDecodeError:
73
+ logger.error("Invalid JSON format in feedback submission")
74
+ return JsonResponse({"success": False, "message": _("Invalid JSON format")}, status=400)
75
+ except Exception as e:
76
+ logger.error("Error saving feedback: %s", e, exc_info=True)
77
+ return JsonResponse({"success": False, "message": _("An unexpected error occurred. Please try again.")}, status=500)
78
+
79
+ urlpatterns = [
80
+ path("feedback/", receive_feedback, name="feedback_endpoint"),
81
+ ]
@@ -0,0 +1,10 @@
1
+ from whiteneuron.base.utils import base_badge_callback
2
+ from .models import FeedbackData
3
+
4
+ def feedback_data_badge_callback(request):
5
+ # Lọc feedback theo user hiện tại
6
+ if request.user.is_superuser:
7
+ filter_kwargs = None # Superuser xem tất cả feedback
8
+ else:
9
+ filter_kwargs = {'user': request.user}
10
+ return base_badge_callback(request, FeedbackData, filter_kwargs)