1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

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

This commit is contained in:
jeremystretch
2022-03-31 11:40:02 -04:00
parent 7a54658710
commit 1d55c04c21
12 changed files with 68 additions and 46 deletions

View File

@ -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

View File

@ -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):

View File

@ -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): class JournalEntryFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
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(

View File

@ -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, add_blank_choice, APISelectMultiple, BOOLEAN_WITH_BLANK_CHOICES, ContentTypeChoiceField,
DynamicModelMultipleChoiceField, FilterForm, MultipleChoiceField, StaticSelect, BOOLEAN_WITH_BLANK_CHOICES, 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):

View File

@ -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): class JournalEntryForm(NetBoxModelForm):
comments = CommentField()
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,

View File

@ -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

View File

@ -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'),
),
]

View File

@ -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

View File

@ -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): class JournalEntryTable(NetBoxTable):
"""
Used for displaying a set of JournalEntries within the context of a single object.
"""
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'

View File

@ -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(

View File

@ -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>

View File

@ -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 %} {% render_form form %}
{{ field }}
{% endfor %}
{% render_field form.kind %}
{% render_field form.comments %}
</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>