internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: netbox-community/netbox

Issue:
Add current NetBox version in the `PluginConfig.validate()` error output
### NetBox version

v4.0.1

### Feature type

New functionality

### Proposed functionality

When [`PluginConfig.validate()`](https://github.com/netbox-community/netbox/blob/4a64a3f6e0d0edf27996422eb2dbe0e197a6bea5/netbox/netbox/plugins/__init__.py#L133) determines that the current NetBox version does not meet the plugin requirements, also print out the current NetBox version in the exception message.

### Use case

Currently the error message only prints the version that the plugin mandates, but due to possible installation directory confusion it would be good to also print the current NetBox version. Example case: https://github.com/netbox-community/netbox/issues/16088


### Database changes

None

### External dependencies

None


Discussion:
If this is accepted, I can send the PR.

Resolving patch:
diff --git a/netbox/netbox/plugins/__init__.py b/netbox/netbox/plugins/__init__.py
--- a/netbox/netbox/plugins/__init__.py
+++ b/netbox/netbox/plugins/__init__.py
@@ -138,13 +138,15 @@ def validate(cls, user_config, netbox_version):
             min_version = version.parse(cls.min_version)
             if current_version < min_version:
                 raise ImproperlyConfigured(
-                    f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version}."
+                    f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version} (current: "
+                    f"{netbox_version})."
                 )
         if cls.max_version is not None:
             max_version = version.parse(cls.max_version)
             if current_version > max_version:
                 raise ImproperlyConfigured(
-                    f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version}."
+                    f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version} (current: "
+                    f"{netbox_version})."
                 )
 
         # Verify required configuration settings


Test patch:
Repository: netbox-community/netbox

Issue:
GraphQL error "Expected value of type 'InterfaceType' but got: <VMInterface instance>." when querying interfaces from a VM
### Deployment Type

Self-hosted

### NetBox Version

v4.0.1

### Python Version

3.11

### Steps to Reproduce

1. execute the following GraphQL query
```
{
  virtual_machine_list {
    name
    interfaces {
      mode
    }
  }
}
```

### Expected Behavior

A valid resultset should be returned.

### Observed Behavior

The following result containing the error message "Expected value of type 'InterfaceType' but got: <VMInterface instance>." is being returned.

```
{
  "data": null,
  "errors": [
    {
      "message": "Expected value of type 'InterfaceType' but got: <VMInterface instance>.",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ],
      "path": [
        "virtual_machine_list",
        0,
        "interfaces",
        0
      ]
    }
  ]
}
```

This can be reproduced on the [NetBox demo page](https://demo.netbox.dev/graphql/).

I updated from 3.7.8 (where it worked as intended) to 4.0.1.


Discussion:

Resolving patch:
diff --git a/netbox/virtualization/graphql/types.py b/netbox/virtualization/graphql/types.py
--- a/netbox/virtualization/graphql/types.py
+++ b/netbox/virtualization/graphql/types.py
@@ -84,7 +84,7 @@ class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
     primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
     primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
 
-    interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
+    interfaces: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
     services: List[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]
     virtualdisks: List[Annotated["VirtualDiskType", strawberry.lazy('virtualization.graphql.types')]]
 
@@ -102,8 +102,8 @@ class VMInterfaceType(IPAddressesMixin, ComponentType):
     vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
 
     tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
-    bridge_interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
-    child_interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
+    bridge_interfaces: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
+    child_interfaces: List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]
 
 
 @strawberry_django.type(


Test patch:
Repository: netbox-community/netbox

Issue:
Permit loading settings from a designated external module
### NetBox version

v4.0.1

### Feature type

New functionality

### Proposed functionality

Extend `settings.py` to load arbitrary settings from a designated external module (`local_settings.py`), if it exists, and set `settings._UNSUPPORTED_SETTINGS` to true, indicating the use of unsupported settings.

### Use case

This will permit an administrator to extend or override NetBox's core settings without needing to modify the core code base.

As we (the maintainers) cannot predict what changes may be made by an external module, **modification to the core settings is entirely unsupported.** If in use, users will need to remove `local_settings.py` and re-test for any suspect behavior prior to submitting a bug report.

### Database changes

None

### External dependencies

None


Discussion:

Resolving patch:
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -801,3 +801,10 @@ def _setting(name, default=None):
     RQ_QUEUES.update({
         f"{plugin_name}.{queue}": RQ_PARAMS for queue in plugin_config.queues
     })
+
+# UNSUPPORTED FUNCTIONALITY: Import any local overrides.
+try:
+    from .local_settings import *
+    _UNSUPPORTED_SETTINGS = True
+except ImportError:
+    pass


Test patch:
Repository: netbox-community/netbox

Issue:
'Object is not iterable' when setting a custom field (object) on Object Type through API
### Deployment Type

Self-hosted

### NetBox Version

v4.0.2

### Python Version

3.11

### Steps to Reproduce

Create a virtual machine

Create a custom field for "Virtualization > Virtual Machine" with type Object (Journal Entry)

Create a Journal Entry, ex. on the demo vm1 (361)

Set the custom field to the Journal Entry with the API, it works with the GUI

ex.
```
curl -X 'PUT' \
  'https://demo.netbox.dev/api/virtualization/virtual-machines/361/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFTOKEN: xxxx' \
  -d '{"custom_fields": {"api_test_custom_field_object": "1"}}'
```

### Expected Behavior

Get repsonse 200, set the journal entry to the custom field



### Observed Behavior

Cant add the entry through the api
```
{
  "error": "argument of type 'JournalEntry' is not iterable",
  "exception": "TypeError",
  "netbox_version": "4.0.2",
  "python_version": "3.10.14"
}
```


Discussion:

Resolving patch:
diff --git a/netbox/extras/api/serializers_/journaling.py b/netbox/extras/api/serializers_/journaling.py
--- a/netbox/extras/api/serializers_/journaling.py
+++ b/netbox/extras/api/serializers_/journaling.py
@@ -43,7 +43,7 @@ class Meta:
     def validate(self, data):
 
         # Validate that the parent object exists
-        if 'assigned_object_type' in data and 'assigned_object_id' in data:
+        if not self.nested and 'assigned_object_type' in data and 'assigned_object_id' in data:
             try:
                 data['assigned_object_type'].get_object_for_this_type(id=data['assigned_object_id'])
             except ObjectDoesNotExist:
@@ -51,10 +51,7 @@ def validate(self, data):
                     f"Invalid assigned_object: {data['assigned_object_type']} ID {data['assigned_object_id']}"
                 )
 
-        # Enforce model validation
-        super().validate(data)
-
-        return data
+        return super().validate(data)
 
     @extend_schema_field(serializers.JSONField(allow_null=True))
     def get_assigned_object(self, instance):


Test patch:
Repository: netbox-community/netbox

Issue:
User and group queries are not properly restricted via GraphQL API in v4.0.2 Re-Open
### Deployment Type

Self-hosted

### NetBox Version

v4.0.2

### Python Version

3.10

### Steps to Reproduce

This is is to re-opent #7814

Create New Group netbox-graphql. Don't add any permission to the group.
Add new user to the group
Login as new user
Access https://netbox/graphql

query {
  user_list{
    username
    password
    }
  }

Username and hash in password returned.



### Expected Behavior

Empty result retured because the user in a group without permission to Group/User view.

### Observed Behavior

All Username and hash in Database returned.


Discussion:

Resolving patch:
diff --git a/netbox/users/graphql/types.py b/netbox/users/graphql/types.py
--- a/netbox/users/graphql/types.py
+++ b/netbox/users/graphql/types.py
@@ -1,13 +1,10 @@
 from typing import List
 
-import strawberry
 import strawberry_django
 from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
-from strawberry import auto
-from users import filtersets
+
+from netbox.graphql.types import BaseObjectType
 from users.models import Group
-from utilities.querysets import RestrictedQuerySet
 from .filters import *
 
 __all__ = (
@@ -21,17 +18,16 @@
     fields=['id', 'name'],
     filters=GroupFilter
 )
-class GroupType:
+class GroupType(BaseObjectType):
     pass
 
 
 @strawberry_django.type(
     get_user_model(),
     fields=[
-        'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff',
-        'is_active', 'date_joined', 'groups',
+        'id', 'username', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'date_joined', 'groups',
     ],
     filters=UserFilter
 )
-class UserType:
+class UserType(BaseObjectType):
     groups: List[GroupType]


Test patch:
Repository: netbox-community/netbox

Issue:
Netbox UI. Checkbox disappearing in any Table (Site, Rack, etc) after clicking to next page
### Deployment Type

Self-hosted

### NetBox Version

v4.0.2

### Python Version

3.11

### Steps to Reproduce

1. Open Sites or Rack
2. Create more then default pagination (50) records
3. Move between pages or change per page number



### Expected Behavior

Moving between pages in Tables should not hide checkbox element
![image](https://github.com/netbox-community/netbox/assets/24622850/83b93ce7-fdbb-458b-9ed0-365a9161808c)

### Observed Behavior

Checkbox in first column disappeared. Not possible to select object
![image](https://github.com/netbox-community/netbox/assets/24622850/9f2bcdbc-26a3-420a-8a02-86610cdae8ff)



Discussion:

Resolving patch:
diff --git a/netbox/netbox/tables/tables.py b/netbox/netbox/tables/tables.py
--- a/netbox/netbox/tables/tables.py
+++ b/netbox/netbox/tables/tables.py
@@ -1,4 +1,5 @@
 from copy import deepcopy
+from functools import cached_property
 
 import django_tables2 as tables
 from django.contrib.auth.models import AnonymousUser
@@ -189,6 +190,7 @@ class NetBoxTable(BaseTable):
     actions = columns.ActionsColumn()
 
     exempt_columns = ('pk', 'actions')
+    embedded = False
 
     class Meta(BaseTable.Meta):
         pass
@@ -218,12 +220,12 @@ def __init__(self, *args, extra_columns=None, **kwargs):
 
         super().__init__(*args, extra_columns=extra_columns, **kwargs)
 
-    @property
+    @cached_property
     def htmx_url(self):
         """
         Return the base HTML request URL for embedded tables.
         """
-        if getattr(self, 'embedded', False):
+        if self.embedded:
             viewname = get_viewname(self._meta.model, action='list')
             try:
                 return reverse(viewname)
diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py
--- a/netbox/netbox/views/generic/bulk_views.py
+++ b/netbox/netbox/views/generic/bulk_views.py
@@ -163,7 +163,7 @@ def get(self, request):
 
         # If this is an HTMX request, return only the rendered table HTML
         if htmx_partial(request):
-            if not request.htmx.target:
+            if request.GET.get('embedded', False):
                 table.embedded = True
                 # Hide selection checkboxes
                 if 'pk' in table.base_columns:


Test patch:
Repository: netbox-community/netbox

Issue:
Clicking column header in Changelog causes panel to nest
### Deployment Type

Self-hosted

### NetBox Version

4.0.3

### Python Version

3.11

### Steps to Reproduce

1. Open main page / dashboard
2. Click on a column header of the changelog, e.g. "request id"


### Expected Behavior

Table is re-sorted accordingly.

### Observed Behavior

![grafik](https://github.com/netbox-community/netbox/assets/97662717/4d884dad-68ec-42c7-ac69-3c88c5a9686a)



Discussion:
i noticed this this morning too. 
The changelog in the dashboard is sorting by the last sort method when your in the changelog list view. The dashboard changelog should always sort by latest IMHO and ignore sort savings from the changelog list view
Thanks for reporting a problem you've encountered in NetBox.

I've reproduced the problem in the lab and am moving along its status to `needs owner`. I'm assigning it a low severity since the _Operations -> Change Log_ UI is not similarly affected, but we'll put a relatively high priority on addressing it.
Upgraded severity to medium because this issue in fact impacts any dashboard widget that includes a sortable table.

Resolving patch:
diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py
--- a/netbox/extras/dashboard/widgets.py
+++ b/netbox/extras/dashboard/widgets.py
@@ -265,6 +265,7 @@ def render(self, request):
         parameters = self.config.get('url_params') or {}
         if page_size := self.config.get('page_size'):
             parameters['per_page'] = page_size
+        parameters['embedded'] = True
 
         if parameters:
             try:


Test patch:
Repository: netbox-community/netbox

Issue:
Change log for terminating object is not updated when a cable is connected
### Deployment Type

NetBox Cloud

### NetBox Version

v4.0.3

### Python Version

3.12

### Steps to Reproduce

1. Create a device with two interfaces
2. Connect a cable between these two interfaces
3. Edit the description of the first interface (merely to trigger the creation of a new change record)
4. View the change log for the first interface

### Expected Behavior

The interface's change log should contain three entries:

1. The creation of the interface
2. The association of the interface with the cable (the `cable` and `cable_end` fields)
3. The modification of the `description` field

### Observed Behavior

Only two entries exist in the change log: There is no record indicating the change of the associated cable. In the first entry, the `cable` and `cable_end` fields are empty; in the second, they have been populated.


Discussion:

Resolving patch:
diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py
--- a/netbox/dcim/models/cables.py
+++ b/netbox/dcim/models/cables.py
@@ -355,11 +355,11 @@ def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
 
         # Set the cable on the terminating object
-        termination_model = self.termination._meta.model
-        termination_model.objects.filter(pk=self.termination_id).update(
-            cable=self.cable,
-            cable_end=self.cable_end
-        )
+        termination = self.termination._meta.model.objects.get(pk=self.termination_id)
+        termination.snapshot()
+        termination.cable = self.cable
+        termination.cable_end = self.cable_end
+        termination.save()
 
     def delete(self, *args, **kwargs):
 


Test patch:
Repository: mozmeao/snippets-service

Issue:
Convert some admin list_filters to dropdowns to improve the UI



Discussion:

Resolving patch:
diff --git a/snippets/base/admin.py b/snippets/base/admin.py
--- a/snippets/base/admin.py
+++ b/snippets/base/admin.py
@@ -9,6 +9,7 @@
 
 from advanced_filters.admin import AdminAdvancedFiltersMixin
 from django_ace import AceWidget
+from django_admin_listfilter_dropdown.filters import RelatedDropdownFilter
 from django_statsd.clients import statsd
 from jinja2.meta import find_undeclared_variables
 from reversion.admin import VersionAdmin
@@ -113,13 +114,6 @@ class BaseSnippetAdmin(AdminAdvancedFiltersMixin, VersionAdmin,
         'locale_list',
         'modified',
     )
-    list_filter = (
-        ModifiedFilter,
-        'disabled',
-        ReleaseFilter,
-        ('locales', admin.RelatedOnlyFieldListFilter),
-        ('client_match_rules', admin.RelatedOnlyFieldListFilter),
-    )
     list_editable = (
         'disabled',
     )
@@ -177,9 +171,13 @@ def get_changelist_form(self, request, **kwargs):
     readonly_fields = BaseSnippetAdmin.readonly_fields + ('preview_url',)
     search_fields = ('name', 'client_match_rules__description',
                      'template__name', 'campaign')
-    list_filter = BaseSnippetAdmin.list_filter + (
-        ('template', admin.RelatedOnlyFieldListFilter),
-        'exclude_from_search_providers',
+    list_filter = (
+        ModifiedFilter,
+        'disabled',
+        ReleaseFilter,
+        ('locales', RelatedDropdownFilter),
+        ('client_match_rules', RelatedDropdownFilter),
+        ('template', RelatedDropdownFilter),
     )
     filter_horizontal = (BaseSnippetAdmin.filter_horizontal +
                          ('exclude_from_search_providers', 'client_match_rules'))
@@ -334,6 +332,13 @@ def save_related(self, request, form, formsets, change):
 class JSONSnippetAdmin(BaseSnippetAdmin):
     form = forms.JSONSnippetAdminForm
     search_fields = ('name', 'client_match_rules__description')
+    list_filter = (
+        ModifiedFilter,
+        'disabled',
+        ReleaseFilter,
+        ('locales', RelatedDropdownFilter),
+        ('client_match_rules', RelatedDropdownFilter),
+    )
 
     fieldsets = (
         (None, {'fields': ('name', 'disabled', 'created', 'modified')}),
diff --git a/snippets/settings.py b/snippets/settings.py
--- a/snippets/settings.py
+++ b/snippets/settings.py
@@ -53,6 +53,7 @@
     'advanced_filters',
     'watchman',
     'quickedit',
+    'django_admin_listfilter_dropdown',
 
     # Django apps
     'django.contrib.admin',


Test patch:
Repository: mozmeao/snippets-service

Issue:
Save as new sets published to True
Clicking `save as new` when editing a snippet always sets `published` property to `True`.

This is a regression that appeared after #546 


Discussion:

Resolving patch:
diff --git a/snippets/base/admin.py b/snippets/base/admin.py
--- a/snippets/base/admin.py
+++ b/snippets/base/admin.py
@@ -142,7 +142,7 @@ def change_view(self, request, *args, **kwargs):
         if request.method == 'POST' and '_saveasnew' in request.POST:
             # Always saved cloned snippets as un-published.
             post_data = request.POST.copy()
-            post_data['published'] = u'off'
+            post_data.pop('published', None)
             request.POST = post_data
         return super(BaseSnippetAdmin, self).change_view(request, *args, **kwargs)
 


Test patch:
diff --git a/snippets/base/tests/test_admin.py b/snippets/base/tests/test_admin.py
--- a/snippets/base/tests/test_admin.py
+++ b/snippets/base/tests/test_admin.py
@@ -28,7 +28,7 @@ def test_save_as_published(self):
             self.model_admin.change_view(request, 999)
             change_view_mock.assert_called_with(request, 999, '', None)
             request = change_view_mock.call_args[0][0]
-            self.assertEqual(request.POST['published'], u'off')
+            self.assertTrue('published' not in request.POST)
 
     def test_normal_save_published(self):
         """Test that normal save doesn't alter 'published' attribute."""
Repository: mozmeao/snippets-service

Issue:
Add `generated_at` key in AS Router payload
Add `generated_at` with UTC datetime in ISO format in AS Router payload for debugging purposes

Final payload should look like this:

```json
"messages": {
 ...
}
"metadata": {
 "generated_at": "2018-07-24T13:48:24.531139"
}


Discussion:

Resolving patch:
diff --git a/snippets/base/models.py b/snippets/base/models.py
--- a/snippets/base/models.py
+++ b/snippets/base/models.py
@@ -194,7 +194,12 @@ def generate(self):
         if self.client.startpage_version == '6':
             # Generate the new AS Router bundle format
             data = [snippet.render_to_as_router() for snippet in self.snippets]
-            bundle_content = json.dumps({'messages': data})
+            bundle_content = json.dumps({
+                'messages': data,
+                'metadata': {
+                    'generated_at': datetime.utcnow().isoformat(),
+                }
+            })
         else:
             template = 'base/fetch_snippets.jinja'
             if self.client.startpage_version == '5':


Test patch:
diff --git a/snippets/base/tests/test_models.py b/snippets/base/tests/test_models.py
--- a/snippets/base/tests/test_models.py
+++ b/snippets/base/tests/test_models.py
@@ -539,9 +539,11 @@ def test_generate_activity_stream_router(self):
         self.snippet2.render_to_as_router = Mock()
         self.snippet2.render_to_as_router.return_value = 'snippet2'
 
-        with patch('snippets.base.models.cache') as cache:
-            with patch('snippets.base.models.default_storage') as default_storage:
-                bundle.generate()
+        with patch('snippets.base.models.datetime') as datetime:
+            with patch('snippets.base.models.cache') as cache:
+                with patch('snippets.base.models.default_storage') as default_storage:
+                    datetime.utcnow.return_value.isoformat.return_value = 'now'
+                    bundle.generate()
 
         self.assertTrue(bundle.filename.endswith('.json'))
         default_storage.save.assert_called_with(bundle.filename, ANY)
@@ -549,7 +551,9 @@ def test_generate_activity_stream_router(self):
 
         # Check content of saved file.
         content_file = default_storage.save.call_args[0][1]
-        self.assertEqual(content_file.read(), '{"messages": ["snippet1", "snippet2"]}')
+        content_json = json.load(content_file)
+        self.assertEqual(content_json['messages'], ['snippet1', 'snippet2'])
+        self.assertEqual(content_json['metadata']['generated_at'], 'now')
 
     @override_settings(BUNDLE_BROTLI_COMPRESS=True)
     def test_generate_brotli(self):