mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
refactor: update links to different endpoints (#338)
This commit is contained in:
parent
7b4d7f12f5
commit
1b369feb6a
@ -36,9 +36,15 @@ from .issue import (
|
|||||||
IssueSerializer,
|
IssueSerializer,
|
||||||
IssueFlatSerializer,
|
IssueFlatSerializer,
|
||||||
IssueStateSerializer,
|
IssueStateSerializer,
|
||||||
|
IssueLinkSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .module import ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer
|
from .module import (
|
||||||
|
ModuleWriteSerializer,
|
||||||
|
ModuleSerializer,
|
||||||
|
ModuleIssueSerializer,
|
||||||
|
ModuleLinkSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
from .api_token import APITokenSerializer
|
from .api_token import APITokenSerializer
|
||||||
|
|
||||||
|
@ -28,11 +28,6 @@ from plane.db.models import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class IssueLinkCreateSerializer(serializers.Serializer):
|
|
||||||
url = serializers.CharField(required=True)
|
|
||||||
title = serializers.CharField(required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class IssueFlatSerializer(BaseSerializer):
|
class IssueFlatSerializer(BaseSerializer):
|
||||||
## Contain only flat fields
|
## Contain only flat fields
|
||||||
|
|
||||||
@ -82,11 +77,6 @@ class IssueCreateSerializer(BaseSerializer):
|
|||||||
write_only=True,
|
write_only=True,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
links_list = serializers.ListField(
|
|
||||||
child=IssueLinkCreateSerializer(),
|
|
||||||
write_only=True,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Issue
|
model = Issue
|
||||||
@ -105,7 +95,6 @@ class IssueCreateSerializer(BaseSerializer):
|
|||||||
assignees = validated_data.pop("assignees_list", None)
|
assignees = validated_data.pop("assignees_list", None)
|
||||||
labels = validated_data.pop("labels_list", None)
|
labels = validated_data.pop("labels_list", None)
|
||||||
blocks = validated_data.pop("blocks_list", None)
|
blocks = validated_data.pop("blocks_list", None)
|
||||||
links = validated_data.pop("links_list", None)
|
|
||||||
|
|
||||||
project = self.context["project"]
|
project = self.context["project"]
|
||||||
issue = Issue.objects.create(**validated_data, project=project)
|
issue = Issue.objects.create(**validated_data, project=project)
|
||||||
@ -174,24 +163,6 @@ class IssueCreateSerializer(BaseSerializer):
|
|||||||
batch_size=10,
|
batch_size=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
if links is not None:
|
|
||||||
IssueLink.objects.bulk_create(
|
|
||||||
[
|
|
||||||
IssueLink(
|
|
||||||
issue=issue,
|
|
||||||
project=project,
|
|
||||||
workspace=project.workspace,
|
|
||||||
created_by=issue.created_by,
|
|
||||||
updated_by=issue.updated_by,
|
|
||||||
title=link.get("title", None),
|
|
||||||
url=link.get("url", None),
|
|
||||||
)
|
|
||||||
for link in links
|
|
||||||
],
|
|
||||||
batch_size=10,
|
|
||||||
ignore_conflicts=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return issue
|
return issue
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
@ -199,7 +170,6 @@ class IssueCreateSerializer(BaseSerializer):
|
|||||||
assignees = validated_data.pop("assignees_list", None)
|
assignees = validated_data.pop("assignees_list", None)
|
||||||
labels = validated_data.pop("labels_list", None)
|
labels = validated_data.pop("labels_list", None)
|
||||||
blocks = validated_data.pop("blocks_list", None)
|
blocks = validated_data.pop("blocks_list", None)
|
||||||
links = validated_data.pop("links_list", None)
|
|
||||||
|
|
||||||
if blockers is not None:
|
if blockers is not None:
|
||||||
IssueBlocker.objects.filter(block=instance).delete()
|
IssueBlocker.objects.filter(block=instance).delete()
|
||||||
@ -269,25 +239,6 @@ class IssueCreateSerializer(BaseSerializer):
|
|||||||
batch_size=10,
|
batch_size=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
if links is not None:
|
|
||||||
IssueLink.objects.filter(issue=instance).delete()
|
|
||||||
IssueLink.objects.bulk_create(
|
|
||||||
[
|
|
||||||
IssueLink(
|
|
||||||
issue=instance,
|
|
||||||
project=instance.project,
|
|
||||||
workspace=instance.project.workspace,
|
|
||||||
created_by=instance.created_by,
|
|
||||||
updated_by=instance.updated_by,
|
|
||||||
title=link.get("title", None),
|
|
||||||
url=link.get("url", None),
|
|
||||||
)
|
|
||||||
for link in links
|
|
||||||
],
|
|
||||||
batch_size=10,
|
|
||||||
ignore_conflicts=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
@ -456,6 +407,15 @@ class IssueLinkSerializer(BaseSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = IssueLink
|
model = IssueLink
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
read_only_fields = [
|
||||||
|
"workspace",
|
||||||
|
"project",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"issue",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Issue Serializer with state details
|
# Issue Serializer with state details
|
||||||
|
@ -10,24 +10,12 @@ from .issue import IssueStateSerializer
|
|||||||
from plane.db.models import User, Module, ModuleMember, ModuleIssue, ModuleLink
|
from plane.db.models import User, Module, ModuleMember, ModuleIssue, ModuleLink
|
||||||
|
|
||||||
|
|
||||||
class LinkCreateSerializer(serializers.Serializer):
|
|
||||||
|
|
||||||
url = serializers.CharField(required=True)
|
|
||||||
title = serializers.CharField(required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleWriteSerializer(BaseSerializer):
|
class ModuleWriteSerializer(BaseSerializer):
|
||||||
|
|
||||||
members_list = serializers.ListField(
|
members_list = serializers.ListField(
|
||||||
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
||||||
write_only=True,
|
write_only=True,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
links_list = serializers.ListField(
|
|
||||||
child=LinkCreateSerializer(),
|
|
||||||
write_only=True,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
@ -42,9 +30,7 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
|
||||||
members = validated_data.pop("members_list", None)
|
members = validated_data.pop("members_list", None)
|
||||||
links = validated_data.pop("links_list", None)
|
|
||||||
|
|
||||||
project = self.context["project"]
|
project = self.context["project"]
|
||||||
|
|
||||||
@ -67,30 +53,10 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if links is not None:
|
|
||||||
ModuleLink.objects.bulk_create(
|
|
||||||
[
|
|
||||||
ModuleLink(
|
|
||||||
module=module,
|
|
||||||
project=project,
|
|
||||||
workspace=project.workspace,
|
|
||||||
created_by=module.created_by,
|
|
||||||
updated_by=module.updated_by,
|
|
||||||
title=link.get("title", None),
|
|
||||||
url=link.get("url", None),
|
|
||||||
)
|
|
||||||
for link in links
|
|
||||||
],
|
|
||||||
batch_size=10,
|
|
||||||
ignore_conflicts=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
|
|
||||||
members = validated_data.pop("members_list", None)
|
members = validated_data.pop("members_list", None)
|
||||||
links = validated_data.pop("links_list", None)
|
|
||||||
|
|
||||||
if members is not None:
|
if members is not None:
|
||||||
ModuleMember.objects.filter(module=instance).delete()
|
ModuleMember.objects.filter(module=instance).delete()
|
||||||
@ -110,25 +76,6 @@ class ModuleWriteSerializer(BaseSerializer):
|
|||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if links is not None:
|
|
||||||
ModuleLink.objects.filter(module=instance).delete()
|
|
||||||
ModuleLink.objects.bulk_create(
|
|
||||||
[
|
|
||||||
ModuleLink(
|
|
||||||
module=instance,
|
|
||||||
project=instance.project,
|
|
||||||
workspace=instance.project.workspace,
|
|
||||||
created_by=instance.created_by,
|
|
||||||
updated_by=instance.updated_by,
|
|
||||||
title=link.get("title", None),
|
|
||||||
url=link.get("url", None),
|
|
||||||
)
|
|
||||||
for link in links
|
|
||||||
],
|
|
||||||
batch_size=10,
|
|
||||||
ignore_conflicts=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
@ -147,7 +94,6 @@ class ModuleFlatSerializer(BaseSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleIssueSerializer(BaseSerializer):
|
class ModuleIssueSerializer(BaseSerializer):
|
||||||
|
|
||||||
module_detail = ModuleFlatSerializer(read_only=True, source="module")
|
module_detail = ModuleFlatSerializer(read_only=True, source="module")
|
||||||
issue_detail = IssueStateSerializer(read_only=True, source="issue")
|
issue_detail = IssueStateSerializer(read_only=True, source="issue")
|
||||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||||
@ -167,7 +113,6 @@ class ModuleIssueSerializer(BaseSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleLinkSerializer(BaseSerializer):
|
class ModuleLinkSerializer(BaseSerializer):
|
||||||
|
|
||||||
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
|
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -180,11 +125,11 @@ class ModuleLinkSerializer(BaseSerializer):
|
|||||||
"updated_by",
|
"updated_by",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
"module",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ModuleSerializer(BaseSerializer):
|
class ModuleSerializer(BaseSerializer):
|
||||||
|
|
||||||
project_detail = ProjectSerializer(read_only=True, source="project")
|
project_detail = ProjectSerializer(read_only=True, source="project")
|
||||||
lead_detail = UserLiteSerializer(read_only=True, source="lead")
|
lead_detail = UserLiteSerializer(read_only=True, source="lead")
|
||||||
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
|
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
|
||||||
|
@ -65,6 +65,8 @@ from plane.api.views import (
|
|||||||
IssuePropertyViewSet,
|
IssuePropertyViewSet,
|
||||||
LabelViewSet,
|
LabelViewSet,
|
||||||
SubIssuesEndpoint,
|
SubIssuesEndpoint,
|
||||||
|
IssueLinkViewSet,
|
||||||
|
ModuleLinkViewSet,
|
||||||
## End Issues
|
## End Issues
|
||||||
# States
|
# States
|
||||||
StateViewSet,
|
StateViewSet,
|
||||||
@ -573,6 +575,28 @@ urlpatterns = [
|
|||||||
SubIssuesEndpoint.as_view(),
|
SubIssuesEndpoint.as_view(),
|
||||||
name="sub-issues",
|
name="sub-issues",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-links/",
|
||||||
|
IssueLinkViewSet.as_view(
|
||||||
|
{
|
||||||
|
"get": "list",
|
||||||
|
"post": "create",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
name="project-issue-links",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-links/<uuid:pk>/",
|
||||||
|
IssueLinkViewSet.as_view(
|
||||||
|
{
|
||||||
|
"get": "retrieve",
|
||||||
|
"put": "update",
|
||||||
|
"patch": "partial_update",
|
||||||
|
"delete": "destroy",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
name="project-issue-links",
|
||||||
|
),
|
||||||
## End Issues
|
## End Issues
|
||||||
## Issue Activity
|
## Issue Activity
|
||||||
path(
|
path(
|
||||||
@ -705,6 +729,28 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
name="project-module-issues",
|
name="project-module-issues",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-links/",
|
||||||
|
ModuleLinkViewSet.as_view(
|
||||||
|
{
|
||||||
|
"get": "list",
|
||||||
|
"post": "create",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
name="project-issue-module-links",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-links/<uuid:pk>/",
|
||||||
|
ModuleLinkViewSet.as_view(
|
||||||
|
{
|
||||||
|
"get": "retrieve",
|
||||||
|
"put": "update",
|
||||||
|
"patch": "partial_update",
|
||||||
|
"delete": "destroy",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
name="project-issue-module-links",
|
||||||
|
),
|
||||||
## End Modules
|
## End Modules
|
||||||
# API Tokens
|
# API Tokens
|
||||||
path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-tokens"),
|
path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-tokens"),
|
||||||
|
@ -58,6 +58,7 @@ from .issue import (
|
|||||||
BulkDeleteIssuesEndpoint,
|
BulkDeleteIssuesEndpoint,
|
||||||
UserWorkSpaceIssues,
|
UserWorkSpaceIssues,
|
||||||
SubIssuesEndpoint,
|
SubIssuesEndpoint,
|
||||||
|
IssueLinkViewSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .auth_extended import (
|
from .auth_extended import (
|
||||||
@ -76,7 +77,7 @@ from .authentication import (
|
|||||||
MagicSignInGenerateEndpoint,
|
MagicSignInGenerateEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .module import ModuleViewSet, ModuleIssueViewSet
|
from .module import ModuleViewSet, ModuleIssueViewSet, ModuleLinkViewSet
|
||||||
|
|
||||||
from .api_token import ApiTokenEndpoint
|
from .api_token import ApiTokenEndpoint
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ from plane.api.serializers import (
|
|||||||
IssueSerializer,
|
IssueSerializer,
|
||||||
LabelSerializer,
|
LabelSerializer,
|
||||||
IssueFlatSerializer,
|
IssueFlatSerializer,
|
||||||
|
IssueLinkSerializer,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import (
|
from plane.api.permissions import (
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
@ -690,3 +691,29 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||||||
{"error": "Something went wrong please try again later"},
|
{"error": "Something went wrong please try again later"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IssueLinkViewSet(BaseViewSet):
|
||||||
|
permission_classes = [
|
||||||
|
ProjectEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
model = IssueLink
|
||||||
|
serializer_class = IssueLinkSerializer
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
serializer.save(
|
||||||
|
project_id=self.kwargs.get("project_id"),
|
||||||
|
issue_id=self.kwargs.get("issue_id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return (
|
||||||
|
super()
|
||||||
|
.get_queryset()
|
||||||
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(project_id=self.kwargs.get("project_id"))
|
||||||
|
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||||
|
.filter(project__project_projectmember__member=self.request.user)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
@ -17,6 +17,7 @@ from plane.api.serializers import (
|
|||||||
ModuleWriteSerializer,
|
ModuleWriteSerializer,
|
||||||
ModuleSerializer,
|
ModuleSerializer,
|
||||||
ModuleIssueSerializer,
|
ModuleIssueSerializer,
|
||||||
|
ModuleLinkSerializer,
|
||||||
)
|
)
|
||||||
from plane.api.permissions import ProjectEntityPermission
|
from plane.api.permissions import ProjectEntityPermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
@ -258,3 +259,29 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||||||
{"error": "Something went wrong please try again later"},
|
{"error": "Something went wrong please try again later"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleLinkViewSet(BaseViewSet):
|
||||||
|
permission_classes = [
|
||||||
|
ProjectEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
model = ModuleLink
|
||||||
|
serializer_class = ModuleLinkSerializer
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
serializer.save(
|
||||||
|
project_id=self.kwargs.get("project_id"),
|
||||||
|
module_id=self.kwargs.get("module_id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return (
|
||||||
|
super()
|
||||||
|
.get_queryset()
|
||||||
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(project_id=self.kwargs.get("project_id"))
|
||||||
|
.filter(module_id=self.kwargs.get("module_id"))
|
||||||
|
.filter(project__project_projectmember__member=self.request.user)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
@ -174,6 +174,7 @@ class IssueLink(ProjectBaseModel):
|
|||||||
issue = models.ForeignKey(
|
issue = models.ForeignKey(
|
||||||
"db.Issue", on_delete=models.CASCADE, related_name="issue_link"
|
"db.Issue", on_delete=models.CASCADE, related_name="issue_link"
|
||||||
)
|
)
|
||||||
|
metadata = models.JSONField(default=dict)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Issue Link"
|
verbose_name = "Issue Link"
|
||||||
|
@ -7,7 +7,6 @@ from . import ProjectBaseModel
|
|||||||
|
|
||||||
|
|
||||||
class Module(ProjectBaseModel):
|
class Module(ProjectBaseModel):
|
||||||
|
|
||||||
name = models.CharField(max_length=255, verbose_name="Module Name")
|
name = models.CharField(max_length=255, verbose_name="Module Name")
|
||||||
description = models.TextField(verbose_name="Module Description", blank=True)
|
description = models.TextField(verbose_name="Module Description", blank=True)
|
||||||
description_text = models.JSONField(
|
description_text = models.JSONField(
|
||||||
@ -41,7 +40,6 @@ class Module(ProjectBaseModel):
|
|||||||
through_fields=("module", "member"),
|
through_fields=("module", "member"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ["name", "project"]
|
unique_together = ["name", "project"]
|
||||||
verbose_name = "Module"
|
verbose_name = "Module"
|
||||||
@ -54,7 +52,6 @@ class Module(ProjectBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleMember(ProjectBaseModel):
|
class ModuleMember(ProjectBaseModel):
|
||||||
|
|
||||||
module = models.ForeignKey("db.Module", on_delete=models.CASCADE)
|
module = models.ForeignKey("db.Module", on_delete=models.CASCADE)
|
||||||
member = models.ForeignKey("db.User", on_delete=models.CASCADE)
|
member = models.ForeignKey("db.User", on_delete=models.CASCADE)
|
||||||
|
|
||||||
@ -70,7 +67,6 @@ class ModuleMember(ProjectBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleIssue(ProjectBaseModel):
|
class ModuleIssue(ProjectBaseModel):
|
||||||
|
|
||||||
module = models.ForeignKey(
|
module = models.ForeignKey(
|
||||||
"db.Module", on_delete=models.CASCADE, related_name="issue_module"
|
"db.Module", on_delete=models.CASCADE, related_name="issue_module"
|
||||||
)
|
)
|
||||||
@ -89,10 +85,12 @@ class ModuleIssue(ProjectBaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleLink(ProjectBaseModel):
|
class ModuleLink(ProjectBaseModel):
|
||||||
|
|
||||||
title = models.CharField(max_length=255, null=True)
|
title = models.CharField(max_length=255, null=True)
|
||||||
url = models.URLField()
|
url = models.URLField()
|
||||||
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name="link_module")
|
module = models.ForeignKey(
|
||||||
|
Module, on_delete=models.CASCADE, related_name="link_module"
|
||||||
|
)
|
||||||
|
metadata = models.JSONField(default=dict)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Module Link"
|
verbose_name = "Module Link"
|
||||||
|
Loading…
Reference in New Issue
Block a user