django-unfold 0.46.0__py3-none-any.whl → 0.48.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- {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"
|