Compare commits

...

5 Commits

Author SHA1 Message Date
pablohashescobar
4c1b100ee0 dev: fix workspace import file 2024-03-25 21:23:12 +05:30
pablohashescobar
8e725b0f75 Merge branch 'develop' of github.com:makeplane/plane into feat-workspace-export 2024-03-25 21:03:15 +05:30
pablohashescobar
cf75cb35b1 dev: fix user mapping 2024-03-20 10:09:44 +05:30
pablohashescobar
0487579f4e dev: workspace export and import functionality for instance admins 2024-03-19 11:43:50 +05:30
pablohashescobar
60232130f4 dev: workspace export endpoint 2024-03-18 17:34:07 +05:30
10 changed files with 923 additions and 34 deletions

View File

@@ -1,33 +1,31 @@
from django.urls import path
from plane.app.views import (
UserWorkspaceInvitationsViewSet,
WorkSpaceViewSet,
WorkspaceJoinEndpoint,
WorkSpaceMemberViewSet,
WorkspaceInvitationsViewset,
WorkspaceMemberUserEndpoint,
WorkspaceMemberUserViewsEndpoint,
WorkSpaceAvailabilityCheckEndpoint,
ExportWorkspaceUserActivityEndpoint,
TeamMemberViewSet,
UserLastProjectWithWorkspaceEndpoint,
UserWorkspaceInvitationsViewSet,
WorkSpaceAvailabilityCheckEndpoint,
WorkspaceCyclesEndpoint,
WorkspaceEstimatesEndpoint,
WorkspaceInvitationsViewset,
WorkspaceJoinEndpoint,
WorkspaceLabelsEndpoint,
WorkspaceMemberUserEndpoint,
WorkspaceMemberUserViewsEndpoint,
WorkSpaceMemberViewSet,
WorkspaceModulesEndpoint,
WorkspaceProjectMemberEndpoint,
WorkspaceStatesEndpoint,
WorkspaceThemeViewSet,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserActivityEndpoint,
WorkspaceUserProfileEndpoint,
WorkspaceUserProfileIssuesEndpoint,
WorkspaceLabelsEndpoint,
WorkspaceProjectMemberEndpoint,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserPropertiesEndpoint,
WorkspaceStatesEndpoint,
WorkspaceEstimatesEndpoint,
ExportWorkspaceUserActivityEndpoint,
WorkspaceModulesEndpoint,
WorkspaceCyclesEndpoint,
WorkSpaceViewSet,
)
urlpatterns = [
path(
"workspace-slug-check/",

View File

@@ -38,7 +38,7 @@ from .workspace.base import (
WorkSpaceAvailabilityCheckEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
ExportWorkspaceUserActivityEndpoint
ExportWorkspaceUserActivityEndpoint,
)
from .workspace.member import (

View File

@@ -0,0 +1,311 @@
# Python imports
import io
import json
import logging
import zipfile
# Third party imports
import boto3
from botocore.client import Config
from celery import shared_task
# Django imports
from django.conf import settings
from django.core.mail import EmailMultiAlternatives, get_connection
from django.core.serializers.json import DjangoJSONEncoder
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.html import strip_tags
# Module imports
from plane.db.models import (
APIToken,
CommentReaction,
Cycle,
CycleFavorite,
CycleIssue,
CycleUserProperties,
Estimate,
EstimatePoint,
FileAsset,
Inbox,
InboxIssue,
Issue,
IssueActivity,
IssueAssignee,
IssueAttachment,
IssueComment,
IssueLabel,
IssueLink,
IssueMention,
IssueProperty,
IssueReaction,
IssueRelation,
IssueSequence,
IssueSubscriber,
IssueView,
IssueViewFavorite,
IssueVote,
Label,
Module,
ModuleFavorite,
ModuleIssue,
ModuleLink,
ModuleMember,
ModuleUserProperties,
Notification,
Page,
PageFavorite,
PageLabel,
PageLog,
Project,
ProjectDeployBoard,
ProjectFavorite,
ProjectIdentifier,
ProjectMember,
ProjectMemberInvite,
ProjectPublicMember,
State,
User,
UserNotificationPreference,
Webhook,
Workspace,
WorkspaceMember,
WorkspaceMemberInvite,
WorkspaceTheme,
WorkspaceUserProperties,
)
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.exception_logger import log_exception
def create_zip_file(files):
# Create zip
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
for file in files:
filename = file.get("filename")
file_content = file.get("data")
zipf.writestr(filename, file_content)
zip_buffer.seek(0)
return zip_buffer
def upload_to_s3(zip_file, workspace_id, slug):
# Upload the zip to s3
file_name = f"{workspace_id}/export-{slug}-{timezone.now()}.zip"
expires_in = 7 * 24 * 60 * 60
if settings.USE_MINIO:
s3 = boto3.client(
"s3",
endpoint_url=settings.AWS_S3_ENDPOINT_URL,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
s3.upload_fileobj(
zip_file,
settings.AWS_STORAGE_BUCKET_NAME,
file_name,
ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"},
)
presigned_url = s3.generate_presigned_url(
"get_object",
Params={
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
"Key": file_name,
},
ExpiresIn=expires_in,
)
# Create the new url with updated domain and protocol
presigned_url = presigned_url.replace(
f"{settings.AWS_S3_ENDPOINT_URL}/{settings.AWS_STORAGE_BUCKET_NAME}/",
f"{settings.AWS_S3_URL_PROTOCOL}//{settings.AWS_S3_CUSTOM_DOMAIN}/",
)
else:
s3 = boto3.client(
"s3",
region_name=settings.AWS_REGION,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
s3.upload_fileobj(
zip_file,
settings.AWS_STORAGE_BUCKET_NAME,
file_name,
ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"},
)
presigned_url = s3.generate_presigned_url(
"get_object",
Params={
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
"Key": file_name,
},
ExpiresIn=expires_in,
)
return presigned_url
@shared_task
def workspace_export(workspace_id, email):
# Get the workspace
workspace = Workspace.objects.get(pk=workspace_id)
slug = workspace.slug
# Store all files
files = []
# Users that need to be exported
emails = WorkspaceMember.objects.filter(
workspace_id=workspace_id
).values_list("member__email", flat=True)
users = User.objects.filter(email__in=emails).values()
users_json = json.dumps(list(users), cls=DjangoJSONEncoder)
files.append({"filename": "users.json", "data": users_json})
workspace = list(Workspace.objects.filter(pk=workspace_id).values())
workspace_json = json.dumps(workspace, cls=DjangoJSONEncoder)
files.append({"filename": "workspaces.json", "data": workspace_json})
models = {
# Workspace
WorkspaceMemberInvite: "workspace_member_invites.json",
WorkspaceMember: "workspace_members.json",
WorkspaceTheme: "workspace_themes.json",
WorkspaceUserProperties: "workspace_user_properties.json",
# Projects
Project: "projects.json",
ProjectDeployBoard: "project_deploy_boards.json",
ProjectFavorite: "project_favorites.json",
ProjectIdentifier: "project_identifier.json",
ProjectMember: "project_members.json",
ProjectMemberInvite: "project_member_invites.json",
ProjectPublicMember: "project_public_members.json",
# APIToken
APIToken: "api_tokens.json",
# Assets
FileAsset: "file_assets.json",
# States
State: "states.json",
# Issues
Issue: "issues.json",
IssueAssignee: "issue_assignees.json",
Label: "labels.json",
IssueLabel: "issue_labels.json",
IssueLink: "issue_links.json",
IssueMention: "issue_mention.json",
IssueVote: "issue_votes.json",
IssueSubscriber: "issue_subscribers.json",
IssueProperty: "issue_properties.json",
IssueSequence: "issue_sequences.json",
IssueReaction: "issue_reactions.json",
IssueRelation: "issue_relations.json",
IssueAttachment: "issue_attachments.json",
IssueActivity: "issue_activities.json",
CommentReaction: "comment_reactions.json",
IssueComment: "issue_comments.json",
# Cycles
Cycle: "cycles.json",
CycleIssue: "cycle_issues.json",
CycleFavorite: "cycle_favorites.json",
CycleUserProperties: "cycle_user_properties.json",
# Modules
Module: "modules.json",
ModuleIssue: "module_issues.json",
ModuleFavorite: "module_favorites.json",
ModuleLink: "module_links.json",
ModuleMember: "module_members.json",
ModuleUserProperties: "module_user_properties.json",
# Page
Page: "pages.json",
PageLog: "page_logs.json",
PageLabel: "page_labels.json",
PageFavorite: "page_favorites.json",
# Estimate
Estimate: "estimates.json",
EstimatePoint: "estimate_points.json",
# Webhook
Webhook: "webhooks.json",
# Views
IssueView: "views.json",
IssueViewFavorite: "view_favorites.json",
# Notification
Notification: "notifications.json",
UserNotificationPreference: "user_notification_preferences.json",
# Inbox
Inbox: "inboxes.json",
InboxIssue: "inbox_issues.json",
}
# Loop through the models
for model in models:
file_name = models[model]
files.append(
{
"filename": file_name,
"data": json.dumps(
list(
model.objects.filter(
workspace_id=workspace_id
).values()
),
cls=DjangoJSONEncoder,
),
}
)
# Create zip
zip_buffer = create_zip_file(files)
# Get the presigned url
url = upload_to_s3(
workspace_id=workspace_id, slug=slug, zip_file=zip_buffer
)
# Send mail
try:
(
EMAIL_HOST,
EMAIL_HOST_USER,
EMAIL_HOST_PASSWORD,
EMAIL_PORT,
EMAIL_USE_TLS,
EMAIL_FROM,
) = get_email_configuration()
# Send the mail
subject = "Your Plane Export Link"
context = {"url": url, "email": email}
html_content = render_to_string(
"emails/exports/workspace_exports.html", context
)
text_content = strip_tags(html_content)
connection = get_connection(
host=EMAIL_HOST,
port=int(EMAIL_PORT),
username=EMAIL_HOST_USER,
password=EMAIL_HOST_PASSWORD,
use_tls=EMAIL_USE_TLS == "1",
)
msg = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=EMAIL_FROM,
to=[email],
connection=connection,
)
msg.attach_alternative(html_content, "text/html")
msg.send()
logging.getLogger("plane").info("Email sent successfully.")
return
except Exception as e:
log_exception(e)
return

View File

@@ -0,0 +1,475 @@
# Python imports
import json
# Third party imports
from celery import shared_task
from plane.db.models import (
APIToken,
CommentReaction,
Cycle,
CycleFavorite,
CycleIssue,
CycleUserProperties,
Estimate,
EstimatePoint,
FileAsset,
Inbox,
InboxIssue,
Issue,
IssueActivity,
IssueAssignee,
IssueAttachment,
IssueComment,
IssueLabel,
IssueLink,
IssueMention,
IssueProperty,
IssueReaction,
IssueRelation,
IssueSequence,
IssueSubscriber,
IssueView,
IssueViewFavorite,
IssueVote,
Label,
Module,
ModuleFavorite,
ModuleIssue,
ModuleLink,
ModuleMember,
ModuleUserProperties,
Notification,
Page,
PageFavorite,
PageLabel,
PageLog,
Project,
ProjectDeployBoard,
ProjectFavorite,
ProjectIdentifier,
ProjectMember,
ProjectMemberInvite,
ProjectPublicMember,
State,
User,
UserNotificationPreference,
Webhook,
Workspace,
WorkspaceMember,
WorkspaceMemberInvite,
WorkspaceTheme,
WorkspaceUserProperties,
)
from plane.utils.exception_logger import log_exception
def replace_model_data(fields, model_data, user_maps):
for field in fields:
model_data[field] = user_maps.get(
model_data.get(field), model_data.get(field)
)
return model_data
def data_transformer(model, model_data, user_maps):
# model name mapper
mapper = {
"workspace": [
"created_by_id",
"updated_by_id",
"owner_id",
],
"workspacememberinvite": [
"created_by_id",
"updated_by_id",
],
"workspacetheme": [
"created_by_id",
"updated_by_id",
],
"workspacemember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"workspaceuserproperties": [
"created_by_id",
"updated_by_id",
"user_id",
],
"project": [
"created_by_id",
"updated_by_id",
"default_assignee_id",
"project_lead_id",
],
"projectdeployboard": [
"created_by_id",
"updated_by_id",
],
"projectfavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"projectmember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"projectidentifier": [
"created_by_id",
"updated_by_id",
],
"projectmemberinvite": [
"created_by_id",
"updated_by_id",
],
"projectpublicmember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"state": [
"created_by_id",
"updated_by_id",
],
"label": [
"created_by_id",
"updated_by_id",
],
"estimate": [
"created_by_id",
"updated_by_id",
],
"estimatepoint": [
"created_by_id",
"updated_by_id",
],
"issue": [
"created_by_id",
"updated_by_id",
],
"issuecomment": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"issueassignee": [
"created_by_id",
"updated_by_id",
"assignee_id",
],
"issuelabel": [
"created_by_id",
"updated_by_id",
],
"issuelink": [
"created_by_id",
"updated_by_id",
],
"issuemention": [
"created_by_id",
"updated_by_id",
"mention_id",
],
"issuevote": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"issuesubscriber": [
"created_by_id",
"updated_by_id",
"subscriber_id",
],
"issueproperty": [
"created_by_id",
"updated_by_id",
"user_id",
],
"issuesequence": [
"created_by_id",
"updated_by_id",
],
"issuereaction": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"issuerelation": [
"created_by_id",
"updated_by_id",
],
"issueattachment": [
"created_by_id",
"updated_by_id",
],
"issueactivity": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"apitoken": [
"created_by_id",
"updated_by_id",
"user_id",
],
"fileasset": [
"created_by_id",
"updated_by_id",
],
"commentreaction": [
"created_by_id",
"updated_by_id",
"actor_id",
],
"cycle": [
"created_by_id",
"updated_by_id",
"owned_by_id",
],
"cycleissue": [
"created_by_id",
"updated_by_id",
],
"cyclefavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"cycleuserproperties": [
"user_id",
"created_by_id",
"updated_by_id",
],
"module": [
"created_by_id",
"updated_by_id",
"lead_id",
],
"moduleissue": [
"created_by_id",
"updated_by_id",
],
"modulefavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"modulelink": [
"created_by_id",
"updated_by_id",
],
"modulemember": [
"created_by_id",
"updated_by_id",
"member_id",
],
"moduleuserproperties": [
"user_id",
"created_by_id",
"updated_by_id",
],
"page": [
"created_by_id",
"updated_by_id",
"owned_by_id",
],
"pagelog": [
"created_by_id",
"updated_by_id",
],
"pagelabel": [
"created_by_id",
"updated_by_id",
],
"pagefavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"webhook": [
"created_by_id",
"updated_by_id",
],
"issueview": [
"created_by_id",
"updated_by_id",
],
"issueviewfavorite": [
"user_id",
"created_by_id",
"updated_by_id",
],
"notification": [
"created_by_id",
"updated_by_id",
"triggered_by_id",
"receiver_id",
],
"usernotificationpreference": [
"user_id",
"created_by_id",
"updated_by_id",
],
"inbox": [
"created_by_id",
"updated_by_id",
],
"inboxissue": [
"created_by_id",
"updated_by_id",
],
}
# Get all the fields for the current model
fields = mapper[model._meta.model_name]
if fields:
model_data = replace_model_data(
fields=fields, model_data=model_data, user_maps=user_maps
)
# return modified model data
return model_data
# return model data
return model_data
@shared_task
def workspace_import(workspace_data):
try:
# Create Users
users = json.loads(workspace_data.get("users.json"))
# get user emails
user_emails = [user.get("email") for user in users]
imported_users = {user.get("email"): user.get("id") for user in users}
existing_users = User.objects.filter(email__in=user_emails).values(
"id", "email"
)
user_maps = {
imported_users.get(exuser.get("email")): str(exuser.get("id"))
for exuser in existing_users
if imported_users.get(exuser.get("email"))
}
User.objects.bulk_create(
[User(**user) for user in (users)],
batch_size=100,
ignore_conflicts=True,
)
# Workspaces
workspaces = workspace_data.get("workspaces.json")
Workspace.objects.bulk_create(
[
Workspace(
**data_transformer(
model=Workspace,
model_data=workspace,
user_maps=user_maps,
)
)
for workspace in json.loads(workspaces)
],
batch_size=100,
ignore_conflicts=True,
)
models = {
# Workspace
WorkspaceMemberInvite: "workspace_member_invites.json",
WorkspaceMember: "workspace_members.json",
WorkspaceTheme: "workspace_themes.json",
WorkspaceUserProperties: "workspace_user_properties.json",
# Projects
Project: "projects.json",
ProjectDeployBoard: "project_deploy_boards.json",
ProjectFavorite: "project_favorites.json",
ProjectIdentifier: "project_identifier.json",
ProjectMember: "project_members.json",
ProjectMemberInvite: "project_member_invites.json",
ProjectPublicMember: "project_public_members.json",
# States
State: "states.json",
# Labels
Label: "labels.json",
# Estimate
Estimate: "estimates.json",
EstimatePoint: "estimate_points.json",
# Issues
Issue: "issues.json",
IssueComment: "issue_comments.json",
IssueAssignee: "issue_assignees.json",
IssueLabel: "issue_labels.json",
IssueLink: "issue_links.json",
IssueMention: "issue_mention.json",
IssueVote: "issue_votes.json",
IssueSubscriber: "issue_subscribers.json",
IssueProperty: "issue_properties.json",
IssueSequence: "issue_sequences.json",
IssueReaction: "issue_reactions.json",
IssueRelation: "issue_relations.json",
IssueAttachment: "issue_attachments.json",
IssueActivity: "issue_activities.json",
# APIToken
APIToken: "api_tokens.json",
# Assets
FileAsset: "file_assets.json",
CommentReaction: "comment_reactions.json",
# Cycles
Cycle: "cycles.json",
CycleIssue: "cycle_issues.json",
CycleFavorite: "cycle_favorites.json",
CycleUserProperties: "cycle_user_properties.json",
# Modules
Module: "modules.json",
ModuleIssue: "module_issues.json",
ModuleFavorite: "module_favorites.json",
ModuleLink: "module_links.json",
ModuleMember: "module_members.json",
ModuleUserProperties: "module_user_properties",
# Page
Page: "pages.json",
PageLog: "page_logs.json",
PageLabel: "page_labels.json",
PageFavorite: "page_favorites.json",
# Webhook
Webhook: "webhooks.json",
# Views
IssueView: "views.json",
IssueViewFavorite: "view_favorites.json",
# Notification
Notification: "notifications.json",
UserNotificationPreference: "user_notification_preferences.json",
# Inbox
Inbox: "inboxes.json",
InboxIssue: "inbox_issues.json",
}
for model in models:
file_name = models[model]
data = workspace_data.get(file_name)
# Loop through all the models and create the records accordingly
model.objects.bulk_create(
[
model(
**data_transformer(
model=model,
model_data=model_data,
user_maps=user_maps,
)
)
for model_data in json.loads(data)
],
batch_size=100,
ignore_conflicts=True,
)
return
except Exception as e:
log_exception(e)
return

View File

@@ -1,6 +1,6 @@
# Django imports
from django.db import models
from django.conf import settings
from django.db import models
# Module imports
from . import ProjectBaseModel

View File

@@ -1,12 +1,11 @@
# Django imports
from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
# Module imports
from . import BaseModel
ROLE_CHOICES = (
(20, "Owner"),
(15, "Admin"),

View File

@@ -4,4 +4,6 @@ from .instance import (
InstanceConfigurationEndpoint,
InstanceAdminSignInEndpoint,
SignUpScreenVisitedEndpoint,
ExportWorkspaceEndpoint,
ImportWorkspaceEndpoint,
)

View File

@@ -1,33 +1,39 @@
# Python imports
import uuid
import zipfile
# Django imports
from django.utils import timezone
from django.contrib.auth.hashers import make_password
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.utils import timezone
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
# Module imports
from plane.app.serializers.workspace import WorkspaceLiteSerializer
from plane.app.views import BaseAPIView
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
from plane.license.api.serializers import (
InstanceSerializer,
InstanceAdminSerializer,
InstanceConfigurationSerializer,
)
from plane.bgtasks.workspace_export_task import workspace_export
from plane.bgtasks.workspace_import_task import workspace_import
from plane.db.models import User, Workspace
from plane.license.api.permissions import (
InstanceAdminPermission,
)
from plane.db.models import User
from plane.license.api.serializers import (
InstanceAdminSerializer,
InstanceConfigurationSerializer,
InstanceSerializer,
)
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
from plane.license.utils.encryption import encrypt_data
from plane.utils.cache import cache_response, invalidate_cache
class InstanceEndpoint(BaseAPIView):
def get_permissions(self):
if self.request.method == "PATCH":
@@ -272,3 +278,84 @@ class SignUpScreenVisitedEndpoint(BaseAPIView):
instance.is_signup_screen_visited = True
instance.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class InstanceWorkspacesEndpoint(BaseAPIView):
permission_classes = [
InstanceAdminPermission,
]
def get(self, request):
workspaces = Workspace.objects.all()
serializer = WorkspaceLiteSerializer(workspaces, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class ExportWorkspaceEndpoint(BaseAPIView):
permission_classes = [
InstanceAdminPermission,
]
def post(self, request):
workspace_id = request.data.get("workspace_id", False)
if not workspace_id:
return Response(
{"error": "Workspace ID is required"},
status=status.HTTP_400_BAD_REQUEST,
)
workspace_export.delay(
workspace_id=workspace_id,
email=request.user.email,
)
return Response(
{
"message": "An email will be sent to download the exports when they are ready"
},
status=status.HTTP_200_OK,
)
class ImportWorkspaceEndpoint(BaseAPIView):
parser_classes = (
MultiPartParser,
FormParser,
JSONParser,
)
permission_classes = [
InstanceAdminPermission,
]
def post(self, request):
file_obj = request.FILES.get("zip_file")
if file_obj is None:
return Response(
"No file uploaded.", status=status.HTTP_400_BAD_REQUEST
)
# Ensure the uploaded file is a ZIP file
if not zipfile.is_zipfile(file_obj):
return Response(
"Uploaded file is not a valid zip file.",
status=status.HTTP_400_BAD_REQUEST,
)
# Reading contents of the ZIP file
file_contents = {}
with zipfile.ZipFile(file_obj, "r") as zip_ref:
for file_name in zip_ref.namelist():
with zip_ref.open(file_name) as file:
# Assuming the file content is text. Use file.read() for binary content.
content = file.read().decode("utf-8")
file_contents[file_name] = content
workspace_import.delay(workspace_data=file_contents)
return Response(
{"message": "Files processed.", "file_count": len(file_contents)},
status=status.HTTP_200_OK,
)

View File

@@ -1,10 +1,12 @@
from django.urls import path
from plane.license.api.views import (
InstanceEndpoint,
ExportWorkspaceEndpoint,
ImportWorkspaceEndpoint,
InstanceAdminEndpoint,
InstanceConfigurationEndpoint,
InstanceAdminSignInEndpoint,
InstanceConfigurationEndpoint,
InstanceEndpoint,
SignUpScreenVisitedEndpoint,
)
@@ -39,4 +41,14 @@ urlpatterns = [
SignUpScreenVisitedEndpoint.as_view(),
name="instance-sign-up",
),
path(
"export-workspace/",
ExportWorkspaceEndpoint.as_view(),
name="workspace-exports",
),
path(
"import-workspace/",
ImportWorkspaceEndpoint.as_view(),
name="workspace-imports",
),
]

View File

@@ -0,0 +1,5 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
Hey there,<br />
Your requested url {{ url }}
</html>