mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 03:56:53 -06:00
Merge branch 'develop' of github.com:netbox-community/netbox into 7090-fix-cablebulkedit-length-field
This commit is contained in:
commit
4618cc2b22
@ -1,11 +1,20 @@
|
||||
# NetBox v3.0
|
||||
|
||||
## v2.11.12 (2021-08-23)
|
||||
## v3.0.1 (FUTURE)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#7070](https://github.com/netbox-community/netbox/issues/7070) - Fix exception when filtering by prefix max length in UI
|
||||
* [#7071](https://github.com/netbox-community/netbox/issues/7071) - Fix exception when removing a primary IP from a device/VM
|
||||
* [#7072](https://github.com/netbox-community/netbox/issues/7072) - Fix table configuration under prefix child object views
|
||||
* [#7075](https://github.com/netbox-community/netbox/issues/7075) - Fix UI bug when a custom field has a space in the name
|
||||
* [#7082](https://github.com/netbox-community/netbox/issues/7082) - Avoid exception when referencing invalid content type in table
|
||||
* [#7083](https://github.com/netbox-community/netbox/issues/7083) - Correct labeling for VM memory attribute
|
||||
* [#7084](https://github.com/netbox-community/netbox/issues/7084) - Fix KeyError exception when editing access VLAN on an interface
|
||||
* [#7089](https://github.com/netbox-community/netbox/issues/7089) - Fix ContentTypeFilterSet not filtering on q filter
|
||||
* [#7093](https://github.com/netbox-community/netbox/issues/7093) - Multi-select custom field filters should employ exact match
|
||||
* [#7096](https://github.com/netbox-community/netbox/issues/7096) - Home links should honor `BASE_PATH` configuration
|
||||
* [#7101](https://github.com/netbox-community/netbox/issues/7101) - Enforce `MAX_PAGE_SIZE` for table and REST API pagination
|
||||
|
||||
---
|
||||
|
||||
|
@ -129,7 +129,7 @@ class InterfaceCommonForm(forms.Form):
|
||||
super().clean()
|
||||
|
||||
parent_field = 'device' if 'device' in self.cleaned_data else 'virtual_machine'
|
||||
tagged_vlans = self.cleaned_data['tagged_vlans']
|
||||
tagged_vlans = self.cleaned_data.get('tagged_vlans')
|
||||
|
||||
# Untagged interfaces cannot be assigned tagged VLANs
|
||||
if self.cleaned_data['mode'] == InterfaceModeChoices.MODE_ACCESS and tagged_vlans:
|
||||
@ -142,7 +142,7 @@ class InterfaceCommonForm(forms.Form):
|
||||
self.cleaned_data['tagged_vlans'] = []
|
||||
|
||||
# Validate tagged VLANs; must be a global VLAN or in the same site
|
||||
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED:
|
||||
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED and tagged_vlans:
|
||||
valid_sites = [None, self.cleaned_data[parent_field].site]
|
||||
invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
|
||||
|
||||
|
@ -14,6 +14,7 @@ EXACT_FILTER_TYPES = (
|
||||
CustomFieldTypeChoices.TYPE_DATE,
|
||||
CustomFieldTypeChoices.TYPE_INTEGER,
|
||||
CustomFieldTypeChoices.TYPE_SELECT,
|
||||
CustomFieldTypeChoices.TYPE_MULTISELECT,
|
||||
)
|
||||
|
||||
|
||||
@ -35,7 +36,9 @@ class CustomFieldFilter(django_filters.Filter):
|
||||
|
||||
self.field_name = f'custom_field_data__{self.field_name}'
|
||||
|
||||
if custom_field.type not in EXACT_FILTER_TYPES:
|
||||
if custom_field.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
||||
self.lookup_expr = 'has_key'
|
||||
elif custom_field.type not in EXACT_FILTER_TYPES:
|
||||
if custom_field.filter_logic == CustomFieldFilterLogicChoices.FILTER_LOOSE:
|
||||
self.lookup_expr = 'icontains'
|
||||
|
||||
|
@ -367,7 +367,19 @@ class JobResultFilterSet(BaseFilterSet):
|
||||
#
|
||||
|
||||
class ContentTypeFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = ['id', 'app_label', 'model']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(app_label__icontains=value) |
|
||||
Q(model__icontains=value)
|
||||
)
|
||||
|
@ -681,7 +681,12 @@ class CustomFieldFilterTest(TestCase):
|
||||
cf.content_types.set([obj_type])
|
||||
|
||||
# Selection filtering
|
||||
cf = CustomField(name='cf8', type=CustomFieldTypeChoices.TYPE_URL, choices=['Foo', 'Bar', 'Baz'])
|
||||
cf = CustomField(name='cf8', type=CustomFieldTypeChoices.TYPE_SELECT, choices=['Foo', 'Bar', 'Baz'])
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
|
||||
# Multiselect filtering
|
||||
cf = CustomField(name='cf9', type=CustomFieldTypeChoices.TYPE_MULTISELECT, choices=['A', 'AA', 'B', 'C'])
|
||||
cf.save()
|
||||
cf.content_types.set([obj_type])
|
||||
|
||||
@ -695,6 +700,7 @@ class CustomFieldFilterTest(TestCase):
|
||||
'cf6': 'http://foo.example.com/',
|
||||
'cf7': 'http://foo.example.com/',
|
||||
'cf8': 'Foo',
|
||||
'cf9': ['A', 'B'],
|
||||
}),
|
||||
Site(name='Site 2', slug='site-2', custom_field_data={
|
||||
'cf1': 200,
|
||||
@ -705,9 +711,9 @@ class CustomFieldFilterTest(TestCase):
|
||||
'cf6': 'http://bar.example.com/',
|
||||
'cf7': 'http://bar.example.com/',
|
||||
'cf8': 'Bar',
|
||||
'cf9': ['AA', 'B'],
|
||||
}),
|
||||
Site(name='Site 3', slug='site-3', custom_field_data={
|
||||
}),
|
||||
Site(name='Site 3', slug='site-3'),
|
||||
])
|
||||
|
||||
def test_filter_integer(self):
|
||||
@ -730,3 +736,10 @@ class CustomFieldFilterTest(TestCase):
|
||||
|
||||
def test_filter_select(self):
|
||||
self.assertEqual(self.filterset({'cf_cf8': 'Foo'}, self.queryset).qs.count(), 1)
|
||||
self.assertEqual(self.filterset({'cf_cf8': 'Bar'}, self.queryset).qs.count(), 1)
|
||||
self.assertEqual(self.filterset({'cf_cf8': 'Baz'}, self.queryset).qs.count(), 0)
|
||||
|
||||
def test_filter_multiselect(self):
|
||||
self.assertEqual(self.filterset({'cf_cf9': 'A'}, self.queryset).qs.count(), 1)
|
||||
self.assertEqual(self.filterset({'cf_cf9': 'B'}, self.queryset).qs.count(), 2)
|
||||
self.assertEqual(self.filterset({'cf_cf9': 'C'}, self.queryset).qs.count(), 0)
|
||||
|
@ -404,12 +404,11 @@ class PrefixPrefixesView(generic.ObjectView):
|
||||
bulk_querystring = 'vrf_id={}&within={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
||||
|
||||
return {
|
||||
'first_available_prefix': instance.get_first_available_prefix(),
|
||||
'table': table,
|
||||
'bulk_querystring': bulk_querystring,
|
||||
'active_tab': 'prefixes',
|
||||
'first_available_prefix': instance.get_first_available_prefix(),
|
||||
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||
'table_config_form': TableConfigForm(table=table),
|
||||
}
|
||||
|
||||
|
||||
@ -421,7 +420,7 @@ class PrefixIPRangesView(generic.ObjectView):
|
||||
# Find all IPRanges belonging to this Prefix
|
||||
ip_ranges = instance.get_child_ranges().restrict(request.user, 'view').prefetch_related('vrf')
|
||||
|
||||
table = tables.IPRangeTable(ip_ranges)
|
||||
table = tables.IPRangeTable(ip_ranges, user=request.user)
|
||||
if request.user.has_perm('ipam.change_iprange') or request.user.has_perm('ipam.delete_iprange'):
|
||||
table.columns.show('pk')
|
||||
paginate_table(table, request)
|
||||
@ -449,7 +448,7 @@ class PrefixIPAddressesView(generic.ObjectView):
|
||||
if request.GET.get('show_available', 'true') == 'true':
|
||||
ipaddresses = add_available_ipaddresses(instance.prefix, ipaddresses, instance.is_pool)
|
||||
|
||||
table = tables.IPAddressTable(ipaddresses)
|
||||
table = tables.IPAddressTable(ipaddresses, user=request.user)
|
||||
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
|
||||
table.columns.show('pk')
|
||||
paginate_table(table, request)
|
||||
@ -457,10 +456,10 @@ class PrefixIPAddressesView(generic.ObjectView):
|
||||
bulk_querystring = 'vrf_id={}&parent={}'.format(instance.vrf.pk if instance.vrf else '0', instance.prefix)
|
||||
|
||||
return {
|
||||
'first_available_ip': instance.get_first_available_ip(),
|
||||
'table': table,
|
||||
'bulk_querystring': bulk_querystring,
|
||||
'active_tab': 'ip-addresses',
|
||||
'first_available_ip': instance.get_first_available_ip(),
|
||||
'show_available': request.GET.get('show_available', 'true') == 'true',
|
||||
}
|
||||
|
||||
|
@ -34,23 +34,13 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
||||
return list(queryset[self.offset:])
|
||||
|
||||
def get_limit(self, request):
|
||||
limit = super().get_limit(request)
|
||||
|
||||
if self.limit_query_param:
|
||||
try:
|
||||
limit = int(request.query_params[self.limit_query_param])
|
||||
if limit < 0:
|
||||
raise ValueError()
|
||||
# Enforce maximum page size, if defined
|
||||
if settings.MAX_PAGE_SIZE:
|
||||
if limit == 0:
|
||||
return settings.MAX_PAGE_SIZE
|
||||
else:
|
||||
return min(limit, settings.MAX_PAGE_SIZE)
|
||||
return limit
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
# Enforce maximum page size
|
||||
if settings.MAX_PAGE_SIZE:
|
||||
limit = min(limit, settings.MAX_PAGE_SIZE)
|
||||
|
||||
return self.default_limit
|
||||
return limit
|
||||
|
||||
def get_next_link(self):
|
||||
|
||||
|
@ -560,6 +560,10 @@ RQ_QUEUES = {
|
||||
#
|
||||
|
||||
# Pagination
|
||||
if MAX_PAGE_SIZE and PAGINATE_COUNT > MAX_PAGE_SIZE:
|
||||
raise ImproperlyConfigured(
|
||||
f"PAGINATE_COUNT ({PAGINATE_COUNT}) must be less than or equal to MAX_PAGE_SIZE ({MAX_PAGE_SIZE}), if set."
|
||||
)
|
||||
PER_PAGE_DEFAULTS = [
|
||||
25, 50, 100, 250, 500, 1000
|
||||
]
|
||||
|
@ -181,7 +181,6 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
||||
'table': table,
|
||||
'permissions': permissions,
|
||||
'action_buttons': self.action_buttons,
|
||||
'table_config_form': TableConfigForm(table=table),
|
||||
'filter_form': self.filterset_form(request.GET, label_suffix='') if self.filterset_form else None,
|
||||
}
|
||||
context.update(self.extra_context())
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<head>
|
||||
<title>Server Error</title>
|
||||
<link rel="stylesheet" href="{% static 'netbox.css'%}" />
|
||||
<link rel="stylesheet" href="{% static 'netbox-light.css'%}" />
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col col-md-6 offset-md-3">
|
||||
<div class="card bg-danger mt-5">
|
||||
<div class="card border-danger mt-5">
|
||||
<h5 class="card-header">
|
||||
<i class="mdi mdi-alert"></i> Server Error
|
||||
</h5>
|
||||
@ -32,7 +32,7 @@
|
||||
Python version: {{ python_version }}
|
||||
NetBox version: {{ netbox_version }}</pre>
|
||||
<p>
|
||||
If further assistance is required, please post to the <a href="https://groups.google.com/g/netbox-discuss">NetBox mailing list</a>.
|
||||
If further assistance is required, please post to the <a href="https://github.com/netbox-community/netbox/discussions">NetBox discussion forum</a> on GitHub.
|
||||
</p>
|
||||
<div class="text-end">
|
||||
<a href="{% url 'home' %}" class="btn btn-primary">Home Page</a>
|
||||
|
@ -7,12 +7,12 @@
|
||||
{# Brand #}
|
||||
|
||||
{# Full Logo #}
|
||||
<a class="sidenav-brand" href="/">
|
||||
<a class="sidenav-brand" href="{% url 'home' %}">
|
||||
<img src="{% static 'netbox_logo.svg' %}" height="48" class="sidenav-brand-img" alt="NetBox Logo">
|
||||
</a>
|
||||
|
||||
{# Icon Logo #}
|
||||
<a class="sidenav-brand-icon" href="/">
|
||||
<a class="sidenav-brand-icon" href="{% url 'home' %}">
|
||||
<img src="{% static 'netbox_icon.svg' %}" height="32" class="sidenav-brand-img" alt="NetBox Logo">
|
||||
</a>
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
{% extends 'ipam/prefix/base.html' %}
|
||||
{% load helpers %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_controls %}
|
||||
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
||||
@ -11,7 +13,13 @@
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% include 'inc/table_controls.html' with table_modal="IPAddressTable_config" %}
|
||||
{% include 'utilities/obj_table.html' with heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
||||
</div>
|
||||
</div>
|
||||
{% table_config_form table table_name="IPAddressTable" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/tableconfig.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
@ -1,10 +1,17 @@
|
||||
{% extends 'ipam/prefix/base.html' %}
|
||||
|
||||
{% load helpers %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% include 'inc/table_controls.html' with table_modal="IPRangeTable_config" %}
|
||||
{% include 'utilities/obj_table.html' with heading='Child IP Ranges' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
||||
</div>
|
||||
</div>
|
||||
{% table_config_form table table_name="IPRangeTable" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/tableconfig.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
@ -2,20 +2,17 @@
|
||||
{% load helpers %}
|
||||
{% load static %}
|
||||
|
||||
{% block buttons %}
|
||||
{% block extra_controls %}
|
||||
{% include 'ipam/inc/toggle_available.html' %}
|
||||
{% if request.user.is_authenticated and table_config_form %}
|
||||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#PrefixDetailTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
|
||||
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ object.vrf.pk }}&site={{ object.site.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-success">
|
||||
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ object.vrf.pk }}&site={{ object.site.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-sm btn-success">
|
||||
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> Add Child Prefix
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
|
||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}&vrf={{ object.vrf.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-success">
|
||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}&vrf={{ object.vrf.pk }}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}" class="btn btn-sm btn-success">
|
||||
<span class="mdi mdi-plus-thick" aria-hidden="true"></span>
|
||||
Add an IP Address
|
||||
Add Child IP Address
|
||||
</a>
|
||||
{% endif %}
|
||||
{{ block.super }}
|
||||
@ -24,12 +21,13 @@
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
{% include 'inc/table_controls.html' with table_modal="PrefixDetailTable_config" %}
|
||||
{% include 'utilities/obj_table.html' with heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
|
||||
</div>
|
||||
</div>
|
||||
{% table_config_form prefix_table table_name="PrefixDetailTable" %}
|
||||
{% table_config_form table table_name="PrefixDetailTable" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/tableconfig.js' %}"></script>
|
||||
<script src="{% static 'js/tableconfig.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
@ -42,7 +42,7 @@
|
||||
The file <code>{{ filename }}</code> exists in the static root directory and is readable by the HTTP process.
|
||||
</li>
|
||||
</ul>
|
||||
<p>Click <a href="/">here</a> to attempt loading NetBox again.</p>
|
||||
<p>Click <a href="{% url 'home' %}">here</a> to attempt loading NetBox again.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -9,5 +9,5 @@
|
||||
{% block title %}{% if name %}{{ name }} | {% endif %}NetBox REST API{% endblock %}
|
||||
|
||||
{% block branding %}
|
||||
<a class="navbar-brand" href="/{{ settings.BASE_PATH }}">NetBox</a>
|
||||
<a class="navbar-brand" href="{% url 'home' %}">NetBox</a>
|
||||
{% endblock branding %}
|
||||
|
@ -7,11 +7,11 @@
|
||||
<h5 class="modal-title">Table Configuration</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form class="form-horizontal userconfigform" data-config-root="tables.{{ table_config_form.table_name }}">
|
||||
<form class="form-horizontal userconfigform" data-config-root="tables.{{ form.table_name }}">
|
||||
<div class="modal-body row">
|
||||
<div class="col-5 text-center">
|
||||
{{ table_config_form.available_columns.label }}
|
||||
{{ table_config_form.available_columns }}
|
||||
{{ form.available_columns.label }}
|
||||
{{ form.available_columns }}
|
||||
</div>
|
||||
<div class="col-2 d-flex align-items-center">
|
||||
<div>
|
||||
@ -24,8 +24,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5 text-center">
|
||||
{{ table_config_form.columns.label }}
|
||||
{{ table_config_form.columns }}
|
||||
{{ form.columns.label }}
|
||||
{{ form.columns }}
|
||||
<a class="btn btn-primary btn-sm mt-2" id="move-option-up" data-target="id_columns">
|
||||
<i class="mdi mdi-arrow-up-bold"></i> Move Up
|
||||
</a>
|
||||
|
@ -131,7 +131,7 @@
|
||||
<th scope="row"><i class="mdi mdi-chip"></i> Memory</th>
|
||||
<td>
|
||||
{% if object.memory %}
|
||||
{{ object.memory|humanize_megabytes }} MB
|
||||
{{ object.memory|humanize_megabytes }}
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
|
@ -21,8 +21,8 @@ class Command(_Command):
|
||||
raise CommandError(
|
||||
"This command is available for development purposes only. It will\n"
|
||||
"NOT resolve any issues with missing or unapplied migrations. For assistance,\n"
|
||||
"please post to the NetBox mailing list:\n"
|
||||
" https://groups.google.com/g/netbox-discuss"
|
||||
"please post to the NetBox discussion forum on GitHub:\n"
|
||||
" https://github.com/netbox-community/netbox/discussions"
|
||||
)
|
||||
|
||||
super().handle(*args, **kwargs)
|
||||
|
@ -49,21 +49,25 @@ class EnhancedPage(Page):
|
||||
|
||||
def get_paginate_count(request):
|
||||
"""
|
||||
Determine the length of a page, using the following in order:
|
||||
Determine the desired length of a page, using the following in order:
|
||||
|
||||
1. per_page URL query parameter
|
||||
2. Saved user preference
|
||||
3. PAGINATE_COUNT global setting.
|
||||
|
||||
Return the lesser of the calculated value and MAX_PAGE_SIZE.
|
||||
"""
|
||||
if 'per_page' in request.GET:
|
||||
try:
|
||||
per_page = int(request.GET.get('per_page'))
|
||||
if request.user.is_authenticated:
|
||||
request.user.config.set('pagination.per_page', per_page, commit=True)
|
||||
return per_page
|
||||
return min(per_page, settings.MAX_PAGE_SIZE)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if request.user.is_authenticated:
|
||||
return request.user.config.get('pagination.per_page', settings.PAGINATE_COUNT)
|
||||
return settings.PAGINATE_COUNT
|
||||
per_page = request.user.config.get('pagination.per_page', settings.PAGINATE_COUNT)
|
||||
return min(per_page, settings.MAX_PAGE_SIZE)
|
||||
|
||||
return min(settings.PAGINATE_COUNT, settings.MAX_PAGE_SIZE)
|
||||
|
@ -237,9 +237,13 @@ class ContentTypeColumn(tables.Column):
|
||||
Display a ContentType instance.
|
||||
"""
|
||||
def render(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return content_type_name(value)
|
||||
|
||||
def value(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
return f"{value.app_label}.{value.model}"
|
||||
|
||||
|
||||
|
@ -401,7 +401,7 @@ def badge(value, bg_class='secondary', show_empty=False):
|
||||
def table_config_form(table, table_name=None):
|
||||
return {
|
||||
'table_name': table_name or table.__class__.__name__,
|
||||
'table_config_form': TableConfigForm(table=table),
|
||||
'form': TableConfigForm(table=table),
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user