django-unfold 0.46.0__py3-none-any.whl → 0.48.0__py3-none-any.whl
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_unfold-0.46.0.dist-info → django_unfold-0.48.0.dist-info}/METADATA +5 -6
- {django_unfold-0.46.0.dist-info → django_unfold-0.48.0.dist-info}/RECORD +52 -43
- {django_unfold-0.46.0.dist-info → django_unfold-0.48.0.dist-info}/WHEEL +1 -1
- unfold/admin.py +15 -16
- unfold/checks.py +4 -4
- unfold/components.py +5 -5
- unfold/contrib/filters/admin/__init__.py +43 -0
- unfold/contrib/filters/admin/autocomplete_filters.py +16 -0
- unfold/contrib/filters/admin/datetime_filters.py +212 -0
- unfold/contrib/filters/admin/dropdown_filters.py +100 -0
- unfold/contrib/filters/admin/mixins.py +146 -0
- unfold/contrib/filters/admin/numeric_filters.py +196 -0
- unfold/contrib/filters/admin/text_filters.py +65 -0
- unfold/contrib/filters/admin.py +32 -32
- unfold/contrib/filters/forms.py +68 -17
- unfold/contrib/forms/widgets.py +9 -9
- unfold/contrib/inlines/checks.py +2 -4
- unfold/contrib/simple_history/templates/simple_history/object_history.html +17 -1
- unfold/contrib/simple_history/templates/simple_history/object_history_list.html +1 -1
- unfold/dataclasses.py +9 -2
- unfold/decorators.py +4 -3
- unfold/settings.py +4 -2
- unfold/sites.py +176 -140
- unfold/static/unfold/css/styles.css +1 -1
- unfold/static/unfold/js/app.js +2 -2
- unfold/templates/admin/app_index.html +1 -5
- unfold/templates/admin/base_site.html +1 -1
- unfold/templates/admin/filter.html +1 -1
- unfold/templates/admin/index.html +1 -5
- unfold/templates/admin/login.html +1 -1
- unfold/templates/admin/search_form.html +4 -2
- unfold/templates/unfold/helpers/account_links.html +1 -1
- unfold/templates/unfold/helpers/actions_row.html +1 -1
- unfold/templates/unfold/helpers/change_list_filter.html +2 -2
- unfold/templates/unfold/helpers/change_list_filter_actions.html +1 -1
- unfold/templates/unfold/helpers/header_back_button.html +2 -2
- unfold/templates/unfold/helpers/language_switch.html +1 -1
- unfold/templates/unfold/helpers/navigation_header.html +15 -5
- unfold/templates/unfold/helpers/site_branding.html +9 -0
- unfold/templates/unfold/helpers/site_dropdown.html +19 -0
- unfold/templates/unfold/helpers/site_icon.html +10 -2
- unfold/templates/unfold/helpers/tab_list.html +7 -1
- unfold/templates/unfold/helpers/theme_switch.html +1 -1
- unfold/templates/unfold/layouts/base.html +1 -5
- unfold/templates/unfold/layouts/skeleton.html +1 -1
- unfold/templatetags/unfold.py +55 -22
- unfold/templatetags/unfold_list.py +2 -2
- unfold/typing.py +5 -4
- unfold/utils.py +3 -2
- unfold/views.py +2 -2
- unfold/widgets.py +27 -27
- {django_unfold-0.46.0.dist-info → django_unfold-0.48.0.dist-info}/LICENSE.md +0 -0
unfold/static/unfold/js/app.js
CHANGED
@@ -78,9 +78,9 @@ const filterForm = () => {
|
|
78
78
|
}
|
79
79
|
|
80
80
|
filterForm.addEventListener("formdata", (event) => {
|
81
|
-
|
81
|
+
Array.from(event.formData.entries()).forEach(([key, value]) => {
|
82
82
|
if (value === "") event.formData.delete(key);
|
83
|
-
}
|
83
|
+
});
|
84
84
|
});
|
85
85
|
};
|
86
86
|
|
@@ -25,11 +25,7 @@
|
|
25
25
|
{% endif %}
|
26
26
|
|
27
27
|
{% block branding %}
|
28
|
-
|
29
|
-
<a href="{% url 'admin:index' %}">
|
30
|
-
{{ site_header|default:_('Django administration') }}
|
31
|
-
</a>
|
32
|
-
</h1>
|
28
|
+
{% include "unfold/helpers/site_branding.html" %}
|
33
29
|
{% endblock %}
|
34
30
|
|
35
31
|
{% block content %}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
4
4
|
|
5
5
|
{% block branding %}
|
6
|
-
|
6
|
+
{% include "unfold/helpers/site_branding.html" %}
|
7
7
|
{% endblock %}
|
8
8
|
|
9
9
|
{% block nav-global %}{% endblock %}
|
@@ -12,7 +12,7 @@
|
|
12
12
|
{% endfor %}
|
13
13
|
|
14
14
|
{% if spec|class_name == "BooleanFieldListFilter" %}
|
15
|
-
<ul class="
|
15
|
+
<ul class="dark:bg-base-900 border border-base-200 flex min-w-20 rounded shadow-sm text-font-default-light dark:border-base-700 dark:text-font-default-dark w-full">
|
16
16
|
{% for choice in choices %}
|
17
17
|
<li class="basis-1/3 border-r border-base-200 flex-grow truncate last:border-r-0 dark:border-base-700 {% if choice.selected %}font-semibold text-primary-600 dark:text-primary-500 {% else %}hover:text-base-700 dark:hover:text-base-200{% endif %}">
|
18
18
|
<a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}" class="block px-3 py-2 text-center hover:text-primary-600 dark:hover:text-primary-500">
|
@@ -7,11 +7,7 @@
|
|
7
7
|
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
8
8
|
|
9
9
|
{% block branding %}
|
10
|
-
|
11
|
-
<a href="{% url 'admin:index' %}">
|
12
|
-
{{ site_header|default:_('Django administration') }}
|
13
|
-
</a>
|
14
|
-
</h1>
|
10
|
+
{% include "unfold/helpers/site_branding.html" %}
|
15
11
|
{% endblock %}
|
16
12
|
|
17
13
|
{% block content %}
|
@@ -29,7 +29,7 @@
|
|
29
29
|
<div class="w-full sm:w-96">
|
30
30
|
<h1 class="font-semibold mb-10">
|
31
31
|
<span class="block text-font-important-light dark:text-font-important-dark">{% trans 'Welcome back to' %}</span>
|
32
|
-
<span class="block text-primary-600 text-xl dark:text-primary-500">{{ site_title }}</span>
|
32
|
+
<span class="block text-primary-600 text-xl dark:text-primary-500">{{ site_title|default:_('Django site admin') }}</span>
|
33
33
|
</h1>
|
34
34
|
|
35
35
|
{% include "unfold/helpers/messages.html" %}
|
@@ -11,8 +11,10 @@
|
|
11
11
|
</button>
|
12
12
|
</div>
|
13
13
|
|
14
|
-
{% for pair in cl.
|
15
|
-
{%
|
14
|
+
{% for pair in cl.filter_params.items %}
|
15
|
+
{% for val in pair.1 %}
|
16
|
+
{% if pair.0 != search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ val }}">{% endif %}
|
17
|
+
{% endfor %}
|
16
18
|
{% endfor %}
|
17
19
|
</form>
|
18
20
|
</div>
|
@@ -5,7 +5,7 @@
|
|
5
5
|
<span class="material-symbols-outlined">person</span>
|
6
6
|
</a>
|
7
7
|
|
8
|
-
<nav class="absolute bg-white border flex flex-col leading-none py-1 -right-2 rounded shadow-lg top-7 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openUserLinks"
|
8
|
+
<nav class="absolute bg-white border flex flex-col leading-none py-1 -right-2 rounded shadow-lg top-7 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openUserLinks" x-transition x-on:click.outside="openUserLinks = false">
|
9
9
|
<div class="border-b border-base-100 flex flex-row flex-shrink-0 items-start justify-start mb-1 pb-1 dark:border-base-700">
|
10
10
|
<span class="block mx-1 px-3 py-2 truncate">
|
11
11
|
{% firstof user.get_short_name user.get_username %}
|
@@ -9,7 +9,7 @@
|
|
9
9
|
</span>
|
10
10
|
|
11
11
|
<template x-teleport="body">
|
12
|
-
<nav x-anchor.bottom-end.offset.4="$refs.rowDropdown{{ action_id }}" class="bg-white border flex flex-col leading-none py-1 rounded shadow-lg text-sm top-7 z-50 w-48 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openActionsId{{ action_id }}"
|
12
|
+
<nav x-anchor.bottom-end.offset.4="$refs.rowDropdown{{ action_id }}" class="bg-white border flex flex-col leading-none py-1 rounded shadow-lg text-sm top-7 z-50 w-48 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openActionsId{{ action_id }}" x-transition x-on:click.outside="openActionsId{{ action_id }} = false">
|
13
13
|
{% for action in actions %}
|
14
14
|
<a href="{% url action.raw_path instance_pk %}" class="mx-1 px-3 py-2 rounded truncate hover:bg-base-100 dark:hover:bg-base-700 dark:hover:text-base-200"{% for attr_name, attr_value in action.attrs.items %} {{ attr_name }}="{{ attr_value }}"{% endfor %}>
|
15
15
|
{{ action.title }}
|
@@ -10,8 +10,8 @@
|
|
10
10
|
{% preserve_filters %}
|
11
11
|
{% endif %}
|
12
12
|
|
13
|
-
<div class="flex flex-col grow gap-4 overflow-auto *:mb-0"
|
14
|
-
<div class="flex flex-col gap-4 {% if cl.model_admin.list_filter_sheet %}px-
|
13
|
+
<div class="flex flex-col grow gap-4 overflow-auto *:mb-0" data-simplebar data-simplebar-direction="rtl">
|
14
|
+
<div class="flex flex-col gap-4 mx-1 px-2 py-2.5 {% if not cl.model_admin.list_filter_sheet %} 2xl:px-0 2xl:py-0{% endif %} *:mb-0">
|
15
15
|
{% for spec in cl.filter_specs %}
|
16
16
|
{% admin_list_filter cl spec %}
|
17
17
|
{% endfor %}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{% load i18n %}
|
2
2
|
|
3
|
-
<div class="
|
3
|
+
<div class="bg-white border-t border-base-200 p-3 py-2.5 dark:bg-base-800 dark:border-base-700{% if not cl.model_admin.list_filter_sheet %} 2xl:!border-t-0 2xl:!bg-transparent 2xl:px-0{% endif %}">
|
4
4
|
{% if cl.model_admin.list_filter_submit %}
|
5
5
|
<button type="submit" class="bg-primary-600 block border border-transparent font-medium px-3 py-2 rounded text-white w-full">
|
6
6
|
{% trans "Apply Filters" %}
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
{% if show_back_button %}
|
4
4
|
{% if opts and adminform %}
|
5
|
-
{% url opts|admin_urlname:'changelist' as
|
5
|
+
{% url opts|admin_urlname:'changelist' as changelist_url %}
|
6
6
|
|
7
|
-
<a href="{
|
7
|
+
<a href="{% add_preserved_filters changelist_url %}" class="block h-4.5 mr-3" title="{% trans "Go back" %}">
|
8
8
|
<span class="material-symbols-outlined">
|
9
9
|
arrow_back
|
10
10
|
</span>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
<span class="material-symbols-outlined">translate</span>
|
11
11
|
</a>
|
12
12
|
|
13
|
-
<div class="absolute bg-white border flex flex-col leading-none py-1 -right-2 rounded shadow-lg top-7 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openLanguageLinks"
|
13
|
+
<div class="absolute bg-white border flex flex-col leading-none py-1 -right-2 rounded shadow-lg top-7 w-52 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openLanguageLinks" x-transition x-on:click.outside="openLanguageLinks = false">
|
14
14
|
{% for language in languages %}
|
15
15
|
<form action="{% url 'set_language' %}" method="post" class="flex w-full">
|
16
16
|
{% csrf_token %}
|
@@ -1,13 +1,23 @@
|
|
1
|
-
<div class="border-b border-base-200 mb-5
|
2
|
-
<div class="flex font-semibold gap-3 h-
|
1
|
+
<div class="border-b border-base-200 flex gap-3 items-center h-[65px] mb-5 dark:border-base-800 px-3" {% if site_dropdown %}x-data="{ openDropdown: false }"{% endif %}>
|
2
|
+
<div class="bg-transparent border border-transparent flex font-semibold gap-3 grow -mx-px h-[48px] items-center px-3 {% if site_dropdown %}cursor-pointer rounded transition-all hover:bg-white hover:border-base-200 hover:shadow-sm hover:dark:bg-base-800 hover:dark:border-base-700{% endif %}"
|
3
|
+
x-on:click="openDropdown = !openDropdown"
|
4
|
+
x-bind:class="{'bg-white border-base-200 shadow-sm dark:bg-base-800 dark:border-base-700': openDropdown, 'bg-transparent border-transparent': !openDropdown}">
|
3
5
|
{% if site_logo %}
|
4
6
|
{% include "unfold/helpers/site_logo.html" %}
|
5
7
|
{% elif branding %}
|
6
8
|
{% include "unfold/helpers/site_icon.html" %}
|
7
9
|
{% endif %}
|
8
10
|
|
9
|
-
|
10
|
-
<span class="material-symbols-outlined">
|
11
|
-
|
11
|
+
{% if site_dropdown %}
|
12
|
+
<span class="material-symbols-outlined ml-auto select-none">
|
13
|
+
unfold_more
|
14
|
+
</span>
|
15
|
+
{% endif %}
|
12
16
|
</div>
|
17
|
+
|
18
|
+
<span class="material-symbols-outlined block cursor-pointer h-4.5 xl:!hidden hover:text-base-700 dark:hover:text-base-200" x-on:click="sidebarMobileOpen = !sidebarMobileOpen">
|
19
|
+
close
|
20
|
+
</span>
|
21
|
+
|
22
|
+
{% include "unfold/helpers/site_dropdown.html" %}
|
13
23
|
</div>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{% load i18n %}
|
2
|
+
|
3
|
+
{% if site_dropdown %}
|
4
|
+
<div class="absolute bg-white border flex flex-col left-3 py-1 rounded shadow-lg top-[73px] w-[264px] z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openDropdown" x-transition x-on:click.outside="openDropdown = false">
|
5
|
+
{% for item in site_dropdown %}
|
6
|
+
<a href="{{ item.link }}" class="flex items-center gap-3 max-h-[30px] mx-1 px-2 py-2 rounded hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200">
|
7
|
+
{% if item.icon %}
|
8
|
+
<span class="material-symbols-outlined">
|
9
|
+
{{ item.icon }}
|
10
|
+
</span>
|
11
|
+
{% endif %}
|
12
|
+
|
13
|
+
<span class="grow truncate">
|
14
|
+
{{ item.title }}
|
15
|
+
</span>
|
16
|
+
</a>
|
17
|
+
{% endfor %}
|
18
|
+
</div>
|
19
|
+
{% endif %}
|
@@ -16,6 +16,14 @@
|
|
16
16
|
</a>
|
17
17
|
{% endif %}
|
18
18
|
|
19
|
-
<div class="
|
20
|
-
{{
|
19
|
+
<div class="flex flex-col gap-1">
|
20
|
+
<div class="text-font-important-light leading-none tracking-tight dark:text-font-important-dark *:leading-none {% if site_subheader %}xl:text-sm{% else %}xl:text-base{% endif %}">
|
21
|
+
{{ branding }}
|
22
|
+
</div>
|
23
|
+
|
24
|
+
{% if site_subheader %}
|
25
|
+
<div class="font-normal leading-none text-font-subtle-light text-xs dark:text-font-subtle-dark">
|
26
|
+
{{ site_subheader }}
|
27
|
+
</div>
|
28
|
+
{% endif %}
|
21
29
|
</div>
|
@@ -8,7 +8,13 @@
|
|
8
8
|
{% for item in tabs_list %}
|
9
9
|
{% if item.has_permission %}
|
10
10
|
<li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
|
11
|
-
<a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}"
|
11
|
+
<a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}"
|
12
|
+
class="block px-3 py-2 md:py-4 md:px-0 dark:border-base-800 {% if item.active and not item.inline %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}"
|
13
|
+
{% if item.inline %}
|
14
|
+
x-on:click="activeTab = '{{ item.inline }}'"
|
15
|
+
x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ item.inline }}'}"
|
16
|
+
{% endif %}
|
17
|
+
>
|
12
18
|
{{ item.title }}
|
13
19
|
</a>
|
14
20
|
</li>
|
@@ -7,7 +7,7 @@
|
|
7
7
|
</span>
|
8
8
|
</a>
|
9
9
|
|
10
|
-
<nav class="absolute bg-white border flex flex-col leading-none py-1 -right-2 rounded shadow-lg top-7 w-40 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openTheme"
|
10
|
+
<nav class="absolute bg-white border flex flex-col leading-none py-1 -right-2 rounded shadow-lg top-7 w-40 z-50 dark:bg-base-800 dark:border-base-700" x-cloak x-show="openTheme" x-transition x-on:click.outside="openTheme = false">
|
11
11
|
<a class="cursor-pointer flex flex-row leading-none mx-1 px-3 py-1.5 rounded hover:bg-base-100 hover:text-base-700 dark:hover:bg-base-700 dark:hover:text-base-200"
|
12
12
|
x-on:click="adminTheme = 'dark'"
|
13
13
|
x-bind:class="adminTheme == 'dark' && 'text-primary-600 dark:text-primary-500 dark:hover:!text-primary-500 hover:!text-primary-600'">
|
@@ -1,11 +1,7 @@
|
|
1
1
|
{% extends "unfold/layouts/base_simple.html" %}
|
2
2
|
|
3
3
|
{% block branding %}
|
4
|
-
|
5
|
-
<a href="{% url 'admin:index' %}">
|
6
|
-
{{ site_header|default:_('Django administration') }}
|
7
|
-
</a>
|
8
|
-
</h1>
|
4
|
+
{% include "unfold/helpers/site_branding.html" %}
|
9
5
|
{% endblock %}
|
10
6
|
|
11
7
|
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
@@ -59,7 +59,7 @@
|
|
59
59
|
{% endblock %}
|
60
60
|
</head>
|
61
61
|
|
62
|
-
<body class="antialiased bg-white font-sans text-font-default-light text-sm dark:bg-base-900 dark:text-font-default-dark {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{ mainWidth: 0, activeTab: 'general', sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }" x-init="activeTab = window.location.hash?.replace('#', '') || 'general'">
|
62
|
+
<body class="antialiased bg-white font-sans text-font-default-light text-sm dark:bg-base-900 dark:text-font-default-dark {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{ mainWidth: 0, {% if opts %}activeTab: 'general',{% endif %} sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }" x-init="activeTab = {% if opts %}window.location.hash?.replace('#', '') || 'general'{% else %}''{% endif %}">
|
63
63
|
{% if colors %}
|
64
64
|
<style id="unfold-theme-colors">
|
65
65
|
:root {
|
unfold/templatetags/unfold.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
from
|
1
|
+
from collections.abc import Mapping
|
2
|
+
from typing import Any, Optional, Union
|
2
3
|
|
3
4
|
from django import template
|
4
5
|
from django.contrib.admin.helpers import AdminForm, Fieldset
|
5
6
|
from django.contrib.admin.views.main import ChangeList
|
7
|
+
from django.db.models.options import Options
|
6
8
|
from django.forms import Field
|
7
9
|
from django.http import HttpRequest
|
8
10
|
from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
|
@@ -15,9 +17,44 @@ from unfold.components import ComponentRegistry
|
|
15
17
|
register = Library()
|
16
18
|
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
def _get_tabs_list(
|
21
|
+
context: RequestContext, page: str, opts: Optional[Options] = None
|
22
|
+
) -> list:
|
20
23
|
tabs_list = []
|
24
|
+
page_id = None
|
25
|
+
|
26
|
+
if page not in ["changeform", "changelist"]:
|
27
|
+
page_id = page
|
28
|
+
|
29
|
+
for tab in context.get("tab_list", []):
|
30
|
+
if page_id:
|
31
|
+
if tab.get("page") == page_id:
|
32
|
+
tabs_list = tab["items"]
|
33
|
+
break
|
34
|
+
|
35
|
+
continue
|
36
|
+
|
37
|
+
if "models" not in tab:
|
38
|
+
continue
|
39
|
+
|
40
|
+
for tab_model in tab["models"]:
|
41
|
+
if isinstance(tab_model, str):
|
42
|
+
if str(opts) == tab_model and page == "changelist":
|
43
|
+
tabs_list = tab["items"]
|
44
|
+
break
|
45
|
+
elif isinstance(tab_model, dict) and str(opts) == tab_model["name"]:
|
46
|
+
is_detail = tab_model.get("detail", False)
|
47
|
+
|
48
|
+
if (page == "changeform" and is_detail) or (
|
49
|
+
page == "changelist" and not is_detail
|
50
|
+
):
|
51
|
+
tabs_list = tab["items"]
|
52
|
+
break
|
53
|
+
return tabs_list
|
54
|
+
|
55
|
+
|
56
|
+
@register.simple_tag(name="tab_list", takes_context=True)
|
57
|
+
def tab_list(context: RequestContext, page: str, opts: Optional[Options] = None) -> str:
|
21
58
|
inlines_list = []
|
22
59
|
|
23
60
|
data = {
|
@@ -26,22 +63,18 @@ def tab_list(context, page, opts) -> str:
|
|
26
63
|
"actions_list": context.get("actions_list"),
|
27
64
|
"actions_items": context.get("actions_items"),
|
28
65
|
"is_popup": context.get("is_popup"),
|
66
|
+
"tabs_list": _get_tabs_list(context, page, opts),
|
29
67
|
}
|
30
68
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
data["tabs_list"] = tabs_list
|
38
|
-
|
39
|
-
for inline in context.get("inline_admin_formsets", []):
|
40
|
-
if hasattr(inline.opts, "tab"):
|
41
|
-
inlines_list.append(inline)
|
69
|
+
# If the changeform is rendered and there are no custom tab navigation
|
70
|
+
# specified, check for inlines to put into tabs
|
71
|
+
if page == "changeform" and len(data["tabs_list"]) == 0:
|
72
|
+
for inline in context.get("inline_admin_formsets", []):
|
73
|
+
if opts and hasattr(inline.opts, "tab"):
|
74
|
+
inlines_list.append(inline)
|
42
75
|
|
43
|
-
|
44
|
-
|
76
|
+
if len(inlines_list) > 0:
|
77
|
+
data["inlines_list"] = inlines_list
|
45
78
|
|
46
79
|
return render_to_string(
|
47
80
|
"unfold/helpers/tab_list.html",
|
@@ -70,7 +103,7 @@ def index(indexable: Mapping[int, Any], i: int) -> Any:
|
|
70
103
|
|
71
104
|
|
72
105
|
@register.filter
|
73
|
-
def tabs(adminform: AdminForm) ->
|
106
|
+
def tabs(adminform: AdminForm) -> list[Fieldset]:
|
74
107
|
result = []
|
75
108
|
|
76
109
|
for fieldset in adminform:
|
@@ -86,7 +119,7 @@ class CaptureNode(Node):
|
|
86
119
|
self.varname = varname
|
87
120
|
self.silent = silent
|
88
121
|
|
89
|
-
def render(self, context:
|
122
|
+
def render(self, context: dict[str, Any]) -> Union[str, SafeText]:
|
90
123
|
output = self.nodelist.render(context)
|
91
124
|
context[self.varname] = output
|
92
125
|
if self.silent:
|
@@ -155,7 +188,7 @@ class RenderComponentNode(template.Node):
|
|
155
188
|
self,
|
156
189
|
template_name: str,
|
157
190
|
nodelist: NodeList,
|
158
|
-
extra_context: Optional[
|
191
|
+
extra_context: Optional[dict] = None,
|
159
192
|
include_context: bool = False,
|
160
193
|
*args,
|
161
194
|
**kwargs,
|
@@ -252,7 +285,7 @@ def add_css_class(field: Field, classes: Union[list, tuple]) -> Field:
|
|
252
285
|
takes_context=True,
|
253
286
|
name="preserve_filters",
|
254
287
|
)
|
255
|
-
def preserve_changelist_filters(context: Context) ->
|
288
|
+
def preserve_changelist_filters(context: Context) -> dict[str, dict[str, str]]:
|
256
289
|
"""
|
257
290
|
Generate hidden input fields to preserve filters for POST forms.
|
258
291
|
"""
|
@@ -262,10 +295,10 @@ def preserve_changelist_filters(context: Context) -> Dict[str, Dict[str, str]]:
|
|
262
295
|
if not request or not changelist:
|
263
296
|
return {"params": {}}
|
264
297
|
|
265
|
-
used_params:
|
298
|
+
used_params: set[str] = {
|
266
299
|
param for spec in changelist.filter_specs for param in spec.used_parameters
|
267
300
|
}
|
268
|
-
preserved_params:
|
301
|
+
preserved_params: dict[str, str] = {
|
269
302
|
param: value for param, value in request.GET.items() if param not in used_params
|
270
303
|
}
|
271
304
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import datetime
|
2
|
-
from typing import Any,
|
2
|
+
from typing import Any, Optional, Union
|
3
3
|
|
4
4
|
from django.contrib.admin.templatetags.admin_list import (
|
5
5
|
ResultList,
|
@@ -355,7 +355,7 @@ def results(cl: ChangeList):
|
|
355
355
|
yield UnfoldResultList(pk_value, None, items_for_result(cl, res, None))
|
356
356
|
|
357
357
|
|
358
|
-
def result_list(context:
|
358
|
+
def result_list(context: dict[str, Any], cl: ChangeList) -> dict[str, Any]:
|
359
359
|
"""
|
360
360
|
Display the headers and data list together.
|
361
361
|
"""
|
unfold/typing.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
from
|
1
|
+
from collections.abc import Iterable
|
2
|
+
from typing import Any, Protocol, Union
|
2
3
|
|
3
4
|
|
4
5
|
class ActionFunction(Protocol):
|
@@ -11,13 +12,13 @@ class ActionFunction(Protocol):
|
|
11
12
|
allowed_permissions: Iterable[str]
|
12
13
|
short_description: str
|
13
14
|
url_path: str
|
14
|
-
attrs:
|
15
|
+
attrs: dict[str, Any]
|
15
16
|
|
16
17
|
def __call__(self, *args, **kwargs):
|
17
18
|
pass
|
18
19
|
|
19
20
|
|
20
21
|
FieldsetsType = Union[
|
21
|
-
|
22
|
-
|
22
|
+
list[tuple[Union[str, None], dict[str, Any]]],
|
23
|
+
tuple[tuple[Union[str, None], dict[str, Any]]],
|
23
24
|
]
|
unfold/utils.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
import datetime
|
2
2
|
import decimal
|
3
3
|
import json
|
4
|
-
from
|
4
|
+
from collections.abc import Iterable
|
5
|
+
from typing import Any, Optional
|
5
6
|
|
6
7
|
from django.conf import settings
|
7
8
|
from django.db import models
|
@@ -114,7 +115,7 @@ def display_for_field(value: Any, field: Any, empty_value_display: str) -> str:
|
|
114
115
|
return display_for_value(value, empty_value_display)
|
115
116
|
|
116
117
|
|
117
|
-
def hex_to_rgb(hex_color: str) ->
|
118
|
+
def hex_to_rgb(hex_color: str) -> list[int]:
|
118
119
|
hex_color = hex_color.lstrip("#")
|
119
120
|
|
120
121
|
r = int(hex_color[0:2], 16)
|
unfold/views.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any
|
1
|
+
from typing import Any
|
2
2
|
|
3
3
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
4
4
|
|
@@ -16,7 +16,7 @@ class UnfoldModelAdminViewMixin(PermissionRequiredMixin):
|
|
16
16
|
self.model_admin = model_admin
|
17
17
|
super().__init__(**kwargs)
|
18
18
|
|
19
|
-
def get_context_data(self, **kwargs: Any) ->
|
19
|
+
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
20
20
|
if not hasattr(self, "model_admin"):
|
21
21
|
raise UnfoldException(
|
22
22
|
"UnfoldModelAdminViewMixin was not provided with 'model_admin' argument"
|