mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into feature
This commit is contained in:
@@ -57,6 +57,7 @@ HTTP_REQUEST_META_SAFE_COPY = [
|
||||
'HTTP_HOST',
|
||||
'HTTP_REFERER',
|
||||
'HTTP_USER_AGENT',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'QUERY_STRING',
|
||||
'REMOTE_ADDR',
|
||||
'REMOTE_HOST',
|
||||
|
||||
@@ -12,7 +12,7 @@ from django_tables2.data import TableQuerysetData
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from extras.choices import CustomFieldTypeChoices
|
||||
from extras.models import CustomField
|
||||
from extras.models import CustomField, CustomLink
|
||||
from .utils import content_type_identifier, content_type_name
|
||||
from .paginator import EnhancedPaginator, get_paginate_count
|
||||
|
||||
@@ -34,15 +34,18 @@ class BaseTable(tables.Table):
|
||||
}
|
||||
|
||||
def __init__(self, *args, user=None, extra_columns=None, **kwargs):
|
||||
if extra_columns is None:
|
||||
extra_columns = []
|
||||
|
||||
# Add custom field columns
|
||||
obj_type = ContentType.objects.get_for_model(self._meta.model)
|
||||
cf_columns = [
|
||||
(f'cf_{cf.name}', CustomFieldColumn(cf)) for cf in CustomField.objects.filter(content_types=obj_type)
|
||||
]
|
||||
if extra_columns is not None:
|
||||
extra_columns.extend(cf_columns)
|
||||
else:
|
||||
extra_columns = cf_columns
|
||||
cl_columns = [
|
||||
(f'cl_{cl.name}', CustomLinkColumn(cl)) for cl in CustomLink.objects.filter(content_type=obj_type)
|
||||
]
|
||||
extra_columns.extend([*cf_columns, *cl_columns])
|
||||
|
||||
super().__init__(*args, extra_columns=extra_columns, **kwargs)
|
||||
|
||||
@@ -418,6 +421,37 @@ class CustomFieldColumn(tables.Column):
|
||||
return self.default
|
||||
|
||||
|
||||
class CustomLinkColumn(tables.Column):
|
||||
"""
|
||||
Render a custom links as a table column.
|
||||
"""
|
||||
def __init__(self, customlink, *args, **kwargs):
|
||||
self.customlink = customlink
|
||||
kwargs['accessor'] = Accessor('pk')
|
||||
if 'verbose_name' not in kwargs:
|
||||
kwargs['verbose_name'] = customlink.name
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def render(self, record):
|
||||
try:
|
||||
rendered = self.customlink.render({'obj': record})
|
||||
if rendered:
|
||||
return mark_safe(f'<a href="{rendered["link"]}"{rendered["link_target"]}>{rendered["text"]}</a>')
|
||||
except Exception as e:
|
||||
return mark_safe(f'<span class="text-danger" title="{e}"><i class="mdi mdi-alert"></i> Error</span>')
|
||||
return ''
|
||||
|
||||
def value(self, record):
|
||||
try:
|
||||
rendered = self.customlink.render({'obj': record})
|
||||
if rendered:
|
||||
return rendered['link']
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class MPTTColumn(tables.TemplateColumn):
|
||||
"""
|
||||
Display a nested hierarchy for MPTT-enabled models.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{% load form_helpers %}
|
||||
|
||||
{% for field in form %}
|
||||
{% if field.name in form.custom_fields %}
|
||||
{% render_field field %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
31
netbox/utilities/templates/form_helpers/render_errors.html
Normal file
31
netbox/utilities/templates/form_helpers/render_errors.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% load form_helpers %}
|
||||
{% load helpers %}
|
||||
|
||||
{% if form.errors or form.non_field_errors %}
|
||||
<div class="alert alert-danger mt-3" role="alert">
|
||||
<h4 class="alert-heading">Errors</h4>
|
||||
{% if form.errors and '__all__' not in form.errors %}
|
||||
<hr />
|
||||
{% endif %}
|
||||
<div class="ps-2">
|
||||
{% if form.errors and '__all__' not in form.errors %}
|
||||
{% for field_name, errors in form.errors.items %}
|
||||
{% if not field_name|startswith:'__' %}
|
||||
{% with field=form|getfield:field_name %}
|
||||
<strong>{{ field.label }}</strong>
|
||||
<ul>
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.non_field_errors %}
|
||||
<hr />
|
||||
{{ form.non_field_errors }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
127
netbox/utilities/templates/form_helpers/render_field.html
Normal file
127
netbox/utilities/templates/form_helpers/render_field.html
Normal file
@@ -0,0 +1,127 @@
|
||||
{% load form_helpers %}
|
||||
{% load helpers %}
|
||||
|
||||
{% if field|widget_type == 'checkboxinput' %}
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col">
|
||||
<div class="form-check{% if field.errors %} has-error{% endif %}">
|
||||
{{ field }}
|
||||
<label for="{{ field.id_for_label }}" class="form-check-label">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
</div>
|
||||
{% if field.help_text %}
|
||||
<span class="form-text">{{ field.help_text|safe }}</span>
|
||||
{% endif %}
|
||||
{% if bulk_nullable %}
|
||||
<div class="form-check my-1">
|
||||
<input type="checkbox" class="form-check-input" name="_nullify" value="{{ field.name }}" />
|
||||
<label class="form-check-label">Set Null</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif field|widget_type == 'textarea' and not field.label %}
|
||||
<div class="row mb-3">
|
||||
{% if label %}
|
||||
<label class="col-sm-3 col-form-label text-lg-end{% if field.field.required %} required{% endif %}" for="{{ field.id_for_label }}">
|
||||
{{ label }}
|
||||
</label>
|
||||
{% else %}
|
||||
{% endif %}
|
||||
<div class="col">
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<span class="form-text">{{ field.help_text|safe }}</span>
|
||||
{% endif %}
|
||||
{% if bulk_nullable %}
|
||||
<div class="form-check my-1">
|
||||
<input type="checkbox" class="form-check-input" name="_nullify" value="{{ field.name }}" />
|
||||
<label class="form-check-label">Set Null</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif field|widget_type == 'slugwidget' %}
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-3 col-form-label text-lg-end{% if field.field.required %} required{% endif %}" for="{{ field.id_for_label }}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
{{ field }}
|
||||
<button id="reslug" type="button" title="Regenerate Slug" class="btn btn-outline-dark border-input">
|
||||
<i class="mdi mdi-reload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif field|widget_type == 'fileinput' %}
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
class="form-control"
|
||||
type="file"
|
||||
name="{{ field.name }}"
|
||||
placeholder="{{ field.placeholder }}"
|
||||
id="id_{{ field.name }}"
|
||||
accept="{{ field.field.widget.attrs.accept }}"
|
||||
{% if field.is_required %}required{% endif %}
|
||||
/>
|
||||
<label for="{{ field.id_for_label }}" class="input-group-text">{{ field.label|bettertitle }}</label>
|
||||
</div>
|
||||
|
||||
{% elif field|widget_type == 'clearablefileinput' %}
|
||||
<div class="row mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label col col-md-3 text-lg-end{% if field.field.required %} required{% endif %}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
<div class="col col-md-9">
|
||||
{{ field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif field|widget_type == 'selectmultiple' %}
|
||||
<div class="row mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="form-label col col-md-3 text-lg-end{% if field.field.required %} required{% endif %}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
<div class="col col-md-9">
|
||||
{{ field }}
|
||||
{% if bulk_nullable %}
|
||||
<div class="form-check my-1">
|
||||
<input type="checkbox" class="form-check-input" name="_nullify" value="{{ field.name }}" />
|
||||
<label class="form-check-label">Set Null</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="row mb-3">
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-3 col-form-label text-lg-end{% if field.field.required %} required{% endif %}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
<div class="col">
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<span class="form-text">{{ field.help_text|safe }}</span>
|
||||
{% endif %}
|
||||
<div class="invalid-feedback">
|
||||
{% if field.field.required %}
|
||||
<strong>{{ field.label }}</strong> field is required.
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if bulk_nullable %}
|
||||
<div class="form-check my-1">
|
||||
<input type="checkbox" class="form-check-input" name="_nullify" value="{{ field.name }}" />
|
||||
<label class="form-check-label">Set Null</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
8
netbox/utilities/templates/form_helpers/render_form.html
Normal file
8
netbox/utilities/templates/form_helpers/render_form.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% load form_helpers %}
|
||||
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
{% for field in form.visible_fields %}
|
||||
{% render_field field %}
|
||||
{% endfor %}
|
||||
14
netbox/utilities/templates/helpers/applied_filters.html
Normal file
14
netbox/utilities/templates/helpers/applied_filters.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% if applied_filters %}
|
||||
<div class="mb-3">
|
||||
{% for filter in applied_filters %}
|
||||
<a href="{{ filter.link_url }}" class="badge rounded-pill bg-primary text-decoration-none me-1">
|
||||
<i class="mdi mdi-close"></i> {{ filter.link_text }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if applied_filters|length > 1 %}
|
||||
<a href="?" class="badge rounded-pill bg-danger text-decoration-none me-1">
|
||||
<i class="mdi mdi-tag-off"></i> Clear all
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
1
netbox/utilities/templates/helpers/badge.html
Normal file
1
netbox/utilities/templates/helpers/badge.html
Normal file
@@ -0,0 +1 @@
|
||||
{% if value or show_empty %}<span class="badge bg-{{ bg_class }}">{{ value }}</span>{% endif %}
|
||||
44
netbox/utilities/templates/helpers/table_config_form.html
Normal file
44
netbox/utilities/templates/helpers/table_config_form.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% load form_helpers %}
|
||||
|
||||
<div class="modal fade" tabindex="-1" id="{{ table_name }}_config">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<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-url="{% url 'users-api:userconfig-list' %}" data-config-root="tables.{{ form.table_name }}">
|
||||
<div class="modal-body row">
|
||||
<div class="col-5 text-center">
|
||||
{{ form.available_columns.label }}
|
||||
{{ form.available_columns }}
|
||||
</div>
|
||||
<div class="col-2 d-flex align-items-center">
|
||||
<div>
|
||||
<a class="btn btn-success btn-sm w-100 my-2" id="add_columns">
|
||||
<i class="mdi mdi-arrow-right-bold"></i> Add
|
||||
</a>
|
||||
<a class="btn btn-danger btn-sm w-100 my-2" id="remove_columns">
|
||||
<i class="mdi mdi-arrow-left-bold"></i> Remove
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5 text-center">
|
||||
{{ 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>
|
||||
<a class="btn btn-primary btn-sm mt-2" id="move-option-down" data-target="id_columns">
|
||||
<i class="mdi mdi-arrow-down-bold"></i> Move Down
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-outline-danger" id="reset_tableconfig" value="Reset">Reset</button>
|
||||
<button type="submit" class="btn btn-primary" id="save_tableconfig" value="Save">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
3
netbox/utilities/templates/helpers/tag.html
Normal file
3
netbox/utilities/templates/helpers/tag.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{% load helpers %}
|
||||
|
||||
{% if url_name %}<a href="{% url url_name %}?tag={{ tag.slug }}">{% endif %}<span class="badge" style="color: {{ tag.color|fgcolor }}; background-color: #{{ tag.color }}">{{ tag }}</span>{% if url_name %}</a>{% endif %}
|
||||
21
netbox/utilities/templates/helpers/utilization_graph.html
Normal file
21
netbox/utilities/templates/helpers/utilization_graph.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% if utilization == 0 %}
|
||||
<div class="progress align-items-center justify-content-center">
|
||||
<span class="w-100 text-center">{{ utilization }}%</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="progress">
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="{{ utilization }}"
|
||||
class="progress-bar {{ bar_class }}"
|
||||
style="width: {{ utilization }}%;"
|
||||
>
|
||||
{% if utilization >= 25 %}{{ utilization }}%{% endif %}
|
||||
</div>
|
||||
{% if utilization < 25 %}
|
||||
<span class="ps-1">{{ utilization }}%</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -4,6 +4,10 @@ from django import template
|
||||
register = template.Library()
|
||||
|
||||
|
||||
#
|
||||
# Filters
|
||||
#
|
||||
|
||||
@register.filter()
|
||||
def getfield(form, fieldname):
|
||||
"""
|
||||
@@ -12,38 +16,6 @@ def getfield(form, fieldname):
|
||||
return form[fieldname]
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/render_field.html')
|
||||
def render_field(field, bulk_nullable=False, label=None):
|
||||
"""
|
||||
Render a single form field from template
|
||||
"""
|
||||
return {
|
||||
'field': field,
|
||||
'label': label,
|
||||
'bulk_nullable': bulk_nullable,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/render_custom_fields.html')
|
||||
def render_custom_fields(form):
|
||||
"""
|
||||
Render all custom fields in a form
|
||||
"""
|
||||
return {
|
||||
'form': form,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/render_form.html')
|
||||
def render_form(form):
|
||||
"""
|
||||
Render an entire form from template
|
||||
"""
|
||||
return {
|
||||
'form': form,
|
||||
}
|
||||
|
||||
|
||||
@register.filter(name='widget_type')
|
||||
def widget_type(field):
|
||||
"""
|
||||
@@ -57,7 +29,43 @@ def widget_type(field):
|
||||
return None
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/render_errors.html')
|
||||
#
|
||||
# Inclusion tags
|
||||
#
|
||||
|
||||
@register.inclusion_tag('form_helpers/render_field.html')
|
||||
def render_field(field, bulk_nullable=False, label=None):
|
||||
"""
|
||||
Render a single form field from template
|
||||
"""
|
||||
return {
|
||||
'field': field,
|
||||
'label': label,
|
||||
'bulk_nullable': bulk_nullable,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('form_helpers/render_custom_fields.html')
|
||||
def render_custom_fields(form):
|
||||
"""
|
||||
Render all custom fields in a form
|
||||
"""
|
||||
return {
|
||||
'form': form,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('form_helpers/render_form.html')
|
||||
def render_form(form):
|
||||
"""
|
||||
Render an entire form from template
|
||||
"""
|
||||
return {
|
||||
'form': form,
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('form_helpers/render_errors.html')
|
||||
def render_errors(form):
|
||||
"""
|
||||
Render form errors, if they exist.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
TERMS_DANGER = ("delete", "deleted", "remove", "removed")
|
||||
TERMS_WARNING = ("changed", "updated", "change", "update")
|
||||
TERMS_SUCCESS = ("created", "added", "create", "add")
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def get_status(text: str) -> str:
|
||||
lower = text.lower()
|
||||
|
||||
if lower in TERMS_DANGER:
|
||||
return "danger"
|
||||
elif lower in TERMS_WARNING:
|
||||
return "warning"
|
||||
elif lower in TERMS_SUCCESS:
|
||||
return "success"
|
||||
else:
|
||||
return "info"
|
||||
@@ -59,6 +59,10 @@ def render_markdown(value):
|
||||
# Render Markdown
|
||||
html = markdown(value, extensions=['fenced_code', 'tables', StrikethroughExtension()])
|
||||
|
||||
# If the string is not empty wrap it in rendered-markdown to style tables
|
||||
if html:
|
||||
html = f'<div class="rendered-markdown">{html}</div>'
|
||||
|
||||
return mark_safe(html)
|
||||
|
||||
|
||||
@@ -380,7 +384,7 @@ def querystring(request, **kwargs):
|
||||
return ''
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/templatetags/utilization_graph.html')
|
||||
@register.inclusion_tag('helpers/utilization_graph.html')
|
||||
def utilization_graph(utilization, warning_threshold=75, danger_threshold=90):
|
||||
"""
|
||||
Display a horizontal bar graph indicating a percentage of utilization.
|
||||
@@ -399,7 +403,7 @@ def utilization_graph(utilization, warning_threshold=75, danger_threshold=90):
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/templatetags/tag.html')
|
||||
@register.inclusion_tag('helpers/tag.html')
|
||||
def tag(tag, url_name=None):
|
||||
"""
|
||||
Display a tag, optionally linked to a filtered list of objects.
|
||||
@@ -410,7 +414,7 @@ def tag(tag, url_name=None):
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/templatetags/badge.html')
|
||||
@register.inclusion_tag('helpers/badge.html')
|
||||
def badge(value, bg_class='secondary', show_empty=False):
|
||||
"""
|
||||
Display the specified number as a badge.
|
||||
@@ -422,7 +426,7 @@ def badge(value, bg_class='secondary', show_empty=False):
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/templatetags/table_config_form.html')
|
||||
@register.inclusion_tag('helpers/table_config_form.html')
|
||||
def table_config_form(table, table_name=None):
|
||||
return {
|
||||
'table_name': table_name or table.__class__.__name__,
|
||||
@@ -430,7 +434,7 @@ def table_config_form(table, table_name=None):
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('utilities/templatetags/applied_filters.html')
|
||||
@register.inclusion_tag('helpers/applied_filters.html')
|
||||
def applied_filters(form, query_params):
|
||||
"""
|
||||
Display the active filters for a given filter form.
|
||||
|
||||
@@ -8,7 +8,7 @@ from netbox.navigation_menu import MENUS
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag("navigation/nav_items.html", takes_context=True)
|
||||
@register.inclusion_tag("navigation/menu.html", takes_context=True)
|
||||
def nav(context: Context) -> Dict:
|
||||
"""
|
||||
Render the navigation menu.
|
||||
@@ -288,45 +288,6 @@ def flatten_dict(d, prefix='', separator='.'):
|
||||
return ret
|
||||
|
||||
|
||||
def decode_dict(encoded_dict: Dict, *, decode_keys: bool = True) -> Dict:
|
||||
"""
|
||||
Recursively URL decode string keys and values of a dict.
|
||||
|
||||
For example, `{'1%2F1%2F1': {'1%2F1%2F2': ['1%2F1%2F3', '1%2F1%2F4']}}` would
|
||||
become: `{'1/1/1': {'1/1/2': ['1/1/3', '1/1/4']}}`
|
||||
|
||||
:param encoded_dict: Dictionary to be decoded.
|
||||
:param decode_keys: (Optional) Enable/disable decoding of dict keys.
|
||||
"""
|
||||
|
||||
def decode_value(value: Any, _decode_keys: bool) -> Any:
|
||||
"""
|
||||
Handle URL decoding of any supported value type.
|
||||
"""
|
||||
# Decode string values.
|
||||
if isinstance(value, str):
|
||||
return urllib.parse.unquote(value)
|
||||
# Recursively decode each list item.
|
||||
elif isinstance(value, list):
|
||||
return [decode_value(v, _decode_keys) for v in value]
|
||||
# Recursively decode each tuple item.
|
||||
elif isinstance(value, Tuple):
|
||||
return tuple(decode_value(v, _decode_keys) for v in value)
|
||||
# Recursively decode each dict key/value pair.
|
||||
elif isinstance(value, dict):
|
||||
# Don't decode keys, if `decode_keys` is false.
|
||||
if not _decode_keys:
|
||||
return {k: decode_value(v, _decode_keys) for k, v in value.items()}
|
||||
return {urllib.parse.unquote(k): decode_value(v, _decode_keys) for k, v in value.items()}
|
||||
return value
|
||||
|
||||
if not decode_keys:
|
||||
# Don't decode keys, if `decode_keys` is false.
|
||||
return {k: decode_value(v, decode_keys) for k, v in encoded_dict.items()}
|
||||
|
||||
return {urllib.parse.unquote(k): decode_value(v, decode_keys) for k, v in encoded_dict.items()}
|
||||
|
||||
|
||||
def array_to_string(array):
|
||||
"""
|
||||
Generate an efficient, human-friendly string from a set of integers. Intended for use with ArrayField.
|
||||
|
||||
Reference in New Issue
Block a user