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:
@ -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
|
||||
* [#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
|
||||
* [#9006](https://github.com/netbox-community/netbox/issues/9006) - Enable custom fields, custom links, and tags for journal entries
|
||||
|
||||
### 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
|
||||
* extras.CustomLink
|
||||
* Added `enabled` field
|
||||
* extras.JournalEntry
|
||||
* Added `custom_fields` and `tags` fields
|
||||
* ipam.ASN
|
||||
* Added `provider_count` field
|
||||
* ipam.VLANGroup
|
||||
* Added the `/availables-vlans/` endpoint
|
||||
* Added the `min_vid` and `max_vid` fields
|
||||
* Added `min_vid` and `max_vid` fields
|
||||
* tenancy.Contact
|
||||
* Added `link` field
|
||||
* virtualization.VMInterface
|
||||
|
@ -14,7 +14,7 @@ from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.api import ChoiceField, ContentTypeField, SerializedPKRelatedField
|
||||
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.models import Tenant, TenantGroup
|
||||
from users.api.nested_serializers import NestedUserSerializer
|
||||
@ -200,7 +200,7 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
|
||||
# Journal entries
|
||||
#
|
||||
|
||||
class JournalEntrySerializer(ValidatedModelSerializer):
|
||||
class JournalEntrySerializer(NetBoxModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:journalentry-detail')
|
||||
assigned_object_type = ContentTypeField(
|
||||
queryset=ContentType.objects.all()
|
||||
@ -221,7 +221,7 @@ class JournalEntrySerializer(ValidatedModelSerializer):
|
||||
model = JournalEntry
|
||||
fields = [
|
||||
'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):
|
||||
|
@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
|
||||
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 utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
@ -134,11 +134,7 @@ class ImageAttachmentFilterSet(BaseFilterSet):
|
||||
return queryset.filter(name__icontains=value)
|
||||
|
||||
|
||||
class JournalEntryFilterSet(ChangeLoggedModelFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
class JournalEntryFilterSet(NetBoxModelFilterSet):
|
||||
created = django_filters.DateTimeFromToRangeFilter()
|
||||
assigned_object_type = ContentTypeFilter()
|
||||
created_by_id = django_filters.ModelMultipleChoiceFilter(
|
||||
|
@ -7,10 +7,12 @@ from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGrou
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.forms.base import NetBoxModelFilterSetForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
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
|
||||
|
||||
@ -237,10 +239,10 @@ class LocalConfigContextFilterForm(forms.Form):
|
||||
)
|
||||
|
||||
|
||||
class JournalEntryFilterForm(FilterForm):
|
||||
class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
||||
model = JournalEntry
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
(None, ('q', 'tag')),
|
||||
('Creation', ('created_before', 'created_after', 'created_by_id')),
|
||||
('Attributes', ('assigned_object_type_id', 'kind'))
|
||||
)
|
||||
@ -275,6 +277,7 @@ class JournalEntryFilterForm(FilterForm):
|
||||
required=False,
|
||||
widget=StaticSelect()
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ObjectChangeFilterForm(FilterForm):
|
||||
|
@ -5,6 +5,7 @@ from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGrou
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
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(
|
||||
choices=add_blank_choice(JournalEntryKindChoices),
|
||||
required=False,
|
||||
widget=StaticSelect()
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = JournalEntry
|
||||
fields = ['assigned_object_type', 'assigned_object_id', 'kind', 'comments']
|
||||
fields = ['assigned_object_type', 'assigned_object_id', 'kind', 'tags', 'comments']
|
||||
widgets = {
|
||||
'assigned_object_type': forms.HiddenInput,
|
||||
'assigned_object_id': forms.HiddenInput,
|
||||
|
@ -1,4 +1,5 @@
|
||||
from extras import filtersets, models
|
||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
|
||||
from netbox.graphql.types import BaseObjectType, ObjectType
|
||||
|
||||
__all__ = (
|
||||
@ -54,7 +55,7 @@ class ImageAttachmentType(BaseObjectType):
|
||||
filterset_class = filtersets.ImageAttachmentFilterSet
|
||||
|
||||
|
||||
class JournalEntryType(ObjectType):
|
||||
class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.JournalEntry
|
||||
|
@ -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'),
|
||||
),
|
||||
]
|
@ -19,7 +19,9 @@ from extras.constants import *
|
||||
from extras.conditions import ConditionSet
|
||||
from extras.utils import FeatureQuery, image_upload
|
||||
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.utils import render_jinja2
|
||||
|
||||
@ -419,7 +421,7 @@ class ImageAttachment(WebhooksMixin, ChangeLoggedModel):
|
||||
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
|
||||
preserve historical context around an object, and complements NetBox's built-in change logging. For example, you
|
||||
|
@ -12,7 +12,6 @@ __all__ = (
|
||||
'ExportTemplateTable',
|
||||
'JournalEntryTable',
|
||||
'ObjectChangeTable',
|
||||
'ObjectJournalTable',
|
||||
'TaggedItemTable',
|
||||
'TagTable',
|
||||
'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(
|
||||
linkify=True,
|
||||
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(
|
||||
verbose_name='Object type'
|
||||
)
|
||||
@ -237,13 +222,22 @@ class JournalEntryTable(ObjectJournalTable):
|
||||
orderable=False,
|
||||
verbose_name='Object'
|
||||
)
|
||||
kind = columns.ChoiceFieldColumn()
|
||||
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):
|
||||
model = JournalEntry
|
||||
fields = (
|
||||
'pk', 'id', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments',
|
||||
'actions',
|
||||
'comments_short', 'tags', 'actions',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'
|
||||
|
@ -86,8 +86,10 @@ class ObjectJournalView(View):
|
||||
assigned_object_type=content_type,
|
||||
assigned_object_id=obj.pk
|
||||
)
|
||||
journalentry_table = tables.ObjectJournalTable(journalentries)
|
||||
journalentry_table = tables.JournalEntryTable(journalentries, user=request.user)
|
||||
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'):
|
||||
form = forms.JournalEntryForm(
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col col-md-4">
|
||||
<div class="col col-md-5">
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
Journal Entry
|
||||
@ -35,8 +35,10 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/panels/custom_fields.html' %}
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
</div>
|
||||
<div class="col col-md-8">
|
||||
<div class="col col-md-7">
|
||||
{% include 'inc/panels/comments.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,11 +11,7 @@
|
||||
<div class="field-group">
|
||||
<h4>New Journal Entry</h4>
|
||||
{% csrf_token %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
{% render_field form.kind %}
|
||||
{% render_field form.comments %}
|
||||
{% render_form form %}
|
||||
</div>
|
||||
<div class="col col-md-12 text-end my-3">
|
||||
<a href="{{ object.get_absolute_url }}" class="btn btn-outline-danger">Cancel</a>
|
||||
|
Reference in New Issue
Block a user