Skip to content

Commit

Permalink
feat: implements views
Browse files Browse the repository at this point in the history
Implements views for:
- design
- design instance
- journal
- journal entry
  • Loading branch information
Kircheneer committed Aug 16, 2023
1 parent 24a473a commit 2b4bb07
Show file tree
Hide file tree
Showing 22 changed files with 1,722 additions and 848 deletions.
Empty file added design_builder/api/__init__.py
Empty file.
41 changes: 41 additions & 0 deletions design_builder/api/nested_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Nested serializers for design builder."""
from nautobot.core.api import BaseModelSerializer
from rest_framework.relations import HyperlinkedIdentityField

from design_builder.models import Design, DesignInstance, Journal


class NestedDesignSerializer(BaseModelSerializer):
"""Nested serializer for the design model."""

url = HyperlinkedIdentityField(view_name="plugins-api:design_builder-api:design-detail")

class Meta:
"""Nested serializer options for the design model."""

model = Design
fields = ["id", "url", "name"]


class NestedDesignInstanceSerializer(BaseModelSerializer):
"""Nested serializer for the design instance model."""

url = HyperlinkedIdentityField(view_name="plugins-api:design_builder-api:designinstance-detail")

class Meta:
"""Nested serializer options for the design instance model."""

model = DesignInstance
fields = ["id", "url", "name"]


class NestedJournalSerializer(BaseModelSerializer):
"""Nested serializer for the journal model."""

url = HyperlinkedIdentityField(view_name="plugins-api:design_builder-api:journal-detail")

class Meta:
"""Nested serializer options for the journal model."""

model = Journal
fields = ["id", "url"]
89 changes: 89 additions & 0 deletions design_builder/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Serializers for design builder."""
from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from nautobot.apps.api import NautobotModelSerializer, TaggedModelSerializerMixin
from nautobot.core.api import ContentTypeField
from nautobot.extras.api.nested_serializers import NestedJobResultSerializer
from nautobot.utilities.api import get_serializer_for_model
from rest_framework.fields import SerializerMethodField, DictField
from rest_framework.relations import HyperlinkedIdentityField

from design_builder.models import Design, DesignInstance, Journal, JournalEntry

from design_builder.api.nested_serializers import (
NestedDesignSerializer,
NestedDesignInstanceSerializer,
NestedJournalSerializer,
)


class DesignSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""Serializer for the design model."""

url = HyperlinkedIdentityField(view_name="plugins-api:design_builder-api:design-detail")

class Meta:
"""Serializer options for the design model."""

model = Design
fields = [
"id",
"url",
"name",
]


class DesignInstanceSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""Serializer for the design instance model."""

url = HyperlinkedIdentityField(view_name="plugins-api:design_builder-api:design-detail")
design = NestedDesignSerializer()

class Meta:
"""Serializer options for the design model."""

model = DesignInstance
fields = [
"id",
"url",
"design",
"name",
"owner",
"first_implemented",
"last_implemented",
]


class JournalSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""Serializer for the journal model."""

url = HyperlinkedIdentityField(view_name="plugins-api:design_builder-api:journal-detail")
design_instance = NestedDesignInstanceSerializer()
job_result = NestedJobResultSerializer()

class Meta:
"""Serializer options for the journal model."""

model = Journal
fields = ["id", "url", "design_instance", "job_result"]


class JournalEntrySerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
"""Serializer for the journal entry model."""

url = HyperlinkedIdentityField(view_name="plugins-api:design_builder-api:journalentry-detail")
journal = NestedJournalSerializer()
_design_object_type = ContentTypeField(queryset=ContentType.objects.all(), label="design_object_type")
design_object = SerializerMethodField(read_only=True)

class Meta:
"""Serializer options for the journal entry model."""

model = JournalEntry
fields = ["id", "url", "journal", "_design_object_type", "design_object", "changes", "full_control"]

@extend_schema_field(DictField())
def get_design_object(self, obj):
serializer = get_serializer_for_model(obj.design_object, prefix="Nested")
context = {"request": self.context["request"]}
return serializer(obj.design_object, context=context).data
17 changes: 17 additions & 0 deletions design_builder/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""API URLs for design builder."""
from nautobot.core.api import OrderedDefaultRouter
from design_builder.api.views import (
DesignAPIViewSet,
DesignInstanceAPIViewSet,
JournalAPIViewSet,
JournalEntryAPIViewSet,
)

router = OrderedDefaultRouter()

router.register("designs", DesignAPIViewSet)
router.register("design-instances", DesignInstanceAPIViewSet)
router.register("journals", JournalAPIViewSet)
router.register("journal-entries", JournalEntryAPIViewSet)

urlpatterns = router.urls
43 changes: 43 additions & 0 deletions design_builder/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""UI Views for design builder."""
from nautobot.extras.api.views import NautobotModelViewSet

from design_builder.api.serializers import (
DesignSerializer,
DesignInstanceSerializer,
JournalSerializer,
JournalEntrySerializer,
)
from design_builder.filters import DesignFilterSet, DesignInstanceFilterSet, JournalFilterSet, JournalEntryFilterSet
from design_builder.models import Design, DesignInstance, Journal, JournalEntry


class DesignAPIViewSet(NautobotModelViewSet):
"""API views for the design model."""

queryset = Design.objects.all()
serializer_class = DesignSerializer
filterset_class = DesignFilterSet


class DesignInstanceAPIViewSet(NautobotModelViewSet):
"""API views for the design instance model."""

queryset = DesignInstance.objects.all()
serializer_class = DesignInstanceSerializer
filterset_class = DesignInstanceFilterSet


class JournalAPIViewSet(NautobotModelViewSet):
"""API views for the journal model."""

queryset = Journal.objects.all()
serializer_class = JournalSerializer
filterset_class = JournalFilterSet


class JournalEntryAPIViewSet(NautobotModelViewSet):
"""API views for the journal entry model."""

queryset = JournalEntry.objects.all()
serializer_class = JournalEntrySerializer
filterset_class = JournalEntryFilterSet
71 changes: 71 additions & 0 deletions design_builder/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Filters for the design builder app."""
from nautobot.apps.filters import NautobotFilterSet, NaturalKeyOrPKMultipleChoiceFilter
from nautobot.extras.models import Job, JobResult

from design_builder.models import Design, DesignInstance, Journal, JournalEntry


class DesignFilterSet(NautobotFilterSet):
"""Filter set for the design model."""

job = NaturalKeyOrPKMultipleChoiceFilter(
queryset=Job.objects.all(),
label="Job (ID or slug)",
)

class Meta:
"""Meta attributes for filter."""

model = Design
fields = ["id", "job"]


class DesignInstanceFilterSet(NautobotFilterSet):
"""Filter set for the design instance model."""

design = NaturalKeyOrPKMultipleChoiceFilter(
queryset=Design.objects.all(),
label="Design (ID or slug)",
)

class Meta:
"""Meta attributes for filter."""

model = DesignInstance
fields = ["id", "design", "name", "owner", "first_implemented", "last_implemented"]


class JournalFilterSet(NautobotFilterSet):
"""Filter set for the journal model."""

design_instance = NaturalKeyOrPKMultipleChoiceFilter(
queryset=DesignInstance.objects.all(),
label="Design Instance (ID)",
)

job_result = NaturalKeyOrPKMultipleChoiceFilter(
queryset=JobResult.objects.all(),
label="Job Result (ID)",
)

class Meta:
"""Meta attributes for filter."""

model = Journal
fields = ["id", "design_instance", "job_result"]


class JournalEntryFilterSet(NautobotFilterSet):
"""Filter set for the journal entrymodel."""

journal = NaturalKeyOrPKMultipleChoiceFilter(
queryset=Journal.objects.all(),
label="Journal (ID)",
)

class Meta:
"""Meta attributes for filter."""

model = JournalEntry
# TODO: Support design_object somehow?
fields = ["id", "journal", "changes", "full_control"]
45 changes: 45 additions & 0 deletions design_builder/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Forms for the design builder app."""
from django.forms import NullBooleanField
from nautobot.extras.forms import NautobotFilterForm
from nautobot.extras.models import Job, JobResult
from nautobot.utilities.forms import TagFilterField, DynamicModelChoiceField, StaticSelect2, BOOLEAN_WITH_BLANK_CHOICES

from design_builder.models import Design, DesignInstance, Journal, JournalEntry


class DesignFilterForm(NautobotFilterForm):
"""Filter form for the design model."""
model = Design

job = DynamicModelChoiceField(queryset=Job.objects.all())
tag = TagFilterField(model)


class DesignInstanceFilterForm(NautobotFilterForm):
"""Filter form for the design instance model."""
model = DesignInstance

design = DynamicModelChoiceField(queryset=Design.objects.all())
tag = TagFilterField(model)


class JournalFilterForm(NautobotFilterForm):
"""Filter form for the journal model."""
model = Journal

design_instance = DynamicModelChoiceField(queryset=DesignInstance.objects.all())
job_result = DynamicModelChoiceField(queryset=JobResult.objects.all())
tag = TagFilterField(model)


class JournalEntryFilterForm(NautobotFilterForm):
"""Filter form for the journal entry model."""
model = JournalEntry

journal = DynamicModelChoiceField(queryset=Journal.objects.all())
full_control = NullBooleanField(
required=False,
label="Does the design have full control over the object?",
widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
)
tag = TagFilterField(model)
10 changes: 7 additions & 3 deletions design_builder/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 3.2.20 on 2023-07-28 18:51
# Generated by Django 3.2.20 on 2023-08-08 13:34

import django.core.serializers.json
from django.db import migrations, models
Expand Down Expand Up @@ -36,7 +36,7 @@ class Migration(migrations.Migration):
),
(
"job",
models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to="extras.job"),
models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, to="extras.job"),
),
(
"status",
Expand Down Expand Up @@ -71,12 +71,16 @@ class Migration(migrations.Migration):
models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
),
("name", models.CharField(max_length=100)),
("owner", models.CharField(blank=True, max_length=100, null=True)),
("first_implemented", models.DateTimeField(blank=True, null=True)),
("last_implemented", models.DateTimeField(blank=True, null=True)),
(
"design",
models.ForeignKey(
editable=False, on_delete=django.db.models.deletion.PROTECT, to="design_builder.design"
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="instances",
to="design_builder.design",
),
),
("tags", taggit.managers.TaggableManager(through="extras.TaggedItem", to="extras.Tag")),
Expand Down
18 changes: 13 additions & 5 deletions design_builder/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def name(self):

def get_absolute_url(self):
"""Return detail view for Designs."""
return reverse("plugins:design_builder:design", args=[self.name])
return reverse("plugins:design_builder:design", args=[self.pk])

def __str__(self):
"""Stringify instance."""
Expand All @@ -115,9 +115,9 @@ class DesignInstance(PrimaryModel):

# TODO: add version field to indicate which version of a design
# this instance is on. (future feature)
design = models.ForeignKey(to=Design, on_delete=models.PROTECT, editable=False)
design = models.ForeignKey(to=Design, on_delete=models.PROTECT, editable=False, related_name="instances")
name = models.CharField(max_length=100)
owner = models.CharField(max_length=100)
owner = models.CharField(max_length=100, blank=True, null=True)
first_implemented = models.DateTimeField(blank=True, null=True)
last_implemented = models.DateTimeField(blank=True, null=True)

Expand All @@ -141,8 +141,8 @@ def clean(self):
enforce_managed_fields(self, ["design"], message="is a field that cannot be changed")

def get_absolute_url(self):
"""Return detail view for Designs."""
return reverse("plugins:design_builder:design", args=[self.design.name, self.name])
"""Return detail view for design instances."""
return reverse("plugins:design_builder:designinstance", args=[self.pk])

def __str__(self):
"""Stringify instance."""
Expand All @@ -166,6 +166,10 @@ class Journal(PrimaryModel):
design_instance = models.ForeignKey(to=DesignInstance, on_delete=models.CASCADE, editable=False)
job_result = models.ForeignKey(to=JobResult, on_delete=models.PROTECT, editable=False)

def get_absolute_url(self):
"""Return detail view for design instances."""
return reverse("plugins:design_builder:journal", args=[self.pk])

@property
def user_input(self):
"""Get the user input provided when the job was run.
Expand Down Expand Up @@ -208,3 +212,7 @@ class JournalEntry(PrimaryModel):
design_object = ct_fields.GenericForeignKey(ct_field="_design_object_type", fk_field="_design_object_id")
changes = models.JSONField(encoder=NautobotKombuJSONEncoder, editable=False, null=True, blank=True)
full_control = models.BooleanField(editable=False)

def get_absolute_url(self):
"""Return detail view for design instances."""
return reverse("plugins:design_builder:journalentry", args=[self.pk])
Loading

0 comments on commit 2b4bb07

Please sign in to comment.