Преглед изворни кода

Merge pull request #15631 from netbox-community/develop

Release v3.7.5
Jeremy Stretch пре 1 година
родитељ
комит
1c76034069
44 измењених фајлова са 1479 додато и 1501 уклоњено
  1. 2 2
      .github/ISSUE_TEMPLATE/bug_report.yaml
  2. 1 1
      .github/ISSUE_TEMPLATE/documentation_change.yaml
  3. 2 2
      .github/ISSUE_TEMPLATE/feature_request.yaml
  4. 21 0
      .github/workflows/auto-assign-issue.yml
  5. 2 3
      .github/workflows/close-stale-issues.yml
  6. 1 1
      .github/workflows/lock-threads.yml
  7. 1 1
      README.md
  8. 2 0
      contrib/generated_schema.json
  9. 13 5
      docs/integrations/rest-api.md
  10. 2 2
      docs/plugins/index.md
  11. 156 255
      docs/reference/markdown.md
  12. 21 0
      docs/release-notes/version-3.7.md
  13. 6 0
      netbox/circuits/filtersets.py
  14. 1 5
      netbox/circuits/forms/filtersets.py
  15. 3 1
      netbox/circuits/tests/test_filtersets.py
  16. 12 9
      netbox/dcim/models/devices.py
  17. 6 0
      netbox/extras/forms/bulk_import.py
  18. 1 1
      netbox/extras/models/reports.py
  19. 1 1
      netbox/extras/models/tags.py
  20. 1 1
      netbox/ipam/forms/bulk_import.py
  21. 18 0
      netbox/ipam/forms/model_forms.py
  22. 3 2
      netbox/netbox/search/__init__.py
  23. 1 1
      netbox/netbox/settings.py
  24. 1 1
      netbox/templates/dcim/device/base.html
  25. 2 0
      netbox/templates/extras/schema/devicetype_schema.jinja2
  26. 166 168
      netbox/translations/en/LC_MESSAGES/django.po
  27. BIN
      netbox/translations/es/LC_MESSAGES/django.mo
  28. 167 169
      netbox/translations/es/LC_MESSAGES/django.po
  29. BIN
      netbox/translations/fr/LC_MESSAGES/django.mo
  30. 167 169
      netbox/translations/fr/LC_MESSAGES/django.po
  31. BIN
      netbox/translations/ja/LC_MESSAGES/django.mo
  32. 167 169
      netbox/translations/ja/LC_MESSAGES/django.po
  33. BIN
      netbox/translations/pt/LC_MESSAGES/django.mo
  34. 167 169
      netbox/translations/pt/LC_MESSAGES/django.po
  35. BIN
      netbox/translations/ru/LC_MESSAGES/django.mo
  36. 173 174
      netbox/translations/ru/LC_MESSAGES/django.po
  37. BIN
      netbox/translations/tr/LC_MESSAGES/django.mo
  38. 167 169
      netbox/translations/tr/LC_MESSAGES/django.po
  39. 4 4
      netbox/users/forms/filtersets.py
  40. 6 1
      netbox/users/models.py
  41. 2 2
      netbox/users/views.py
  42. 3 3
      netbox/vpn/forms/model_forms.py
  43. 1 1
      netbox/vpn/tables/tunnels.py
  44. 9 9
      requirements.txt

+ 2 - 2
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -1,7 +1,7 @@
 ---
 name: 🐛 Bug Report
 description: Report a reproducible bug in the current release of NetBox
-labels: ["type: bug"]
+labels: ["type: bug", "needs triage"]
 body:
   - type: markdown
     attributes:
@@ -26,7 +26,7 @@ body:
     attributes:
       label: NetBox Version
       description: What version of NetBox are you currently running?
-      placeholder: v3.7.4
+      placeholder: v3.7.5
     validations:
       required: true
   - type: dropdown

+ 1 - 1
.github/ISSUE_TEMPLATE/documentation_change.yaml

@@ -1,7 +1,7 @@
 ---
 name: 📖 Documentation Change
 description: Suggest an addition or modification to the NetBox documentation
-labels: ["type: documentation"]
+labels: ["type: documentation", "needs triage"]
 body:
   - type: dropdown
     attributes:

+ 2 - 2
.github/ISSUE_TEMPLATE/feature_request.yaml

@@ -1,7 +1,7 @@
 ---
 name: ✨ Feature Request
 description: Propose a new NetBox feature or enhancement
-labels: ["type: feature"]
+labels: ["type: feature", "needs triage"]
 body:
   - type: markdown
     attributes:
@@ -14,7 +14,7 @@ body:
     attributes:
       label: NetBox version
       description: What version of NetBox are you currently running?
-      placeholder: v3.7.4
+      placeholder: v3.7.5
     validations:
       required: true
   - type: dropdown

+ 21 - 0
.github/workflows/auto-assign-issue.yml

@@ -0,0 +1,21 @@
+# auto-assign-issue (https://github.com/marketplace/actions/auto-assign-issue)
+name: Issue assignment
+
+on:
+  issues:
+    types: [opened]
+
+permissions:
+  issues: write
+
+jobs:
+  auto-assign:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: pozil/auto-assign-issue@v1
+        if: "contains(github.event.issue.labels.*.name, 'needs triage')"
+        with:
+          # Weighted assignments
+          assignees: arthanson:3, jeffgdotorg:3, jeremystretch:3, abhi1693, DanSheps
+          numOfAssignee: 1
+          abortIfPreviousAssignees: true

+ 2 - 3
.github/workflows/stale.yml → .github/workflows/close-stale-issues.yml

@@ -1,5 +1,5 @@
 # close-stale-issues (https://github.com/marketplace/actions/close-stale-issues)
-name: 'Close stale issues/PRs'
+name: Close stale issues/PRs
 
 on:
   schedule:
@@ -12,10 +12,9 @@ permissions:
 
 jobs:
   stale:
-
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v8
+      - uses: actions/stale@v9
         with:
           close-issue-message: >
             This issue has been automatically closed due to lack of activity. In an

+ 1 - 1
.github/workflows/lock.yml → .github/workflows/lock-threads.yml

@@ -1,5 +1,5 @@
 # lock-threads (https://github.com/marketplace/actions/lock-threads)
-name: 'Lock threads'
+name: Lock threads
 
 on:
   schedule:

+ 1 - 1
README.md

@@ -84,7 +84,7 @@ NetBox automatically logs the creation, modification, and deletion of all manage
 
 <p align="center">
   <a href="https://netboxlabs.com/netbox-cloud/"><img src="docs/media/misc/netbox_cloud.png" alt="NetBox Cloud" /></a><br />
-  Looking for an enterprise solution? Check out <strong><a href="https://netboxlabs.com/netbox-cloud/">NetBox Cloud</a></strong>!
+  Looking for a managed solution? Check out <strong><a href="https://netboxlabs.com/netbox-cloud/">NetBox Cloud</a></strong> or <strong><a href="https://netboxlabs.com/netbox-enterprise/">NetBox Enterprise</a></strong>!
 </p>
 
 ## Get Involved

+ 2 - 0
contrib/generated_schema.json

@@ -1,5 +1,7 @@
 {
     "type": "object",
+    "$id": "urn:devicetype-library:generated-schema",
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
     "additionalProperties": false,
     "definitions": {
         "airflow": {

+ 13 - 5
docs/integrations/rest-api.md

@@ -85,13 +85,19 @@ Each model generally has two views associated with it: a list view and a detail
 * `/api/dcim/devices/` - List existing devices or create a new device
 * `/api/dcim/devices/123/` - Retrieve, update, or delete the device with ID 123
 
-Lists of objects can be filtered using a set of query parameters. For example, to find all interfaces belonging to the device with ID 123:
+Lists of objects can be filtered and ordered using a set of query parameters. For example, to find all interfaces belonging to the device with ID 123:
 
 ```
 GET /api/dcim/interfaces/?device_id=123
 ```
 
-See the [filtering documentation](../reference/filtering.md) for more details.
+An optional `ordering` parameter can be used to define how to sort the results. Building off the previous example, to sort all the interfaces in reverse order of creation (newest to oldest) for a device with ID 123:
+
+```
+GET /api/dcim/interfaces/?device_id=123&ordering=-created
+```
+
+See the [filtering documentation](../reference/filtering.md) for more details on topics related to filtering, ordering and lookup expressions.
 
 ## Serialization
 
@@ -647,18 +653,20 @@ Note that we are _not_ passing an existing REST API token with this request. If
 {
     "id": 6,
     "url": "https://netbox/api/users/tokens/6/",
-    "display": "3c9cb9 (hankhill)",
+    "display": "**********************************3c9cb9",
     "user": {
         "id": 2,
         "url": "https://netbox/api/users/users/2/",
         "display": "hankhill",
         "username": "hankhill"
     },
-    "created": "2021-06-11T20:09:13.339367Z",
+    "created": "2024-03-11T20:09:13.339367Z",
     "expires": null,
+    "last_used": null,
     "key": "9fc9b897abec9ada2da6aec9dbc34596293c9cb9",
     "write_enabled": true,
-    "description": ""
+    "description": "",
+    "allowed_ips": []
 }
 ```
 

+ 2 - 2
docs/plugins/index.md

@@ -82,10 +82,10 @@ Plugins may package static files to be served directly by the HTTP front end. En
 
 ### Restart WSGI Service
 
-Restart the WSGI service to load the new plugin:
+Restart the WSGI service and RQ workers to load the new plugin:
 
 ```no-highlight
-# sudo systemctl restart netbox
+# sudo systemctl restart netbox netbox-rq
 ```
 
 ## Removing Plugins

+ 156 - 255
docs/reference/markdown.md

@@ -1,353 +1,254 @@
----
-hide:
-  - toc
----
-
 # Markdown
 
-NetBox supports markdown rendering for certain text fields.
-
-## Syntax
-
-##### Table of Contents  
-[Headers](#headers)  
-[Emphasis](#emphasis)  
-[Lists](#lists)  
-[Links](#links)  
-[Images](#images)  
-[Code Blocks](#code)  
-[Tables](#tables)  
-[Blockquotes](#blockquotes)  
-[Inline HTML](#html)  
-[Horizontal Rule](#hr)  
-[Line Breaks](#lines)  
-
-<a name="headers"></a>
+NetBox supports Markdown rendering for certain text fields. Some common examples are provided below. For a complete Markdown reference, please see [Markdownguide.org](https://www.markdownguide.org/basic-syntax/).
 
-## Headers
+## Headings
 
 ```no-highlight
-# H1
-## H2
-### H3
-#### H4
-##### H5
-###### H6
-
-Alternatively, for H1 and H2, an underline-ish style:
-
-Alt-H1
-======
-
-Alt-H2
-------
+# Heading 1
+## Heading 2
+### Heading 3
+#### Heading 4
+##### Heading 5
+###### Heading 6
 ```
 
-# H1
-## H2
-### H3
-#### H4
-##### H5
-###### H6
-
-<a name="emphasis"></a>
+<h1>Heading 1</h1>
+<h2>Heading 2</h2>
+<h3>Heading 3</h3>
+<h4>Heading 4</h4>
+<h5>Heading 5</h5>
+<h6>Heading 6</h6>
 
-## Emphasis
+Alternatively, for H1 and H2, an underline-ish style:
 
 ```no-highlight
-Emphasis, aka italics, with *asterisks* or _underscores_.
-
-Strong emphasis, aka bold, with **asterisks** or __underscores__.
-
-Combined emphasis with **asterisks and _underscores_**.
+Heading 1
+=========
 
-Strikethrough uses two tildes. ~~Scratch this.~~
+Heading 2
+---------
 ```
 
-Emphasis, aka italics, with *asterisks* or _underscores_.
-
-Strong emphasis, aka bold, with **asterisks** or __underscores__.
-
-Combined emphasis with **asterisks and _underscores_**.
+<h1>Heading 1</h1>
+<h2>Heading 2</h2>
 
-Strikethrough uses two tildes. ~~Scratch this.~~
+## Text
 
+```no-highlight
+Italicize text with *asterisks* or _underscores_.
+```
 
-<a name="lists"></a>
+Italicize text with *asterisks* or _underscores_.
 
-## Lists
+```no-highlight
+Bold text with two **asterisks** or __underscores__.
+```
 
-(In this example, leading and trailing spaces are shown with with dots: ⋅)
+Bold text with two **asterisks** or __underscores__.
 
 ```no-highlight
-1. First ordered list item
-2. Another item
-⋅⋅* Unordered sub-list. 
-1. Actual numbers don't matter, just that it's a number
-⋅⋅1. Ordered sub-list
-4. And another item.
-
-⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
-
-⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅
-⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅
-⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
-
-* Unordered list can use asterisks
-- Or minuses
-+ Or pluses
+Strike text with two tildes. ~~Deleted text.~~
 ```
 
-1. First ordered list item
-2. Another item
-  * Unordered sub-list. 
-1. Actual numbers don't matter, just that it's a number
-  1. Ordered sub-list
-4. And another item.
+Strike text with two tildes. ~~Deleted text.~~
 
-   You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
-
-   To have a line break without a paragraph, you will need to use two trailing spaces.  
-   Note that this line is separate, but within the same paragraph.  
-   (This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
+## Line Breaks
 
-* Unordered list can use asterisks
-- Or minuses
-+ Or pluses
+By default, Markdown will remove line breaks between successive lines of text. For example:
 
-<a name="links"></a>
+```no-highlight
+This is one line.
+And this is another line.
+One more line here.
+```
 
-## Links
+This is one line.
+And this is another line.
+One more line here.
 
-There are two ways to create links.
+To preserve line breaks, append two spaces to each line (represented below with the `⋅` character).
 
 ```no-highlight
-[I'm an inline-style link](https://www.google.com)
+This is one line.⋅⋅
+And this is another line.⋅⋅
+One more line here.
+```
 
-[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
+This is one line.  
+And this is another line.  
+One more line here.
 
-[I'm a reference-style link][Arbitrary case-insensitive reference text]
+## Lists
 
-[You can use numbers for reference-style link definitions][1]
+Use asterisks or hyphens for unordered lists. Indent items by four spaces to start a child list.
 
-Or leave it empty and use the [link text itself].
+```no-highlight
+* Alpha
+* Bravo
+* Charlie
+  * Child item 1
+  * Child item 2
+* Delta
+```
 
-URLs and URLs in angle brackets will automatically get turned into links. 
-http://www.example.com or <http://www.example.com> and sometimes 
-example.com (but not on Github, for example).
+* Alpha
+* Bravo
+* Charlie
+    * Child item 1
+    * Child item 2
+* Delta
 
-Some text to show that the reference links can follow later.
+Use digits followed by periods for ordered (numbered) lists.
 
-[arbitrary case-insensitive reference text]: https://www.mozilla.org
-[1]: http://slashdot.org
-[link text itself]: http://www.reddit.com
+```no-highlight
+1. Red
+2. Green
+3. Blue
+    1. Light blue
+    2. Dark blue
+4. Orange
 ```
 
-[I'm an inline-style link](https://www.google.com)
-
-[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
+1. Red
+2. Green
+3. Blue
+    1. Light blue
+    2. Dark blue
+4. Orange
 
-[I'm a reference-style link][Arbitrary case-insensitive reference text]
-
-[You can use numbers for reference-style link definitions][1]
+## Links
 
-Or leave it empty and use the [link text itself].
+Text can be rendered as a hyperlink by encasing it in square brackets, followed by a URL in parentheses. A title (text displayed on hover) may optionally be included as well.
 
-URLs and URLs in angle brackets will automatically get turned into links. 
-http://www.example.com or <http://www.example.com> and sometimes 
-example.com (but not on Github, for example).
+```no-highlight
+Here's an [example](https://www.example.com) of a link.
 
-Some text to show that the reference links can follow later.
+And here's [another link](https://www.example.com "Click me!"), this time with a title.
+```
 
-[arbitrary case-insensitive reference text]: https://www.mozilla.org
-[1]: http://slashdot.org
-[link text itself]: http://www.reddit.com
+Here's an [example](https://www.example.com) of a link.
 
-<a name="images"></a>
+And here's [another link](https://www.example.com "Click me!"), with a title.
 
 ## Images
 
-```
-Here's the NetBox logo (hover to see the title text):
-
-Inline-style: 
-![alt text](/media/misc/netbox_logo.png "Logo Title Text 1")
-
-Reference-style: 
-![alt text][logo]
+The syntax for embedding an image is very similar to that used for a hyperlink. Alternate text should always be provided; this will be displayed if the image fails to load. As with hyperlinks, title text is optional.
 
-[logo]: /media/misc/netbox_logo.png "Logo Title Text 2"
+```no-highlight
+![Alternate text](/path/to/image.png "Image title text")
 ```
 
-Here's the NetBox logo (hover to see the title text):
-
-Inline-style: 
-![alt text](../media/misc/netbox_logo.png "Logo Title Text 1")
-
-Reference-style: 
-![alt text][logo]
+## Code Blocks
 
-[logo]: ../media/misc/netbox_logo.png "Logo Title Text 2"
+Single backticks can be used to annotate code inline. Text enclosed by lines of three backticks will be displayed as a code block.
 
-<a name="code"></a>
-
-## Code blocks
-
-```
-Inline `code` has `back-ticks around` it.
+```no-highlight
+Paragraphs are rendered in HTML using `<p>` and `</p>` tags.
 ```
 
-Inline `code` has `back-ticks around` it.
-
-Blocks of code are fenced by lines with three back-ticks <code>```</code>
+Paragraphs are rendered in HTML using `<p>` and `</p>` tags.
 
 ````
 ```
-var s = "Code block";
-alert(s);
+def my_func(foo, bar):
+    # Do something
+    return foo * bar
 ```
 ````
 
+```no-highlight
+def my_func(foo, bar):
+    # Do something
+    return foo * bar
 ```
-var s = "Code block";
-alert(s);
-```
-
-<a name="tables"></a>
 
 ## Tables
 
+Simple tables can be constructed using the pipe character (`|`) to denote columns, and hyphens (`-`) to denote the heading. Inline Markdown can be used to style text within columns.
+
 ```no-highlight
-Colons can be used to align columns.
-
-| Tables        | Are           | Cool  |
-| ------------- |:-------------:| -----:|
-| col 3 is      | right-aligned | $1600 |
-| col 2 is      | centered      |   $12 |
-| zebra stripes | are neat      |    $1 |
-
-There must be at least 3 dashes separating each header cell.
-The outer pipes (|) are optional, and you don't need to make the 
-raw Markdown line up prettily. You can also use inline Markdown.
-
-Markdown | Less | Pretty
---- | --- | ---
-*Still* | `renders` | **nicely**
-1 | 2 | 3
+| Heading 1 | Heading 2 | Heading 3 |
+|-----------|-----------|-----------|
+| Row 1     | Alpha     | Red       |
+| Row 2     | **Bravo** | Green     |
+| Row 3     | Charlie   | ~~Blue~~  |
 ```
 
-Colons can be used to align columns.
-
-| Tables        | Are           | Cool |
-| ------------- |:-------------:| -----:|
-| col 3 is      | right-aligned | $1600 |
-| col 2 is      | centered      |   $12 |
-| zebra stripes | are neat      |    $1 |
-
-There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
+| Heading 1 | Heading 2 | Heading 3 |
+|-----------|-----------|-----------|
+| _Row 1_   | Alpha     | Red       |
+| Row 2     | **Bravo** | Green     |
+| Row 3     | Charlie   | ~~Blue~~  |
 
-Markdown | Less | Pretty
---- | --- | ---
-*Still* | `renders` | **nicely**
-1 | 2 | 3
-
-<a name="blockquotes"></a>
-
-## Blockquotes
+Colons can be used to align text to the left or right side of a column.
 
 ```no-highlight
-> Blockquotes are very handy in email to emulate reply text.
-> This line is part of the same quote.
-
-Quote break.
-
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. 
+| Left-aligned | Centered | Right-aligned |
+|:-------------|:--------:|--------------:|
+| Text         | Text     | Text          |
+| Text         | Text     | Text          |
+| Text         | Text     | Text          |
 ```
 
-> Blockquotes are very handy in email to emulate reply text.
-> This line is part of the same quote.
+| Left-aligned | Centered | Right-aligned |
+|:-------------|:--------:|--------------:|
+| Text         | Text     | Text          |
+| Text         | Text     | Text          |
+| Text         | Text     | Text          |
 
-Quote break.
+## Blockquotes
 
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. 
+Text can be wrapped in a blockquote by prepending a right angle bracket (`>`) before each line.
 
-<a name="html"></a>
+```no-highlight
+> I think that I shall never see
+> a graph more lovely than a tree.
+> A tree whose crucial property
+> is loop-free connectivity.
+```
 
-## Inline HTML
+> I think that I shall never see
+> a graph more lovely than a tree.
+> A tree whose crucial property
+> is loop-free connectivity.
 
-You can also use raw HTML in your Markdown, and it'll mostly work pretty well. 
+Markdown removes line breaks by default. To preserve line breaks, append two spaces to each line (represented below with the `⋅` character).
 
 ```no-highlight
-<dl>
-  <dt>Definition list</dt>
-  <dd>Is something people use sometimes.</dd>
-
-  <dt>Markdown in HTML</dt>
-  <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
-</dl>
+> I think that I shall never see⋅⋅
+> a graph more lovely than a tree.⋅⋅
+> A tree whose crucial property⋅⋅
+> is loop-free connectivity.
 ```
 
-<dl>
-  <dt>Definition list</dt>
-  <dd>Is something people use sometimes.</dd>
-
-  <dt>Markdown in HTML</dt>
-  <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
-</dl>
-
-<a name="hr"></a>
+> I think that I shall never see  
+> a graph more lovely than a tree.  
+> A tree whose crucial property  
+> is loop-free connectivity.
 
 ## Horizontal Rule
 
-```
-Three or more...
+A horizontal rule is a single line rendered across the width of the page using a series of three or more hyphens or asterisks. It can be useful for separating sections of content.
+
+```no-highlight
+Content
 
 ---
 
-Hyphens
+More content
 
 ***
 
-Asterisks
-
-___
-
-Underscores
+Final content
 ```
 
-Three or more...
+Content
 
 ---
 
-Hyphens
+More content
 
 ***
 
-Asterisks
-
-___
-
-Underscores
-
-<a name="lines"></a>
-
-## Line Breaks
-
-
-```
-Here's a line for us to start with.
-
-This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
-
-This line is also a separate paragraph, but...
-This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-```
-
-Here's a line for us to start with.
-
-This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
-
-This line is also begins a separate paragraph, but...  
-This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-
-Based on [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) by [adam-p](https://github.com/adam-p) licensed under [CC-BY](https://creativecommons.org/licenses/by/3.0/)
+Final content

+ 21 - 0
docs/release-notes/version-3.7.md

@@ -1,5 +1,26 @@
 # NetBox v3.7
 
+## v3.7.5 (2024-04-04)
+
+### Enhancements
+
+* [#14707](https://github.com/netbox-community/netbox/issues/14707) - Clarify interface designation when creating tunnel terminations
+* [#15039](https://github.com/netbox-community/netbox/issues/15039) - Allow API tokens to be cloned
+
+### Bug Fixes
+
+* [#14799](https://github.com/netbox-community/netbox/issues/14799) - Avoid caching modified reports & scripts
+* [#15029](https://github.com/netbox-community/netbox/issues/15029) - Raise a clean validation error when attempting to make duplicate FHRP group assignments
+* [#15102](https://github.com/netbox-community/netbox/issues/15102) - Fix usage of selector widget for form fields referencing users/groups
+* [#15435](https://github.com/netbox-community/netbox/issues/15435) - Correct permissions name to allow adding a module bay to a device via the UI
+* [#15502](https://github.com/netbox-community/netbox/issues/15502) - Fix KeyError exception when modifying an IP address assigned to a virtual machine
+* [#15597](https://github.com/netbox-community/netbox/issues/15597) - Restore help modal for `button_class` field on custom link bulk import form
+* [#15598](https://github.com/netbox-community/netbox/issues/15598) - Fix exception when creating a device from a device type with one or more child inventory items
+* [#15608](https://github.com/netbox-community/netbox/issues/15608) - Avoid caching values of null fields in search index
+* [#15609](https://github.com/netbox-community/netbox/issues/15609) - Fix filtering of the providers list by assigned ASN
+
+---
+
 ## v3.7.4 (2024-03-13)
 
 ### Enhancements

+ 6 - 0
netbox/circuits/filtersets.py

@@ -64,6 +64,12 @@ class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
         queryset=ASN.objects.all(),
         label=_('ASN (ID)'),
     )
+    asn = django_filters.ModelMultipleChoiceFilter(
+        field_name='asns__asn',
+        queryset=ASN.objects.all(),
+        to_field_name='asn',
+        label=_('ASN'),
+    )
 
     class Meta:
         model = Provider

+ 1 - 5
netbox/circuits/forms/filtersets.py

@@ -24,7 +24,7 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
     fieldsets = (
         (None, ('q', 'filter_id', 'tag')),
         (_('Location'), ('region_id', 'site_group_id', 'site_id')),
-        (_('ASN'), ('asn',)),
+        (_('ASN'), ('asn_id',)),
         (_('Contacts'), ('contact', 'contact_role', 'contact_group')),
     )
     region_id = DynamicModelMultipleChoiceField(
@@ -46,10 +46,6 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
         },
         label=_('Site')
     )
-    asn = forms.IntegerField(
-        required=False,
-        label=_('ASN (legacy)')
-    )
     asn_id = DynamicModelMultipleChoiceField(
         queryset=ASN.objects.all(),
         required=False,

+ 3 - 1
netbox/circuits/tests/test_filtersets.py

@@ -90,10 +90,12 @@ class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'description': ['foobar1', 'foobar2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
-    def test_asn_id(self):  # ASN object assignment
+    def test_asn(self):
         asns = ASN.objects.all()[:2]
         params = {'asn_id': [asns[0].pk, asns[1].pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+        params = {'asn': [asns[0].asn, asns[1].asn]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
     def test_region(self):
         regions = Region.objects.all()[:2]

+ 12 - 9
netbox/dcim/models/devices.py

@@ -996,17 +996,16 @@ class Device(
             bulk_create: If True, bulk_create() will be called to create all components in a single query
                          (default). Otherwise, save() will be called on each instance individually.
         """
-        components = [obj.instantiate(device=self) for obj in queryset]
-        if not components:
-            return
-
-        # Set default values for any applicable custom fields
         model = queryset.model.component_model
-        if cf_defaults := CustomField.objects.get_defaults_for_model(model):
-            for component in components:
-                component.custom_field_data = cf_defaults
 
         if bulk_create:
+            components = [obj.instantiate(device=self) for obj in queryset]
+            if not components:
+                return
+            # Set default values for any applicable custom fields
+            if cf_defaults := CustomField.objects.get_defaults_for_model(model):
+                for component in components:
+                    component.custom_field_data = cf_defaults
             model.objects.bulk_create(components)
             # Manually send the post_save signal for each of the newly created components
             for component in components:
@@ -1019,7 +1018,11 @@ class Device(
                     update_fields=None
                 )
         else:
-            for component in components:
+            for obj in queryset:
+                component = obj.instantiate(device=self)
+                # Set default values for any applicable custom fields
+                if cf_defaults := CustomField.objects.get_defaults_for_model(model):
+                    component.custom_field_data = cf_defaults
                 component.save()
 
     def save(self, *args, **kwargs):

+ 6 - 0
netbox/extras/forms/bulk_import.py

@@ -116,6 +116,12 @@ class CustomLinkImportForm(CSVModelForm):
         queryset=ContentType.objects.with_feature('custom_links'),
         help_text=_("One or more assigned object types")
     )
+    button_class = CSVChoiceField(
+        label=_('button class'),
+        required=False,
+        choices=CustomLinkButtonClassChoices,
+        help_text=_('The class of the first link in a group will be used for the dropdown button')
+    )
 
     class Meta:
         model = CustomLink

+ 1 - 1
netbox/extras/models/reports.py

@@ -52,7 +52,7 @@ class ReportModule(PythonModuleMixin, JobsMixin, ManagedFile):
     def __str__(self):
         return self.python_name
 
-    @cached_property
+    @property
     def reports(self):
 
         def _get_name(cls):

+ 1 - 1
netbox/extras/models/tags.py

@@ -37,7 +37,7 @@ class Tag(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel, TagBase):
         to='contenttypes.ContentType',
         related_name='+',
         blank=True,
-        help_text=_("The object type(s) to which this this tag can be applied.")
+        help_text=_("The object type(s) to which this tag can be applied.")
     )
 
     clone_fields = (

+ 1 - 1
netbox/ipam/forms/bulk_import.py

@@ -378,7 +378,7 @@ class IPAddressImportForm(NetBoxModelImportForm):
 
         # Set as primary for device/VM
         if self.cleaned_data.get('is_primary'):
-            parent = self.cleaned_data['device'] or self.cleaned_data['virtual_machine']
+            parent = self.cleaned_data.get('device') or self.cleaned_data.get('virtual_machine')
             if self.instance.address.version == 4:
                 parent.primary_ip4 = ipaddress
             elif self.instance.address.version == 6:

+ 18 - 0
netbox/ipam/forms/model_forms.py

@@ -507,6 +507,24 @@ class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
         for ipaddress in ipaddresses:
             self.fields['group'].widget.add_query_param('related_ip', ipaddress.pk)
 
+    def clean_group(self):
+        group = self.cleaned_data['group']
+
+        conflicting_assignments = FHRPGroupAssignment.objects.filter(
+            interface_type=self.instance.interface_type,
+            interface_id=self.instance.interface_id,
+            group=group
+        )
+        if self.instance.id:
+            conflicting_assignments = conflicting_assignments.exclude(id=self.instance.id)
+
+        if conflicting_assignments.exists():
+            raise forms.ValidationError(
+                _('Assignment already exists')
+            )
+
+        return group
+
 
 class VLANGroupForm(NetBoxModelForm):
     scope_type = ContentTypeChoiceField(

+ 3 - 2
netbox/netbox/search/__init__.py

@@ -59,9 +59,10 @@ class SearchIndex:
     @staticmethod
     def get_field_value(instance, field_name):
         """
-        Return the value of the specified model field as a string.
+        Return the value of the specified model field as a string (or None).
         """
-        return str(getattr(instance, field_name))
+        if value := getattr(instance, field_name):
+            return str(value)
 
     @classmethod
     def get_category(cls):

+ 1 - 1
netbox/netbox/settings.py

@@ -28,7 +28,7 @@ from netbox.plugins import PluginConfig
 # Environment setup
 #
 
-VERSION = '3.7.4'
+VERSION = '3.7.5'
 
 # Hostname
 HOSTNAME = platform.node()

+ 1 - 1
netbox/templates/dcim/device/base.html

@@ -42,7 +42,7 @@
                 {% if perms.dcim.add_rearport %}
                     <li><a class="dropdown-item" href="{% url 'dcim:rearport_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_rearports' pk=object.pk %}">{% trans "Rear Ports" %}</a></li>
                 {% endif %}
-                {% if perms.dcim.add_devicebay %}
+                {% if perms.dcim.add_modulebay %}
                     <li><a class="dropdown-item" href="{% url 'dcim:modulebay_add' %}?device={{ object.pk }}&return_url={% url 'dcim:device_modulebays' pk=object.pk %}">{% trans "Module Bays" %}</a></li>
                 {% endif %}
                 {% if perms.dcim.add_devicebay %}

+ 2 - 0
netbox/templates/extras/schema/devicetype_schema.jinja2

@@ -1,5 +1,7 @@
 {
   "type": "object",
+  "$id": "urn:devicetype-library:generated-schema",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
   "additionalProperties": false,
   "definitions": {
     "airflow": {

Разлика између датотеке није приказан због своје велике величине
+ 166 - 168
netbox/translations/en/LC_MESSAGES/django.po


BIN
netbox/translations/es/LC_MESSAGES/django.mo


Разлика између датотеке није приказан због своје велике величине
+ 167 - 169
netbox/translations/es/LC_MESSAGES/django.po


BIN
netbox/translations/fr/LC_MESSAGES/django.mo


Разлика између датотеке није приказан због своје велике величине
+ 167 - 169
netbox/translations/fr/LC_MESSAGES/django.po


BIN
netbox/translations/ja/LC_MESSAGES/django.mo


Разлика између датотеке није приказан због своје велике величине
+ 167 - 169
netbox/translations/ja/LC_MESSAGES/django.po


BIN
netbox/translations/pt/LC_MESSAGES/django.mo


Разлика између датотеке није приказан због своје велике величине
+ 167 - 169
netbox/translations/pt/LC_MESSAGES/django.po


BIN
netbox/translations/ru/LC_MESSAGES/django.mo


Разлика између датотеке није приказан због своје велике величине
+ 173 - 174
netbox/translations/ru/LC_MESSAGES/django.po


BIN
netbox/translations/tr/LC_MESSAGES/django.mo


Разлика између датотеке није приказан због своје велике величине
+ 167 - 169
netbox/translations/tr/LC_MESSAGES/django.po


+ 4 - 4
netbox/users/forms/filtersets.py

@@ -11,21 +11,21 @@ from utilities.forms.fields import DynamicModelMultipleChoiceField
 from utilities.forms.widgets import DateTimePicker
 
 __all__ = (
-    'GroupFilterForm',
+    'NetBoxGroupFilterForm',
     'ObjectPermissionFilterForm',
-    'UserFilterForm',
+    'NetBoxUserFilterForm',
     'TokenFilterForm',
 )
 
 
-class GroupFilterForm(NetBoxModelFilterSetForm):
+class NetBoxGroupFilterForm(NetBoxModelFilterSetForm):
     model = NetBoxGroup
     fieldsets = (
         (None, ('q', 'filter_id',)),
     )
 
 
-class UserFilterForm(NetBoxModelFilterSetForm):
+class NetBoxUserFilterForm(NetBoxModelFilterSetForm):
     model = NetBoxUser
     fieldsets = (
         (None, ('q', 'filter_id',)),

+ 6 - 1
netbox/users/models.py

@@ -17,6 +17,7 @@ from netaddr import IPNetwork
 from core.models import ContentType
 from ipam.fields import IPNetworkField
 from netbox.config import get_config
+from netbox.models.features import CloningMixin
 from utilities.querysets import RestrictedQuerySet
 from utilities.utils import flatten_dict
 from .constants import *
@@ -234,7 +235,7 @@ def create_userconfig(instance, created, raw=False, **kwargs):
 # REST API
 #
 
-class Token(models.Model):
+class Token(CloningMixin, models.Model):
     """
     An API token used for user authentication. This extends the stock model to allow each user to have multiple tokens.
     It also supports setting an expiration time and toggling write ability.
@@ -285,6 +286,10 @@ class Token(models.Model):
         ),
     )
 
+    clone_fields = (
+        'user', 'expires', 'write_enabled', 'description', 'allowed_ips',
+    )
+
     objects = RestrictedQuerySet.as_manager()
 
     class Meta:

+ 2 - 2
netbox/users/views.py

@@ -58,7 +58,7 @@ class TokenBulkDeleteView(generic.BulkDeleteView):
 class UserListView(generic.ObjectListView):
     queryset = NetBoxUser.objects.all()
     filterset = filtersets.UserFilterSet
-    filterset_form = forms.UserFilterForm
+    filterset_form = forms.NetBoxUserFilterForm
     table = tables.UserTable
 
 
@@ -112,7 +112,7 @@ class UserBulkDeleteView(generic.BulkDeleteView):
 class GroupListView(generic.ObjectListView):
     queryset = NetBoxGroup.objects.annotate(users_count=Count('user'))
     filterset = filtersets.GroupFilterSet
-    filterset_form = forms.GroupFilterForm
+    filterset_form = forms.NetBoxGroupFilterForm
     table = tables.GroupTable
 
 

+ 3 - 3
netbox/vpn/forms/model_forms.py

@@ -91,7 +91,7 @@ class TunnelCreateForm(TunnelForm):
     termination1_termination = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
         required=False,
-        label=_('Interface'),
+        label=_('Tunnel interface'),
         query_params={
             'device_id': '$termination1_parent',
         }
@@ -126,7 +126,7 @@ class TunnelCreateForm(TunnelForm):
     termination2_termination = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
         required=False,
-        label=_('Interface'),
+        label=_('Tunnel interface'),
         query_params={
             'device_id': '$termination2_parent',
         }
@@ -238,7 +238,7 @@ class TunnelTerminationForm(NetBoxModelForm):
     )
     termination = DynamicModelChoiceField(
         queryset=Interface.objects.all(),
-        label=_('Interface'),
+        label=_('Tunnel interface'),
         query_params={
             'device_id': '$parent',
         }

+ 1 - 1
netbox/vpn/tables/tunnels.py

@@ -88,7 +88,7 @@ class TunnelTerminationTable(TenancyColumnsMixin, NetBoxTable):
         verbose_name=_('Host')
     )
     termination = tables.Column(
-        verbose_name=_('Interface'),
+        verbose_name=_('Tunnel interface'),
         linkify=True
     )
     ip_addresses = tables.ManyToManyColumn(

+ 9 - 9
requirements.txt

@@ -2,34 +2,34 @@ bleach==6.1.0
 Django==4.2.11
 django-cors-headers==4.3.1
 django-debug-toolbar==4.3.0
-django-filter==24.1
+django-filter==24.2
 django-graphiql-debug-toolbar==0.2.0
 django-mptt==0.14.0
 django-pglocks==1.0.4
 django-prometheus==2.3.1
 django-redis==5.4.0
 django-rich==1.8.0
-django-rq==2.10.1
+django-rq==2.10.2
 django-taggit==5.0.1
 django-tables2==2.7.0
 django-timezone-field==6.1.0
 djangorestframework==3.14.0
-drf-spectacular==0.27.1
-drf-spectacular-sidecar==2024.3.4
+drf-spectacular==0.27.2
+drf-spectacular-sidecar==2024.4.1
 feedparser==6.0.11
 graphene-django==3.0.0
 gunicorn==21.2.0
 Jinja2==3.1.3
-Markdown==3.5.2
-mkdocs-material==9.5.13
-mkdocstrings[python-legacy]==0.24.1
+Markdown==3.6
+mkdocs-material==9.5.17
+mkdocstrings[python-legacy]==0.24.2
 netaddr==1.2.1
-Pillow==10.2.0
+Pillow==10.3.0
 psycopg[binary,pool]==3.1.18
 PyYAML==6.0.1
 requests==2.31.0
 social-auth-app-django==5.4.0
 social-auth-core[openidconnect]==4.5.3
 svgwrite==1.4.3
-tablib==3.5.0
+tablib==3.6.1
 tzdata==2024.1

Неке датотеке нису приказане због велике количине промена