accrete 0.0.35__tar.gz → 0.0.37__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.
- {accrete-0.0.35 → accrete-0.0.37}/PKG-INFO +1 -1
- accrete-0.0.37/accrete/annotation.py +46 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/__init__.py +12 -3
- accrete-0.0.37/accrete/contrib/ui/context.py +218 -0
- accrete-0.0.35/accrete/contrib/ui/components.py → accrete-0.0.37/accrete/contrib/ui/elements.py +30 -48
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/filter.py +110 -44
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/css/accrete.css +128 -128
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/css/accrete.css.map +1 -1
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/css/accrete.scss +9 -9
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/js/filter.js +24 -0
- accrete-0.0.37/accrete/contrib/ui/static/js/htmx.min.js +1 -0
- accrete-0.0.37/accrete/contrib/ui/templates/ui/layout.html +178 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/list.html +3 -3
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/filter.html +29 -5
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/header.html +7 -7
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/pagination_detail.html +3 -3
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/pagination_list.html +4 -4
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/table.html +18 -13
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templatetags/accrete_ui.py +12 -1
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/forms.py +0 -4
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/templates/user/login.html +6 -12
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/views.py +31 -21
- {accrete-0.0.35 → accrete-0.0.37}/accrete/middleware.py +15 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/models.py +9 -7
- {accrete-0.0.35 → accrete-0.0.37}/accrete/querystring.py +11 -8
- accrete-0.0.37/accrete/utils/models.py +14 -0
- {accrete-0.0.35 → accrete-0.0.37}/pyproject.toml +1 -1
- accrete-0.0.35/accrete/contrib/ui/context.py +0 -291
- accrete-0.0.35/accrete/contrib/ui/elements.py +0 -2
- accrete-0.0.35/accrete/contrib/ui/querystring.py +0 -19
- accrete-0.0.35/accrete/contrib/ui/templates/ui/layout.html +0 -173
- {accrete-0.0.35 → accrete-0.0.37}/.gitignore +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/LICENSE +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/README.md +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/admin.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/apps.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/config.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/admin.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/apps.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/forms.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/migrations/0001_initial.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/migrations/0002_alter_sequence_name.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/migrations/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/models.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/queries.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/tests.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/sequence/views.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/admin.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/apps.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/forms.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/migrations/0001_initial.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/migrations/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/models.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/tasks.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/tests.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/system_mail/views.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/admin.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/apps.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/migrations/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/LICENSE +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/README.md +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/bulma.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/css/bulma-rtl.css +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/css/bulma-rtl.css.map +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/css/bulma-rtl.min.css +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/css/bulma.css +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/css/bulma.css.map +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/css/bulma.min.css +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/package.json +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/base/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/base/animations.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/base/generic.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/base/helpers.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/base/minireset.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/breadcrumb.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/card.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/dropdown.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/level.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/media.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/menu.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/message.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/modal.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/navbar.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/pagination.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/panel.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/components/tabs.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/box.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/button.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/container.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/content.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/form.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/icon.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/image.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/notification.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/other.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/progress.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/table.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/tag.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/elements/title.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/form/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/form/file.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/form/input-textarea.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/form/select.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/form/shared.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/form/tools.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/grid/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/grid/columns.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/grid/tiles.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/color.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/flexbox.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/float.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/other.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/overflow.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/position.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/spacing.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/typography.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/helpers/visibility.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/layout/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/layout/footer.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/layout/hero.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/layout/section.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/_all.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/animations.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/controls.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/derived-variables.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/extends.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/functions.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/bulma/sass/utilities/mixins.sass +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/css/accrete.css.bak +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/css/icons.css +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/icons/Logo.svg +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/icons/accrete.svg +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/static/js/list.js +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/django/forms/widgets/attrs.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/django/forms/widgets/email.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/django/forms/widgets/file.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/django/forms/widgets/input.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/django/forms/widgets/select.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/django/forms/widgets/text.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/django/forms/widgets/textarea.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/detail.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/form.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/form_errors.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/form_modal.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/onchange_form.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/table_field.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/table_field_float.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/table_field_monetary.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templates/ui/partials/table_field_string.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/templatetags/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/tests.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/urls.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/ui/views.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/admin.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/apps.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/locale/de/LC_MESSAGES/django.mo +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/locale/de/LC_MESSAGES/django.po +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/middleware.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/migrations/0001_initial.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/migrations/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/models.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/templates/user/accrete_navbar_end_dropdown.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/templates/user/change_email.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/templates/user/change_password.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/templates/user/user_detail.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/templates/user/user_form.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/tests.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user/urls.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/admin.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/apps.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/config.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/forms.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/migrations/0001_initial.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/migrations/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/models.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/templates/user_registration/mail_templates/confirmation_mail.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/templates/user_registration/registration.html +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/tests.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/urls.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/contrib/user_registration/views.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/forms.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/migrations/0001_initial.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/migrations/0002_initial.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/migrations/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/queries.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/tenant.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/tests.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/utils/__init__.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/utils/dates.py +0 -0
- {accrete-0.0.35 → accrete-0.0.37}/accrete/views.py +0 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
from django.db.models import Field, QuerySet
|
2
|
+
from django.db.models.expressions import Func
|
3
|
+
from django.db.models.aggregates import Aggregate
|
4
|
+
|
5
|
+
|
6
|
+
class Annotation:
|
7
|
+
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
verbose_name: str,
|
11
|
+
field: type[Field],
|
12
|
+
function: type[Func] | type[Aggregate],
|
13
|
+
help_text: str = '',
|
14
|
+
**kwargs
|
15
|
+
):
|
16
|
+
self.verbose_name = verbose_name or self
|
17
|
+
self.field = field
|
18
|
+
self.function = function
|
19
|
+
self.help_text = help_text
|
20
|
+
self.__dict__.update(kwargs)
|
21
|
+
|
22
|
+
|
23
|
+
class AnnotationModelMixin:
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def get_annotations(cls) -> list[dict]:
|
27
|
+
return list({'name': a, 'annotation': getattr(cls, a)} for a in filter(
|
28
|
+
lambda a:
|
29
|
+
not a.startswith('__')
|
30
|
+
and isinstance(getattr(cls, a), Annotation),
|
31
|
+
cls.__dict__
|
32
|
+
))
|
33
|
+
|
34
|
+
|
35
|
+
class AnnotationManagerMixin:
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def add_annotations(queryset: QuerySet):
|
39
|
+
model = queryset.model
|
40
|
+
if not hasattr(model, 'get_annotations'):
|
41
|
+
return queryset
|
42
|
+
return queryset.annotate(**{
|
43
|
+
annotation['name']: annotation['annotation'].function
|
44
|
+
for annotation in model.get_annotations()
|
45
|
+
})
|
46
|
+
|
@@ -1,10 +1,20 @@
|
|
1
1
|
from .filter import Filter
|
2
2
|
from .context import (
|
3
|
-
ListContext,
|
4
3
|
DetailContext,
|
4
|
+
TableContext,
|
5
|
+
ListContext,
|
5
6
|
FormContext,
|
7
|
+
form_actions,
|
8
|
+
list_page,
|
9
|
+
detail_page,
|
10
|
+
cast_param,
|
11
|
+
prepare_url_params,
|
12
|
+
extract_url_params,
|
13
|
+
exclude_params,
|
14
|
+
url_param_str,
|
15
|
+
get_table_fields
|
6
16
|
)
|
7
|
-
from .
|
17
|
+
from .elements import (
|
8
18
|
ClientAction,
|
9
19
|
ActionMethod,
|
10
20
|
BreadCrumb,
|
@@ -13,4 +23,3 @@ from .components import (
|
|
13
23
|
TableFieldType,
|
14
24
|
Icon
|
15
25
|
)
|
16
|
-
from .querystring import load_querystring, build_querystring
|
@@ -0,0 +1,218 @@
|
|
1
|
+
import logging
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
from typing import TypedDict
|
4
|
+
from django.utils.translation import gettext_lazy as _t
|
5
|
+
from django.db.models import Model, QuerySet
|
6
|
+
from django.core.paginator import Paginator
|
7
|
+
from django.core import paginator
|
8
|
+
from django.forms import Form, ModelForm
|
9
|
+
from accrete.utils.models import get_related_model
|
10
|
+
from .elements import ClientAction, BreadCrumb, TableField, TableFieldType
|
11
|
+
from .filter import Filter
|
12
|
+
from ...annotation import Annotation
|
13
|
+
|
14
|
+
_logger = logging.getLogger(__name__)
|
15
|
+
|
16
|
+
DEFAULT_PAGINATE_BY = 40
|
17
|
+
|
18
|
+
|
19
|
+
class DetailPagination(TypedDict):
|
20
|
+
previous_object_url: str
|
21
|
+
next_object_url: str
|
22
|
+
current_object_idx: int
|
23
|
+
total_objects: int
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass(kw_only=True)
|
27
|
+
class Context:
|
28
|
+
|
29
|
+
title: str = ''
|
30
|
+
breadcrumbs: list[BreadCrumb] = field(default_factory=list)
|
31
|
+
actions: list[ClientAction] = field(default_factory=list)
|
32
|
+
kwargs: dict = field(default_factory=dict)
|
33
|
+
|
34
|
+
def __post_init__(self):
|
35
|
+
for key, value in self.kwargs.items():
|
36
|
+
setattr(self, key, value)
|
37
|
+
|
38
|
+
def dict(self):
|
39
|
+
return {
|
40
|
+
attr: getattr(self, attr, '') for attr
|
41
|
+
in filter(lambda x: not x.startswith('_'), self.__dict__)
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
@dataclass
|
46
|
+
class TableContext(Context):
|
47
|
+
|
48
|
+
object_label: str
|
49
|
+
fields: list[TableField]
|
50
|
+
list_page: paginator.Page
|
51
|
+
pagination_param_str: str
|
52
|
+
endless_scroll: bool
|
53
|
+
filter: Filter
|
54
|
+
object_param_str: str = field(default='', kw_only=True)
|
55
|
+
|
56
|
+
|
57
|
+
@dataclass
|
58
|
+
class ListContext(Context):
|
59
|
+
|
60
|
+
object_label: str
|
61
|
+
object_param_str: str
|
62
|
+
list_page: paginator.Page
|
63
|
+
pagination_param_str: str
|
64
|
+
filter: Filter
|
65
|
+
endless_scroll: bool = True
|
66
|
+
column_height: int = 4
|
67
|
+
column_width: int = 12
|
68
|
+
|
69
|
+
def __post_init__(self):
|
70
|
+
super().__post_init__()
|
71
|
+
if self.column_width not in range(1, 13):
|
72
|
+
_logger.warning(
|
73
|
+
'ListContext parameter column_width should be in range 1 - 12'
|
74
|
+
)
|
75
|
+
|
76
|
+
|
77
|
+
class DetailContext(TypedDict, total=False):
|
78
|
+
|
79
|
+
title: str
|
80
|
+
object: Model
|
81
|
+
breadcrumbs: list[BreadCrumb]
|
82
|
+
detail_page: DetailPagination
|
83
|
+
pagination_param_str: str
|
84
|
+
actions: list[ClientAction]
|
85
|
+
|
86
|
+
|
87
|
+
class FormContext(TypedDict, total=False):
|
88
|
+
|
89
|
+
title: str
|
90
|
+
breadcrumbs: list[BreadCrumb]
|
91
|
+
form: Form | ModelForm
|
92
|
+
form_id: str
|
93
|
+
actions: list[ClientAction]
|
94
|
+
|
95
|
+
|
96
|
+
def cast_param(params: dict, param: str, cast_to: callable, default):
|
97
|
+
if param not in params:
|
98
|
+
return default
|
99
|
+
try:
|
100
|
+
return cast_to(params.get(param, default))
|
101
|
+
except Exception as e:
|
102
|
+
_logger.exception(e)
|
103
|
+
return default
|
104
|
+
|
105
|
+
|
106
|
+
def prepare_url_params(get_params: dict) -> dict:
|
107
|
+
return {key: f'&{key}={value}' for key, value in get_params.items()}
|
108
|
+
|
109
|
+
|
110
|
+
def url_param_str(params: dict, extract: list[str] = None) -> str:
|
111
|
+
"""
|
112
|
+
Return a URL Querystring from the given parameters
|
113
|
+
If extract is supplied, extract the value from the dictionary and prepare
|
114
|
+
them, so that each value is formatted e.g. {'page': '&page=1'}
|
115
|
+
"""
|
116
|
+
if extract:
|
117
|
+
params = prepare_url_params(extract_url_params(params, extract))
|
118
|
+
param_str = (
|
119
|
+
"".join(str(value) for value in params.values())
|
120
|
+
.replace('&&', '&')
|
121
|
+
.replace('?&', '?')
|
122
|
+
.strip('?&')
|
123
|
+
)
|
124
|
+
return f'?{param_str}'
|
125
|
+
|
126
|
+
|
127
|
+
def extract_url_params(params: dict, keys: list[str]) -> dict:
|
128
|
+
return {key: params[key] for key in keys if key in params}
|
129
|
+
|
130
|
+
|
131
|
+
def exclude_params(params: dict, keys: list[str]) -> dict:
|
132
|
+
return {key: val for key, val in params.items() if key not in keys}
|
133
|
+
|
134
|
+
|
135
|
+
def list_page(queryset: QuerySet, paginate_by: int, page_number: int) -> paginator.Page:
|
136
|
+
pages = Paginator(queryset, per_page=paginate_by)
|
137
|
+
return pages.page(page_number <= pages.num_pages and page_number or pages.num_pages)
|
138
|
+
|
139
|
+
|
140
|
+
def detail_page(queryset: QuerySet, obj: Model) -> dict:
|
141
|
+
if not hasattr(obj, 'get_absolute_url'):
|
142
|
+
_logger.warning(
|
143
|
+
'Detail pagination disabled for models without the '
|
144
|
+
'get_absolute_url attribute. Set paginate_by to 0 to '
|
145
|
+
'deactivate pagination.'
|
146
|
+
)
|
147
|
+
return {}
|
148
|
+
idx = (*queryset,).index(obj)
|
149
|
+
previous_object_url = (
|
150
|
+
queryset[idx - 1] if idx - 1 >= 0 else queryset.last()
|
151
|
+
).get_absolute_url()
|
152
|
+
next_object_url = (
|
153
|
+
queryset[idx + 1] if idx + 1 <= queryset.count() - 1 else queryset.first()
|
154
|
+
).get_absolute_url()
|
155
|
+
return {
|
156
|
+
'previous_object_url': previous_object_url,
|
157
|
+
'next_object_url': next_object_url,
|
158
|
+
'current_object_idx': idx + 1,
|
159
|
+
'total_objects': queryset.count()
|
160
|
+
}
|
161
|
+
|
162
|
+
|
163
|
+
def form_actions(discard_url: str, form_id: str = 'form') -> list[ClientAction]:
|
164
|
+
return [
|
165
|
+
ClientAction(
|
166
|
+
name=_t('Save'),
|
167
|
+
submit=True,
|
168
|
+
class_list=['is-success'],
|
169
|
+
form_id=form_id,
|
170
|
+
),
|
171
|
+
ClientAction(
|
172
|
+
name=_t('Discard'),
|
173
|
+
url=discard_url
|
174
|
+
)
|
175
|
+
]
|
176
|
+
|
177
|
+
|
178
|
+
def get_table_fields(
|
179
|
+
fields: list[str],
|
180
|
+
model: type[Model],
|
181
|
+
field_definition: dict[str | TableField] = None
|
182
|
+
) -> list[TableField]:
|
183
|
+
print(fields, model, field_definition)
|
184
|
+
|
185
|
+
if field_definition is None:
|
186
|
+
field_definition = {}
|
187
|
+
|
188
|
+
def get_field_definition(f_name: str) -> TableField:
|
189
|
+
if definition := field_definition.get(f_name):
|
190
|
+
return definition
|
191
|
+
if len(f_name.split('__')) == 1:
|
192
|
+
rel_model = model
|
193
|
+
else:
|
194
|
+
rel_model = get_related_model(model, f_name)
|
195
|
+
print(rel_model)
|
196
|
+
try:
|
197
|
+
return rel_model.table_field_definition[f_name]
|
198
|
+
except (AttributeError, KeyError):
|
199
|
+
attr = getattr(rel_model, f_name)
|
200
|
+
if isinstance(attr, Annotation):
|
201
|
+
attr = attr
|
202
|
+
elif hasattr(attr, 'field'):
|
203
|
+
attr = attr.field
|
204
|
+
return TableField(
|
205
|
+
label=str(attr.verbose_name),
|
206
|
+
name=f_name,
|
207
|
+
header_info=str(attr.help_text),
|
208
|
+
truncate_after=50
|
209
|
+
)
|
210
|
+
|
211
|
+
table_fields = []
|
212
|
+
for field_name in fields:
|
213
|
+
try:
|
214
|
+
table_fields.append(get_field_definition(field_name))
|
215
|
+
except AttributeError as e:
|
216
|
+
print(e)
|
217
|
+
pass
|
218
|
+
return table_fields
|
accrete-0.0.35/accrete/contrib/ui/components.py → accrete-0.0.37/accrete/contrib/ui/elements.py
RENAMED
@@ -1,8 +1,19 @@
|
|
1
|
-
from dataclasses import dataclass, field
|
2
1
|
from enum import Enum
|
3
|
-
from
|
2
|
+
from dataclasses import dataclass, field
|
4
3
|
|
5
|
-
|
4
|
+
|
5
|
+
class Icon(Enum):
|
6
|
+
|
7
|
+
ADD = 'icon-add'
|
8
|
+
EDIT = 'icon-edit'
|
9
|
+
LIST = 'icon-list'
|
10
|
+
OPEN_RELATED = 'icon-open-related'
|
11
|
+
ENVELOPE = 'icon-envelope'
|
12
|
+
CLEAR = 'icon-clear'
|
13
|
+
BACKSPACE = 'icon-backspace'
|
14
|
+
FILTER = 'icon-filter'
|
15
|
+
DELETE_FILTER = 'icon-delete-filter'
|
16
|
+
SELECT = 'icon-select'
|
6
17
|
|
7
18
|
|
8
19
|
class ActionMethod(Enum):
|
@@ -14,6 +25,22 @@ class ActionMethod(Enum):
|
|
14
25
|
DELETE = 'hx-delete'
|
15
26
|
|
16
27
|
|
28
|
+
@dataclass
|
29
|
+
class ClientAction:
|
30
|
+
|
31
|
+
name: str
|
32
|
+
url: str = ''
|
33
|
+
method: ActionMethod = ActionMethod.HREF
|
34
|
+
attrs: list[tuple[str, str]] = field(default_factory=list)
|
35
|
+
submit: bool = False
|
36
|
+
form_id: str = 'form'
|
37
|
+
class_list: list[str] = field(default_factory=list)
|
38
|
+
icon: Icon | type[Enum] = None
|
39
|
+
|
40
|
+
def attrs_str(self):
|
41
|
+
return ' '.join([f'{str(attr[0])}={str(attr[1])}' for attr in self.attrs])
|
42
|
+
|
43
|
+
|
17
44
|
class TableFieldAlignment(Enum):
|
18
45
|
|
19
46
|
LEFT = 'left'
|
@@ -49,48 +76,3 @@ class BreadCrumb:
|
|
49
76
|
|
50
77
|
name: str
|
51
78
|
url: str
|
52
|
-
add_url_params: bool = True
|
53
|
-
|
54
|
-
|
55
|
-
class Icon(Enum):
|
56
|
-
|
57
|
-
ADD = 'icon-add'
|
58
|
-
EDIT = 'icon-edit'
|
59
|
-
LIST = 'icon-list'
|
60
|
-
OPEN_RELATED = 'icon-open-related'
|
61
|
-
ENVELOPE = 'icon-envelope'
|
62
|
-
CLEAR = 'icon-clear'
|
63
|
-
BACKSPACE = 'icon-backspace'
|
64
|
-
FILTER = 'icon-filter'
|
65
|
-
DELETE_FILTER = 'icon-delete-filter'
|
66
|
-
SELECT = 'icon-select'
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
@dataclass
|
71
|
-
class ClientAction:
|
72
|
-
|
73
|
-
name: str
|
74
|
-
url: str = ''
|
75
|
-
method: ActionMethod = ActionMethod.HREF
|
76
|
-
query_params: str = ''
|
77
|
-
attrs: list[tuple[str, str]] = field(default_factory=list)
|
78
|
-
submit: bool = False
|
79
|
-
form_id: str = 'form'
|
80
|
-
class_list: list[str] = field(default_factory=list)
|
81
|
-
add_url_params: bool = False
|
82
|
-
icon: Icon | type[Enum] = None
|
83
|
-
|
84
|
-
def attrs_str(self):
|
85
|
-
return ' '.join([f'{str(attr[0])}={str(attr[1])}' for attr in self.attrs])
|
86
|
-
|
87
|
-
|
88
|
-
@dataclass
|
89
|
-
class List:
|
90
|
-
|
91
|
-
queryset: QuerySet
|
92
|
-
title: str = None
|
93
|
-
paginate_by: int = DEFAULT_PAGINATE_BY
|
94
|
-
breadcrumbs: list[BreadCrumb] = field(default_factory=list)
|
95
|
-
actions: list[ClientAction] = field(default_factory=list)
|
96
|
-
fields: list[TableField] = field(default_factory=list)
|
@@ -1,8 +1,12 @@
|
|
1
1
|
import time
|
2
2
|
import logging
|
3
|
+
from django.db import models
|
3
4
|
from django.core.cache import cache
|
4
5
|
from django.utils.translation import gettext_lazy as _
|
5
6
|
from django.utils.safestring import mark_safe
|
7
|
+
from django.utils.translation import get_language
|
8
|
+
|
9
|
+
from accrete.annotation import Annotation
|
6
10
|
|
7
11
|
_logger = logging.getLogger(__name__)
|
8
12
|
|
@@ -22,11 +26,15 @@ class Filter:
|
|
22
26
|
LABEL_SET = _('Is Set')
|
23
27
|
LABEL_NOT_SET = _('Is Not Set')
|
24
28
|
|
25
|
-
def __init__(
|
29
|
+
def __init__(
|
30
|
+
self, model, query_relation_depth: int = 4,
|
31
|
+
default_exclude: list[str] = None, default_filter_term: str = None
|
32
|
+
):
|
26
33
|
self.model = model
|
27
34
|
self.query_relation_depth = query_relation_depth
|
28
35
|
self.default_exclude = default_exclude or ['tenant', 'user']
|
29
|
-
self.
|
36
|
+
self.default_filter_term = default_filter_term or ''
|
37
|
+
self.fields = []
|
30
38
|
self.field_paths = []
|
31
39
|
|
32
40
|
@staticmethod
|
@@ -39,8 +47,8 @@ class Filter:
|
|
39
47
|
def get_fields(self, model_path: list, field_path: str):
|
40
48
|
fields = self.get_local_fields(model_path[-1], field_path)
|
41
49
|
if len(model_path) <= self.query_relation_depth:
|
42
|
-
fields.
|
43
|
-
return fields
|
50
|
+
fields.extend(self.get_relation_fields(model_path, field_path))
|
51
|
+
return sorted(fields, key=lambda x: x['label'].lower())
|
44
52
|
|
45
53
|
def get_relation_fields(self, model_path, field_path):
|
46
54
|
filter_exclude = getattr(model_path[-1], 'filter_exclude', [])
|
@@ -49,7 +57,7 @@ class Filter:
|
|
49
57
|
lambda x: x.is_relation and x.name not in filter_exclude,
|
50
58
|
model_path[-1]._meta.get_fields()
|
51
59
|
)
|
52
|
-
res =
|
60
|
+
res = []
|
53
61
|
for field in fields:
|
54
62
|
if field.related_model in model_path:
|
55
63
|
continue
|
@@ -60,13 +68,14 @@ class Filter:
|
|
60
68
|
label = field.verbose_name
|
61
69
|
except AttributeError:
|
62
70
|
label = field.related_model._meta.verbose_name
|
63
|
-
res
|
64
|
-
'
|
71
|
+
res.append({
|
72
|
+
'name': field.name,
|
73
|
+
'label': str(label),
|
65
74
|
'type': field.get_internal_type(),
|
66
75
|
'null': field.null,
|
67
76
|
'choices': [],
|
68
77
|
'fields': self.get_fields(model_path_copy, rel_path)
|
69
|
-
}
|
78
|
+
})
|
70
79
|
return res
|
71
80
|
|
72
81
|
def get_local_fields(self, model, path):
|
@@ -76,34 +85,39 @@ class Filter:
|
|
76
85
|
lambda x: not x.is_relation and x.name not in filter_exclude,
|
77
86
|
model._meta.get_fields()
|
78
87
|
)
|
79
|
-
res =
|
88
|
+
res = []
|
80
89
|
for field in fields:
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
field_path = f'{path}{"__" if path else ""}{field.name}'
|
91
|
+
self.field_paths.append(field_path)
|
92
|
+
step = (hasattr(field, 'decimal_places')
|
93
|
+
and self.cast_decimal_places_to_step(field.decimal_places)
|
94
|
+
or 1)
|
95
|
+
res.append({
|
96
|
+
'name': field_path,
|
97
|
+
'label': str(field.verbose_name),
|
98
|
+
'type': field.get_internal_type(),
|
99
|
+
'choices': field.choices or [],
|
100
|
+
'null': field.null,
|
101
|
+
'step': step
|
91
102
|
})
|
92
|
-
|
93
|
-
res
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
103
|
+
if not hasattr(model, 'get_annotations'):
|
104
|
+
return res
|
105
|
+
for annotation in model.get_annotations():
|
106
|
+
field_path = f'{path}{"__" if path else ""}{annotation["name"]}'
|
107
|
+
self.field_paths.append(field_path)
|
108
|
+
res.append({
|
109
|
+
'name': field_path,
|
110
|
+
'label': str(annotation['annotation'].verbose_name),
|
111
|
+
'type': annotation['annotation'].field.__name__,
|
112
|
+
'choices': [],
|
113
|
+
'null': False,
|
114
|
+
'step': getattr(annotation['annotation'], 'step', '1')
|
101
115
|
})
|
102
116
|
return res
|
103
117
|
|
104
118
|
def to_html(self):
|
105
119
|
start = time.time()
|
106
|
-
key = f'filter-
|
120
|
+
key = f'filter-{self.model.__module__}.{self.model.__name__}-{get_language()}'
|
107
121
|
html = cache.get(key)
|
108
122
|
if html:
|
109
123
|
_logger.info(f'Fetching Filter-Terms from cache in {time.time() - start} seconds')
|
@@ -111,22 +125,27 @@ class Filter:
|
|
111
125
|
if not self.fields:
|
112
126
|
self.fields = self.get_fields([self.model], '')
|
113
127
|
html = ''
|
114
|
-
for
|
115
|
-
html += self.field_params(
|
116
|
-
html =
|
128
|
+
for f in self.fields:
|
129
|
+
html += self.field_params(f)
|
130
|
+
html = {
|
131
|
+
'params': mark_safe(html.strip().replace('\n', '')),
|
132
|
+
'field_paths': mark_safe(
|
133
|
+
self.field_path_selection().strip().replace('\n', '')
|
134
|
+
)
|
135
|
+
}
|
117
136
|
cache.set(key, html, 60 * 15)
|
118
137
|
_logger.info(f'Generated Filter-Terms in {time.time() - start} seconds')
|
119
|
-
return html
|
138
|
+
return {'params': html['params'], 'field_paths': html['field_paths']}
|
120
139
|
|
121
|
-
def field_params(self,
|
140
|
+
def field_params(self, field):
|
122
141
|
params = ''
|
123
|
-
params += self.params(
|
124
|
-
for
|
125
|
-
params += self.field_params(
|
142
|
+
params += self.params(field)
|
143
|
+
for f in field.get('fields', []):
|
144
|
+
params += self.field_params(f)
|
126
145
|
return f"""
|
127
|
-
<div class="query-param" tabindex="-1" data-param="{
|
128
|
-
<p class="px-1 arrow">{
|
129
|
-
<div class="query-params is-hidden" data-param="{
|
146
|
+
<div class="query-param" tabindex="-1" data-param="{field['name']}" data-param-label="{field['label']}">
|
147
|
+
<p class="px-1 arrow">{field['label']}</p>
|
148
|
+
<div class="query-params is-hidden" data-param="{field['name']}">
|
130
149
|
{params}
|
131
150
|
</div>
|
132
151
|
</div>
|
@@ -154,8 +173,8 @@ class Filter:
|
|
154
173
|
for choice in choices
|
155
174
|
])
|
156
175
|
|
157
|
-
def params(self,
|
158
|
-
return self.field_map().get(
|
176
|
+
def params(self, field):
|
177
|
+
return self.field_map().get(field['type'], self.no_param)(field['name'], field)
|
159
178
|
|
160
179
|
def param(
|
161
180
|
self, key: str, value: dict, param: str, data_type: str,
|
@@ -191,7 +210,6 @@ class Filter:
|
|
191
210
|
html += param_div(inverted=True)
|
192
211
|
return html
|
193
212
|
|
194
|
-
|
195
213
|
def char_param(self, key, value):
|
196
214
|
if value.get('choices'):
|
197
215
|
return self.char_choice_param(key, value)
|
@@ -258,7 +276,10 @@ class Filter:
|
|
258
276
|
return ''
|
259
277
|
|
260
278
|
def null_param(self, key, value):
|
261
|
-
options = self.parse_choices([
|
279
|
+
options = self.parse_choices([
|
280
|
+
('true', _('True')),
|
281
|
+
('false', _('False'))
|
282
|
+
])
|
262
283
|
return f"""
|
263
284
|
<div id="filter-id-~{key}__isnull"
|
264
285
|
class="query-param" tabindex="-1" data-type="selection"
|
@@ -274,3 +295,48 @@ class Filter:
|
|
274
295
|
|
275
296
|
def no_param(self, key, value):
|
276
297
|
return ''
|
298
|
+
|
299
|
+
def field_path_selection(self):
|
300
|
+
html = ''
|
301
|
+
for field in sorted(self.field_paths, key=lambda f: (len(f.split('__')), f.lower())):
|
302
|
+
label = self.get_field_path_label(field)
|
303
|
+
if not label:
|
304
|
+
continue
|
305
|
+
html += f"""
|
306
|
+
<label class="checkbox my-1" style="width: 100%">
|
307
|
+
<input type="checkbox" name="{field}">
|
308
|
+
{label}
|
309
|
+
</label>
|
310
|
+
"""
|
311
|
+
return html
|
312
|
+
|
313
|
+
def get_field_path_label(self, field_path):
|
314
|
+
label_parts = []
|
315
|
+
model = self.model
|
316
|
+
for field_part in field_path.split('__'):
|
317
|
+
if field_part.startswith('_a_'):
|
318
|
+
field_part = field_part.removeprefix('_a_')
|
319
|
+
annotations = {
|
320
|
+
annotation['name']: annotation
|
321
|
+
for annotation in getattr(model, 'annotations', [])
|
322
|
+
}
|
323
|
+
if annotations.get(field_part):
|
324
|
+
label_parts.append(str(annotations[field_part]['label']))
|
325
|
+
return ' / '.join(label_parts)
|
326
|
+
return False
|
327
|
+
|
328
|
+
field = getattr(model, field_part, False)
|
329
|
+
|
330
|
+
if not field:
|
331
|
+
return False
|
332
|
+
|
333
|
+
if isinstance(field.field, models.ForeignKey):
|
334
|
+
model = field.field.related_model
|
335
|
+
label_parts.append(str(model._meta.verbose_name))
|
336
|
+
continue
|
337
|
+
|
338
|
+
if not isinstance(field, Annotation):
|
339
|
+
field = field.field
|
340
|
+
|
341
|
+
label_parts.append(str(field.verbose_name))
|
342
|
+
return ' / '.join(label_parts)
|