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.
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/PKG-INFO +26 -3
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/README.md +25 -2
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/PKG-INFO +26 -3
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/pyproject.toml +1 -1
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/modeladmin.py +23 -16
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/utils.py +6 -2
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/admin.py +39 -26
- django_whiteneuron-0.2.44/whiteneuron/feedbacks/urls.py +81 -0
- django_whiteneuron-0.2.44/whiteneuron/feedbacks/utils.py +10 -0
- django_whiteneuron-0.2.44/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
- django_whiteneuron-0.2.44/whiteneuron/locale/vi/LC_MESSAGES/django.po +1423 -0
- django_whiteneuron-0.2.44/whiteneuron/static/base/css/styles.css +2 -0
- django_whiteneuron-0.2.44/whiteneuron/templates/admin/feedbacks/feedback_change_form.html +132 -0
- django_whiteneuron-0.2.44/whiteneuron/templates/admin/submit_line.html +157 -0
- django_whiteneuron-0.2.42/whiteneuron/feedbacks/urls.py +0 -69
- django_whiteneuron-0.2.42/whiteneuron/feedbacks/utils.py +0 -5
- django_whiteneuron-0.2.42/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
- django_whiteneuron-0.2.42/whiteneuron/locale/vi/LC_MESSAGES/django.po +0 -1182
- django_whiteneuron-0.2.42/whiteneuron/static/base/css/styles.css +0 -2
- django_whiteneuron-0.2.42/whiteneuron/templates/admin/feedbacks/feedback_change_form.html +0 -41
- django_whiteneuron-0.2.42/whiteneuron/templates/admin/submit_line.html +0 -67
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/LICENSE +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/SOURCES.txt +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/dependency_links.txt +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/requires.txt +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/top_level.txt +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/setup.cfg +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/apps.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/asgi.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/ckeditor.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/filters.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/forms.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/init_admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/init_groupuser.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/management/commands/init_guest.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/middleware.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0001_initial.py +0 -0
- {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
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0003_notification_obj_link.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0004_delete_notification.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0005_user_show_softdelete.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0006_user_is_bot.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0007_app.py +0 -0
- {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
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0009_alter_app_table.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0010_app_category.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0011_alter_app_options.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0012_app_permission.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0013_app_thumbnail_url.py +0 -0
- {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
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0015_ipblacklist.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0016_uablacklist.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/0017_alter_uablacklist_pattern.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/migrations/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/models.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/resources.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/settings.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/sites.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/tasks.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/templatetags/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/templatetags/unfold_list_custom.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/templatetags/wn_filters.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/tests.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/thread_local.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/urls.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/views.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/base/widgets.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/constants.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/mixins.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/tasks.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_import_export/utils.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_modeltranslation/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_modeltranslation/admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/contrib/wn_modeltranslation/utils.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/apps.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/migrations/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/models.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/tests.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/dashboard/views.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/apps.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0001_initial.py +0 -0
- {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
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0003_feedbackdata_note.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0004_alter_feedbackdata_object_id.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0005_feedbackdata_field.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/0006_alter_feedbackdata_created_at_and_more.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/migrations/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/models.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/tests.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/feedbacks/views.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/apps.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/migrations/0001_initial.py +0 -0
- {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
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/migrations/0003_alter_excelfile_created_at_and_more.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/migrations/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/models.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/tests.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/utils.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/file_management/views.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/manage.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/apps.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/consumers.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/management/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/management/commands/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/management/commands/call_demo_noti.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0001_initial.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0002_alter_notificationconfig_event_type.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0003_notification.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0004_alter_notification_flag.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0005_notification_keywords.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0006_remove_notification_keywords.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0007_notification_changed_data.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0008_alter_notification_changed_data.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0009_notification_action_by_alter_notification_user.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0010_alter_notification_user.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0011_alter_notification_user.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/0012_alter_notification_action_and_more.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/migrations/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/models.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/routing.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/tests.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/utils.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/notification/views.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/css/btn-styles.css +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/css/ckeditor5.css +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/css/loading.css +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/forms/css/html-editor.css +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/forms/js/format.html.js +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/forms/js/trix.config.js +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/delete.svg +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/edit.svg +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/icon-viewlink.svg +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/loading.gif +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/login-bg.jpg +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/login_bg.jpg +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/images/logo/WhiteNeuron.png +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/chart.min.js +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/d3.v7.min.js +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/loading.js +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/static/base/js/whiteneuron.js +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/admin.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/apps.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/migrations/__init__.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/models.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/tests.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/synchronization/views.py +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/400.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/403.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/404.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/429.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/500.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/auth/user/add_form.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base/app_change_list.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base/image_change_form.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base/userprofile_change_form.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/base.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/change_list.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/grid_view_results.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/header.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/index.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/login.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/login_after.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/admin/search_form.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/base/forms/helpers/toolbar.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/base/forms/wysiwyg.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/base.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/account_links.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/app_list_badge.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/field_password.html +0 -0
- {django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/whiteneuron/templates/unfold/helpers/navigation_user.html +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
{django_whiteneuron-0.2.42 → django_whiteneuron-0.2.44}/django_whiteneuron.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-whiteneuron
|
|
3
|
-
Version: 0.2.
|
|
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.
|
|
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.
|
|
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.
|
|
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=
|
|
142
|
-
content_html=
|
|
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=
|
|
156
|
-
content_html=
|
|
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=
|
|
173
|
-
content_html=
|
|
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=
|
|
192
|
-
content_html=
|
|
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': ["
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
-
)
|
|
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
|
-
'
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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=
|
|
102
|
-
<p>
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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)
|
|
Binary file
|