From 961a026a74c62d27d1b3402fb93a2e8df379369a Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 17:58:43 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix:=20[#64]=20tz=5F?= =?UTF-8?q?aware(UTC=20=EA=B4=80=EB=A0=A8=20=EC=A0=95=EB=B3=B4)=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/utils/mongodb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/utils/mongodb.py b/common/utils/mongodb.py index 80dc3cc..bdcb564 100644 --- a/common/utils/mongodb.py +++ b/common/utils/mongodb.py @@ -4,5 +4,6 @@ from django.conf import settings def connect_colio_mongo(): mongoengine.connect( db=settings.MONGODB_NAME, - host=settings.MONGODB_URI + host=settings.MONGODB_URI, + tz_aware=True, ) \ No newline at end of file From 57d0efbbd6ab1bc541c3c0affcc8954bd1a5d0a0 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:02:12 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=94=A7=20Settings:=20[#64]=20USE=5F?= =?UTF-8?q?TZ=20True=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/settings.py b/config/settings.py index a407419..2127470 100644 --- a/config/settings.py +++ b/config/settings.py @@ -167,7 +167,7 @@ TIME_ZONE = 'Asia/Seoul' USE_I18N = True -USE_TZ = False +USE_TZ = True MEDIA_URL = '/media/' From c693acf79d1542ae7c3699034c68a9da804dd6ae Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:17:11 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix:=20[#64]=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80,=20=ED=8C=8C=EC=9D=BC=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=95=A8=EC=88=98=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/utils/fileManager.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/common/utils/fileManager.py b/common/utils/fileManager.py index 6a19d18..b51d7e6 100644 --- a/common/utils/fileManager.py +++ b/common/utils/fileManager.py @@ -1,18 +1,20 @@ import os +from django.utils.deconstruct import deconstructible -def dynamic_upload_to(prefix, field_name_func): - def wrapper(instance, filename): +@deconstructible +class DynamicUploadTo: + def __init__(self, prefix, field_name): + self.prefix = prefix + self.field_name = field_name + + def __call__(self, instance, filename): ext = filename.split('.')[-1] - field_name = field_name_func(instance) - - if prefix == 'user': - filename = f'{instance.nickname}-{field_name}.{ext}' + fname = self.field_name(instance) if callable(self.field_name) else self.field_name + if self.prefix == "user": + filename = f"{instance.nickname}-{fname}.{ext}" else: - filename = f'{instance.id}-{field_name}.{ext}' - - return os.path.join(prefix, filename) - - return wrapper + filename = f"{instance.id}-{fname}.{ext}" + return os.path.join(self.prefix, filename) def file_delete(obj, field): getattr(obj, field).delete(save=False) From 0b413322f962776da7be51e5da4f07bbb17e6121 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:22:38 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20now=5Fworker?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80,=20thumbnail,=20code=5Fid=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- portfolios/models.py | 9 ++++++--- projects/models.py | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/portfolios/models.py b/portfolios/models.py index f66ec11..d80dc3a 100644 --- a/portfolios/models.py +++ b/portfolios/models.py @@ -2,7 +2,7 @@ from django.db import models from common.models.baseModels import BaseModel -from common.utils.fileManager import dynamic_upload_to +from common.utils.fileManager import DynamicUploadTo from django.contrib.postgres.fields import ArrayField from django.conf import settings @@ -10,6 +10,8 @@ from django.conf import settings from users.models import User +NICKNAME_LEN = 20 + class Portfolio(BaseModel): title = models.CharField(max_length=20) category = ArrayField(models.CharField(max_length=20), default=list) @@ -18,8 +20,9 @@ class Portfolio(BaseModel): like_count = models.IntegerField(default=0) scrap_count = models.IntegerField(default=0) is_represent = models.BooleanField(default=False) - thumbnail = models.ImageField(upload_to=dynamic_upload_to('portfolio', lambda instance: 'thumbnail'), blank=True) - code_id = models.CharField(max_length=26, blank=True) + now_worker = models.CharField(max_length=NICKNAME_LEN, blank=True) + thumbnail = models.ImageField(upload_to=DynamicUploadTo("portfolio", "thumbnail"), null=True, blank=True) + code_id = models.CharField(max_length=26, null=True, blank=True) owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='owned_portfolios', to_field="id") likers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='liked_portfolios', blank=True) scrappers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='scrapped_portfolios', blank=True) diff --git a/projects/models.py b/projects/models.py index d03f375..246ece8 100644 --- a/projects/models.py +++ b/projects/models.py @@ -3,7 +3,7 @@ from django.db import models from common.models.baseModels import BaseModel from common.models.choiceModels import InvitationStatus -from common.utils.fileManager import dynamic_upload_to +from common.utils.fileManager import DynamicUploadTo from django.contrib.postgres.fields import ArrayField from django.conf import settings @@ -12,6 +12,8 @@ from notifications.models import Notification from users.models import User +NICKNAME_LEN = 20 + class Project(BaseModel): title = models.CharField(max_length=20) is_team = models.BooleanField(default=False) @@ -22,8 +24,9 @@ class Project(BaseModel): like_count = models.IntegerField(default=0) scrap_count = models.IntegerField(default=0) is_represent = models.BooleanField(default=False) - thumbnail = models.ImageField(upload_to=dynamic_upload_to('project', lambda instance: 'thumbnail'), blank=True) - code_id = models.CharField(max_length=26, blank=True) + now_worker = models.CharField(max_length=NICKNAME_LEN, blank=True) + thumbnail = models.ImageField(upload_to=DynamicUploadTo("project", "thumbnail"), null=True, blank=True) + code_id = models.CharField(max_length=26, null=True, blank=True) owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='owned_projects', to_field="id") likers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='liked_projects', blank=True) scrappers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='scrapped_projects', blank=True) From 4ae7afd9df2ee87d3249c8f32cd455c151221e52 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:23:49 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20=EB=85=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=ED=88=B4=EC=97=90=EC=84=9C=20=EC=9E=91?= =?UTF-8?q?=EC=97=85(=ED=94=8C=EC=A0=9D,=ED=8F=AC=ED=8F=B4)=EC=9D=98=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=A0=9C=EA=B3=B5=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EC=8B=9C=EB=A6=AC=EC=96=BC=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=A0=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- portfolios/serializers.py | 5 +++++ projects/serializers.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/portfolios/serializers.py b/portfolios/serializers.py index 6d8e8bc..e1f37d2 100644 --- a/portfolios/serializers.py +++ b/portfolios/serializers.py @@ -10,6 +10,11 @@ class PortfolioListViewSerializer(serializers.ModelSerializer): model = Portfolio fields = ['id', 'category', 'thumbnail', 'title', 'nickname', 'profile_image', 'view_count', 'like_count', 'scrap_count'] +class PortfolioNocodetoolSerializer(serializers.ModelSerializer): + class Meta: + model = Portfolio + fields = ['title', 'is_published', 'now_worker'] + class PortfolioCreateSerializer(serializers.ModelSerializer): class Meta: model = Portfolio diff --git a/projects/serializers.py b/projects/serializers.py index a5f8902..d4a2fd4 100644 --- a/projects/serializers.py +++ b/projects/serializers.py @@ -13,6 +13,11 @@ class ProjectListViewSerializer(serializers.ModelSerializer): model = Project fields = ['id', 'category', 'thumbnail', 'title', 'view_count', 'like_count', 'scrap_count'] +class ProjectNocodetoolSerializer(serializers.ModelSerializer): + class Meta: + model = Project + fields = ['title', 'is_published', 'now_worker'] + class ProjectCreateSerializer(serializers.ModelSerializer): members = serializers.ListField( child=serializers.CharField(), write_only=True, required=False From 7fef67b25884c313f11c5a3cc4b738c351a43067 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:25:07 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix:=20[#64]=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B2=BD=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=95=A8=EC=88=98=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/users/models.py b/users/models.py index 3a24fad..fa9b869 100644 --- a/users/models.py +++ b/users/models.py @@ -2,7 +2,7 @@ from django.db import models from common.models.baseModels import BaseModel from common.models.choiceModels import GenderChoices, CertificateCodeUseType from common.utils.codeManger import set_expire -from common.utils.fileManager import dynamic_upload_to +from common.utils.fileManager import DynamicUploadTo from django.contrib.postgres.fields import ArrayField from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin @@ -52,8 +52,8 @@ class User(BaseModel, AbstractBaseUser, PermissionsMixin): skills = ArrayField(models.CharField(max_length=20), default=list, blank=True) external_links = ArrayField(models.TextField(), default=list, blank=True) short_bio = models.CharField(max_length=100, blank=True) - profile_image = models.ImageField(upload_to=dynamic_upload_to('user', lambda instance: 'profile'), blank=True) - banner_image = models.ImageField(upload_to=dynamic_upload_to('user', lambda instance: 'banner'), blank=True) + profile_image = models.ImageField(upload_to=DynamicUploadTo("user", "profile"), blank=True) + banner_image = models.ImageField(upload_to=DynamicUploadTo("user", "banner"), blank=True) is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True) From 1678865b12d5c915a14f9f22fbd82324318c3143 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:26:45 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20=EB=85=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=ED=88=B4=20=EC=9A=94=EC=B2=AD=20url=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/urls.py b/config/urls.py index 0e2116a..2507916 100644 --- a/config/urls.py +++ b/config/urls.py @@ -5,12 +5,13 @@ from django.conf import settings from django.conf.urls.static import static urlpatterns = [ - path('admin/', admin.site.urls), + # path('admin/', admin.site.urls), path('api/user/', include('users.urls')), path('api/code/', include('codes.urls')), path('api/portfolio/', include('portfolios.urls')), path('api/project/', include('projects.urls')), path('api/notification/', include('notifications.urls')), + path('api/nocodetool/', include('nocodetools.urls')), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # if settings.DEBUG: From 5770a27a523df72043377c6411bfc4b27c6a5cea Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:30:02 +0900 Subject: [PATCH 08/13] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix:=20[#64]=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=ED=95=98=EB=82=98=EC=9D=98=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EB=A1=9C=20Code=20=EA=B0=9D=EC=B2=B4=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20save=20=EC=8B=9C=20=EB=82=A0=EC=A7=9C?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/models.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/nocodetools/models.py b/nocodetools/models.py index 0213803..f4d86bc 100644 --- a/nocodetools/models.py +++ b/nocodetools/models.py @@ -1,14 +1,25 @@ from django.db import models +from django.utils import timezone import mongoengine as me + class Element(me.EmbeddedDocument): + element_id = me.StringField() element_type = me.StringField() content = me.StringField() css = me.DictField() -class Page(me.Document): +class Page(me.EmbeddedDocument): cut = me.IntField() elements = me.ListField(me.EmbeddedDocumentField(Element)) - created_at = me.DateTimeField() - updated_at = me.DateTimeField() \ No newline at end of file + +class Code(me.Document): + pages = me.ListField(me.EmbeddedDocumentField(Page)) + created_at = me.DateTimeField(default=timezone.now) + updated_at = me.DateTimeField(default=timezone.now) + + def save(self, *args, **kwargs): + self.updated_at = timezone.now() + return super().save(*args, **kwargs) + \ No newline at end of file From d1911eb1b82b754dc6769113a037974445d14a82 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:31:06 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20owner=20=ED=98=B9=EC=9D=80=20=ED=8C=80=EC=9B=90?= =?UTF-8?q?=EC=9D=B8=EC=A7=80=20=ED=99=95=EC=9D=B8=20=EC=97=AC=EB=B6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/permissions.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 nocodetools/permissions.py diff --git a/nocodetools/permissions.py b/nocodetools/permissions.py new file mode 100644 index 0000000..30b032f --- /dev/null +++ b/nocodetools/permissions.py @@ -0,0 +1,26 @@ +# permissions.py +from rest_framework.permissions import BasePermission +from projects.models import Project, ProjectTeamList +from portfolios.models import Portfolio + +UNSAFE_REQUEST = ["POST", "PUT", "PATCH", "DELETE"] + +class IsOwnerOrMemberInCreateAndUpdateAndDelete(BasePermission): + def has_permission(self, request, view): + if request.method not in UNSAFE_REQUEST: + return True + + related_type = request.query_params.get("type") + related_id = request.query_params.get("id") + + if not related_type or not related_id: + return False + + user = request.user + + if related_type == "project": + return ProjectTeamList.objects.filter(project=related_id, user=user).exists() + elif related_type == "portfolio": + return Portfolio.objects.filter(id=related_id, owner=user).exists() + else: + return False \ No newline at end of file From d164279ef0eca254b513ffe6b6133afdbec35d8f Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:32:20 +0900 Subject: [PATCH 10/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20code=20CRUD?= =?UTF-8?q?=20serializer=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/serializers.py | 67 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 nocodetools/serializers.py diff --git a/nocodetools/serializers.py b/nocodetools/serializers.py new file mode 100644 index 0000000..293b271 --- /dev/null +++ b/nocodetools/serializers.py @@ -0,0 +1,67 @@ +from rest_framework import serializers +from .models import Code, Page, Element + +# from datetime import datetime, timezone + +from django.utils import timezone +# from zoneinfo import ZoneInfo + +# KST = ZoneInfo("Asia/Seoul") + +class ElementSerializer(serializers.Serializer): + element_id = serializers.CharField(required=False) + element_type = serializers.CharField() + content = serializers.CharField(allow_blank=True) + css = serializers.DictField() + +class PageSerializer(serializers.Serializer): + cut = serializers.IntegerField() + elements = ElementSerializer(many=True) + +class CodeSerializer(serializers.Serializer): + id = serializers.SerializerMethodField() + pages = PageSerializer(many=True, required=False) + created_at = serializers.SerializerMethodField() + updated_at = serializers.SerializerMethodField() + + def get_created_at(self, obj): + return timezone.localtime(obj.created_at).isoformat(timespec="seconds") + + def get_updated_at(self, obj): + return timezone.localtime(obj.updated_at).isoformat(timespec="seconds") + + def get_id(self, obj): + return str(obj.id) + + def create(self, validated_data): + pages_data = validated_data.pop('pages') + pages = [ + Page( + cut=page['cut'], + elements=[Element(**el) for el in page['elements']] + ) for page in pages_data + ] + code = Code(pages=pages, **validated_data) + code.save() + return code + + def update(self, instance, validated_data): + update_pages_data = validated_data.get('pages', []) + existing = {p.cut: p for p in instance.pages} + + for page in update_pages_data: + cut = page.get('cut') + if cut and cut in existing: + page_obj = existing[cut] + page_obj.elements = [Element(**el) for el in page["elements"]] + + else: + instance.pages.append( + Page( + cut=cut, + elements=[Element(**el) for el in page["elements"]] + ) + ) + + instance.save() + return instance From 6321c1bf34c91f263fef922605abe13d3307440f Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:33:01 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20=EA=B0=9D=EC=B2=B4,=20=EC=8B=9C=EB=A6=AC=EC=96=BC?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=A0=80=20=EB=A7=A4=ED=95=91=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/services.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 nocodetools/services.py diff --git a/nocodetools/services.py b/nocodetools/services.py new file mode 100644 index 0000000..b19203b --- /dev/null +++ b/nocodetools/services.py @@ -0,0 +1,29 @@ +from .models import * +from .serializers import * + +from projects.models import Project +from portfolios.models import Portfolio + +from projects.serializers import ProjectNocodetoolSerializer +from portfolios.serializers import PortfolioNocodetoolSerializer + +NOCODETOOL_MODEL_MAP = { + 'project': Project, + 'portfolio': Portfolio, + } + +NOCODETOOL_SERIALIZER_MAP = { + 'project': ProjectNocodetoolSerializer, + 'portfolio': PortfolioNocodetoolSerializer, + } + +class NocodetoolObjectMapService: + @staticmethod + def mapping_model_instance(related_type: str, related_id: str): + object_model = NOCODETOOL_MODEL_MAP.get(related_type) + if not object_model: + return None + return object_model.objects.filter(id=related_id).first() + + def mapping_model_serializer(related_type: str): + return NOCODETOOL_SERIALIZER_MAP.get(related_type, None) From 13fb4d37438d75ff29d24f57a469082bc6eee0fe Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:33:26 +0900 Subject: [PATCH 12/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20=EB=85=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=ED=88=B4=20url=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/urls.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 nocodetools/urls.py diff --git a/nocodetools/urls.py b/nocodetools/urls.py new file mode 100644 index 0000000..83fcb97 --- /dev/null +++ b/nocodetools/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from .views import * + + +app_name = 'nocodetools' + +urlpatterns = [ + path('', NoCodeToolAPIView.as_view()), + path('working/', NocodeToolWorkingAPIView.as_view()), +] \ No newline at end of file From be8ecab1636a999f6b6b90cb7cd532d0e9a1566b Mon Sep 17 00:00:00 2001 From: sm4640 Date: Thu, 5 Jun 2025 18:34:08 +0900 Subject: [PATCH 13/13] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#64]=20=EB=85=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=ED=88=B4=20CRUD=20=EB=B0=8F=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91,=20=EB=81=9D=20=EC=9A=94=EC=B2=AD=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nocodetools/views.py | 164 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/nocodetools/views.py b/nocodetools/views.py index d43f642..839149a 100644 --- a/nocodetools/views.py +++ b/nocodetools/views.py @@ -1 +1,163 @@ -# views.py +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status, mixins, viewsets +from rest_framework.permissions import AllowAny, IsAuthenticated + +from django.db import transaction + +from .models import Code, Page, Element +from .serializers import CodeSerializer +from .permissions import IsOwnerOrMemberInCreateAndUpdateAndDelete +from .services import NocodetoolObjectMapService + +from users.models import User +from portfolios.models import Portfolio +from projects.models import Project + +from bson import ObjectId + + +class NoCodeToolAPIView(APIView): + permission_classes = [IsAuthenticated, IsOwnerOrMemberInCreateAndUpdateAndDelete] + + def get(self, request): + related_type = request.query_params.get("type") + related_id = request.query_params.get("id") + code_id = None + if obj := NocodetoolObjectMapService.mapping_model_instance(related_type, related_id): + code_id = ObjectId(obj.code_id) + + if not code_id: + return Response({"message": "Not validated type or no object"}, status=status.HTTP_400_BAD_REQUEST) + + obj_serializer = NocodetoolObjectMapService.mapping_model_serializer(related_type) + + code = Code.objects.get(id=code_id) + return Response({ + "obj_info": obj_serializer(obj).data, + "codes": CodeSerializer(code).data + }, status=status.HTTP_200_OK) + + @transaction.atomic + def post(self, request): + related_type = request.query_params.get("type") + related_id = request.query_params.get("id") + obj = NocodetoolObjectMapService.mapping_model_instance(related_type, related_id) + if not obj: + return Response({"message": "No object"}, status=status.HTTP_400_BAD_REQUEST) + + if not obj.now_worker: + return Response({"message": "start edit first"}, status=status.HTTP_400_BAD_REQUEST) + + if obj.now_worker != request.user.nickname: + return Response({"message": f"{obj.now_worker} is working now"}, status=status.HTTP_400_BAD_REQUEST) + + data = request.data.copy() + thumbnail_file = data.pop("thumbnail", None) + + obj_serializer = NocodetoolObjectMapService.mapping_model_serializer(related_type) + + serializer = CodeSerializer(data=request.data) + if serializer.is_valid(): + code = serializer.save() + obj.code_id = str(code.id) + obj.save() + + if thumbnail_file: + obj.thumbnail = thumbnail_file + obj.save(update_fields=["thumbnail"]) + + return Response({ + "obj_info": obj_serializer(obj).data, + "codes": CodeSerializer(code).data + }, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @transaction.atomic + def patch(self, request): + related_type = request.query_params.get("type") + related_id = request.query_params.get("id") + obj = NocodetoolObjectMapService.mapping_model_instance(related_type, related_id) + if not obj: + return Response({"message": "No object"}, status=status.HTTP_400_BAD_REQUEST) + + if not obj.now_worker: + return Response({"message": "start edit first"}, status=status.HTTP_400_BAD_REQUEST) + + if obj.now_worker != request.user.nickname: + return Response({"message": f"{obj.now_worker} is working now"}, status=status.HTTP_400_BAD_REQUEST) + + code = Code.objects.filter(id=ObjectId(obj.code_id)).first() + if not code: + return Response({'message': 'No code object'}, status=status.HTTP_400_BAD_REQUEST) + + data = request.data.copy() + thumbnail_file = data.pop("thumbnail", None) + + obj_serializer = NocodetoolObjectMapService.mapping_model_serializer(related_type) + serializer = CodeSerializer(code, data=data, partial=True) + if serializer.is_valid(): + updated_code = serializer.save() + + if thumbnail_file: + obj.thumbnail = thumbnail_file + obj.save(update_fields=["thumbnail"]) + + return Response({ + "obj_info": obj_serializer(obj).data, + "codes": CodeSerializer(updated_code).data + }, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @transaction.atomic + def delete(self, request): + related_type = request.query_params.get("type") + related_id = request.query_params.get("id") + obj = NocodetoolObjectMapService.mapping_model_instance(related_type, related_id) + if not obj: + return Response({"message": "No object"}, status=status.HTTP_400_BAD_REQUEST) + + if not obj.now_worker: + return Response({"message": "start edit first"}, status=status.HTTP_400_BAD_REQUEST) + + if obj.now_worker != request.user.nickname: + return Response({"message": f"{obj.now_worker} is working now"}, status=status.HTTP_400_BAD_REQUEST) + + code = Code.objects.filter(id=ObjectId(obj.code_id)).first() + if not code: + return Response({'message': 'No code object'}, status=status.HTTP_400_BAD_REQUEST) + code.delete() + obj.code_id = None + obj.thumbnail = None + obj.save() + return Response({"message": "delete code success"}, status=status.HTTP_200_OK) + +class NocodeToolWorkingAPIView(APIView): + permission_classes = [IsAuthenticated, IsOwnerOrMemberInCreateAndUpdateAndDelete] + @transaction.atomic + def patch(self, request): # 수정 시작 or 종료 + related_type = request.query_params.get("type") + related_id = request.query_params.get("id") + action = request.query_params.get("action") + obj = NocodetoolObjectMapService.mapping_model_instance(related_type, related_id) + if not obj: + return Response({"message": "No object"}, status=status.HTTP_400_BAD_REQUEST) + + if action == "start": + if obj.now_worker: + return Response({"message": f"{obj.now_worker} is working"}, status=status.HTTP_400_BAD_REQUEST) + obj.now_worker = request.user.nickname + elif action == "end": + if obj.now_worker and obj.now_worker == request.user.nickname: + obj.now_worker = None + else: + return Response( + { + "message": f"{obj.now_worker} is working" if obj.now_worker else "nobody working now" + }, status=status.HTTP_400_BAD_REQUEST) + else: + return Response({"message": "Not supported action"}, status=status.HTTP_400_BAD_REQUEST) + + obj.save() + + return Response({"message": f"work {action}"}, status=status.HTTP_200_OK) \ No newline at end of file