Просмотр исходного кода

Closes #9006: Enable custom fields, custom links, and tags for journal entries

jeremystretch 3 лет назад
Родитель
Сommit
1d55c04c21

+ 4 - 1
docs/release-notes/version-3.2.md

@@ -146,6 +146,7 @@ Where it is desired to limit the range of available VLANs within a group, users
 * [#8572](https://github.com/netbox-community/netbox/issues/8572) - Add a `pre_run()` method for reports
 * [#8572](https://github.com/netbox-community/netbox/issues/8572) - Add a `pre_run()` method for reports
 * [#8593](https://github.com/netbox-community/netbox/issues/8593) - Add a `link` field for contacts
 * [#8593](https://github.com/netbox-community/netbox/issues/8593) - Add a `link` field for contacts
 * [#8649](https://github.com/netbox-community/netbox/issues/8649) - Enable customization of configuration module using `NETBOX_CONFIGURATION` environment variable
 * [#8649](https://github.com/netbox-community/netbox/issues/8649) - Enable customization of configuration module using `NETBOX_CONFIGURATION` environment variable
+* [#9006](https://github.com/netbox-community/netbox/issues/9006) - Enable custom fields, custom links, and tags for journal entries
 
 
 ### Bug Fixes (From Beta2)
 ### Bug Fixes (From Beta2)
 
 
@@ -207,11 +208,13 @@ Where it is desired to limit the range of available VLANs within a group, users
     * Added `data_type` and `object_type` fields
     * Added `data_type` and `object_type` fields
 * extras.CustomLink
 * extras.CustomLink
     * Added `enabled` field
     * Added `enabled` field
+* extras.JournalEntry
+    * Added `custom_fields` and `tags` fields
 * ipam.ASN
 * ipam.ASN
     * Added `provider_count` field
     * Added `provider_count` field
 * ipam.VLANGroup
 * ipam.VLANGroup
     * Added the `/availables-vlans/` endpoint
     * Added the `/availables-vlans/` endpoint
-    * Added the `min_vid` and `max_vid` fields
+    * Added `min_vid` and `max_vid` fields
 * tenancy.Contact
 * tenancy.Contact
     * Added `link` field
     * Added `link` field
 * virtualization.VMInterface
 * virtualization.VMInterface

+ 3 - 3
netbox/extras/api/serializers.py

@@ -14,7 +14,7 @@ from extras.models import *
 from extras.utils import FeatureQuery
 from extras.utils import FeatureQuery
 from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
 from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
 from netbox.api.exceptions import SerializerNotFound
 from netbox.api.exceptions import SerializerNotFound
-from netbox.api.serializers import BaseModelSerializer, ValidatedModelSerializer
+from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer, ValidatedModelSerializer
 from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
 from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantGroupSerializer
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from users.api.nested_serializers import NestedUserSerializer
 from users.api.nested_serializers import NestedUserSerializer
@@ -200,7 +200,7 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
 # Journal entries
 # Journal entries
 #
 #
 
 
-class JournalEntrySerializer(ValidatedModelSerializer):
+class JournalEntrySerializer(NetBoxModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='extras-api:journalentry-detail')
     url = serializers.HyperlinkedIdentityField(view_name='extras-api:journalentry-detail')
     assigned_object_type = ContentTypeField(
     assigned_object_type = ContentTypeField(
         queryset=ContentType.objects.all()
         queryset=ContentType.objects.all()
@@ -221,7 +221,7 @@ class JournalEntrySerializer(ValidatedModelSerializer):
         model = JournalEntry
         model = JournalEntry
         fields = [
         fields = [
             'id', 'url', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'created',
             'id', 'url', 'display', 'assigned_object_type', 'assigned_object_id', 'assigned_object', 'created',
-            'created_by', 'kind', 'comments',
+            'created_by', 'kind', 'comments', 'tags', 'custom_fields',
         ]
         ]
 
 
     def validate(self, data):
     def validate(self, data):

+ 2 - 6
netbox/extras/filtersets.py

@@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 from django.db.models import Q
 
 
 from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
 from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
-from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet
+from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
 from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
 from virtualization.models import Cluster, ClusterGroup, ClusterType
 from virtualization.models import Cluster, ClusterGroup, ClusterType
@@ -134,11 +134,7 @@ class ImageAttachmentFilterSet(BaseFilterSet):
         return queryset.filter(name__icontains=value)
         return queryset.filter(name__icontains=value)
 
 
 
 
-class JournalEntryFilterSet(ChangeLoggedModelFilterSet):
-    q = django_filters.CharFilter(
-        method='search',
-        label='Search',
-    )
+class JournalEntryFilterSet(NetBoxModelFilterSet):
     created = django_filters.DateTimeFromToRangeFilter()
     created = django_filters.DateTimeFromToRangeFilter()
     assigned_object_type = ContentTypeFilter()
     assigned_object_type = ContentTypeFilter()
     created_by_id = django_filters.ModelMultipleChoiceFilter(
     created_by_id = django_filters.ModelMultipleChoiceFilter(

+ 7 - 4
netbox/extras/forms/filtersets.py

@@ -7,10 +7,12 @@ from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGrou
 from extras.choices import *
 from extras.choices import *
 from extras.models import *
 from extras.models import *
 from extras.utils import FeatureQuery
 from extras.utils import FeatureQuery
+from netbox.forms.base import NetBoxModelFilterSetForm
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
-    add_blank_choice, APISelectMultiple, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DateTimePicker,
-    DynamicModelMultipleChoiceField, FilterForm, MultipleChoiceField, StaticSelect, BOOLEAN_WITH_BLANK_CHOICES,
+    add_blank_choice, APISelectMultiple, BOOLEAN_WITH_BLANK_CHOICES, ContentTypeChoiceField,
+    ContentTypeMultipleChoiceField, DateTimePicker, DynamicModelMultipleChoiceField, FilterForm, MultipleChoiceField,
+    StaticSelect, TagFilterField,
 )
 )
 from virtualization.models import Cluster, ClusterGroup, ClusterType
 from virtualization.models import Cluster, ClusterGroup, ClusterType
 
 
@@ -237,10 +239,10 @@ class LocalConfigContextFilterForm(forms.Form):
     )
     )
 
 
 
 
-class JournalEntryFilterForm(FilterForm):
+class JournalEntryFilterForm(NetBoxModelFilterSetForm):
     model = JournalEntry
     model = JournalEntry
     fieldsets = (
     fieldsets = (
-        (None, ('q',)),
+        (None, ('q', 'tag')),
         ('Creation', ('created_before', 'created_after', 'created_by_id')),
         ('Creation', ('created_before', 'created_after', 'created_by_id')),
         ('Attributes', ('assigned_object_type_id', 'kind'))
         ('Attributes', ('assigned_object_type_id', 'kind'))
     )
     )
@@ -275,6 +277,7 @@ class JournalEntryFilterForm(FilterForm):
         required=False,
         required=False,
         widget=StaticSelect()
         widget=StaticSelect()
     )
     )
+    tag = TagFilterField(model)
 
 
 
 
 class ObjectChangeFilterForm(FilterForm):
 class ObjectChangeFilterForm(FilterForm):

+ 4 - 4
netbox/extras/forms/models.py

@@ -5,6 +5,7 @@ from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGrou
 from extras.choices import *
 from extras.choices import *
 from extras.models import *
 from extras.models import *
 from extras.utils import FeatureQuery
 from extras.utils import FeatureQuery
+from netbox.forms import NetBoxModelForm
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
     add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField,
     add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField,
@@ -219,18 +220,17 @@ class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
         ]
         ]
 
 
 
 
-class JournalEntryForm(BootstrapMixin, forms.ModelForm):
-    comments = CommentField()
-
+class JournalEntryForm(NetBoxModelForm):
     kind = forms.ChoiceField(
     kind = forms.ChoiceField(
         choices=add_blank_choice(JournalEntryKindChoices),
         choices=add_blank_choice(JournalEntryKindChoices),
         required=False,
         required=False,
         widget=StaticSelect()
         widget=StaticSelect()
     )
     )
+    comments = CommentField()
 
 
     class Meta:
     class Meta:
         model = JournalEntry
         model = JournalEntry
-        fields = ['assigned_object_type', 'assigned_object_id', 'kind', 'comments']
+        fields = ['assigned_object_type', 'assigned_object_id', 'kind', 'tags', 'comments']
         widgets = {
         widgets = {
             'assigned_object_type': forms.HiddenInput,
             'assigned_object_type': forms.HiddenInput,
             'assigned_object_id': forms.HiddenInput,
             'assigned_object_id': forms.HiddenInput,

+ 2 - 1
netbox/extras/graphql/types.py

@@ -1,4 +1,5 @@
 from extras import filtersets, models
 from extras import filtersets, models
+from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
 from netbox.graphql.types import BaseObjectType, ObjectType
 from netbox.graphql.types import BaseObjectType, ObjectType
 
 
 __all__ = (
 __all__ = (
@@ -54,7 +55,7 @@ class ImageAttachmentType(BaseObjectType):
         filterset_class = filtersets.ImageAttachmentFilterSet
         filterset_class = filtersets.ImageAttachmentFilterSet
 
 
 
 
-class JournalEntryType(ObjectType):
+class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType):
 
 
     class Meta:
     class Meta:
         model = models.JournalEntry
         model = models.JournalEntry

+ 23 - 0
netbox/extras/migrations/0073_journalentry_tags_custom_fields.py

@@ -0,0 +1,23 @@
+import django.core.serializers.json
+from django.db import migrations, models
+import taggit.managers
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extras', '0072_created_datetimefield'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='journalentry',
+            name='custom_field_data',
+            field=models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+        ),
+        migrations.AddField(
+            model_name='journalentry',
+            name='tags',
+            field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
+        ),
+    ]

+ 4 - 2
netbox/extras/models/models.py

@@ -19,7 +19,9 @@ from extras.constants import *
 from extras.conditions import ConditionSet
 from extras.conditions import ConditionSet
 from extras.utils import FeatureQuery, image_upload
 from extras.utils import FeatureQuery, image_upload
 from netbox.models import ChangeLoggedModel
 from netbox.models import ChangeLoggedModel
-from netbox.models.features import ExportTemplatesMixin, JobResultsMixin, WebhooksMixin
+from netbox.models.features import (
+    CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin,
+)
 from utilities.querysets import RestrictedQuerySet
 from utilities.querysets import RestrictedQuerySet
 from utilities.utils import render_jinja2
 from utilities.utils import render_jinja2
 
 
@@ -419,7 +421,7 @@ class ImageAttachment(WebhooksMixin, ChangeLoggedModel):
         return objectchange
         return objectchange
 
 
 
 
-class JournalEntry(WebhooksMixin, ChangeLoggedModel):
+class JournalEntry(CustomFieldsMixin, CustomLinksMixin, TagsMixin, WebhooksMixin, ChangeLoggedModel):
     """
     """
     A historical remark concerning an object; collectively, these form an object's journal. The journal is used to
     A historical remark concerning an object; collectively, these form an object's journal. The journal is used to
     preserve historical context around an object, and complements NetBox's built-in change logging. For example, you
     preserve historical context around an object, and complements NetBox's built-in change logging. For example, you

+ 11 - 17
netbox/extras/tables/tables.py

@@ -12,7 +12,6 @@ __all__ = (
     'ExportTemplateTable',
     'ExportTemplateTable',
     'JournalEntryTable',
     'JournalEntryTable',
     'ObjectChangeTable',
     'ObjectChangeTable',
-    'ObjectJournalTable',
     'TaggedItemTable',
     'TaggedItemTable',
     'TagTable',
     'TagTable',
     'WebhookTable',
     'WebhookTable',
@@ -210,25 +209,11 @@ class ObjectChangeTable(NetBoxTable):
         )
         )
 
 
 
 
-class ObjectJournalTable(NetBoxTable):
-    """
-    Used for displaying a set of JournalEntries within the context of a single object.
-    """
+class JournalEntryTable(NetBoxTable):
     created = tables.DateTimeColumn(
     created = tables.DateTimeColumn(
         linkify=True,
         linkify=True,
         format=settings.SHORT_DATETIME_FORMAT
         format=settings.SHORT_DATETIME_FORMAT
     )
     )
-    kind = columns.ChoiceFieldColumn()
-    comments = tables.TemplateColumn(
-        template_code='{{ value|markdown|truncatewords_html:50 }}'
-    )
-
-    class Meta(NetBoxTable.Meta):
-        model = JournalEntry
-        fields = ('id', 'created', 'created_by', 'kind', 'comments', 'actions')
-
-
-class JournalEntryTable(ObjectJournalTable):
     assigned_object_type = columns.ContentTypeColumn(
     assigned_object_type = columns.ContentTypeColumn(
         verbose_name='Object type'
         verbose_name='Object type'
     )
     )
@@ -237,13 +222,22 @@ class JournalEntryTable(ObjectJournalTable):
         orderable=False,
         orderable=False,
         verbose_name='Object'
         verbose_name='Object'
     )
     )
+    kind = columns.ChoiceFieldColumn()
     comments = columns.MarkdownColumn()
     comments = columns.MarkdownColumn()
+    comments_short = tables.TemplateColumn(
+        accessor=tables.A('comments'),
+        template_code='{{ value|markdown|truncatewords_html:50 }}',
+        verbose_name='Comments (Short)'
+    )
+    tags = columns.TagColumn(
+        url_name='extras:journalentry_list'
+    )
 
 
     class Meta(NetBoxTable.Meta):
     class Meta(NetBoxTable.Meta):
         model = JournalEntry
         model = JournalEntry
         fields = (
         fields = (
             'pk', 'id', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments',
             'pk', 'id', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments',
-            'actions',
+            'comments_short', 'tags', 'actions',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'
             'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'

+ 3 - 1
netbox/netbox/views/generic/feature_views.py

@@ -86,8 +86,10 @@ class ObjectJournalView(View):
             assigned_object_type=content_type,
             assigned_object_type=content_type,
             assigned_object_id=obj.pk
             assigned_object_id=obj.pk
         )
         )
-        journalentry_table = tables.ObjectJournalTable(journalentries)
+        journalentry_table = tables.JournalEntryTable(journalentries, user=request.user)
         journalentry_table.configure(request)
         journalentry_table.configure(request)
+        journalentry_table.columns.hide('assigned_object_type')
+        journalentry_table.columns.hide('assigned_object')
 
 
         if request.user.has_perm('extras.add_journalentry'):
         if request.user.has_perm('extras.add_journalentry'):
             form = forms.JournalEntryForm(
             form = forms.JournalEntryForm(

+ 4 - 2
netbox/templates/extras/journalentry.html

@@ -9,7 +9,7 @@
 
 
 {% block content %}
 {% block content %}
     <div class="row mb-3">
     <div class="row mb-3">
-        <div class="col col-md-4">
+        <div class="col col-md-5">
             <div class="card">
             <div class="card">
                 <h5 class="card-header">
                 <h5 class="card-header">
                     Journal Entry
                     Journal Entry
@@ -35,8 +35,10 @@
                     </table>
                     </table>
                 </div>
                 </div>
             </div>
             </div>
+            {% include 'inc/panels/custom_fields.html' %}
+            {% include 'inc/panels/tags.html' %}
         </div>
         </div>
-        <div class="col col-md-8">
+        <div class="col col-md-7">
             {% include 'inc/panels/comments.html' %}
             {% include 'inc/panels/comments.html' %}
         </div>
         </div>
     </div>
     </div>

+ 1 - 5
netbox/templates/extras/object_journal.html

@@ -11,11 +11,7 @@
         <div class="field-group">
         <div class="field-group">
           <h4>New Journal Entry</h4>
           <h4>New Journal Entry</h4>
           {% csrf_token %}
           {% csrf_token %}
-          {% for field in form.hidden_fields %}
-            {{ field }}
-          {% endfor %}
-          {% render_field form.kind %}
-          {% render_field form.comments %}
+          {% render_form form %}
         </div>
         </div>
         <div class="col col-md-12 text-end my-3">
         <div class="col col-md-12 text-end my-3">
           <a href="{{ object.get_absolute_url }}" class="btn btn-outline-danger">Cancel</a>
           <a href="{{ object.get_absolute_url }}" class="btn btn-outline-danger">Cancel</a>