django-whiteneuron 0.4.0.0__tar.gz → 0.4.0.2__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.4.0.0 → django_whiteneuron-0.4.0.2}/PKG-INFO +20 -5
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/README.md +18 -3
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/django_whiteneuron.egg-info/PKG-INFO +20 -5
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/django_whiteneuron.egg-info/requires.txt +1 -1
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/pyproject.toml +2 -2
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/admin.py +13 -8
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/models.py +16 -11
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/tests_regressions.py +92 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/views.py +12 -4
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/dashboard/views.py +16 -5
- django_whiteneuron-0.4.0.2/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/locale/vi/LC_MESSAGES/django.po +319 -286
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/css/styles.css +1 -1
- django_whiteneuron-0.4.0.0/whiteneuron/locale/vi/LC_MESSAGES/django.mo +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/LICENSE +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/django_whiteneuron.egg-info/SOURCES.txt +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/django_whiteneuron.egg-info/dependency_links.txt +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/django_whiteneuron.egg-info/top_level.txt +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/setup.cfg +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/apps.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/asgi.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/ckeditor.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/filters.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/forms.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/html_sanitize.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/management/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/management/commands/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/management/commands/cleanup_old_activities.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/management/commands/init_admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/management/commands/init_groupuser.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/management/commands/init_guest.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/management/commands/update_visit_profiles.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/middleware.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0001_initial.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0002_imagemodel_userprofile_user_avatar_user_biography_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0003_notification_obj_link.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0004_delete_notification.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0005_user_show_softdelete.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0006_user_is_bot.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0007_app.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0008_app_created_at_app_created_by_app_deleted_at_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0009_alter_app_table.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0010_app_category.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0011_alter_app_options.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0012_app_permission.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0013_app_thumbnail_url.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0014_alter_app_created_at_alter_app_updated_at_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0015_ipblacklist.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0016_uablacklist.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0017_alter_uablacklist_pattern.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0018_user_uuid.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0019_alter_user_uuid.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0020_add_visit_profile_and_fk_fields.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0021_populate_visit_profiles.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0022_remove_duplicate_visit_profiles.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0023_remove_useractivity_ip_address_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0024_alter_visitprofile_user_agent_to_charfield.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0025_alter_anonymousactivity_options_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0026_alter_anonymousactivity_profile_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0027_alter_anonymousactivity_method_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/0028_perf_indexes.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/migrations/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/modeladmin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/queue.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/resources.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/settings.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/sites.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/tasks.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/templatetags/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/templatetags/unfold_list_custom.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/templatetags/wn_filters.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/templatetags/wn_theme.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/testing.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/tests.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/themes.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/thread_local.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/urls.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/urls_testing.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/utils.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/widgets.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_import_export/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_import_export/admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_import_export/constants.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_import_export/mixins.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_import_export/tasks.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_import_export/utils.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_modeltranslation/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_modeltranslation/admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/contrib/wn_modeltranslation/utils.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/dashboard/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/dashboard/admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/dashboard/apps.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/dashboard/migrations/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/dashboard/models.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/dashboard/tests.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/apps.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/migrations/0001_initial.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/migrations/0002_feedbackdata_created_by_feedbackdata_deleted_at_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/migrations/0003_feedbackdata_note.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/migrations/0004_alter_feedbackdata_object_id.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/migrations/0005_feedbackdata_field.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/migrations/0006_alter_feedbackdata_created_at_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/migrations/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/models.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/tests.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/urls.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/utils.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/feedbacks/views.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/apps.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0001_initial.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0002_alter_excelfile_options_alter_pdffile_options_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0003_alter_excelfile_created_at_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0004_excelfile_hash_pdffile_hash.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0005_alter_excelfile_file_alter_excelfile_title_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0006_excelfile_is_public_pdffile_is_public.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0007_htmlfile.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/0008_excelfile_allowed_groups_excelfile_allowed_users_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/migrations/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/models.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/tests.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/urls.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/utils.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/file_management/views.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/manage.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/apps.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/collector.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/management/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/management/commands/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/management/commands/monitoring_collector.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/migrations/0001_initial.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/migrations/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/models.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/permissions.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/stats.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/tasks.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/monitoring/tests.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/admin.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/apps.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/consumers.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/management/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/management/commands/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/management/commands/call_demo_noti.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0001_initial.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0002_alter_notificationconfig_event_type.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0003_notification.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0004_alter_notification_flag.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0005_notification_keywords.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0006_remove_notification_keywords.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0007_notification_changed_data.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0008_alter_notification_changed_data.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0009_notification_action_by_alter_notification_user.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0010_alter_notification_user.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0011_alter_notification_user.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0012_alter_notification_action_and_more.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/0013_perf_indexes.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/migrations/__init__.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/models.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/routing.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/tests.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/utils.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/notification/views.py +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/css/btn-styles.css +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/css/ckeditor5.css +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/css/loading.css +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/forms/js/mdeditor.js +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/delete.svg +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/edit.svg +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/icon-viewlink.svg +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/loading.gif +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/login-bg.jpg +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/login_bg.jpg +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/logo/LOGO.jpg +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/logo/WhiteNeuron-128.png +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/logo/WhiteNeuron-32.png +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/images/logo/WhiteNeuron.png +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/js/chart.min.js +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/js/d3.v7.min.js +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/js/loading.js +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/base/js/whiteneuron.js +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/monitoring/css/dashboard.css +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/static/monitoring/js/dashboard.js +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/400.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/403.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/404.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/429.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/500.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/auth/user/add_form.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/base/app_change_list.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/base/image_change_form.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/base/userprofile_change_form.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/base.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/change_list.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/feedbacks/change_form_feedback_list.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/feedbacks/feedback_change_form.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/grid_view_results.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/header.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/index.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/login.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/login_after.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/monitoring/dashboard.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/monitoring/stat.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/monitoring/timeseries.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/search_form.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/signature.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/submit_line.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/wn_change_form.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/admin/wn_history_panel.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/base/announcement.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/base/forms/helpers/toolbar.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/base/forms/mdeditor.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/base/forms/wysiwyg.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/base.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/file_management/widgets/clearable_file_input_no_download.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/account_links.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/app_list_badge.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/field_password.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/language_switch.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/pagination.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/theme_switch.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/userlinks.html +0 -0
- {django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/templates/unfold/helpers/wn_page_button.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-whiteneuron
|
|
3
|
-
Version: 0.4.0.
|
|
3
|
+
Version: 0.4.0.2
|
|
4
4
|
Summary: A modern Django Admin extension with enhanced UI/UX, dashboard, feedback, file management, rate limiting, IP blacklisting, and advanced admin integrations built on django-unfold.
|
|
5
5
|
Author-email: White Neuron <anhnt@whiteneuron.ai>
|
|
6
6
|
License: MIT License
|
|
@@ -43,7 +43,7 @@ Requires-Python: >=3.13
|
|
|
43
43
|
Description-Content-Type: text/markdown
|
|
44
44
|
License-File: LICENSE
|
|
45
45
|
Requires-Dist: django-debug-toolbar>=5.0.1
|
|
46
|
-
Requires-Dist: django<7.0.0,>=6.0.
|
|
46
|
+
Requires-Dist: django<7.0.0,>=6.0.9
|
|
47
47
|
Requires-Dist: django-celery-beat>=2.7.0
|
|
48
48
|
Requires-Dist: django-guardian>=2.4.0
|
|
49
49
|
Requires-Dist: django-imagekit>=5.0.0
|
|
@@ -96,11 +96,26 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
|
|
|
96
96
|

|
|
97
97
|
|
|
98
98
|
## Current Version
|
|
99
|
-
v0.4.0.
|
|
99
|
+
v0.4.0.2
|
|
100
100
|
|
|
101
101
|
## Changelog
|
|
102
102
|
|
|
103
|
-
### Latest: v0.4.0.
|
|
103
|
+
### Latest: v0.4.0.2 (2026-10-15)
|
|
104
|
+
**Patch release: dependency security updates in the locked set (and the Docker base images)**
|
|
105
|
+
- **Security (dependencies)**: locked django 6.0.9, gunicorn 26.2.0, cryptography 50.0.2, hiredis 3.4.2, msgpack 1.2.3, Markdown 3.11.1 and ujson 6.0.0 (all patched releases). The Docker base images install exactly this lock, so projects building `FROM django-whiteneuron-*:v0.4.0.2` no longer need to pin these packages above the base. `django>=6.0.9` is now the minimum.
|
|
106
|
+
- **Upgrade**: `pip install -U django-whiteneuron==0.4.0.2`. No code, template or migration changes. Docker base images `v0.4.0.2` are published by the release workflow.
|
|
107
|
+
|
|
108
|
+
### v0.4.0.1 (2026-10-14)
|
|
109
|
+
**Patch release: Vietnamese translations lost in 0.4.0.0, daisyUI modals under Unfold, announcement endpoint**
|
|
110
|
+
- **Fixed**: translations that 0.4.0.0 dropped from the Vietnamese catalog. Strings translated with `_()` *inside f-strings* are invisible to `makemessages`, so regenerating the catalog for 0.4.0.0 marked them obsolete and left them out of `django.mo`, so 0.4.0.0 showed them in English: user badges (Inactive / Admin / Bot), every "… has been updated by user …" notification, M2M change notes (added / removed) and the dashboard KPI cards ("progress from last", "in"). The `_()` calls are moved out of the f-strings and the translations restored.
|
|
111
|
+
- **Fixed**: dashboard KPI cards are whole translatable sentences (`%(object)s in %(period)s`, `%(count)s successful (%(rate)s%%)`); "day" and "successful" were hard-coded English; no change now shows as a green +0 instead of a red -0; the color classes are literal so Tailwind compiles them.
|
|
112
|
+
- **Fixed**: notification bodies ("… has been updated by user … with the following changes") were hard-coded English.
|
|
113
|
+
- **Fixed**: an open daisyUI `ui-modal` (announcement, feedback resolve, markdown preview) rendered as a narrow white column without overlay: Unfold's unlayered `dialog { … }` rule beats daisyUI's layered `.ui-modal`. `dialog.ui-modal` now restores the full-screen modal.
|
|
114
|
+
- **Fixed**: `/announcement/` returned a 500 when the project-provided `ANNOUNCEMENT_CONTENT_HTML_FILE` template is missing or the setting is empty; it now returns empty content and logs a warning.
|
|
115
|
+
- **Tests**: no `_()` inside f-strings; every literal msgid in the package's Python code must be in the compiled Vietnamese catalog (catches strings dropped by `makemessages`); dashboard KPI translated; `dialog.ui-modal` override present; announcement endpoint without a template.
|
|
116
|
+
- **Upgrade**: `pip install -U django-whiteneuron==0.4.0.1`, `collectstatic`. No migrations. Docker base images `v0.4.0.1` are published by the release workflow.
|
|
117
|
+
|
|
118
|
+
### v0.4.0.0 (2026-10-13)
|
|
104
119
|
**Feature release: daisyUI multi-theme admin, server monitoring dashboard, django-unfold 0.109, security hardening and performance work**
|
|
105
120
|
- **Added**: 35 daisyUI themes selectable from the user menu (**Themes**); each theme also re-colors Unfold and drives its dark mode. `WN_ADMIN_THEMES` limits the picker (`[]` hides it); `WN_THEMES="nord,dracula" bash scripts/tailwind.sh` builds a smaller CSS bundle.
|
|
106
121
|
- **Added**: `whiteneuron.monitoring` — Grafana-style **Server monitoring** dashboard (CPU, memory, disk, load, network, disk I/O, request throughput, p50/p95/p99 latency, error rate, Apdex, slowest endpoints, DB/cache latency) with time ranges, auto-refresh, host selector and shared crosshair. Collect with `manage.py monitoring_collector` or the Celery task `whiteneuron.monitoring.tasks.collect_metrics`; configure with `WN_MONITORING`. New dependency: `psutil`.
|
|
@@ -529,7 +544,7 @@ Release is published.
|
|
|
529
544
|
```bash
|
|
530
545
|
bash docker/build-base.sh # HEAD, load into local docker
|
|
531
546
|
GIT_REF=v0.3.8.0 bash docker/build-base.sh # a tag / branch / commit
|
|
532
|
-
GIT_REF=v0.4.0.
|
|
547
|
+
GIT_REF=v0.4.0.2 bash docker/push-base.sh # push a release, with provenance and SBOM
|
|
533
548
|
VARIANTS=alpine IMAGE_NAMESPACE=myorg WN_PYTHON_VERSION=3.13 bash docker/build-base.sh
|
|
534
549
|
```
|
|
535
550
|
|
|
@@ -10,11 +10,26 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
|
|
|
10
10
|

|
|
11
11
|
|
|
12
12
|
## Current Version
|
|
13
|
-
v0.4.0.
|
|
13
|
+
v0.4.0.2
|
|
14
14
|
|
|
15
15
|
## Changelog
|
|
16
16
|
|
|
17
|
-
### Latest: v0.4.0.
|
|
17
|
+
### Latest: v0.4.0.2 (2026-10-15)
|
|
18
|
+
**Patch release: dependency security updates in the locked set (and the Docker base images)**
|
|
19
|
+
- **Security (dependencies)**: locked django 6.0.9, gunicorn 26.2.0, cryptography 50.0.2, hiredis 3.4.2, msgpack 1.2.3, Markdown 3.11.1 and ujson 6.0.0 (all patched releases). The Docker base images install exactly this lock, so projects building `FROM django-whiteneuron-*:v0.4.0.2` no longer need to pin these packages above the base. `django>=6.0.9` is now the minimum.
|
|
20
|
+
- **Upgrade**: `pip install -U django-whiteneuron==0.4.0.2`. No code, template or migration changes. Docker base images `v0.4.0.2` are published by the release workflow.
|
|
21
|
+
|
|
22
|
+
### v0.4.0.1 (2026-10-14)
|
|
23
|
+
**Patch release: Vietnamese translations lost in 0.4.0.0, daisyUI modals under Unfold, announcement endpoint**
|
|
24
|
+
- **Fixed**: translations that 0.4.0.0 dropped from the Vietnamese catalog. Strings translated with `_()` *inside f-strings* are invisible to `makemessages`, so regenerating the catalog for 0.4.0.0 marked them obsolete and left them out of `django.mo`, so 0.4.0.0 showed them in English: user badges (Inactive / Admin / Bot), every "… has been updated by user …" notification, M2M change notes (added / removed) and the dashboard KPI cards ("progress from last", "in"). The `_()` calls are moved out of the f-strings and the translations restored.
|
|
25
|
+
- **Fixed**: dashboard KPI cards are whole translatable sentences (`%(object)s in %(period)s`, `%(count)s successful (%(rate)s%%)`); "day" and "successful" were hard-coded English; no change now shows as a green +0 instead of a red -0; the color classes are literal so Tailwind compiles them.
|
|
26
|
+
- **Fixed**: notification bodies ("… has been updated by user … with the following changes") were hard-coded English.
|
|
27
|
+
- **Fixed**: an open daisyUI `ui-modal` (announcement, feedback resolve, markdown preview) rendered as a narrow white column without overlay: Unfold's unlayered `dialog { … }` rule beats daisyUI's layered `.ui-modal`. `dialog.ui-modal` now restores the full-screen modal.
|
|
28
|
+
- **Fixed**: `/announcement/` returned a 500 when the project-provided `ANNOUNCEMENT_CONTENT_HTML_FILE` template is missing or the setting is empty; it now returns empty content and logs a warning.
|
|
29
|
+
- **Tests**: no `_()` inside f-strings; every literal msgid in the package's Python code must be in the compiled Vietnamese catalog (catches strings dropped by `makemessages`); dashboard KPI translated; `dialog.ui-modal` override present; announcement endpoint without a template.
|
|
30
|
+
- **Upgrade**: `pip install -U django-whiteneuron==0.4.0.1`, `collectstatic`. No migrations. Docker base images `v0.4.0.1` are published by the release workflow.
|
|
31
|
+
|
|
32
|
+
### v0.4.0.0 (2026-10-13)
|
|
18
33
|
**Feature release: daisyUI multi-theme admin, server monitoring dashboard, django-unfold 0.109, security hardening and performance work**
|
|
19
34
|
- **Added**: 35 daisyUI themes selectable from the user menu (**Themes**); each theme also re-colors Unfold and drives its dark mode. `WN_ADMIN_THEMES` limits the picker (`[]` hides it); `WN_THEMES="nord,dracula" bash scripts/tailwind.sh` builds a smaller CSS bundle.
|
|
20
35
|
- **Added**: `whiteneuron.monitoring` — Grafana-style **Server monitoring** dashboard (CPU, memory, disk, load, network, disk I/O, request throughput, p50/p95/p99 latency, error rate, Apdex, slowest endpoints, DB/cache latency) with time ranges, auto-refresh, host selector and shared crosshair. Collect with `manage.py monitoring_collector` or the Celery task `whiteneuron.monitoring.tasks.collect_metrics`; configure with `WN_MONITORING`. New dependency: `psutil`.
|
|
@@ -443,7 +458,7 @@ Release is published.
|
|
|
443
458
|
```bash
|
|
444
459
|
bash docker/build-base.sh # HEAD, load into local docker
|
|
445
460
|
GIT_REF=v0.3.8.0 bash docker/build-base.sh # a tag / branch / commit
|
|
446
|
-
GIT_REF=v0.4.0.
|
|
461
|
+
GIT_REF=v0.4.0.2 bash docker/push-base.sh # push a release, with provenance and SBOM
|
|
447
462
|
VARIANTS=alpine IMAGE_NAMESPACE=myorg WN_PYTHON_VERSION=3.13 bash docker/build-base.sh
|
|
448
463
|
```
|
|
449
464
|
|
{django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/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.4.0.
|
|
3
|
+
Version: 0.4.0.2
|
|
4
4
|
Summary: A modern Django Admin extension with enhanced UI/UX, dashboard, feedback, file management, rate limiting, IP blacklisting, and advanced admin integrations built on django-unfold.
|
|
5
5
|
Author-email: White Neuron <anhnt@whiteneuron.ai>
|
|
6
6
|
License: MIT License
|
|
@@ -43,7 +43,7 @@ Requires-Python: >=3.13
|
|
|
43
43
|
Description-Content-Type: text/markdown
|
|
44
44
|
License-File: LICENSE
|
|
45
45
|
Requires-Dist: django-debug-toolbar>=5.0.1
|
|
46
|
-
Requires-Dist: django<7.0.0,>=6.0.
|
|
46
|
+
Requires-Dist: django<7.0.0,>=6.0.9
|
|
47
47
|
Requires-Dist: django-celery-beat>=2.7.0
|
|
48
48
|
Requires-Dist: django-guardian>=2.4.0
|
|
49
49
|
Requires-Dist: django-imagekit>=5.0.0
|
|
@@ -96,11 +96,26 @@ A modern Django Admin extension focused on UI/UX, dashboard, feedback, file mana
|
|
|
96
96
|

|
|
97
97
|
|
|
98
98
|
## Current Version
|
|
99
|
-
v0.4.0.
|
|
99
|
+
v0.4.0.2
|
|
100
100
|
|
|
101
101
|
## Changelog
|
|
102
102
|
|
|
103
|
-
### Latest: v0.4.0.
|
|
103
|
+
### Latest: v0.4.0.2 (2026-10-15)
|
|
104
|
+
**Patch release: dependency security updates in the locked set (and the Docker base images)**
|
|
105
|
+
- **Security (dependencies)**: locked django 6.0.9, gunicorn 26.2.0, cryptography 50.0.2, hiredis 3.4.2, msgpack 1.2.3, Markdown 3.11.1 and ujson 6.0.0 (all patched releases). The Docker base images install exactly this lock, so projects building `FROM django-whiteneuron-*:v0.4.0.2` no longer need to pin these packages above the base. `django>=6.0.9` is now the minimum.
|
|
106
|
+
- **Upgrade**: `pip install -U django-whiteneuron==0.4.0.2`. No code, template or migration changes. Docker base images `v0.4.0.2` are published by the release workflow.
|
|
107
|
+
|
|
108
|
+
### v0.4.0.1 (2026-10-14)
|
|
109
|
+
**Patch release: Vietnamese translations lost in 0.4.0.0, daisyUI modals under Unfold, announcement endpoint**
|
|
110
|
+
- **Fixed**: translations that 0.4.0.0 dropped from the Vietnamese catalog. Strings translated with `_()` *inside f-strings* are invisible to `makemessages`, so regenerating the catalog for 0.4.0.0 marked them obsolete and left them out of `django.mo`, so 0.4.0.0 showed them in English: user badges (Inactive / Admin / Bot), every "… has been updated by user …" notification, M2M change notes (added / removed) and the dashboard KPI cards ("progress from last", "in"). The `_()` calls are moved out of the f-strings and the translations restored.
|
|
111
|
+
- **Fixed**: dashboard KPI cards are whole translatable sentences (`%(object)s in %(period)s`, `%(count)s successful (%(rate)s%%)`); "day" and "successful" were hard-coded English; no change now shows as a green +0 instead of a red -0; the color classes are literal so Tailwind compiles them.
|
|
112
|
+
- **Fixed**: notification bodies ("… has been updated by user … with the following changes") were hard-coded English.
|
|
113
|
+
- **Fixed**: an open daisyUI `ui-modal` (announcement, feedback resolve, markdown preview) rendered as a narrow white column without overlay: Unfold's unlayered `dialog { … }` rule beats daisyUI's layered `.ui-modal`. `dialog.ui-modal` now restores the full-screen modal.
|
|
114
|
+
- **Fixed**: `/announcement/` returned a 500 when the project-provided `ANNOUNCEMENT_CONTENT_HTML_FILE` template is missing or the setting is empty; it now returns empty content and logs a warning.
|
|
115
|
+
- **Tests**: no `_()` inside f-strings; every literal msgid in the package's Python code must be in the compiled Vietnamese catalog (catches strings dropped by `makemessages`); dashboard KPI translated; `dialog.ui-modal` override present; announcement endpoint without a template.
|
|
116
|
+
- **Upgrade**: `pip install -U django-whiteneuron==0.4.0.1`, `collectstatic`. No migrations. Docker base images `v0.4.0.1` are published by the release workflow.
|
|
117
|
+
|
|
118
|
+
### v0.4.0.0 (2026-10-13)
|
|
104
119
|
**Feature release: daisyUI multi-theme admin, server monitoring dashboard, django-unfold 0.109, security hardening and performance work**
|
|
105
120
|
- **Added**: 35 daisyUI themes selectable from the user menu (**Themes**); each theme also re-colors Unfold and drives its dark mode. `WN_ADMIN_THEMES` limits the picker (`[]` hides it); `WN_THEMES="nord,dracula" bash scripts/tailwind.sh` builds a smaller CSS bundle.
|
|
106
121
|
- **Added**: `whiteneuron.monitoring` — Grafana-style **Server monitoring** dashboard (CPU, memory, disk, load, network, disk I/O, request throughput, p50/p95/p99 latency, error rate, Apdex, slowest endpoints, DB/cache latency) with time ranges, auto-refresh, host selector and shared crosshair. Collect with `manage.py monitoring_collector` or the Celery task `whiteneuron.monitoring.tasks.collect_metrics`; configure with `WN_MONITORING`. New dependency: `psutil`.
|
|
@@ -529,7 +544,7 @@ Release is published.
|
|
|
529
544
|
```bash
|
|
530
545
|
bash docker/build-base.sh # HEAD, load into local docker
|
|
531
546
|
GIT_REF=v0.3.8.0 bash docker/build-base.sh # a tag / branch / commit
|
|
532
|
-
GIT_REF=v0.4.0.
|
|
547
|
+
GIT_REF=v0.4.0.2 bash docker/push-base.sh # push a release, with provenance and SBOM
|
|
533
548
|
VARIANTS=alpine IMAGE_NAMESPACE=myorg WN_PYTHON_VERSION=3.13 bash docker/build-base.sh
|
|
534
549
|
```
|
|
535
550
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "django-whiteneuron"
|
|
3
|
-
version = "0.4.0.
|
|
3
|
+
version = "0.4.0.2"
|
|
4
4
|
description = "A modern Django Admin extension with enhanced UI/UX, dashboard, feedback, file management, rate limiting, IP blacklisting, and advanced admin integrations built on django-unfold."
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "White Neuron", email = "anhnt@whiteneuron.ai"}
|
|
@@ -10,7 +10,7 @@ readme = "README.md"
|
|
|
10
10
|
keywords = ["django", "plugin", "admin", "dashboard", "whiteneuron"]
|
|
11
11
|
dependencies = [
|
|
12
12
|
"django-debug-toolbar>=5.0.1",
|
|
13
|
-
"django>=6.0.
|
|
13
|
+
"django>=6.0.9,<7.0.0",
|
|
14
14
|
"django-celery-beat>=2.7.0",
|
|
15
15
|
"django-guardian>=2.4.0",
|
|
16
16
|
"django-imagekit>=5.0.0",
|
|
@@ -184,18 +184,23 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
|
|
|
184
184
|
</div>
|
|
185
185
|
'''
|
|
186
186
|
|
|
187
|
+
# Labels are looked up outside the f-strings: makemessages can't read _() inside them
|
|
188
|
+
active_label, inactive_label = _("Active"), _("Inactive")
|
|
189
|
+
staff_label, admin_label = _("Staff"), _("Admin")
|
|
190
|
+
superuser_label, bot_label = _("Superuser"), _("Bot")
|
|
191
|
+
|
|
187
192
|
# Status badges với dark mode support
|
|
188
193
|
badges = []
|
|
189
194
|
if obj.is_active:
|
|
190
|
-
badges.append(f'<span class="ui-badge ui-badge-success ui-badge-xs gap-1"><span class="w-1.5 h-1.5 bg-success dark:bg-success-300 rounded-full animate-pulse"></span>{
|
|
195
|
+
badges.append(f'<span class="ui-badge ui-badge-success ui-badge-xs gap-1"><span class="w-1.5 h-1.5 bg-success dark:bg-success-300 rounded-full animate-pulse"></span>{active_label}</span>')
|
|
191
196
|
else:
|
|
192
|
-
badges.append(f'<span class="ui-badge ui-badge-error ui-badge-xs gap-1"><span class="w-1.5 h-1.5 bg-error dark:bg-error-300 rounded-full"></span>{
|
|
197
|
+
badges.append(f'<span class="ui-badge ui-badge-error ui-badge-xs gap-1"><span class="w-1.5 h-1.5 bg-error dark:bg-error-300 rounded-full"></span>{inactive_label}</span>')
|
|
193
198
|
|
|
194
199
|
if obj.is_staff:
|
|
195
|
-
badges.append(f'<span class="ui-badge ui-badge-info ui-badge-xs">{
|
|
200
|
+
badges.append(f'<span class="ui-badge ui-badge-info ui-badge-xs">{staff_label}</span>')
|
|
196
201
|
|
|
197
202
|
if obj.is_superuser:
|
|
198
|
-
badges.append(f'<span class="ui-badge ui-badge-warning ui-badge-xs">{
|
|
203
|
+
badges.append(f'<span class="ui-badge ui-badge-warning ui-badge-xs">{admin_label}</span>')
|
|
199
204
|
|
|
200
205
|
badges_html = ' '.join(badges)
|
|
201
206
|
|
|
@@ -205,11 +210,11 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
|
|
|
205
210
|
|
|
206
211
|
role= ""
|
|
207
212
|
if obj.is_superuser:
|
|
208
|
-
role= f'<span class="material-symbols-outlined text-warning" style="font-size:18px" title="{
|
|
213
|
+
role= f'<span class="material-symbols-outlined text-warning" style="font-size:18px" title="{superuser_label}">admin_panel_settings</span>'
|
|
209
214
|
elif obj.is_staff:
|
|
210
|
-
role= f'<span class="material-symbols-outlined text-info" style="font-size:18px" title="{
|
|
215
|
+
role= f'<span class="material-symbols-outlined text-info" style="font-size:18px" title="{staff_label}">badge</span>'
|
|
211
216
|
elif obj.is_bot:
|
|
212
|
-
role= f'<span class="material-symbols-outlined text-secondary" style="font-size:18px" title="{
|
|
217
|
+
role= f'<span class="material-symbols-outlined text-secondary" style="font-size:18px" title="{bot_label}">smart_toy</span>'
|
|
213
218
|
|
|
214
219
|
# Compact vertical card layout với dark mode support
|
|
215
220
|
string = f"""
|
|
@@ -229,7 +234,7 @@ class UserAdmin(BaseUserAdmin, ModelAdmin):
|
|
|
229
234
|
<h5>{display_name}</h5>
|
|
230
235
|
<p class="ui-card-title justify-center text-xs">{username_display}</p>
|
|
231
236
|
<div class="flex flex-wrap items-center justify-center gap-2 mt-3">
|
|
232
|
-
{f'<span class="material-symbols-outlined text-success" style="font-size:18px" title="{
|
|
237
|
+
{f'<span class="material-symbols-outlined text-success" style="font-size:18px" title="{active_label}">check_circle</span>' if obj.is_active else f'<span class="material-symbols-outlined text-error" style="font-size:18px" title="{inactive_label}">cancel</span>'}
|
|
233
238
|
{role}
|
|
234
239
|
</div>
|
|
235
240
|
|
|
@@ -529,7 +529,8 @@ class BaseModel(SoftDeleteModel):
|
|
|
529
529
|
verbose_name = self._meta.verbose_name
|
|
530
530
|
obj_link = self.path()
|
|
531
531
|
if action == 'create':
|
|
532
|
-
|
|
532
|
+
new_label, created_by_label = _("New"), _("has been created by user")
|
|
533
|
+
title = f'{new_label} {verbose_name} "{self}" {created_by_label} "{self.created_by}"'
|
|
533
534
|
content_html = format_html(
|
|
534
535
|
'<p>{} {} <strong>{} ({})</strong> {} <strong>{}</strong></p>',
|
|
535
536
|
_('New'), verbose_name, self, self.pk, _('has been created by user'), self.created_by,
|
|
@@ -538,14 +539,15 @@ class BaseModel(SoftDeleteModel):
|
|
|
538
539
|
content_html += format_html('<p>{} <a href="{}">{}</a>.</p>', _('You can view it at'), obj_link, _('this link'))
|
|
539
540
|
changed_data = []
|
|
540
541
|
else:
|
|
541
|
-
|
|
542
|
+
update_label, updated_by_label = _("Update"), _("has been updated by user")
|
|
543
|
+
title = f'{update_label} {verbose_name} "{self}" ({self.pk}) {updated_by_label} "{self.updated_by}"'
|
|
542
544
|
changed_data = [
|
|
543
545
|
{'field_name': name, 'old_value': old_value, 'new_value': new_value}
|
|
544
546
|
for name, old_value, new_value in fields_changed
|
|
545
547
|
]
|
|
546
548
|
content_html = format_html(
|
|
547
|
-
'{} <strong>{}</strong> ({})
|
|
548
|
-
verbose_name, self, self.pk, self.updated_by,
|
|
549
|
+
'{} <strong>{}</strong> ({}) {} "{}" {}: <ul>{}</ul>',
|
|
550
|
+
verbose_name, self, self.pk, updated_by_label, self.updated_by, _('with the following changes'),
|
|
549
551
|
format_html_join('', '<li>{}: {} -> {}</li>', fields_changed),
|
|
550
552
|
)
|
|
551
553
|
Notification.notify_superusers(
|
|
@@ -621,12 +623,14 @@ def _base_model_m2m_changed(sender, instance, action, pk_set, model, **kwargs):
|
|
|
621
623
|
added_set = set(pk_list)
|
|
622
624
|
old_value = [lbl for lbl in current_labels if not any(f"({pk})" in lbl for pk in added_set)]
|
|
623
625
|
new_value = current_labels
|
|
624
|
-
|
|
626
|
+
added_label = _('added')
|
|
627
|
+
desc = f"{added_label}: {added_labels}"
|
|
625
628
|
elif action == 'post_remove':
|
|
626
629
|
removed_labels = _resolve_labels(pk_list)
|
|
627
630
|
old_value = current_labels + removed_labels
|
|
628
631
|
new_value = current_labels
|
|
629
|
-
|
|
632
|
+
removed_label = _('removed')
|
|
633
|
+
desc = f"{removed_label}: {removed_labels}"
|
|
630
634
|
else: # post_clear
|
|
631
635
|
old_value = '(all)'
|
|
632
636
|
new_value = []
|
|
@@ -634,14 +638,15 @@ def _base_model_m2m_changed(sender, instance, action, pk_set, model, **kwargs):
|
|
|
634
638
|
|
|
635
639
|
changed_data = [{'field_name': field_name, 'old_value': old_value, 'new_value': new_value}]
|
|
636
640
|
|
|
641
|
+
update_label, updated_by_label = _("Update"), _("has been updated by user")
|
|
637
642
|
title = (
|
|
638
|
-
f'{
|
|
639
|
-
f'{
|
|
643
|
+
f'{update_label} {instance._meta.verbose_name} "{instance}" ({instance.pk}) '
|
|
644
|
+
f'{updated_by_label} "{user}"'
|
|
640
645
|
)
|
|
641
646
|
content_html = format_html(
|
|
642
|
-
'{} <strong>{}</strong> ({})
|
|
643
|
-
|
|
644
|
-
|
|
647
|
+
'{} <strong>{}</strong> ({}) {} "{}" {}: <ul><li>{}: {}</li></ul>',
|
|
648
|
+
instance._meta.verbose_name, instance, instance.pk, updated_by_label, user,
|
|
649
|
+
_('with the following changes'), field_verbose, desc,
|
|
645
650
|
)
|
|
646
651
|
try:
|
|
647
652
|
Notification.notify_superusers(
|
{django_whiteneuron-0.4.0.0 → django_whiteneuron-0.4.0.2}/whiteneuron/base/tests_regressions.py
RENAMED
|
@@ -6,6 +6,7 @@ from django.contrib.admin.sites import AdminSite
|
|
|
6
6
|
from django.contrib.auth import get_user_model
|
|
7
7
|
from django.test import RequestFactory, TestCase, override_settings
|
|
8
8
|
from django.urls import reverse
|
|
9
|
+
from django.utils import translation
|
|
9
10
|
|
|
10
11
|
from whiteneuron.base.middleware import get_client_ip
|
|
11
12
|
from whiteneuron.base.testing import package_test_settings
|
|
@@ -369,3 +370,94 @@ class AdminTemplateRegressionTests(TestCase):
|
|
|
369
370
|
self.assertEqual(page.context["cl"].page_num, 1)
|
|
370
371
|
self.assertContains(page, 'aria-disabled="true">Previous', count=1)
|
|
371
372
|
self.assertContains(page, 'p=2" class="ui-btn')
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class I18nCatalogRegressionTests(TestCase):
|
|
376
|
+
"""makemessages can't read _() inside f-strings: such strings silently dropped out of
|
|
377
|
+
the Vietnamese catalog in 0.4.0.0 (marked obsolete, so missing from django.mo)."""
|
|
378
|
+
|
|
379
|
+
GETTEXT_FUNCS = {"_", "gettext", "gettext_lazy", "pgettext", "pgettext_lazy", "ngettext", "ngettext_lazy"}
|
|
380
|
+
|
|
381
|
+
@staticmethod
|
|
382
|
+
def _python_files():
|
|
383
|
+
import pathlib
|
|
384
|
+
root = pathlib.Path(__file__).resolve().parents[1]
|
|
385
|
+
for path in root.rglob("*.py"):
|
|
386
|
+
if "migrations" in path.parts or path.name.startswith("tests"):
|
|
387
|
+
continue
|
|
388
|
+
yield root, path
|
|
389
|
+
|
|
390
|
+
def _func_name(self, call):
|
|
391
|
+
return getattr(call.func, "id", None) or getattr(call.func, "attr", None)
|
|
392
|
+
|
|
393
|
+
def test_no_gettext_inside_fstrings(self):
|
|
394
|
+
import ast
|
|
395
|
+
found = []
|
|
396
|
+
for root, path in self._python_files():
|
|
397
|
+
for node in ast.walk(ast.parse(path.read_text())):
|
|
398
|
+
if isinstance(node, ast.JoinedStr):
|
|
399
|
+
for call in ast.walk(node):
|
|
400
|
+
if isinstance(call, ast.Call) and self._func_name(call) in self.GETTEXT_FUNCS:
|
|
401
|
+
found.append(f"{path.relative_to(root)}:{call.lineno}")
|
|
402
|
+
self.assertEqual(found, [], "move these _() calls out of the f-strings so makemessages sees them")
|
|
403
|
+
|
|
404
|
+
def test_python_msgids_are_in_the_vietnamese_catalog(self):
|
|
405
|
+
import ast
|
|
406
|
+
import gettext
|
|
407
|
+
import pathlib
|
|
408
|
+
|
|
409
|
+
mo = pathlib.Path(__file__).resolve().parents[1] / "locale" / "vi" / "LC_MESSAGES" / "django.mo"
|
|
410
|
+
with mo.open("rb") as fh:
|
|
411
|
+
catalog = gettext.GNUTranslations(fh)._catalog
|
|
412
|
+
missing = {}
|
|
413
|
+
for root, path in self._python_files():
|
|
414
|
+
for call in ast.walk(ast.parse(path.read_text())):
|
|
415
|
+
if (isinstance(call, ast.Call) and self._func_name(call) in {"_", "gettext", "gettext_lazy"}
|
|
416
|
+
and call.args and isinstance(call.args[0], ast.Constant)
|
|
417
|
+
and isinstance(call.args[0].value, str) and call.args[0].value):
|
|
418
|
+
msgid = call.args[0].value
|
|
419
|
+
if msgid not in catalog:
|
|
420
|
+
with translation.override("vi"):
|
|
421
|
+
if str(translation.gettext(msgid)) == msgid: # not covered by Django/Unfold either
|
|
422
|
+
missing.setdefault(msgid, f"{path.relative_to(root)}:{call.lineno}")
|
|
423
|
+
self.assertEqual(missing, {}, "run scripts/makemessages.sh, translate, then scripts/compilemessages.sh")
|
|
424
|
+
|
|
425
|
+
def test_dashboard_kpi_is_fully_translated(self):
|
|
426
|
+
from whiteneuron.dashboard.views import dashboard_callback
|
|
427
|
+
|
|
428
|
+
request = RequestFactory().get("/admin/", {"time": "today"})
|
|
429
|
+
with translation.override("vi"):
|
|
430
|
+
kpi = dashboard_callback(request, {})["kpi"][0]
|
|
431
|
+
self.assertIn("trong", kpi["title"])
|
|
432
|
+
self.assertIn("tiến trình từ lần cuối", str(kpi["footer"]))
|
|
433
|
+
self.assertIn("ngày", str(kpi["footer"]))
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class UiModalCssRegressionTests(TestCase):
|
|
437
|
+
def test_ui_modal_overrides_unfold_dialog_rule(self):
|
|
438
|
+
# Unfold's unlayered `dialog {...}` beat daisyUI's layered .ui-modal (narrow white
|
|
439
|
+
# column, no overlay); styles.css must carry the dialog.ui-modal override.
|
|
440
|
+
import pathlib
|
|
441
|
+
|
|
442
|
+
css = (pathlib.Path(__file__).resolve().parents[1] / "static" / "base" / "css" / "styles.css").read_text()
|
|
443
|
+
rule = css[css.index("dialog.ui-modal{"):]
|
|
444
|
+
rule = rule[: rule.index("}")]
|
|
445
|
+
for decl in ("position:fixed", "inset:0", "translate:none", "width:100%", "padding:0"):
|
|
446
|
+
self.assertIn(decl, rule)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class AnnouncementContentTests(TestCase):
|
|
450
|
+
def _get(self):
|
|
451
|
+
from whiteneuron.base.views import get_announcement_content
|
|
452
|
+
return get_announcement_content(RequestFactory().get("/announcement/"))
|
|
453
|
+
|
|
454
|
+
@override_settings(ANNOUNCEMENT_CONTENT_HTML_FILE="does-not-exist/announcement.html")
|
|
455
|
+
def test_missing_template_returns_empty_content(self):
|
|
456
|
+
with self.assertLogs("whiteneuron.base.views", level="WARNING"):
|
|
457
|
+
response = self._get()
|
|
458
|
+
self.assertEqual((response.status_code, response.content), (200, b""))
|
|
459
|
+
|
|
460
|
+
@override_settings(ANNOUNCEMENT_CONTENT_HTML_FILE=None)
|
|
461
|
+
def test_unset_setting_returns_empty_content(self):
|
|
462
|
+
response = self._get() # used to return None (500)
|
|
463
|
+
self.assertEqual((response.status_code, response.content), (200, b""))
|
|
@@ -43,12 +43,20 @@ class GuestLoginView(View):
|
|
|
43
43
|
return redirect('admin:index')
|
|
44
44
|
|
|
45
45
|
def get_announcement_content(request):
|
|
46
|
+
"""HTML for the announcement dialog. The template is project-provided
|
|
47
|
+
(ANNOUNCEMENT_CONTENT_HTML_FILE): without it the dialog stays empty instead of a 500."""
|
|
46
48
|
from django.conf import settings
|
|
47
|
-
content_file= getattr(settings, 'ANNOUNCEMENT_CONTENT_HTML_FILE', None)
|
|
48
|
-
if not content_file:
|
|
49
|
-
return None
|
|
50
|
-
content = render_to_string(content_file, request=request)
|
|
51
49
|
from django.http import HttpResponse
|
|
50
|
+
from django.template import TemplateDoesNotExist
|
|
51
|
+
|
|
52
|
+
content_file = getattr(settings, 'ANNOUNCEMENT_CONTENT_HTML_FILE', None)
|
|
53
|
+
if not content_file:
|
|
54
|
+
return HttpResponse("")
|
|
55
|
+
try:
|
|
56
|
+
content = render_to_string(str(content_file), request=request)
|
|
57
|
+
except TemplateDoesNotExist:
|
|
58
|
+
logger.warning("Announcement template %r not found (ANNOUNCEMENT_CONTENT_HTML_FILE)", content_file)
|
|
59
|
+
return HttpResponse("")
|
|
52
60
|
return HttpResponse(content)
|
|
53
61
|
|
|
54
62
|
|
|
@@ -2,6 +2,7 @@ from django.shortcuts import render
|
|
|
2
2
|
from django.utils.translation import gettext as _
|
|
3
3
|
from django.contrib import messages
|
|
4
4
|
from django.template.loader import render_to_string
|
|
5
|
+
from django.utils.html import format_html
|
|
5
6
|
from django.utils.safestring import mark_safe
|
|
6
7
|
import random
|
|
7
8
|
import json
|
|
@@ -138,17 +139,27 @@ def dashboard_callback(request, context):
|
|
|
138
139
|
_28_days = [_(WEEKDAYS[d]) for d in stats["weekdays"]]
|
|
139
140
|
|
|
140
141
|
def render_kpi(label, object_name, value, last_value, time, success=None):
|
|
141
|
-
|
|
142
|
+
# Whole translatable sentences with named placeholders (word order differs per
|
|
143
|
+
# language; makemessages can't see _() inside f-strings)
|
|
142
144
|
diff = value - last_value
|
|
143
145
|
diff_percent = diff / last_value * 100 if last_value else 0
|
|
144
|
-
|
|
146
|
+
period = MAPTIME[time] if time != "today" else _("day")
|
|
147
|
+
footer = format_html(
|
|
148
|
+
'<strong class="{} font-medium">{}{} ({}%)</strong> {} - {}',
|
|
149
|
+
"text-green-600" if diff >= 0 else "text-red-600",
|
|
150
|
+
"+" if diff >= 0 else "-", abs(diff), f"{diff_percent:.2f}",
|
|
151
|
+
_("progress from last"), period,
|
|
152
|
+
)
|
|
145
153
|
rate = round(success / value * 100, 1) if value and success else None
|
|
146
154
|
return {
|
|
147
155
|
"label": label,
|
|
148
|
-
"title":
|
|
156
|
+
"title": _("%(object)s in %(period)s") % {"object": object_name, "period": MAPTIME[time]},
|
|
149
157
|
"metric_total": value,
|
|
150
|
-
"metric_success":
|
|
151
|
-
|
|
158
|
+
"metric_success": (
|
|
159
|
+
_("%(count)s successful (%(rate)s%%)") % {"count": success, "rate": rate}
|
|
160
|
+
if rate is not None else None
|
|
161
|
+
),
|
|
162
|
+
"footer": footer,
|
|
152
163
|
}
|
|
153
164
|
|
|
154
165
|
|
|
Binary file
|