Compare commits

..

15 Commits

Author SHA1 Message Date
Idris Foughali
4b40cde7dd Merge 93e5f919ba into dc4bab7477 2025-12-01 09:07:18 +00:00
Idris Foughali
93e5f919ba Merge branch 'netbox-community:main' into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-12-01 10:07:15 +01:00
ifoughali
929d024003 Merge branch 'closes-20817-Fix-datasource-sync-broken-when-cron-is-set' of https://github.com/ifoughal/netbox into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-11-26 09:00:22 +01:00
ifoughali
e4b614038e revert: re-added queued status set for datasource object 2025-11-26 09:00:17 +01:00
Idris Foughali
3016b1d90b Merge branch 'netbox-community:main' into closes-20817-Fix-datasource-sync-broken-when-cron-is-set 2025-11-26 08:55:12 +01:00
ifoughali
57b47dc1ea style: use != instead of not in for single SYNCING check 2025-11-26 08:05:20 +01:00
ifoughali
da4c669312 Feat: reworked status update logic 2025-11-20 11:27:39 +01:00
ifoughali
71f707b7ac Feat: removed SCHEDULED choice due to redundency with sync interval 2025-11-20 11:26:43 +01:00
ifoughali
e11508dd6c Fix: removed status update from the enqueue method 2025-11-20 10:50:35 +01:00
ifoughali
5b5b5c8909 Revert "Feat: set status as editable field"
This reverts commit b4160ad59b.
2025-11-19 20:18:59 +01:00
Idris Foughali
a49869af42 Feat: removed QUEUED from ready for sync condition 2025-11-19 19:01:01 +00:00
Idris Foughali
2e0ff04f84 Feat: added 2 states for DataSourceStatusChoices 2025-11-19 18:52:27 +00:00
Idris Foughali
bfeba36514 Feat: added status update during save method of DataSourceForm 2025-11-19 18:51:25 +00:00
Idris Foughali
111aca115b Feat: added clean method to set data-source state to Ready or scheduled 2025-11-19 18:51:01 +00:00
Idris Foughali
b4160ad59b Feat: set status as editable field 2025-11-19 18:49:47 +00:00
9 changed files with 100 additions and 126 deletions

View File

@@ -13,6 +13,7 @@ class DataSourceStatusChoices(ChoiceSet):
SYNCING = 'syncing'
COMPLETED = 'completed'
FAILED = 'failed'
READY = 'ready'
CHOICES = (
(NEW, _('New'), 'blue'),
@@ -20,6 +21,7 @@ class DataSourceStatusChoices(ChoiceSet):
(SYNCING, _('Syncing'), 'cyan'),
(COMPLETED, _('Completed'), 'green'),
(FAILED, _('Failed'), 'red'),
(READY, _('Ready'), 'green'),
)

View File

@@ -16,6 +16,7 @@ from utilities.forms import get_field_value
from utilities.forms.fields import CommentField, JSONField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import HTMXSelect
from core.choices import DataSourceStatusChoices
__all__ = (
'ConfigRevisionForm',
@@ -79,14 +80,24 @@ class DataSourceForm(NetBoxModelForm):
if self.instance and self.instance.parameters:
self.fields[field_name].initial = self.instance.parameters.get(name)
def save(self, *args, **kwargs):
def clean(self):
super().clean()
if not self.instance.pk:
self.cleaned_data['status'] = DataSourceStatusChoices.NEW
else:
if not self.data.get('sync_interval'):
self.cleaned_data['status'] = DataSourceStatusChoices.READY
def save(self, *args, **kwargs):
parameters = {}
for name in self.fields:
if name.startswith('backend_'):
parameters[name[8:]] = self.cleaned_data[name]
self.instance.parameters = parameters
# update status
self.instance.status = self.cleaned_data.get('status', self.instance.status)
return super().save(*args, **kwargs)

View File

@@ -111,10 +111,7 @@ class DataSource(JobsMixin, PrimaryModel):
@property
def ready_for_sync(self):
return self.enabled and self.status not in (
DataSourceStatusChoices.QUEUED,
DataSourceStatusChoices.SYNCING
)
return self.enabled and self.status != DataSourceStatusChoices.SYNCING
def clean(self):
super().clean()

View File

@@ -472,30 +472,14 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
required=False,
help_text=_('Unit for module weight')
)
attribute_data = forms.JSONField(
label=_('Attributes'),
required=False,
help_text=_('Attribute values for the assigned profile, passed as a dictionary')
)
class Meta:
model = ModuleType
fields = [
'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'profile',
'attribute_data', 'comments', 'tags',
'comments', 'tags'
]
def clean(self):
super().clean()
# Attribute data may be included only if a profile is specified
if self.cleaned_data.get('attribute_data') and not self.cleaned_data.get('profile'):
raise forms.ValidationError(_("Profile must be specified if attribute data is provided."))
# Default attribute_data to an empty dictionary if a profile is specified (to enforce schema validation)
if self.cleaned_data.get('profile') and not self.cleaned_data.get('attribute_data'):
self.cleaned_data['attribute_data'] = {}
class DeviceRoleImportForm(NetBoxModelImportForm):
parent = CSVModelChoiceField(

View File

@@ -1,5 +1,3 @@
import decimal
import django.core.validators
from django.db import migrations, models
@@ -19,8 +17,8 @@ class Migration(migrations.Migration):
max_digits=8,
null=True,
validators=[
django.core.validators.MinValueValidator(decimal.Decimal('-90.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('90.0'))
django.core.validators.MinValueValidator(-90.0),
django.core.validators.MaxValueValidator(90.0),
],
),
),
@@ -33,8 +31,8 @@ class Migration(migrations.Migration):
max_digits=9,
null=True,
validators=[
django.core.validators.MinValueValidator(decimal.Decimal('-180.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('180.0'))
django.core.validators.MinValueValidator(-180.0),
django.core.validators.MaxValueValidator(180.0),
],
),
),
@@ -47,8 +45,8 @@ class Migration(migrations.Migration):
max_digits=8,
null=True,
validators=[
django.core.validators.MinValueValidator(decimal.Decimal('-90.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('90.0'))
django.core.validators.MinValueValidator(-90.0),
django.core.validators.MaxValueValidator(90.0),
],
),
),
@@ -61,8 +59,8 @@ class Migration(migrations.Migration):
max_digits=9,
null=True,
validators=[
django.core.validators.MinValueValidator(decimal.Decimal('-180.0')),
django.core.validators.MaxValueValidator(decimal.Decimal('180.0'))
django.core.validators.MinValueValidator(-180.0),
django.core.validators.MaxValueValidator(180.0),
],
),
),

View File

@@ -646,10 +646,7 @@ class Device(
decimal_places=6,
blank=True,
null=True,
validators=[
MinValueValidator(decimal.Decimal('-90.0')),
MaxValueValidator(decimal.Decimal('90.0'))
],
validators=[MinValueValidator(-90.0), MaxValueValidator(90.0)],
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
)
longitude = models.DecimalField(
@@ -658,10 +655,7 @@ class Device(
decimal_places=6,
blank=True,
null=True,
validators=[
MinValueValidator(decimal.Decimal('-180.0')),
MaxValueValidator(decimal.Decimal('180.0'))
],
validators=[MinValueValidator(-180.0), MaxValueValidator(180.0)],
help_text=_("GPS coordinate in decimal format (xx.yyyyyy)")
)
services = GenericRelation(

View File

@@ -1,5 +1,3 @@
import decimal
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
@@ -213,10 +211,7 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
decimal_places=6,
blank=True,
null=True,
validators=[
MinValueValidator(decimal.Decimal('-90.0')),
MaxValueValidator(decimal.Decimal('90.0'))
],
validators=[MinValueValidator(-90.0), MaxValueValidator(90.0)],
help_text=_('GPS coordinate in decimal format (xx.yyyyyy)')
)
longitude = models.DecimalField(
@@ -225,10 +220,7 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
decimal_places=6,
blank=True,
null=True,
validators=[
MinValueValidator(decimal.Decimal('-180.0')),
MaxValueValidator(decimal.Decimal('180.0'))
],
validators=[MinValueValidator(-180.0), MaxValueValidator(180.0)],
help_text=_('GPS coordinate in decimal format (xx.yyyyyy)')
)

View File

@@ -559,7 +559,6 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
form.instance._replicated_base = hasattr(self.form, "replication_fields")
if form.is_valid():
changelog_message = form.cleaned_data.pop('changelog_message', '')
new_components = []
data = deepcopy(request.POST)
pattern_count = len(form.cleaned_data[self.form.replication_fields[0]])
@@ -586,9 +585,6 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
# Create the new components
new_objs = []
for component_form in new_components:
# Record changelog message (if any)
if changelog_message:
component_form.instance._changelog_message = changelog_message
obj = component_form.save()
new_objs.append(obj)

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-02 05:02+0000\n"
"POT-Creation-Date: 2025-11-26 05:01+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1469,10 +1469,10 @@ msgstr ""
#: netbox/core/models/jobs.py:95 netbox/dcim/models/cables.py:51
#: netbox/dcim/models/device_components.py:488
#: netbox/dcim/models/device_components.py:1319
#: netbox/dcim/models/devices.py:580 netbox/dcim/models/devices.py:1202
#: netbox/dcim/models/devices.py:580 netbox/dcim/models/devices.py:1196
#: netbox/dcim/models/modules.py:210 netbox/dcim/models/power.py:94
#: netbox/dcim/models/racks.py:294 netbox/dcim/models/racks.py:677
#: netbox/dcim/models/sites.py:157 netbox/dcim/models/sites.py:281
#: netbox/dcim/models/sites.py:155 netbox/dcim/models/sites.py:273
#: netbox/ipam/models/ip.py:243 netbox/ipam/models/ip.py:529
#: netbox/ipam/models/ip.py:758 netbox/ipam/models/vlans.py:228
#: netbox/virtualization/models/clusters.py:70
@@ -1603,10 +1603,10 @@ msgstr ""
#: netbox/core/models/jobs.py:56
#: netbox/dcim/models/device_component_templates.py:44
#: netbox/dcim/models/device_components.py:53 netbox/dcim/models/devices.py:524
#: netbox/dcim/models/devices.py:1128 netbox/dcim/models/devices.py:1197
#: netbox/dcim/models/devices.py:1122 netbox/dcim/models/devices.py:1191
#: netbox/dcim/models/modules.py:32 netbox/dcim/models/power.py:38
#: netbox/dcim/models/power.py:89 netbox/dcim/models/racks.py:263
#: netbox/dcim/models/sites.py:145 netbox/extras/models/configs.py:36
#: netbox/dcim/models/sites.py:143 netbox/extras/models/configs.py:36
#: netbox/extras/models/configs.py:78 netbox/extras/models/configs.py:272
#: netbox/extras/models/customfields.py:94 netbox/extras/models/models.py:60
#: netbox/extras/models/models.py:165 netbox/extras/models/models.py:308
@@ -1637,7 +1637,7 @@ msgid "Full name of the provider"
msgstr ""
#: netbox/circuits/models/providers.py:28 netbox/dcim/models/devices.py:89
#: netbox/dcim/models/racks.py:143 netbox/dcim/models/sites.py:152
#: netbox/dcim/models/racks.py:143 netbox/dcim/models/sites.py:150
#: netbox/extras/models/models.py:474 netbox/ipam/models/asns.py:24
#: netbox/ipam/models/vlans.py:43 netbox/netbox/models/__init__.py:146
#: netbox/netbox/models/__init__.py:195 netbox/tenancy/models/tenants.py:25
@@ -3816,8 +3816,8 @@ msgstr ""
#: netbox/dcim/filtersets.py:1197 netbox/dcim/forms/filtersets.py:848
#: netbox/dcim/forms/filtersets.py:1473 netbox/dcim/forms/filtersets.py:1688
#: netbox/dcim/forms/model_forms.py:1900 netbox/dcim/models/devices.py:1298
#: netbox/dcim/models/devices.py:1318 netbox/virtualization/filtersets.py:201
#: netbox/dcim/forms/model_forms.py:1900 netbox/dcim/models/devices.py:1292
#: netbox/dcim/models/devices.py:1312 netbox/virtualization/filtersets.py:201
#: netbox/virtualization/filtersets.py:273
#: netbox/virtualization/forms/filtersets.py:178
#: netbox/virtualization/forms/filtersets.py:231
@@ -6857,12 +6857,12 @@ msgstr ""
msgid "rack face"
msgstr ""
#: netbox/dcim/models/devices.py:598 netbox/dcim/models/devices.py:1218
#: netbox/dcim/models/devices.py:598 netbox/dcim/models/devices.py:1212
#: netbox/virtualization/models/virtualmachines.py:94
msgid "primary IPv4"
msgstr ""
#: netbox/dcim/models/devices.py:606 netbox/dcim/models/devices.py:1226
#: netbox/dcim/models/devices.py:606 netbox/dcim/models/devices.py:1220
#: netbox/virtualization/models/virtualmachines.py:102
msgid "primary IPv6"
msgstr ""
@@ -6887,191 +6887,191 @@ msgstr ""
msgid "Virtual chassis master election priority"
msgstr ""
#: netbox/dcim/models/devices.py:644 netbox/dcim/models/sites.py:211
#: netbox/dcim/models/devices.py:644 netbox/dcim/models/sites.py:209
msgid "latitude"
msgstr ""
#: netbox/dcim/models/devices.py:653 netbox/dcim/models/devices.py:665
#: netbox/dcim/models/sites.py:220 netbox/dcim/models/sites.py:232
#: netbox/dcim/models/devices.py:650 netbox/dcim/models/devices.py:659
#: netbox/dcim/models/sites.py:215 netbox/dcim/models/sites.py:224
msgid "GPS coordinate in decimal format (xx.yyyyyy)"
msgstr ""
#: netbox/dcim/models/devices.py:656 netbox/dcim/models/sites.py:223
#: netbox/dcim/models/devices.py:653 netbox/dcim/models/sites.py:218
msgid "longitude"
msgstr ""
#: netbox/dcim/models/devices.py:739
#: netbox/dcim/models/devices.py:733
msgid "Device name must be unique per site."
msgstr ""
#: netbox/dcim/models/devices.py:750
#: netbox/dcim/models/devices.py:744
msgid "device"
msgstr ""
#: netbox/dcim/models/devices.py:751
#: netbox/dcim/models/devices.py:745
msgid "devices"
msgstr ""
#: netbox/dcim/models/devices.py:770
#: netbox/dcim/models/devices.py:764
#, python-brace-format
msgid "Rack {rack} does not belong to site {site}."
msgstr ""
#: netbox/dcim/models/devices.py:775
#: netbox/dcim/models/devices.py:769
#, python-brace-format
msgid "Location {location} does not belong to site {site}."
msgstr ""
#: netbox/dcim/models/devices.py:781
#: netbox/dcim/models/devices.py:775
#, python-brace-format
msgid "Rack {rack} does not belong to location {location}."
msgstr ""
#: netbox/dcim/models/devices.py:788
#: netbox/dcim/models/devices.py:782
msgid "Cannot select a rack face without assigning a rack."
msgstr ""
#: netbox/dcim/models/devices.py:792
#: netbox/dcim/models/devices.py:786
msgid "Cannot select a rack position without assigning a rack."
msgstr ""
#: netbox/dcim/models/devices.py:798
#: netbox/dcim/models/devices.py:792
msgid "Position must be in increments of 0.5 rack units."
msgstr ""
#: netbox/dcim/models/devices.py:802
#: netbox/dcim/models/devices.py:796
msgid "Must specify rack face when defining rack position."
msgstr ""
#: netbox/dcim/models/devices.py:810
#: netbox/dcim/models/devices.py:804
#, python-brace-format
msgid "A 0U device type ({device_type}) cannot be assigned to a rack position."
msgstr ""
#: netbox/dcim/models/devices.py:821
#: netbox/dcim/models/devices.py:815
msgid ""
"Child device types cannot be assigned to a rack face. This is an attribute "
"of the parent device."
msgstr ""
#: netbox/dcim/models/devices.py:828
#: netbox/dcim/models/devices.py:822
msgid ""
"Child device types cannot be assigned to a rack position. This is an "
"attribute of the parent device."
msgstr ""
#: netbox/dcim/models/devices.py:842
#: netbox/dcim/models/devices.py:836
#, python-brace-format
msgid ""
"U{position} is already occupied or does not have sufficient space to "
"accommodate this device type: {device_type} ({u_height}U)"
msgstr ""
#: netbox/dcim/models/devices.py:857
#: netbox/dcim/models/devices.py:851
#, python-brace-format
msgid "{ip} is not an IPv4 address."
msgstr ""
#: netbox/dcim/models/devices.py:869 netbox/dcim/models/devices.py:887
#: netbox/dcim/models/devices.py:863 netbox/dcim/models/devices.py:881
#, python-brace-format
msgid "The specified IP address ({ip}) is not assigned to this device."
msgstr ""
#: netbox/dcim/models/devices.py:875
#: netbox/dcim/models/devices.py:869
#, python-brace-format
msgid "{ip} is not an IPv6 address."
msgstr ""
#: netbox/dcim/models/devices.py:905
#: netbox/dcim/models/devices.py:899
#, python-brace-format
msgid ""
"The assigned platform is limited to {platform_manufacturer} device types, "
"but this device's type belongs to {devicetype_manufacturer}."
msgstr ""
#: netbox/dcim/models/devices.py:916
#: netbox/dcim/models/devices.py:910
#, python-brace-format
msgid "The assigned cluster belongs to a different site ({site})"
msgstr ""
#: netbox/dcim/models/devices.py:923
#: netbox/dcim/models/devices.py:917
#, python-brace-format
msgid "The assigned cluster belongs to a different location ({location})"
msgstr ""
#: netbox/dcim/models/devices.py:931
#: netbox/dcim/models/devices.py:925
msgid "A device assigned to a virtual chassis must have its position defined."
msgstr ""
#: netbox/dcim/models/devices.py:937
#: netbox/dcim/models/devices.py:931
#, python-brace-format
msgid ""
"Device cannot be removed from virtual chassis {virtual_chassis} because it "
"is currently designated as its master."
msgstr ""
#: netbox/dcim/models/devices.py:1133
#: netbox/dcim/models/devices.py:1127
msgid "domain"
msgstr ""
#: netbox/dcim/models/devices.py:1146 netbox/dcim/models/devices.py:1147
#: netbox/dcim/models/devices.py:1140 netbox/dcim/models/devices.py:1141
msgid "virtual chassis"
msgstr ""
#: netbox/dcim/models/devices.py:1159
#: netbox/dcim/models/devices.py:1153
#, python-brace-format
msgid "The selected master ({master}) is not assigned to this virtual chassis."
msgstr ""
#: netbox/dcim/models/devices.py:1174
#: netbox/dcim/models/devices.py:1168
#, python-brace-format
msgid ""
"Unable to delete virtual chassis {self}. There are member interfaces which "
"form a cross-chassis LAG interfaces."
msgstr ""
#: netbox/dcim/models/devices.py:1207 netbox/vpn/models/l2vpn.py:42
#: netbox/dcim/models/devices.py:1201 netbox/vpn/models/l2vpn.py:42
msgid "identifier"
msgstr ""
#: netbox/dcim/models/devices.py:1208
#: netbox/dcim/models/devices.py:1202
msgid "Numeric identifier unique to the parent device"
msgstr ""
#: netbox/dcim/models/devices.py:1236 netbox/extras/models/customfields.py:231
#: netbox/dcim/models/devices.py:1230 netbox/extras/models/customfields.py:231
#: netbox/extras/models/models.py:111 netbox/extras/models/models.py:800
#: netbox/netbox/models/__init__.py:120 netbox/netbox/models/__init__.py:155
msgid "comments"
msgstr ""
#: netbox/dcim/models/devices.py:1252
#: netbox/dcim/models/devices.py:1246
msgid "virtual device context"
msgstr ""
#: netbox/dcim/models/devices.py:1253
#: netbox/dcim/models/devices.py:1247
msgid "virtual device contexts"
msgstr ""
#: netbox/dcim/models/devices.py:1282
#: netbox/dcim/models/devices.py:1276
#, python-brace-format
msgid "{ip} is not an IPv{family} address."
msgstr ""
#: netbox/dcim/models/devices.py:1288
#: netbox/dcim/models/devices.py:1282
msgid "Primary IP address must belong to an interface on the assigned device."
msgstr ""
#: netbox/dcim/models/devices.py:1319
#: netbox/dcim/models/devices.py:1313
msgid "MAC addresses"
msgstr ""
#: netbox/dcim/models/devices.py:1351
#: netbox/dcim/models/devices.py:1345
msgid ""
"Cannot unassign MAC Address while it is designated as the primary MAC for an "
"object"
msgstr ""
#: netbox/dcim/models/devices.py:1355
#: netbox/dcim/models/devices.py:1349
msgid ""
"Cannot reassign MAC Address while it is designated as the primary MAC for an "
"object"
@@ -7378,91 +7378,91 @@ msgstr ""
msgid "The following units have already been reserved: {unit_list}"
msgstr ""
#: netbox/dcim/models/sites.py:56
#: netbox/dcim/models/sites.py:54
msgid "A top-level region with this name already exists."
msgstr ""
#: netbox/dcim/models/sites.py:66
#: netbox/dcim/models/sites.py:64
msgid "A top-level region with this slug already exists."
msgstr ""
#: netbox/dcim/models/sites.py:69
#: netbox/dcim/models/sites.py:67
msgid "region"
msgstr ""
#: netbox/dcim/models/sites.py:70
#: netbox/dcim/models/sites.py:68
msgid "regions"
msgstr ""
#: netbox/dcim/models/sites.py:112
#: netbox/dcim/models/sites.py:110
msgid "A top-level site group with this name already exists."
msgstr ""
#: netbox/dcim/models/sites.py:122
#: netbox/dcim/models/sites.py:120
msgid "A top-level site group with this slug already exists."
msgstr ""
#: netbox/dcim/models/sites.py:125
#: netbox/dcim/models/sites.py:123
msgid "site group"
msgstr ""
#: netbox/dcim/models/sites.py:126
#: netbox/dcim/models/sites.py:124
msgid "site groups"
msgstr ""
#: netbox/dcim/models/sites.py:148
#: netbox/dcim/models/sites.py:146
msgid "Full name of the site"
msgstr ""
#: netbox/dcim/models/sites.py:184 netbox/dcim/models/sites.py:294
#: netbox/dcim/models/sites.py:182 netbox/dcim/models/sites.py:286
msgid "facility"
msgstr ""
#: netbox/dcim/models/sites.py:187 netbox/dcim/models/sites.py:297
#: netbox/dcim/models/sites.py:185 netbox/dcim/models/sites.py:289
msgid "Local facility ID or description"
msgstr ""
#: netbox/dcim/models/sites.py:199
#: netbox/dcim/models/sites.py:197
msgid "physical address"
msgstr ""
#: netbox/dcim/models/sites.py:202
#: netbox/dcim/models/sites.py:200
msgid "Physical location of the building"
msgstr ""
#: netbox/dcim/models/sites.py:205
#: netbox/dcim/models/sites.py:203
msgid "shipping address"
msgstr ""
#: netbox/dcim/models/sites.py:208
#: netbox/dcim/models/sites.py:206
msgid "If different from the physical address"
msgstr ""
#: netbox/dcim/models/sites.py:256
#: netbox/dcim/models/sites.py:248
msgid "site"
msgstr ""
#: netbox/dcim/models/sites.py:257
#: netbox/dcim/models/sites.py:249
msgid "sites"
msgstr ""
#: netbox/dcim/models/sites.py:330
#: netbox/dcim/models/sites.py:322
msgid "A location with this name already exists within the specified site."
msgstr ""
#: netbox/dcim/models/sites.py:340
#: netbox/dcim/models/sites.py:332
msgid "A location with this slug already exists within the specified site."
msgstr ""
#: netbox/dcim/models/sites.py:343
#: netbox/dcim/models/sites.py:335
msgid "location"
msgstr ""
#: netbox/dcim/models/sites.py:344
#: netbox/dcim/models/sites.py:336
msgid "locations"
msgstr ""
#: netbox/dcim/models/sites.py:355
#: netbox/dcim/models/sites.py:347
#, python-brace-format
msgid "Parent location ({parent}) must belong to the same site ({site})."
msgstr ""