Merge branch 'develop' into feature

This commit is contained in:
Jeremy Stretch 2024-07-26 16:39:58 -04:00
commit a12fdd6e2d
62 changed files with 96214 additions and 16724 deletions

View File

@ -26,7 +26,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.0.7
placeholder: v4.0.8
validations:
required: true
- type: dropdown

View File

@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.0.7
placeholder: v4.0.8
validations:
required: true
- type: dropdown

View File

@ -16,6 +16,6 @@ jobs:
if: "contains(github.event.issue.labels.*.name, 'status: needs triage')"
with:
# Weighted assignments
assignees: arthanson:3, jeffgdotorg:3, jeremystretch:3, DanSheps
assignees: arthanson:3, jeremystretch:3, DanSheps
numOfAssignee: 1
abortIfPreviousAssignees: true

View File

@ -40,7 +40,7 @@ NetBox users are welcome to participate in either role, on stage or in the crowd
* First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases) of NetBox. If you're running an older version, it's likely that the bug has already been fixed.
* Next, search our [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the top right corner of the issue and add a thumbs up (:thumbsup:). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated.
* Next, search our [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the bottom left corner of the issue and add a thumbs up (:thumbsup:). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated.
* If you can't find any existing issues (open or closed) that seem to match yours, you're welcome to [submit a new bug report](https://github.com/netbox-community/netbox/issues/new?label=type%3A+bug&template=bug_report.yaml). Be sure to complete the entire report template, including detailed steps that someone triaging your issue can follow to confirm the reported behavior. (If we're not able to replicate the bug based on the information provided, we'll ask for additional detail.)

View File

@ -5,7 +5,7 @@
<a href="https://github.com/netbox-community/netbox/blob/master/LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache_2.0-blue.svg" alt="License" /></a>
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://img.shields.io/github/contributors/netbox-community/netbox?color=blue" alt="Contributors" /></a>
<a href="https://github.com/netbox-community/netbox/stargazers"><img src="https://img.shields.io/github/stars/netbox-community/netbox?style=flat" alt="GitHub stars" /></a>
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-10-blue" alt="Languages supported" /></a>
<a href="https://explore.transifex.com/netbox-community/netbox/"><img src="https://img.shields.io/badge/languages-15-blue" alt="Languages supported" /></a>
<a href="https://github.com/netbox-community/netbox/actions/workflows/ci.yml"><img src="https://github.com/netbox-community/netbox/workflows/CI/badge.svg?branch=master" alt="CI status" /></a>
<p></p>
</div>

View File

@ -16,7 +16,7 @@ Administrators are encouraged to adhere to industry best practices concerning th
## Reporting a Suspected Vulnerability
If you believe you've uncovered a security vulnerability and wish to report it confidentially, you may do so via email. Please note that any reported vulnerabilities **MUST** meet all the following conditions:
If you believe you've uncovered a security vulnerability and wish to report it confidentially, you may do so by emailing `security@netboxlabs.com`. Please ensure that your report meets all the following conditions:
* Affects the most recent stable release of NetBox, or a current beta release
* Affects a NetBox instance installed and configured per the official documentation
@ -24,7 +24,7 @@ If you believe you've uncovered a security vulnerability and wish to report it c
Please note that we **DO NOT** accept reports generated by automated tooling which merely suggest that a file or file(s) _may_ be vulnerable under certain conditions, as these are most often innocuous.
If you believe that you've found a vulnerability which meets all of these conditions, please [submit a draft security advisory](https://github.com/netbox-community/netbox/security/advisories/new) on GitHub. For any security concerns regarding NetBox deployed via Docker, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project.
For any security concerns regarding the community-maintained Docker image for NetBox, please see the [netbox-docker](https://github.com/netbox-community/netbox-docker) project.
### Bug Bounties

View File

@ -40,3 +40,22 @@ REMOTE_AUTH_BACKEND = 'social_core.backends.google.GoogleOAuth2'
NetBox supports single sign-on authentication via the [python-social-auth](https://github.com/python-social-auth) library. To enable SSO, specify the path to the desired authentication backend within the `social_core` Python package. Please see the complete list of [supported authentication backends](https://github.com/python-social-auth/social-core/tree/master/social_core/backends) for the available options.
Most remote authentication backends require some additional configuration through settings prefixed with `SOCIAL_AUTH_`. These will be automatically imported from NetBox's `configuration.py` file. Additionally, the [authentication pipeline](https://python-social-auth.readthedocs.io/en/latest/pipeline.html) can be customized via the `SOCIAL_AUTH_PIPELINE` parameter. (NetBox's default pipeline is defined in `netbox/settings.py` for your reference.)
#### Configuring the SSO module's appearance
The way a remote authentication backend is displayed to the user on the login
page may be adjusted via the `SOCIAL_AUTH_BACKEND_ATTRS` parameter, defaulting
to an empty dictionary. This dictionary maps a `social_core` module's name (ie.
`REMOTE_AUTH_BACKEND.name`) to a couple of parameters, `(display_name, icon)`.
The `display_name` is the name displayed to the user on the login page. The
icon may either be the URL of an icon; refer to a [Material Design
Icons](https://github.com/google/material-design-icons) icon's name; or be
`None` for no icon.
For instance, the OIDC backend may be customized with
```python
SOCIAL_AUTH_BACKEND_ATTRS = {
'oidc': ("My awesome SSO", "login"),
}
```

View File

@ -113,7 +113,7 @@ Create a [new release](https://github.com/netbox-community/netbox/releases/new)
* **Tag:** Current version (e.g. `v3.3.1`)
* **Target:** `master`
* **Title:** Version and date (e.g. `v3.3.1 - 2022-08-25`)
* **Description:** Copy from the pull request body
* **Description:** Copy from the pull request body, then promote the `###` headers to `##` ones
Once created, the release will become available for users to install.
@ -135,6 +135,6 @@ First, run the `build-site` action, by navigating to Actions > build-site > Run
Once the documentation files have been compiled, they must be published by running the `deploy-kinsta` action. Select the desired deployment environment (staging or production) and specify `latest` as the deploy tag.
Clear the CDN cache from the [Kinsta](https://my.kinsta.com/) portal. Navigate to _Sites_ / _NetBox Labs_ / _Live_, select _CDN_ in the left-nav, click the _Clear CDN cache_ button, and confirm the clear operation.
Clear the CDN cache from the [Kinsta](https://my.kinsta.com/) portal. Navigate to _Sites_ / _NetBox Labs_ / _Live_, select _Cache_ in the left-nav, click the _Clear Cache_ button, and confirm the clear operation.
Finally, verify that the documentation at <https://netboxlabs.com/docs/netbox/en/stable/> has been updated.

View File

@ -20,6 +20,8 @@ Then, commit the change and push to the `develop` branch on GitHub. Any new stri
Typically, translated strings need to be updated only as part of the NetBox [release process](./release-checklist.md).
Check the Transifex dashboard for languages that are not marked _ready for use_, being sure to click _Show all languages_ if it appears at the bottom of the list. Use machine translation to round out any not-ready languages. It's not necessary to review the machine translation immediately as the translation teams will handle that aspect; the goal at this stage is to get translations included in the Transifex pull request.
To update translated strings, start by initiating a sync from Transifex. From the Transifex dashboard, navigate to Settings > Integrations > GitHub > Manage, and click the **Manual Sync** button at top right.
![Transifex manual sync](../media/development/transifex_sync.png)

View File

@ -80,11 +80,11 @@ To create a viewset for a plugin model, subclass `NetBoxModelViewSet` in `api/vi
```python
# api/views.py
from netbox.api.viewsets import ModelViewSet
from netbox.api.viewsets import NetBoxModelViewSet
from my_plugin.models import MyModel
from .serializers import MyModelSerializer
class MyModelViewSet(ModelViewSet):
class MyModelViewSet(NetBoxModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
```

View File

@ -1,6 +1,34 @@
# NetBox v4.0
## v4.0.8 (FUTURE)
## v4.0.9 (FUTURE)
---
## v4.0.8 (2024-07-26)
### Enhancements
* [#14640](https://github.com/netbox-community/netbox/issues/14640) - Add Dutch language support
* [#14792](https://github.com/netbox-community/netbox/issues/14792) - Add Polish language support
* [#15375](https://github.com/netbox-community/netbox/issues/15375) - Enable customization of SSO backend name & icon
* [#15660](https://github.com/netbox-community/netbox/issues/15660) - Add Czech language support
* [#15696](https://github.com/netbox-community/netbox/issues/15696) - Add Danish language support
* [#16793](https://github.com/netbox-community/netbox/issues/16793) - Add Italian language support
* [#16933](https://github.com/netbox-community/netbox/issues/16933) - Enable toggling true/false marks on BooleanColumn
* [#16943](https://github.com/netbox-community/netbox/issues/16943) - Expand navigation breadcrumbs on job view to include the parent object
### Bug Fixes
* [#16357](https://github.com/netbox-community/netbox/issues/16357) - Replicate assigned type & tenant for cable when clicking "create an add another"
* [#16402](https://github.com/netbox-community/netbox/issues/16402) - Remove inoperative links from report result view
* [#16536](https://github.com/netbox-community/netbox/issues/16536) - Revert `role` & `role_id` filters for device components to `device_role` & `device_role_id` to avoid conflict with inventory item `role` field
* [#16624](https://github.com/netbox-community/netbox/issues/16624) - Correct OpenAPI schema definitions for several fields
* [#16760](https://github.com/netbox-community/netbox/issues/16760) - Fix data source syncing using git via a local path
* [#16819](https://github.com/netbox-community/netbox/issues/16819) - Highlight parent device in rack when viewing child device
* [#16838](https://github.com/netbox-community/netbox/issues/16838) - ActionsColumn should render extra buttons even when no stock actions are enabled
* [#16867](https://github.com/netbox-community/netbox/issues/16867) - Fix exception when a dashboard list widget references a model which has been removed
* [#16963](https://github.com/netbox-community/netbox/issues/16963) - Fix filtering of "accounts" link under providers list
* [#16964](https://github.com/netbox-community/netbox/issues/16964) - Ensure configured password validators are enforced
---

View File

@ -46,10 +46,20 @@ class LoginView(View):
return super().dispatch(*args, **kwargs)
def gen_auth_data(self, name, url, params):
display_name, icon_name = get_auth_backend_display(name)
display_name, icon_source = get_auth_backend_display(name)
icon_name = None
icon_img = None
if icon_source:
if '://' in icon_source:
icon_img = icon_source
else:
icon_name = icon_source
return {
'display_name': display_name,
'icon_name': icon_name,
'icon_img': icon_img,
'url': f'{url}?{urlencode(params)}',
}

View File

@ -25,7 +25,7 @@ class ProviderTable(ContactsColumnMixin, NetBoxTable):
account_count = columns.LinkedCountColumn(
accessor=tables.A('accounts__count'),
viewname='circuits:provideraccount_list',
url_params={'account_id': 'pk'},
url_params={'provider_id': 'pk'},
verbose_name=_('Account Count')
)
asns = columns.ManyToManyColumn(

View File

@ -84,9 +84,7 @@ class GitBackend(DataBackend):
clone_args = {
"branch": self.params.get('branch'),
"config": self.config,
"depth": 1,
"errstream": porcelain.NoneStream(),
"quiet": True,
}
if self.url_scheme in ('http', 'https'):
@ -97,6 +95,9 @@ class GitBackend(DataBackend):
"password": self.params.get('password'),
}
)
if self.url_scheme:
clone_args["quiet"] = True
clone_args["depth"] = 1
logger.debug(f"Cloning git repo: {self.url}")
try:

View File

@ -19,6 +19,7 @@ REVISION_BUTTONS = """
class ConfigRevisionTable(NetBoxTable):
is_active = columns.BooleanColumn(
verbose_name=_('Is Active'),
false_mark=None
)
actions = columns.ActionsColumn(
actions=('delete',),

View File

@ -13,7 +13,7 @@ class ConnectedEndpointsSerializer(serializers.ModelSerializer):
"""
Legacy serializer for pre-v3.3 connections
"""
connected_endpoints_type = serializers.SerializerMethodField(read_only=True)
connected_endpoints_type = serializers.SerializerMethodField(read_only=True, allow_null=True)
connected_endpoints = serializers.SerializerMethodField(read_only=True)
connected_endpoints_reachable = serializers.SerializerMethodField(read_only=True)
@ -22,7 +22,7 @@ class ConnectedEndpointsSerializer(serializers.ModelSerializer):
if endpoints := obj.connected_endpoints:
return f'{endpoints[0]._meta.app_label}.{endpoints[0]._meta.model_name}'
@extend_schema_field(serializers.ListField)
@extend_schema_field(serializers.ListField(allow_null=True))
def get_connected_endpoints(self, obj):
"""
Return the appropriate serializer for the type of connected object.

View File

@ -89,7 +89,7 @@ class CablePathSerializer(serializers.ModelSerializer):
class CabledObjectSerializer(serializers.ModelSerializer):
cable = CableSerializer(nested=True, read_only=True, allow_null=True)
cable_end = serializers.CharField(read_only=True)
link_peers_type = serializers.SerializerMethodField(read_only=True)
link_peers_type = serializers.SerializerMethodField(read_only=True, allow_null=True)
link_peers = serializers.SerializerMethodField(read_only=True)
_occupied = serializers.SerializerMethodField(read_only=True)

View File

@ -87,7 +87,7 @@ class DeviceSerializer(NetBoxModelSerializer):
]
brief_fields = ('id', 'url', 'display', 'name', 'description')
@extend_schema_field(NestedDeviceSerializer)
@extend_schema_field(NestedDeviceSerializer(allow_null=True))
def get_parent_device(self, obj):
try:
device_bay = obj.parent_bay

View File

@ -1430,12 +1430,12 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
to_field_name='model',
label=_('Device type (model)'),
)
role_id = django_filters.ModelMultipleChoiceFilter(
device_role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__role',
queryset=DeviceRole.objects.all(),
label=_('Device role (ID)'),
)
role = django_filters.ModelMultipleChoiceFilter(
device_role = django_filters.ModelMultipleChoiceFilter(
field_name='device__role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',

View File

@ -88,6 +88,8 @@ class Cable(PrimaryModel):
null=True
)
clone_fields = ('tenant', 'type',)
class Meta:
ordering = ('pk',)
verbose_name = _('cable')

View File

@ -63,7 +63,10 @@ class DeviceRoleTable(NetBoxTable):
verbose_name=_('VMs')
)
color = columns.ColorColumn()
vm_role = columns.BooleanColumn()
vm_role = columns.BooleanColumn(
verbose_name=_('VM role'),
false_mark=None
)
config_template = tables.Column(
linkify=True
)
@ -329,6 +332,7 @@ class CableTerminationTable(NetBoxTable):
)
mark_connected = columns.BooleanColumn(
verbose_name=_('Mark Connected'),
false_mark=None
)
class Meta:
@ -586,7 +590,8 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
}
)
mgmt_only = columns.BooleanColumn(
verbose_name=_('Management Only')
verbose_name=_('Management Only'),
false_mark=None
)
speed_formatted = columns.TemplateColumn(
template_code='{% load helpers %}{{ value|humanize_speed }}',
@ -913,6 +918,7 @@ class InventoryItemTable(DeviceComponentTable):
)
discovered = columns.BooleanColumn(
verbose_name=_('Discovered'),
false_mark=None
)
parent = tables.Column(
linkify=True,

View File

@ -86,7 +86,8 @@ class DeviceTypeTable(NetBoxTable):
linkify=True
)
is_full_depth = columns.BooleanColumn(
verbose_name=_('Full Depth')
verbose_name=_('Full Depth'),
false_mark=None
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),
@ -98,7 +99,10 @@ class DeviceTypeTable(NetBoxTable):
verbose_name=_('U Height'),
template_code='{{ value|floatformat }}'
)
exclude_from_utilization = columns.BooleanColumn()
exclude_from_utilization = columns.BooleanColumn(
verbose_name=_('Exclude from utilization'),
false_mark=None
)
weight = columns.TemplateColumn(
verbose_name=_('Weight'),
template_code=WEIGHT,
@ -221,7 +225,8 @@ class InterfaceTemplateTable(ComponentTemplateTable):
verbose_name=_('Enabled'),
)
mgmt_only = columns.BooleanColumn(
verbose_name=_('Management Only')
verbose_name=_('Management Only'),
false_mark=None
)
actions = columns.ActionsColumn(
actions=('edit', 'delete'),

View File

@ -32,11 +32,11 @@ class DeviceComponentFilterSetTests:
params = {'device_type': [device_types[0].model, device_types[1].model]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_role(self):
def test_device_role(self):
role = DeviceRole.objects.all()[:2]
params = {'role_id': [role[0].pk, role[1].pk]}
params = {'device_role_id': [role[0].pk, role[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'role': [role[0].slug, role[1].slug]}
params = {'device_role': [role[0].slug, role[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
@ -4693,6 +4693,13 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'device_type': [device_types[0].model, device_types[1].model]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_device_role(self):
role = DeviceRole.objects.all()[:2]
params = {'device_role_id': [role[0].pk, role[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'device_role': [role[0].slug, role[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_role(self):
role = DeviceRole.objects.all()[:2]
params = {'role_id': [role[0].pk, role[1].pk]}

View File

@ -38,7 +38,7 @@ class ScriptSerializer(ValidatedModelSerializer):
def get_display(self, obj):
return f'{obj.name} ({obj.module})'
@extend_schema_field(serializers.CharField())
@extend_schema_field(serializers.CharField(allow_null=True))
def get_description(self, obj):
if obj.python_class:
return obj.python_class().description

View File

@ -251,6 +251,10 @@ class ObjectListWidget(DashboardWidget):
def render(self, request):
app_label, model_name = self.config['model'].split('.')
model = ObjectType.objects.get_by_natural_key(app_label, model_name).model_class()
if not model:
logger.debug(f"Dashboard Widget model_class not found: {app_label}:{model_name}")
return
viewname = get_viewname(model, action='list')
# Evaluate user's permission. Note that this controls only whether the HTMX element is

View File

@ -54,7 +54,8 @@ class CustomFieldTable(NetBoxTable):
verbose_name=_('Object Types')
)
required = columns.BooleanColumn(
verbose_name=_('Required')
verbose_name=_('Required'),
false_mark=None
)
ui_visible = columns.ChoiceFieldColumn(
verbose_name=_('Visible')
@ -79,6 +80,7 @@ class CustomFieldTable(NetBoxTable):
)
is_cloneable = columns.BooleanColumn(
verbose_name=_('Is Cloneable'),
false_mark=None
)
validation_minimum = tables.Column(
verbose_name=_('Minimum Value'),
@ -125,6 +127,7 @@ class CustomFieldChoiceSetTable(NetBoxTable):
)
order_alphabetically = columns.BooleanColumn(
verbose_name=_('Order Alphabetically'),
false_mark=None
)
class Meta(NetBoxTable.Meta):
@ -149,6 +152,7 @@ class CustomLinkTable(NetBoxTable):
)
new_window = columns.BooleanColumn(
verbose_name=_('New Window'),
false_mark=None
)
class Meta(NetBoxTable.Meta):
@ -170,6 +174,7 @@ class ExportTemplateTable(NetBoxTable):
)
as_attachment = columns.BooleanColumn(
verbose_name=_('As Attachment'),
false_mark=None
)
data_source = tables.Column(
verbose_name=_('Data Source'),
@ -238,6 +243,7 @@ class SavedFilterTable(NetBoxTable):
)
shared = columns.BooleanColumn(
verbose_name=_('Shared'),
false_mark=None
)
def value_parameters(self, value):

View File

@ -86,7 +86,8 @@ class RIRTable(NetBoxTable):
linkify=True
)
is_private = columns.BooleanColumn(
verbose_name=_('Private')
verbose_name=_('Private'),
false_mark=None
)
aggregate_count = columns.LinkedCountColumn(
viewname='ipam:aggregate_list',
@ -258,10 +259,12 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
linkify=True
)
is_pool = columns.BooleanColumn(
verbose_name=_('Pool')
verbose_name=_('Pool'),
false_mark=None
)
mark_utilized = columns.BooleanColumn(
verbose_name=_('Marked Utilized')
verbose_name=_('Marked Utilized'),
false_mark=None
)
utilization = PrefixUtilizationColumn(
verbose_name=_('Utilization'),
@ -314,7 +317,8 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
linkify=True
)
mark_utilized = columns.BooleanColumn(
verbose_name=_('Marked Utilized')
verbose_name=_('Marked Utilized'),
false_mark=None
)
utilization = columns.UtilizationColumn(
verbose_name=_('Utilization'),
@ -386,7 +390,8 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
assigned = columns.BooleanColumn(
accessor='assigned_object_id',
linkify=lambda record: record.assigned_object.get_absolute_url(),
verbose_name=_('Assigned')
verbose_name=_('Assigned'),
false_mark=None
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments'),

View File

@ -215,6 +215,7 @@ class InterfaceVLANTable(NetBoxTable):
)
tagged = columns.BooleanColumn(
verbose_name=_('Tagged'),
false_mark=None
)
site = tables.Column(
verbose_name=_('Site'),

View File

@ -30,7 +30,8 @@ class VRFTable(TenancyColumnsMixin, NetBoxTable):
verbose_name=_('RD')
)
enforce_unique = columns.BooleanColumn(
verbose_name=_('Unique')
verbose_name=_('Unique'),
false_mark=None
)
import_targets = columns.TemplateColumn(
verbose_name=_('Import Targets'),

View File

@ -49,12 +49,15 @@ AUTH_BACKEND_ATTRS = {
'okta-openidconnect': ('Okta (OIDC)', None),
'salesforce-oauth2': ('Salesforce', 'salesforce'),
}
# Override with potential user configuration
AUTH_BACKEND_ATTRS.update(getattr(settings, 'SOCIAL_AUTH_BACKEND_ATTRS', {}))
def get_auth_backend_display(name):
"""
Return the user-friendly name and icon name for a remote authentication backend, if known. Defaults to the
raw backend name and no icon.
Return the user-friendly name and icon name for a remote authentication backend, if
known. Obtained from the defaults dictionary AUTH_BACKEND_ATTRS, overridden by the
setting `SOCIAL_AUTH_BACKEND_ATTRS`. Defaults to the raw backend name and no icon.
"""
return AUTH_BACKEND_ATTRS.get(name, (name, None))

View File

@ -742,11 +742,16 @@ RQ_QUEUES.update({
# Supported translation languages
LANGUAGES = (
('cs', _('Czech')),
('da', _('Danish')),
('de', _('German')),
('en', _('English')),
('es', _('Spanish')),
('fr', _('French')),
('it', _('Italian')),
('ja', _('Japanese')),
('nl', _('Dutch')),
('pl', _('Polish')),
('pt', _('Portuguese')),
('ru', _('Russian')),
('tr', _('Turkish')),

View File

@ -194,14 +194,23 @@ class BooleanColumn(tables.Column):
Custom implementation of BooleanColumn to render a nicely-formatted checkmark or X icon instead of a Unicode
character.
"""
TRUE_MARK = mark_safe('<span class="text-success"><i class="mdi mdi-check-bold"></i></span>')
FALSE_MARK = mark_safe('<span class="text-danger"><i class="mdi mdi-close-thick"></i></span>')
EMPTY_MARK = mark_safe('<span class="text-muted">&mdash;</span>') # Placeholder
def __init__(self, *args, true_mark=TRUE_MARK, false_mark=FALSE_MARK, **kwargs):
self.true_mark = true_mark
self.false_mark = false_mark
super().__init__(*args, **kwargs)
def render(self, value):
if value:
rendered = '<span class="text-success"><i class="mdi mdi-check-bold"></i></span>'
elif value is None:
rendered = '<span class="text-muted">&mdash;</span>'
else:
rendered = '<span class="text-danger"><i class="mdi mdi-close-thick"></i></span>'
return mark_safe(rendered)
if value is None:
return self.EMPTY_MARK
if value and self.true_mark:
return self.true_mark
if not value and self.false_mark:
return self.false_mark
return self.EMPTY_MARK
def value(self, value):
return str(value)
@ -249,7 +258,7 @@ class ActionsColumn(tables.Column):
def render(self, record, table, **kwargs):
# Skip dummy records (e.g. available VLANs) or those with no actions
if not getattr(record, 'pk', None) or not self.actions:
if not getattr(record, 'pk', None) or not (self.actions or self.extra_buttons):
return ''
model = table.Meta.model

Binary file not shown.

Binary file not shown.

View File

@ -27,10 +27,10 @@
"bootstrap": "5.3.3",
"clipboard": "2.0.11",
"flatpickr": "4.6.13",
"gridstack": "10.3.0",
"gridstack": "10.3.1",
"htmx.org": "1.9.12",
"query-string": "9.0.0",
"sass": "1.77.6",
"query-string": "9.1.0",
"sass": "1.77.8",
"tom-select": "2.3.1",
"typeface-inter": "3.18.1",
"typeface-roboto-mono": "1.1.13"

View File

@ -1754,10 +1754,10 @@ graphql@16.8.1:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07"
integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==
gridstack@10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-10.3.0.tgz#8fa065f896d0a880c5c54c24d189f3197184488a"
integrity sha512-eGKsmU2TppV4coyDu9IIdIkm4qjgLLdjlEOFwQyQMuSwfOpzSfLdPc8du0HuebGr7CvAIrJxN4lBOmGrWSBg9g==
gridstack@10.3.1:
version "10.3.1"
resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-10.3.1.tgz#4ed704279c40094fc1b9e3318f20b573f2fe9f40"
integrity sha512-Ra82k/88gdeiu3ZP40COS4bI4sGhNQlZAaAQ6szfPfr68zVpsXxiyLKr5zYcTpKX4jjcwyNsNNdcV1tDJc71fA==
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
@ -2346,10 +2346,10 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
query-string@9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.0.0.tgz#1fe177cd95545600f0deab93f5fb02fd4e3e7273"
integrity sha512-4EWwcRGsO2H+yzq6ddHcVqkCQ2EFUSfDMEjF8ryp8ReymyZhIuaFRGLomeOQLkrzacMHoyky2HW0Qe30UbzkKw==
query-string@9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.1.0.tgz#5f12a4653a4ba56021e113b5cf58e56581823e7a"
integrity sha512-t6dqMECpCkqfyv2FfwVS1xcB6lgXW/0XZSaKdsCNGYkqMO76AFiJEg4vINzoDKcZa6MS7JX+OHIjwh06K5vczw==
dependencies:
decode-uri-component "^0.4.1"
filter-obj "^5.1.0"
@ -2482,10 +2482,10 @@ safe-regex-test@^1.0.3:
es-errors "^1.3.0"
is-regex "^1.1.4"
sass@1.77.6:
version "1.77.6"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.6.tgz#898845c1348078c2e6d1b64f9ee06b3f8bd489e4"
integrity sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==
sass@1.77.8:
version "1.77.8"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.8.tgz#9f18b449ea401759ef7ec1752a16373e296b52bd"
integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"

View File

@ -7,7 +7,11 @@
<html
lang="en"
data-netbox-url-name="{{ request.resolver_match.url_name }}"
data-netbox-base-path="{{ settings.BASE_PATH }}"
data-netbox-version="{{ settings.VERSION }}"
{% if request.user.is_authenticated %}
data-netbox-user-name="{{ request.user.username }}"
data-netbox-user-id="{{ request.user.pk }}"
{% endif %}
>
<head>
<meta charset="UTF-8" />

View File

@ -4,6 +4,18 @@
{% load perms %}
{% load i18n %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item">
<a href="{% url 'core:job_list' %}?object_type={{ object.object_type_id }}">{{ object.object|meta:"verbose_name_plural"|bettertitle }}</a>
</li>
{% with parent_jobs_viewname=object.object|viewname:"jobs" %}
<li class="breadcrumb-item">
<a href="{% url parent_jobs_viewname pk=object.object.pk %}">{{ object.object }}</a>
</li>
{% endwith %}
{% endblock breadcrumbs %}
{% block control-buttons %}
{% if request.user|can_delete:object %}
{% delete_button object %}

View File

@ -31,7 +31,7 @@
<td class="d-flex justify-content-between align-items-start">
{% if object.rack %}
{{ object.rack|linkify }}
<a href="{{ object.rack.get_absolute_url }}?device={{ object.pk }}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
<a href="{{ object.rack.get_absolute_url }}?device={% firstof object.parent_bay.device.pk object.pk %}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
<i class="mdi mdi-view-day-outline"></i>
</a>
{% else %}

View File

@ -24,7 +24,7 @@
<table class="table table-hover">
{% for test, data in tests.items %}
<tr>
<td class="font-monospace"><a href="#{{ test }}">{{ test }}</a></td>
<td class="font-monospace">{{ test }}</td>
<td class="text-end report-stats">
<span class="badge text-bg-success">{{ data.success }}</span>
<span class="badge text-bg-info">{{ data.info }}</span>

View File

@ -78,7 +78,8 @@
{% for backend in auth_backends %}
<div class="col">
<a href="{{ backend.url }}" class="btn w-100">
{% if backend.icon_name %}<i class="mdi mdi-{{ backend.icon_name }}"></i>{% endif %}
{% if backend.icon_name %}<i class="mdi mdi-{{ backend.icon_name }}"></i>
{% elif backend.icon_img %}<img src="{{ backend.icon_img }}" height="24" class="me-2" />{% endif %}
{{ backend.display_name }}
</a>
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,21 +6,21 @@
# Translators:
# Quentin Laurent, 2024
# Xavier W, 2024
# Jeremy Stretch, 2024
# Jonathan Senecal, 2024
# Lou Lecrivain, 2024
# Jean Benoit <jean@unistra.fr>, 2024
# thomas rivemale, 2024
# Jeff Gehlbach, 2024
# Jeremy Stretch, 2024
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-09 05:02+0000\n"
"POT-Creation-Date: 2024-07-11 05:01+0000\n"
"PO-Revision-Date: 2023-10-30 17:48+0000\n"
"Last-Translator: Jeff Gehlbach, 2024\n"
"Last-Translator: Jeremy Stretch, 2024\n"
"Language-Team: French (https://app.transifex.com/netbox-community/teams/178115/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -69,7 +69,7 @@ msgstr "Dernière utilisation"
msgid "Allowed IPs"
msgstr "IP autorisées"
#: netbox/account/views.py:204
#: netbox/account/views.py:214
msgid "Your preferences have been updated."
msgstr "Vos préférences ont été mises à jour."
@ -1519,16 +1519,16 @@ msgstr "Mot de passe"
msgid "Branch"
msgstr "Branche"
#: netbox/core/data_backends.py:105
#: netbox/core/data_backends.py:106
#, python-brace-format
msgid "Fetching remote data failed ({name}): {error}"
msgstr "La récupération des données distantes a échoué ({name}) : {error}"
#: netbox/core/data_backends.py:118
#: netbox/core/data_backends.py:119
msgid "AWS access key ID"
msgstr "ID de clé d'accès AWS"
#: netbox/core/data_backends.py:122
#: netbox/core/data_backends.py:123
msgid "AWS secret access key"
msgstr "Clé d'accès secrète AWS"
@ -1877,7 +1877,7 @@ msgstr ""
msgid "last updated"
msgstr "dernière mise à jour"
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:442
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:444
msgid "path"
msgstr "chemin"
@ -4656,53 +4656,53 @@ msgstr "longueur"
msgid "length unit"
msgstr "unité de longueur"
#: netbox/dcim/models/cables.py:93
#: netbox/dcim/models/cables.py:95
msgid "cable"
msgstr "câble"
#: netbox/dcim/models/cables.py:94
#: netbox/dcim/models/cables.py:96
msgid "cables"
msgstr "câbles"
#: netbox/dcim/models/cables.py:163
#: netbox/dcim/models/cables.py:165
msgid "Must specify a unit when setting a cable length"
msgstr ""
"Vous devez spécifier une unité lors du réglage de la longueur du câble"
#: netbox/dcim/models/cables.py:166
#: netbox/dcim/models/cables.py:168
msgid "Must define A and B terminations when creating a new cable."
msgstr ""
"Vous devez définir les terminaisons A et B lors de la création d'un nouveau "
"câble."
#: netbox/dcim/models/cables.py:173
#: netbox/dcim/models/cables.py:175
msgid "Cannot connect different termination types to same end of cable."
msgstr ""
"Impossible de connecter différents types de terminaisons à la même extrémité"
" du câble."
#: netbox/dcim/models/cables.py:181
#: netbox/dcim/models/cables.py:183
#, python-brace-format
msgid "Incompatible termination types: {type_a} and {type_b}"
msgstr "Types de terminaison incompatibles : {type_a} et {type_b}"
#: netbox/dcim/models/cables.py:191
#: netbox/dcim/models/cables.py:193
msgid "A and B terminations cannot connect to the same object."
msgstr "Les terminaisons A et B ne peuvent pas se connecter au même objet."
#: netbox/dcim/models/cables.py:258 netbox/ipam/models/asns.py:37
#: netbox/dcim/models/cables.py:260 netbox/ipam/models/asns.py:37
msgid "end"
msgstr "fin"
#: netbox/dcim/models/cables.py:311
#: netbox/dcim/models/cables.py:313
msgid "cable termination"
msgstr "terminaison de câble"
#: netbox/dcim/models/cables.py:312
#: netbox/dcim/models/cables.py:314
msgid "cable terminations"
msgstr "terminaisons de câble"
#: netbox/dcim/models/cables.py:331
#: netbox/dcim/models/cables.py:333
#, python-brace-format
msgid ""
"Duplicate termination found for {app_label}.{model} {termination_id}: cable "
@ -4711,34 +4711,34 @@ msgstr ""
"Un doublon de terminaison a été trouvé pour {app_label}.{model} "
"{termination_id}: câble {cable_pk}"
#: netbox/dcim/models/cables.py:341
#: netbox/dcim/models/cables.py:343
#, python-brace-format
msgid "Cables cannot be terminated to {type_display} interfaces"
msgstr "Les câbles ne peuvent pas être raccordés à {type_display} interfaces"
#: netbox/dcim/models/cables.py:348
#: netbox/dcim/models/cables.py:350
msgid "Circuit terminations attached to a provider network may not be cabled."
msgstr ""
"Les terminaisons de circuit connectées au réseau d'un fournisseur peuvent ne"
" pas être câblées."
#: netbox/dcim/models/cables.py:446 netbox/extras/models/configs.py:50
#: netbox/dcim/models/cables.py:448 netbox/extras/models/configs.py:50
msgid "is active"
msgstr "est actif"
#: netbox/dcim/models/cables.py:450
#: netbox/dcim/models/cables.py:452
msgid "is complete"
msgstr "est terminé"
#: netbox/dcim/models/cables.py:454
#: netbox/dcim/models/cables.py:456
msgid "is split"
msgstr "est divisé"
#: netbox/dcim/models/cables.py:462
#: netbox/dcim/models/cables.py:464
msgid "cable path"
msgstr "chemin de câble"
#: netbox/dcim/models/cables.py:463
#: netbox/dcim/models/cables.py:465
msgid "cable paths"
msgstr "chemins de câbles"
@ -6983,43 +6983,43 @@ msgstr ""
"Format non valide. Les paramètres d'URL doivent être transmis sous forme de "
"dictionnaire."
#: netbox/extras/dashboard/widgets.py:284
#: netbox/extras/dashboard/widgets.py:288
msgid "RSS Feed"
msgstr "Fil RSS"
#: netbox/extras/dashboard/widgets.py:289
#: netbox/extras/dashboard/widgets.py:293
msgid "Embed an RSS feed from an external website."
msgstr "Intégrez un flux RSS provenant d'un site Web externe."
#: netbox/extras/dashboard/widgets.py:296
#: netbox/extras/dashboard/widgets.py:300
msgid "Feed URL"
msgstr "URL du flux"
#: netbox/extras/dashboard/widgets.py:301
#: netbox/extras/dashboard/widgets.py:305
msgid "The maximum number of objects to display"
msgstr "Le nombre maximum d'objets à afficher"
#: netbox/extras/dashboard/widgets.py:306
#: netbox/extras/dashboard/widgets.py:310
msgid "How long to stored the cached content (in seconds)"
msgstr "Durée de conservation du contenu mis en cache (en secondes)"
#: netbox/extras/dashboard/widgets.py:358
#: netbox/extras/dashboard/widgets.py:362
#: netbox/templates/account/base.html:10
#: netbox/templates/account/bookmarks.html:7
#: netbox/templates/inc/user_menu.html:30
msgid "Bookmarks"
msgstr "Signets"
#: netbox/extras/dashboard/widgets.py:362
#: netbox/extras/dashboard/widgets.py:366
msgid "Show your personal bookmarks"
msgstr "Afficher vos favoris personnels"
#: netbox/extras/events.py:134
#: netbox/extras/events.py:137
#, python-brace-format
msgid "Unknown action type for an event rule: {action_type}"
msgstr "Type d'action inconnu pour une règle d'événement : {action_type}"
#: netbox/extras/events.py:182
#: netbox/extras/events.py:185
#, python-brace-format
msgid "Cannot import events pipeline {name} error: {error}"
msgstr ""
@ -10048,7 +10048,7 @@ msgstr ""
"Valeur non valide. Spécifiez un type de contenu comme "
"«<app_label>.<model_name>'."
#: netbox/netbox/authentication/__init__.py:138
#: netbox/netbox/authentication/__init__.py:141
#, python-brace-format
msgid "Invalid permission {permission} for model {model}"
msgstr "Autorisation non valide {permission} pour modèle {model}"
@ -10357,7 +10357,7 @@ msgstr "Type (s) d'objet"
#: netbox/netbox/forms/__init__.py:40
msgid "Lookup"
msgstr ""
msgstr "Chercher"
#: netbox/netbox/forms/base.py:88
msgid ""
@ -10906,43 +10906,43 @@ msgstr "Impossible d'ajouter des magasins au registre après l'initialisation"
msgid "Cannot delete stores from registry"
msgstr "Impossible de supprimer des magasins du registre"
#: netbox/netbox/settings.py:741
#: netbox/netbox/settings.py:742
msgid "German"
msgstr "allemand"
#: netbox/netbox/settings.py:742
#: netbox/netbox/settings.py:743
msgid "English"
msgstr "Anglais"
#: netbox/netbox/settings.py:743
#: netbox/netbox/settings.py:744
msgid "Spanish"
msgstr "espagnol"
#: netbox/netbox/settings.py:744
#: netbox/netbox/settings.py:745
msgid "French"
msgstr "français"
#: netbox/netbox/settings.py:745
#: netbox/netbox/settings.py:746
msgid "Japanese"
msgstr "japonais"
#: netbox/netbox/settings.py:746
#: netbox/netbox/settings.py:747
msgid "Portuguese"
msgstr "portugais"
#: netbox/netbox/settings.py:747
#: netbox/netbox/settings.py:748
msgid "Russian"
msgstr "russe"
#: netbox/netbox/settings.py:748
#: netbox/netbox/settings.py:749
msgid "Turkish"
msgstr "Turc"
#: netbox/netbox/settings.py:749
#: netbox/netbox/settings.py:750
msgid "Ukrainian"
msgstr "Ukrainien"
#: netbox/netbox/settings.py:750
#: netbox/netbox/settings.py:751
msgid "Chinese"
msgstr "chinois"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-09 05:02+0000\n"
"POT-Creation-Date: 2024-07-11 05:01+0000\n"
"PO-Revision-Date: 2023-10-30 17:48+0000\n"
"Last-Translator: Artem Kotik, 2024\n"
"Language-Team: Russian (https://app.transifex.com/netbox-community/teams/178115/ru/)\n"
@ -70,7 +70,7 @@ msgstr "Последнее использование"
msgid "Allowed IPs"
msgstr "Разрешенные IP-адреса"
#: netbox/account/views.py:204
#: netbox/account/views.py:214
msgid "Your preferences have been updated."
msgstr "Ваши настройки были обновлены."
@ -263,7 +263,7 @@ msgstr "Аккаунт провайдера (ID)"
#: netbox/circuits/filtersets.py:171
msgid "Provider account (account)"
msgstr "Учетная запись провайдера (учетная запись)"
msgstr "Учетная запись провайдера"
#: netbox/circuits/filtersets.py:176
msgid "Provider network (ID)"
@ -1520,16 +1520,16 @@ msgstr "Пароль"
msgid "Branch"
msgstr "Ветка"
#: netbox/core/data_backends.py:105
#: netbox/core/data_backends.py:106
#, python-brace-format
msgid "Fetching remote data failed ({name}): {error}"
msgstr "Не удалось получить удаленные данные ({name}): {error}"
#: netbox/core/data_backends.py:118
#: netbox/core/data_backends.py:119
msgid "AWS access key ID"
msgstr "ID ключа доступа AWS"
#: netbox/core/data_backends.py:122
#: netbox/core/data_backends.py:123
msgid "AWS secret access key"
msgstr "Секретный ключ доступа AWS"
@ -1872,7 +1872,7 @@ msgstr ""
msgid "last updated"
msgstr "последнее обновление"
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:442
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:444
msgid "path"
msgstr "путь"
@ -4652,50 +4652,50 @@ msgstr "Длина"
msgid "length unit"
msgstr "длина единицы"
#: netbox/dcim/models/cables.py:93
#: netbox/dcim/models/cables.py:95
msgid "cable"
msgstr "кабель"
#: netbox/dcim/models/cables.py:94
#: netbox/dcim/models/cables.py:96
msgid "cables"
msgstr "кабели"
#: netbox/dcim/models/cables.py:163
#: netbox/dcim/models/cables.py:165
msgid "Must specify a unit when setting a cable length"
msgstr "При настройке длины кабеля необходимо указать единицу измерения"
#: netbox/dcim/models/cables.py:166
#: netbox/dcim/models/cables.py:168
msgid "Must define A and B terminations when creating a new cable."
msgstr ""
"При создании нового кабеля необходимо определить концевые разъемы A и B."
#: netbox/dcim/models/cables.py:173
#: netbox/dcim/models/cables.py:175
msgid "Cannot connect different termination types to same end of cable."
msgstr ""
"Невозможно подключить разные типы разъемов к одному и тому же концу кабеля."
#: netbox/dcim/models/cables.py:181
#: netbox/dcim/models/cables.py:183
#, python-brace-format
msgid "Incompatible termination types: {type_a} and {type_b}"
msgstr "Несовместимые типы терминации: {type_a} а также {type_b}"
#: netbox/dcim/models/cables.py:191
#: netbox/dcim/models/cables.py:193
msgid "A and B terminations cannot connect to the same object."
msgstr "Окончания A и B не могут подключаться к одному и тому же объекту."
#: netbox/dcim/models/cables.py:258 netbox/ipam/models/asns.py:37
#: netbox/dcim/models/cables.py:260 netbox/ipam/models/asns.py:37
msgid "end"
msgstr "конец"
#: netbox/dcim/models/cables.py:311
#: netbox/dcim/models/cables.py:313
msgid "cable termination"
msgstr "кабельный терминатор"
#: netbox/dcim/models/cables.py:312
#: netbox/dcim/models/cables.py:314
msgid "cable terminations"
msgstr "кабельные терминаторы"
#: netbox/dcim/models/cables.py:331
#: netbox/dcim/models/cables.py:333
#, python-brace-format
msgid ""
"Duplicate termination found for {app_label}.{model} {termination_id}: cable "
@ -4704,34 +4704,34 @@ msgstr ""
"Обнаружен дубликат увольнения для {app_label}.{model} {termination_id}: "
"кабель {cable_pk}"
#: netbox/dcim/models/cables.py:341
#: netbox/dcim/models/cables.py:343
#, python-brace-format
msgid "Cables cannot be terminated to {type_display} interfaces"
msgstr "Кабели не могут быть подключены к {type_display} интерфейсов"
#: netbox/dcim/models/cables.py:348
#: netbox/dcim/models/cables.py:350
msgid "Circuit terminations attached to a provider network may not be cabled."
msgstr ""
"Концевые разъемы, подключенные к сети провайдера, могут не подключаться к "
"кабелям."
#: netbox/dcim/models/cables.py:446 netbox/extras/models/configs.py:50
#: netbox/dcim/models/cables.py:448 netbox/extras/models/configs.py:50
msgid "is active"
msgstr "активен"
#: netbox/dcim/models/cables.py:450
#: netbox/dcim/models/cables.py:452
msgid "is complete"
msgstr "завершен"
#: netbox/dcim/models/cables.py:454
#: netbox/dcim/models/cables.py:456
msgid "is split"
msgstr "разделен"
#: netbox/dcim/models/cables.py:462
#: netbox/dcim/models/cables.py:464
msgid "cable path"
msgstr "кабельная трасса"
#: netbox/dcim/models/cables.py:463
#: netbox/dcim/models/cables.py:465
msgid "cable paths"
msgstr "кабельные трассы"
@ -5274,11 +5274,11 @@ msgstr "Нанесенное на карту положение на соотв
#: netbox/dcim/models/device_components.py:1007
msgid "front port"
msgstr "передний порт"
msgstr "фронтальный порт"
#: netbox/dcim/models/device_components.py:1008
msgid "front ports"
msgstr "передние порты"
msgstr "фронтальные порты"
#: netbox/dcim/models/device_components.py:1022
#, python-brace-format
@ -5541,7 +5541,7 @@ msgstr ""
#: netbox/dcim/models/devices.py:517
msgid "platform"
msgstr "платформ"
msgstr "платформа"
#: netbox/dcim/models/devices.py:518
msgid "platforms"
@ -5604,7 +5604,7 @@ msgstr "широта"
#: netbox/dcim/models/devices.py:711 netbox/dcim/models/devices.py:719
#: netbox/dcim/models/sites.py:212 netbox/dcim/models/sites.py:220
msgid "GPS coordinate in decimal format (xx.yyyyyy)"
msgstr "Координата GPS в десятичном формате (xx.yyyyyy)"
msgstr "GPS координата в десятичном формате (xx.yyyyyy)"
#: netbox/dcim/models/devices.py:714 netbox/dcim/models/sites.py:215
msgid "longitude"
@ -6059,15 +6059,15 @@ msgstr "Группа сайтов верхнего уровня с этой по
#: netbox/dcim/models/sites.py:115
msgid "site group"
msgstr "группа мест"
msgstr "группа сайта"
#: netbox/dcim/models/sites.py:116
msgid "site groups"
msgstr "группы мест"
msgstr "группы сайтов"
#: netbox/dcim/models/sites.py:141
msgid "Full name of the site"
msgstr "Полное название сайта"
msgstr "Полное имя сайта"
#: netbox/dcim/models/sites.py:181 netbox/dcim/models/sites.py:279
msgid "facility"
@ -6095,11 +6095,11 @@ msgstr "Если отличается от физического адреса"
#: netbox/dcim/models/sites.py:238
msgid "site"
msgstr "место"
msgstr "сайт"
#: netbox/dcim/models/sites.py:239
msgid "sites"
msgstr "Сайты"
msgstr "сайты"
#: netbox/dcim/models/sites.py:309
msgid "A location with this name already exists within the specified site."
@ -6137,7 +6137,7 @@ msgstr "Устройство A"
#: netbox/dcim/tables/cables.py:72 netbox/wireless/tables/wirelesslink.py:31
msgid "Device B"
msgstr "Устройство B"
msgstr "Устройство Б"
#: netbox/dcim/tables/cables.py:78
msgid "Location A"
@ -6145,7 +6145,7 @@ msgstr "Локация A"
#: netbox/dcim/tables/cables.py:84
msgid "Location B"
msgstr "Локация B"
msgstr "Локация Б"
#: netbox/dcim/tables/cables.py:90
msgid "Rack A"
@ -6153,7 +6153,7 @@ msgstr "Стойка A"
#: netbox/dcim/tables/cables.py:96
msgid "Rack B"
msgstr "Стойка B"
msgstr "Стойка Б"
#: netbox/dcim/tables/cables.py:102
msgid "Site A"
@ -6161,7 +6161,7 @@ msgstr "Сайт A"
#: netbox/dcim/tables/cables.py:108
msgid "Site B"
msgstr "Сайт B"
msgstr "Сайт Б"
#: netbox/dcim/tables/connections.py:31 netbox/dcim/tables/connections.py:50
#: netbox/dcim/tables/connections.py:71
@ -6224,11 +6224,11 @@ msgstr "Адрес IPv6"
#: netbox/dcim/tables/devices.py:207
msgid "VC Position"
msgstr "Позиция VC"
msgstr "Позиция в шасси"
#: netbox/dcim/tables/devices.py:210
msgid "VC Priority"
msgstr "Приоритет VC"
msgstr "Приоритет шасси"
#: netbox/dcim/tables/devices.py:217 netbox/templates/dcim/device_edit.html:38
#: netbox/templates/dcim/devicebay_populate.html:16
@ -6275,7 +6275,7 @@ msgstr "Интерфейсы"
#: netbox/dcim/tables/devices.py:246
msgid "Front ports"
msgstr "Передние порты"
msgstr "Фронтальные порты"
#: netbox/dcim/tables/devices.py:252
msgid "Device bays"
@ -6472,7 +6472,7 @@ msgstr "Розетки питания"
#: netbox/templates/dcim/module.html:37
#: netbox/templates/dcim/moduletype/base.html:37
msgid "Front Ports"
msgstr "Передние порты"
msgstr "Фронтальные порты"
#: netbox/dcim/tables/devicetypes.py:131 netbox/dcim/views.py:1018
#: netbox/dcim/views.py:1257 netbox/dcim/views.py:1946
@ -6957,43 +6957,43 @@ msgstr "Количество отображаемых объектов по ум
msgid "Invalid format. URL parameters must be passed as a dictionary."
msgstr "Неверный формат. Параметры URL должны быть переданы в виде словаря."
#: netbox/extras/dashboard/widgets.py:284
#: netbox/extras/dashboard/widgets.py:288
msgid "RSS Feed"
msgstr "RSS-канал"
#: netbox/extras/dashboard/widgets.py:289
#: netbox/extras/dashboard/widgets.py:293
msgid "Embed an RSS feed from an external website."
msgstr "Вставьте RSS-канал с внешнего веб-сайта."
#: netbox/extras/dashboard/widgets.py:296
#: netbox/extras/dashboard/widgets.py:300
msgid "Feed URL"
msgstr "URL-адрес ленты"
#: netbox/extras/dashboard/widgets.py:301
#: netbox/extras/dashboard/widgets.py:305
msgid "The maximum number of objects to display"
msgstr "Максимальное количество отображаемых объектов"
#: netbox/extras/dashboard/widgets.py:306
#: netbox/extras/dashboard/widgets.py:310
msgid "How long to stored the cached content (in seconds)"
msgstr "Как долго хранить кэшированный контент (в секундах)"
#: netbox/extras/dashboard/widgets.py:358
#: netbox/extras/dashboard/widgets.py:362
#: netbox/templates/account/base.html:10
#: netbox/templates/account/bookmarks.html:7
#: netbox/templates/inc/user_menu.html:30
msgid "Bookmarks"
msgstr "Закладки"
#: netbox/extras/dashboard/widgets.py:362
#: netbox/extras/dashboard/widgets.py:366
msgid "Show your personal bookmarks"
msgstr "Покажите свои личные закладки"
#: netbox/extras/events.py:134
#: netbox/extras/events.py:137
#, python-brace-format
msgid "Unknown action type for an event rule: {action_type}"
msgstr "Неизвестный тип действия для правила события: {action_type}"
#: netbox/extras/events.py:182
#: netbox/extras/events.py:185
#, python-brace-format
msgid "Cannot import events pipeline {name} error: {error}"
msgstr "Невозможно импортировать конвейер событий {name} ошибка: {error}"
@ -9975,7 +9975,7 @@ msgid "Invalid value. Specify a content type as '<app_label>.<model_name>'."
msgstr ""
"Неверное значение. Укажите тип контента как '<app_label>.<model_name>'."
#: netbox/netbox/authentication/__init__.py:138
#: netbox/netbox/authentication/__init__.py:141
#, python-brace-format
msgid "Invalid permission {permission} for model {model}"
msgstr "Неверное разрешение {permission} для модели {model}"
@ -10826,43 +10826,43 @@ msgstr "Невозможно добавить магазины в реестр
msgid "Cannot delete stores from registry"
msgstr "Невозможно удалить магазины из реестра"
#: netbox/netbox/settings.py:741
#: netbox/netbox/settings.py:742
msgid "German"
msgstr "Немецкий"
#: netbox/netbox/settings.py:742
#: netbox/netbox/settings.py:743
msgid "English"
msgstr "Английский"
#: netbox/netbox/settings.py:743
#: netbox/netbox/settings.py:744
msgid "Spanish"
msgstr "Испанский"
#: netbox/netbox/settings.py:744
#: netbox/netbox/settings.py:745
msgid "French"
msgstr "Французский"
#: netbox/netbox/settings.py:745
#: netbox/netbox/settings.py:746
msgid "Japanese"
msgstr "Японский"
#: netbox/netbox/settings.py:746
#: netbox/netbox/settings.py:747
msgid "Portuguese"
msgstr "Португальский"
#: netbox/netbox/settings.py:747
#: netbox/netbox/settings.py:748
msgid "Russian"
msgstr "Русский"
#: netbox/netbox/settings.py:748
#: netbox/netbox/settings.py:749
msgid "Turkish"
msgstr "Турецкий"
#: netbox/netbox/settings.py:749
#: netbox/netbox/settings.py:750
msgid "Ukrainian"
msgstr "украинский"
#: netbox/netbox/settings.py:750
#: netbox/netbox/settings.py:751
msgid "Chinese"
msgstr "Китайский"

View File

@ -5,17 +5,17 @@
#
# Translators:
# Burak Senturk, 2024
# Jeremy Stretch, 2024
# Hamdi Suat Aknar, 2024
# Jeremy Stretch, 2024
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-09 05:02+0000\n"
"POT-Creation-Date: 2024-07-11 05:01+0000\n"
"PO-Revision-Date: 2023-10-30 17:48+0000\n"
"Last-Translator: Hamdi Suat Aknar, 2024\n"
"Last-Translator: Jeremy Stretch, 2024\n"
"Language-Team: Turkish (https://app.transifex.com/netbox-community/teams/178115/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -64,7 +64,7 @@ msgstr "Son Kullanım"
msgid "Allowed IPs"
msgstr "İzin verilen IP'ler"
#: netbox/account/views.py:204
#: netbox/account/views.py:214
msgid "Your preferences have been updated."
msgstr "Tercihleriniz güncellendi."
@ -1510,16 +1510,16 @@ msgstr "Şifre"
msgid "Branch"
msgstr "Şube"
#: netbox/core/data_backends.py:105
#: netbox/core/data_backends.py:106
#, python-brace-format
msgid "Fetching remote data failed ({name}): {error}"
msgstr "Uzaktan veri getirilemedi ({name}): {error}"
#: netbox/core/data_backends.py:118
#: netbox/core/data_backends.py:119
msgid "AWS access key ID"
msgstr "AWS erişim anahtarı kimliği"
#: netbox/core/data_backends.py:122
#: netbox/core/data_backends.py:123
msgid "AWS secret access key"
msgstr "AWS gizli erişim anahtarı"
@ -1863,7 +1863,7 @@ msgstr ""
msgid "last updated"
msgstr "son güncellendi"
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:442
#: netbox/core/models/data.py:296 netbox/dcim/models/cables.py:444
msgid "path"
msgstr "yol"
@ -4636,48 +4636,48 @@ msgstr "uzunluk"
msgid "length unit"
msgstr "uzunluk birimi"
#: netbox/dcim/models/cables.py:93
#: netbox/dcim/models/cables.py:95
msgid "cable"
msgstr "kablo"
#: netbox/dcim/models/cables.py:94
#: netbox/dcim/models/cables.py:96
msgid "cables"
msgstr "kablolar"
#: netbox/dcim/models/cables.py:163
#: netbox/dcim/models/cables.py:165
msgid "Must specify a unit when setting a cable length"
msgstr "Kablo uzunluğu ayarlarken bir birim belirtmeniz gerekir"
#: netbox/dcim/models/cables.py:166
#: netbox/dcim/models/cables.py:168
msgid "Must define A and B terminations when creating a new cable."
msgstr "Yeni bir kablo oluştururken A ve B sonlandırmalarını tanımlamalıdır."
#: netbox/dcim/models/cables.py:173
#: netbox/dcim/models/cables.py:175
msgid "Cannot connect different termination types to same end of cable."
msgstr "Farklı sonlandırma türleri kablonun aynı ucuna bağlanamaz."
#: netbox/dcim/models/cables.py:181
#: netbox/dcim/models/cables.py:183
#, python-brace-format
msgid "Incompatible termination types: {type_a} and {type_b}"
msgstr "Uyumsuz sonlandırma türleri: {type_a} ve {type_b}"
#: netbox/dcim/models/cables.py:191
#: netbox/dcim/models/cables.py:193
msgid "A and B terminations cannot connect to the same object."
msgstr "A ve B sonlandırmaları aynı nesneye bağlanamaz."
#: netbox/dcim/models/cables.py:258 netbox/ipam/models/asns.py:37
#: netbox/dcim/models/cables.py:260 netbox/ipam/models/asns.py:37
msgid "end"
msgstr "son"
#: netbox/dcim/models/cables.py:311
#: netbox/dcim/models/cables.py:313
msgid "cable termination"
msgstr "kablo sonlandırma"
#: netbox/dcim/models/cables.py:312
#: netbox/dcim/models/cables.py:314
msgid "cable terminations"
msgstr "kablo sonlandırmaları"
#: netbox/dcim/models/cables.py:331
#: netbox/dcim/models/cables.py:333
#, python-brace-format
msgid ""
"Duplicate termination found for {app_label}.{model} {termination_id}: cable "
@ -4686,32 +4686,32 @@ msgstr ""
"Yinelenen sonlandırma bulundu {app_label}.{model} {termination_id}: kablo "
"{cable_pk}"
#: netbox/dcim/models/cables.py:341
#: netbox/dcim/models/cables.py:343
#, python-brace-format
msgid "Cables cannot be terminated to {type_display} interfaces"
msgstr "Kablolar sonlandırılamaz {type_display} arayüzleri"
#: netbox/dcim/models/cables.py:348
#: netbox/dcim/models/cables.py:350
msgid "Circuit terminations attached to a provider network may not be cabled."
msgstr "Bir sağlayıcıına bağlı devre sonlandırmaları kablolanmayabilir."
#: netbox/dcim/models/cables.py:446 netbox/extras/models/configs.py:50
#: netbox/dcim/models/cables.py:448 netbox/extras/models/configs.py:50
msgid "is active"
msgstr "aktiftir"
#: netbox/dcim/models/cables.py:450
#: netbox/dcim/models/cables.py:452
msgid "is complete"
msgstr "tamamlandı"
#: netbox/dcim/models/cables.py:454
#: netbox/dcim/models/cables.py:456
msgid "is split"
msgstr "bölünmüş"
#: netbox/dcim/models/cables.py:462
#: netbox/dcim/models/cables.py:464
msgid "cable path"
msgstr "kablo yolu"
#: netbox/dcim/models/cables.py:463
#: netbox/dcim/models/cables.py:465
msgid "cable paths"
msgstr "kablo yolları"
@ -6639,11 +6639,11 @@ msgstr "En eski"
#: netbox/extras/choices.py:126
msgid "Alphabetical (A-Z)"
msgstr ""
msgstr "Alfabetik (A-Z)"
#: netbox/extras/choices.py:127
msgid "Alphabetical (Z-A)"
msgstr ""
msgstr "Alfabetik (Z-A)"
#: netbox/extras/choices.py:143 netbox/templates/generic/object.html:61
msgid "Updated"
@ -6831,10 +6831,12 @@ msgstr "Kural seti bir sözlük olmalı, değil {ruleset}."
#: netbox/extras/conditions.py:142
msgid "Invalid logic type: must be 'AND' or 'OR'. Please check documentation."
msgstr ""
"Geçersiz mantık türü: 'AND' veya 'OR' olmalıdır. Lütfen belgeleri kontrol "
"edin."
#: netbox/extras/conditions.py:154
msgid "Incorrect key(s) informed. Please check documentation."
msgstr ""
msgstr "Yanlış anahtar (ler) bilgilendirildi. Lütfen belgeleri kontrol edin."
#: netbox/extras/dashboard/forms.py:38
msgid "Widget type"
@ -6894,44 +6896,44 @@ msgstr "Görüntülenecek nesnelerin varsayılan sayısı"
msgid "Invalid format. URL parameters must be passed as a dictionary."
msgstr "Geçersiz biçim. URL parametreleri sözlük olarak iletilmelidir."
#: netbox/extras/dashboard/widgets.py:284
#: netbox/extras/dashboard/widgets.py:288
msgid "RSS Feed"
msgstr "RSS Beslemesi"
#: netbox/extras/dashboard/widgets.py:289
#: netbox/extras/dashboard/widgets.py:293
msgid "Embed an RSS feed from an external website."
msgstr "Harici bir web sitesinden bir RSS beslemesi ekleyin."
#: netbox/extras/dashboard/widgets.py:296
#: netbox/extras/dashboard/widgets.py:300
msgid "Feed URL"
msgstr "Akış URL'si"
#: netbox/extras/dashboard/widgets.py:301
#: netbox/extras/dashboard/widgets.py:305
msgid "The maximum number of objects to display"
msgstr "Görüntülenecek maksimum nesne sayısı"
#: netbox/extras/dashboard/widgets.py:306
#: netbox/extras/dashboard/widgets.py:310
msgid "How long to stored the cached content (in seconds)"
msgstr ""
"Önbelleğe alınan içeriğin ne kadar süre saklanacağı (saniye cinsinden)"
#: netbox/extras/dashboard/widgets.py:358
#: netbox/extras/dashboard/widgets.py:362
#: netbox/templates/account/base.html:10
#: netbox/templates/account/bookmarks.html:7
#: netbox/templates/inc/user_menu.html:30
msgid "Bookmarks"
msgstr "Yer İşaretleri"
#: netbox/extras/dashboard/widgets.py:362
#: netbox/extras/dashboard/widgets.py:366
msgid "Show your personal bookmarks"
msgstr "Kişisel yer imlerinizi gösterin"
#: netbox/extras/events.py:134
#: netbox/extras/events.py:137
#, python-brace-format
msgid "Unknown action type for an event rule: {action_type}"
msgstr "Bir olay kuralı için bilinmeyen eylem türü: {action_type}"
#: netbox/extras/events.py:182
#: netbox/extras/events.py:185
#, python-brace-format
msgid "Cannot import events pipeline {name} error: {error}"
msgstr "Olaylar boru hattı içe aktarılamıyor {name} hata: {error}"
@ -9893,7 +9895,7 @@ msgid "Invalid value. Specify a content type as '<app_label>.<model_name>'."
msgstr ""
"Geçersiz değer. İçerik türünü 'olarak belirtin<app_label>.<model_name>'."
#: netbox/netbox/authentication/__init__.py:138
#: netbox/netbox/authentication/__init__.py:141
#, python-brace-format
msgid "Invalid permission {permission} for model {model}"
msgstr "Geçersiz izin {permission} model için {model}"
@ -10198,7 +10200,7 @@ msgstr "Nesne türü (ler)"
#: netbox/netbox/forms/__init__.py:40
msgid "Lookup"
msgstr ""
msgstr "Aramak"
#: netbox/netbox/forms/base.py:88
msgid ""
@ -10741,43 +10743,43 @@ msgstr "Başlatıldıktan sonra kayıt defterine mağazalar eklenemiyor"
msgid "Cannot delete stores from registry"
msgstr "Mağazalar kayıt defterinden silinemiyor"
#: netbox/netbox/settings.py:741
#: netbox/netbox/settings.py:742
msgid "German"
msgstr "Alman"
#: netbox/netbox/settings.py:742
#: netbox/netbox/settings.py:743
msgid "English"
msgstr "İngilizce"
#: netbox/netbox/settings.py:743
#: netbox/netbox/settings.py:744
msgid "Spanish"
msgstr "İspanyolca"
#: netbox/netbox/settings.py:744
#: netbox/netbox/settings.py:745
msgid "French"
msgstr "Fransızca"
#: netbox/netbox/settings.py:745
#: netbox/netbox/settings.py:746
msgid "Japanese"
msgstr "Japonca"
#: netbox/netbox/settings.py:746
#: netbox/netbox/settings.py:747
msgid "Portuguese"
msgstr "Portekizce"
#: netbox/netbox/settings.py:747
#: netbox/netbox/settings.py:748
msgid "Russian"
msgstr "Rusça"
#: netbox/netbox/settings.py:748
#: netbox/netbox/settings.py:749
msgid "Turkish"
msgstr "Türkçe"
#: netbox/netbox/settings.py:749
#: netbox/netbox/settings.py:750
msgid "Ukrainian"
msgstr "Ukraynalı"
#: netbox/netbox/settings.py:750
#: netbox/netbox/settings.py:751
msgid "Chinese"
msgstr "Çince"
@ -13083,11 +13085,11 @@ msgstr "Veriler yukarı akış dosyasıyla senkronize değil"
#: netbox/templates/inc/table_controls_htmx.html:7
msgid "Quick search"
msgstr ""
msgstr "Hızlı arama"
#: netbox/templates/inc/table_controls_htmx.html:20
msgid "Saved filter"
msgstr ""
msgstr "Kaydedilen filtre"
#: netbox/templates/inc/user_menu.html:23
msgid "Django Admin"
@ -14163,7 +14165,7 @@ msgstr "50'den fazla"
#: netbox/utilities/fields.py:30
msgid "RGB color in hexadecimal. Example: "
msgstr ""
msgstr "Onaltılık olarak RGB rengi. Örnek: "
#: netbox/utilities/fields.py:159
#, python-format
@ -14477,11 +14479,11 @@ msgstr "Aşağı hareket et"
#: netbox/utilities/templates/navigation/menu.html:14
msgid "Search…"
msgstr ""
msgstr "Arama..."
#: netbox/utilities/templates/navigation/menu.html:14
msgid "Search NetBox"
msgstr ""
msgstr "Arama NetBox"
#: netbox/utilities/templates/widgets/apiselect.html:7
msgid "Open selector"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
from django.contrib.auth import get_user_model
from django.contrib.auth import get_user_model, password_validation
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
@ -59,6 +59,14 @@ class UserSerializer(ValidatedModelSerializer):
'password': {'write_only': True}
}
def validate(self, data):
# Enforce password validation rules (if configured)
if not self.nested and data.get('password'):
password_validation.validate_password(data['password'], self.instance)
return super().validate(data)
def create(self, validated_data):
"""
Extract the password from validated data and set it separately to ensure proper hash generation.

View File

@ -1,6 +1,6 @@
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth import get_user_model, password_validation
from django.contrib.postgres.forms import SimpleArrayField
from django.core.exceptions import FieldError
from django.utils.safestring import mark_safe
@ -227,6 +227,10 @@ class UserForm(forms.ModelForm):
if self.cleaned_data['password'] and self.cleaned_data['password'] != self.cleaned_data['confirm_password']:
raise forms.ValidationError(_("Passwords do not match! Please check your input and try again."))
# Enforce password validation rules (if configured)
if self.cleaned_data['password']:
password_validation.validate_password(self.cleaned_data['password'], self.instance)
class GroupForm(forms.ModelForm):
users = DynamicModelMultipleChoiceField(

View File

@ -1,4 +1,5 @@
from django.contrib.auth import get_user_model
from django.test import override_settings
from django.urls import reverse
from core.models import ObjectType
@ -93,6 +94,31 @@ class UserTest(APIViewTestCases.APIViewTestCase):
user.refresh_from_db()
self.assertTrue(user.check_password(data['password']))
@override_settings(AUTH_PASSWORD_VALIDATORS=[{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 8}
}])
def test_password_validation_enforced(self):
"""
Test that any configured password validation rules (AUTH_PASSWORD_VALIDATORS) are enforced.
"""
self.add_permissions('users.add_user')
data = {
'username': 'new_user',
'password': 'foo',
}
url = reverse('users-api:user-list')
# Password too short
response = self.client.post(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 400)
# Password long enough
data['password'] = 'foobar123'
response = self.client.post(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 201)
class GroupTest(APIViewTestCases.APIViewTestCase):
model = Group

View File

@ -1,6 +1,8 @@
from django.test import override_settings
from core.models import ObjectType
from users.models import *
from utilities.testing import ViewTestCases, create_test_user
from utilities.testing import ViewTestCases, create_test_user, extract_form_failures
class UserTestCase(
@ -58,6 +60,34 @@ class UserTestCase(
'last_name': 'newlastname',
}
@override_settings(AUTH_PASSWORD_VALIDATORS=[{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 8}
}])
def test_password_validation_enforced(self):
"""
Test that any configured password validation rules (AUTH_PASSWORD_VALIDATORS) are enforced.
"""
self.add_permissions('users.add_user')
data = {
'username': 'new_user',
'password': 'foo',
'confirm_password': 'foo',
}
# Password too short
request = {
'path': self._get_url('add'),
'data': data,
}
response = self.client.post(**request)
self.assertHttpStatus(response, 200)
# Password long enough
data['password'] = 'foobar123'
data['confirm_password'] = 'foobar123'
self.assertHttpStatus(self.client.post(**request), 302)
class GroupTestCase(
ViewTestCases.GetObjectViewTestCase,

View File

@ -9,9 +9,9 @@ from django.test import override_settings
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from strawberry.lazy_type import LazyType
from strawberry.type import StrawberryList, StrawberryOptional
from strawberry.union import StrawberryUnion
from strawberry.types.base import StrawberryList, StrawberryOptional
from strawberry.types.lazy_type import LazyType
from strawberry.types.union import StrawberryUnion
from core.choices import ObjectChangeActionChoices
from core.models import ObjectChange, ObjectType

View File

@ -20,18 +20,18 @@ feedparser==6.0.11
gunicorn==22.0.0
Jinja2==3.1.4
Markdown==3.6
mkdocs-material==9.5.28
mkdocstrings[python-legacy]==0.25.1
mkdocs-material==9.5.30
mkdocstrings[python-legacy]==0.25.2
netaddr==1.3.0
nh3==0.2.18
Pillow==10.4.0
psycopg[c,pool]==3.2.1
PyYAML==6.0.1
requests==2.32.3
social-auth-app-django==5.4.1
social-auth-app-django==5.4.2
social-auth-core==4.5.4
strawberry-graphql==0.235.2
strawberry-graphql-django==0.46.1
strawberry-graphql==0.237.2
strawberry-graphql-django==0.47.1
svgwrite==1.4.3
tablib==3.6.1
tzdata==2024.1