From 48bfca639eab040a5d3fa948498f316eff57f758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20O=2E=20=C5=9Eeng=C3=B6r?= Date: Sun, 28 May 2023 14:48:48 +0300 Subject: [PATCH 01/11] :bug: Fix settings regression --- kaplancloud/settings.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/kaplancloud/settings.py b/kaplancloud/settings.py index 7164129..6bbb23a 100644 --- a/kaplancloud/settings.py +++ b/kaplancloud/settings.py @@ -132,8 +132,6 @@ STATIC_ROOT = BASE_DIR / 'staticfiles' -STATICFILES_STORAGE = os.environ.get('STATICFILES_STORAGE', 'django.contrib.staticfiles.storage.StaticFilesStorage') - # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field @@ -143,11 +141,20 @@ PROJECTS_DIR = 'kaplancloudapp/projects' -DEFAULT_FILE_STORAGE = os.environ.get('FILE_STORAGE', 'django.core.files.storage.FileSystemStorage') +# https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-STORAGES + +STORAGES = { + 'default': { + 'BACKEND': os.environ.get('FILE_STORAGE', 'django.core.files.storage.FileSystemStorage'), + }, + 'staticfiles': { + 'BACKEND': os.environ.get('STATICFILES_STORAGE', 'django.contrib.staticfiles.storage.StaticFilesStorage'), + } +} # This will set s3 parameters only if default file storage and/or staticfiles # storage is set to S3Boto3Storage -if DEFAULT_FILE_STORAGE == 'storages.backends.s3boto3.S3Boto3Storage' or STATICFILES_STORAGE == 'storages.backends.s3boto3.S3StaticStorage': +if STORAGES['default']['BACKEND'] == 'storages.backends.s3boto3.S3Boto3Storage' or STORAGES['staticfiles']['BACKEND'] == 'storages.backends.s3boto3.S3StaticStorage': AWS_DEFAULT_ACL = os.environ.get('S3_DEFAULT_ACL') AWS_S3_ACCESS_KEY_ID = os.environ.get('S3_ACCESS_KEY_ID') AWS_S3_SECRET_ACCESS_KEY = os.environ.get('S3_SECRET_ACCESS_KEY') @@ -163,7 +170,7 @@ # This will set GCP Cloud Storage parameters only if the default file storage # and/or staticfiles storage is set to GoogleCloudStorage -elif 'storages.backends.gcloud.GoogleCloudStorage' in (DEFAULT_FILE_STORAGE, STATICFILES_STORAGE): +elif 'storages.backends.gcloud.GoogleCloudStorage' in (STORAGES['default']['BACKEND'], STORAGES['staticfiles']['BACKEND']): # Environmental variable GOOGLE_APPLICATION_CREDENTIALS is to be set to the # path of the key file GS_BUCKET_NAME = os.environ.get('GS_PUBLIC_BUCKET_NAME') From 27525e5eba8984a35ff61658b70ea6c7b4ba7766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20O=2E=20=C5=9Eeng=C3=B6r?= Date: Thu, 8 Jun 2023 20:00:59 +0300 Subject: [PATCH 02/11] :sparkles: Create kaplancloudapi app --- kaplancloud/settings.py | 17 +++ kaplancloud/urls.py | 1 + kaplancloudapi/__init__.py | 0 kaplancloudapi/admin.py | 7 + kaplancloudapi/apps.py | 11 ++ kaplancloudapi/migrations/0001_initial.py | 40 ++++++ kaplancloudapi/migrations/__init__.py | 0 kaplancloudapi/models.py | 33 +++++ kaplancloudapi/serializers.py | 91 +++++++++++++ kaplancloudapi/signals.py | 15 +++ kaplancloudapi/tests.py | 3 + kaplancloudapi/urls.py | 20 +++ kaplancloudapi/views.py | 154 ++++++++++++++++++++++ requirements.txt | 1 + 14 files changed, 393 insertions(+) create mode 100644 kaplancloudapi/__init__.py create mode 100644 kaplancloudapi/admin.py create mode 100644 kaplancloudapi/apps.py create mode 100644 kaplancloudapi/migrations/0001_initial.py create mode 100644 kaplancloudapi/migrations/__init__.py create mode 100644 kaplancloudapi/models.py create mode 100644 kaplancloudapi/serializers.py create mode 100644 kaplancloudapi/signals.py create mode 100644 kaplancloudapi/tests.py create mode 100644 kaplancloudapi/urls.py create mode 100644 kaplancloudapi/views.py diff --git a/kaplancloud/settings.py b/kaplancloud/settings.py index 6bbb23a..1eae37e 100644 --- a/kaplancloud/settings.py +++ b/kaplancloud/settings.py @@ -42,7 +42,10 @@ 'django.contrib.staticfiles', 'storages', + 'rest_framework', + 'rest_framework.authtoken', 'kaplancloudaccounts', + 'kaplancloudapi', 'kaplancloudapp' ] @@ -180,3 +183,17 @@ GS_LOCATION = os.environ.get('GS_PUBLIC_BUCKET_LOCATION', 'static') GS_PRIVATE_BUCKET_LOCATION = os.environ.get('GS_PRIVATE_BUCKET_LOCATION', '') GS_QUERYSTRING_AUTH = os.environ.get('GS_QUERYSTRING_AUTH', 'False') == 'True' + +# https://www.django-rest-framework.org/api-guide/permissions/ +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAdminUser' + ], + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication' + ], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 100 +} \ No newline at end of file diff --git a/kaplancloud/urls.py b/kaplancloud/urls.py index db0ef74..2bbc280 100644 --- a/kaplancloud/urls.py +++ b/kaplancloud/urls.py @@ -19,5 +19,6 @@ urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('kaplancloudaccounts.urls')), + path('api/', include('kaplancloudapi.urls')), path('', include('kaplancloudapp.urls')), ] diff --git a/kaplancloudapi/__init__.py b/kaplancloudapi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kaplancloudapi/admin.py b/kaplancloudapi/admin.py new file mode 100644 index 0000000..76614c6 --- /dev/null +++ b/kaplancloudapi/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from kaplancloudapi.models import ProjectWebHook, ProjectFileWebHook + +admin.site.register(ProjectWebHook) + +admin.site.register(ProjectFileWebHook) diff --git a/kaplancloudapi/apps.py b/kaplancloudapi/apps.py new file mode 100644 index 0000000..af36afe --- /dev/null +++ b/kaplancloudapi/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig + + +class KaplancloudapiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'kaplancloudapi' + + def ready(self): + # Import the signal instances and connect them + # from myapp.signals import my_signal + import kaplancloudapi.signals \ No newline at end of file diff --git a/kaplancloudapi/migrations/0001_initial.py b/kaplancloudapi/migrations/0001_initial.py new file mode 100644 index 0000000..adad436 --- /dev/null +++ b/kaplancloudapi/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 4.2.2 on 2023-06-25 11:29 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('kaplancloudapp', '0016_add_uuid_fields'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectWebHook', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('target', models.URLField()), + ('header', models.JSONField()), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='kaplancloudapp.project')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ProjectFileWebHook', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('target', models.URLField()), + ('header', models.JSONField()), + ('project_file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='kaplancloudapp.projectfile')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/kaplancloudapi/migrations/__init__.py b/kaplancloudapi/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kaplancloudapi/models.py b/kaplancloudapi/models.py new file mode 100644 index 0000000..cc1bbc5 --- /dev/null +++ b/kaplancloudapi/models.py @@ -0,0 +1,33 @@ +from django.db import models + +import json +import logging +import requests + + +class WebHook(models.Model): + target = models.URLField() + header = models.JSONField() + + class Meta: + abstract = True + + def fire_hook(self, body: dict): + try: + requests.post(self.target, data=json.dumps(body), headers=self.header) + except Exception as e: + logging.error(e) + + +class ProjectWebHook(WebHook): + project = models.ForeignKey('kaplancloudapp.Project', on_delete=models.CASCADE) + + def fire_hook(self): + return super().fire_hook({'id': self.project.id, 'status': self.project.get_status()}) + + +class ProjectFileWebHook(WebHook): + project_file = models.ForeignKey('kaplancloudapp.ProjectFile', on_delete=models.CASCADE) + + def fire_hook(self): + return super().fire_hook({'id': self.project_file.id, 'status': self.project_file.get_status()}) diff --git a/kaplancloudapi/serializers.py b/kaplancloudapi/serializers.py new file mode 100644 index 0000000..8524e26 --- /dev/null +++ b/kaplancloudapi/serializers.py @@ -0,0 +1,91 @@ +from django.contrib.auth.models import Group, User +from rest_framework import serializers + +from kaplancloudapi.models import ProjectWebHook, ProjectFileWebHook + +from kaplancloudapp.models import ( + Client, LanguageProfile, Project, ProjectFile, + ProjectReferenceFile, TranslationMemory +) + + +class ClientSerializer(serializers.ModelSerializer): + class Meta: + model = Client + fields = ('id', 'name', 'team') + + +class GroupSerializer(serializers.ModelSerializer): + id = serializers.ReadOnlyField() + + class Meta: + model = Group + fields = '__all__' + + +class LanguageProfileSerializer(serializers.ModelSerializer): + is_ltr = serializers.BooleanField(default=True, initial=True) + + class Meta: + model = LanguageProfile + fields = ('id', 'name', 'iso_code', 'is_ltr') + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + + return super().create(validated_data) + + +class ProjectSerializer(serializers.ModelSerializer): + class Meta: + model = Project + fields = ( + 'id', 'uuid', 'name', 'source_language', 'target_language', 'client', + 'managed_by', 'status', 'translationmemories', 'due_by', '_are_all_files_submitted', + ) + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + + return super().create(validated_data) + + +class ProjectWebHookSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectWebHook + fields = '__all__' + + +class ProjectFileSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectFile + exclude = ('bilingual_file', 'source_language', 'target_language') + + +class ProjectFileWebHookSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectFileWebHook + fields = '__all__' + + +class ProjectReferenceFileSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectReferenceFile + fields = ('id', 'uuid', 'name', 'reference_file', 'project') + + +class TranslationMemorySerializer(serializers.ModelSerializer): + class Meta: + model = TranslationMemory + fields = ('id', 'uuid', 'name', 'source_language', 'target_language', 'client') + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + + return super().create(validated_data) + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('id', 'username', 'email', 'groups') diff --git a/kaplancloudapi/signals.py b/kaplancloudapi/signals.py new file mode 100644 index 0000000..fab7f52 --- /dev/null +++ b/kaplancloudapi/signals.py @@ -0,0 +1,15 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from kaplancloudapi.models import ProjectWebHook, ProjectFileWebHook +from kaplancloudapp.models import Project, ProjectFile + +@receiver(post_save, sender=Project) +def on_project_saved(sender, instance, **kwargs): + for project_webhook in ProjectWebHook.objects.filter(project=instance): + project_webhook.fire_hook() + +@receiver(post_save, sender=ProjectFile) +def on_project_file_saved(sender, instance, **kwargs): + for project_file_webhook in ProjectFileWebHook.objects.filter(project_file=instance): + project_file_webhook.fire_hook() \ No newline at end of file diff --git a/kaplancloudapi/tests.py b/kaplancloudapi/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/kaplancloudapi/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/kaplancloudapi/urls.py b/kaplancloudapi/urls.py new file mode 100644 index 0000000..bf8824f --- /dev/null +++ b/kaplancloudapi/urls.py @@ -0,0 +1,20 @@ +from django.urls import include, path +from rest_framework import routers + +from . import views + +router = routers.DefaultRouter() +router.register(r'clients', views.ClientViewSet) +router.register(r'groups', views.GroupViewSet) +router.register(r'language-profiles', views.LanguageProfileViewSet) +router.register(r'projects', views.ProjectViewSet) +router.register(r'project-webhook', views.ProjectWebHookViewSet) +router.register(r'project-files', views.ProjectFileViewSet) +router.register(r'project-file-webhook', views.ProjectFileWebHookViewSet) +router.register(r'project-reference-files', views.ProjectReferenceFileViewSet) +router.register(r'translation-memories', views.TranslationMemoryViewSet) +router.register(r'users', views.UserViewSet) + +urlpatterns = [ + path('', include(router.urls)), +] \ No newline at end of file diff --git a/kaplancloudapi/views.py b/kaplancloudapi/views.py new file mode 100644 index 0000000..38d28e6 --- /dev/null +++ b/kaplancloudapi/views.py @@ -0,0 +1,154 @@ +from django.contrib.auth.models import Group, User +from rest_framework import viewsets +from rest_framework.filters import SearchFilter + +from kaplancloudapp.models import ( + Client, LanguageProfile, Project, ProjectFile, + ProjectReferenceFile, TranslationMemory +) +from kaplancloudapi.models import ProjectFileWebHook, ProjectWebHook +from kaplancloudapi.serializers import ( + ClientSerializer, GroupSerializer, LanguageProfileSerializer, ProjectFileWebHookSerializer, + ProjectSerializer, ProjectFileSerializer, ProjectReferenceFileSerializer, ProjectWebHookSerializer, + TranslationMemorySerializer, UserSerializer +) + + +class ClientViewSet(viewsets.ModelViewSet): + queryset = Client.objects.all() + serializer_class = ClientSerializer + filter_backends = (SearchFilter,) + search_fields = ('name','team_member_username') + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + client_name = self.request.query_params.get('name', None) + + if client_name is not None: + queryset = queryset.filter(name=client_name) + + team_member_username = self.request.query_params.get('team_member_username', None) + + if team_member_username is not None: + queryset = queryset.filter(team=User.objects.get(username=team_member_username)) + + return queryset + + +class GroupViewSet(viewsets.ModelViewSet): + queryset = Group.objects.all() + serializer_class = GroupSerializer + + +class LanguageProfileViewSet(viewsets.ModelViewSet): + queryset = LanguageProfile.objects.all() + serializer_class = LanguageProfileSerializer + + +class ProjectViewSet(viewsets.ModelViewSet): + queryset = Project.objects.all() + serializer_class = ProjectSerializer + filter_backends = (SearchFilter,) + search_fields = ('source_language', 'target_language', 'client_name') + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + client_name = self.request.query_params.get('client_name', None) + + if client_name is not None: + queryset = queryset.filter(client=Client.objects.get(name=client_name)) + + source_language = self.request.query_params.get('source_language', None) + + if source_language is not None: + queryset = queryset.filter(source_language=source_language) + + target_language = self.request.query_params.get('target_language', None) + + if target_language is not None: + queryset = queryset.filter(target_language=target_language) + + return queryset + + +class ProjectWebHookViewSet(viewsets.ModelViewSet): + queryset = ProjectWebHook.objects.all() + serializer_class = ProjectWebHookSerializer + + +class ProjectFileViewSet(viewsets.ModelViewSet): + queryset = ProjectFile.objects.all() + serializer_class = ProjectFileSerializer + filter_backends = (SearchFilter,) + search_fields = ('project_id') + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + project_id = self.request.query_params.get('project_id', None) + + if project_id is not None: + queryset = queryset.filter(project=Project.objects.get(id=project_id)) + + return queryset + + +class ProjectFileWebHookViewSet(viewsets.ModelViewSet): + queryset = ProjectFileWebHook.objects.all() + serializer_class = ProjectFileWebHookSerializer + + +class ProjectReferenceFileViewSet(viewsets.ModelViewSet): + queryset = ProjectReferenceFile.objects.all() + serializer_class = ProjectReferenceFileSerializer + + +class TranslationMemoryViewSet(viewsets.ModelViewSet): + queryset = TranslationMemory.objects.all() + serializer_class = TranslationMemorySerializer + filter_backends = (SearchFilter,) + search_fields = ('source_language', 'target_language', 'client_name') + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + client_name = self.request.query_params.get('client_name', None) + + if client_name is not None: + queryset = queryset.filter(client=Client.objects.get(name=client_name)) + + source_language = self.request.query_params.get('source_language', None) + + if source_language is not None: + queryset = queryset.filter(source_language=source_language) + + target_language = self.request.query_params.get('target_language', None) + + if target_language is not None: + queryset = queryset.filter(target_language=target_language) + + return queryset + + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + filter_backends = (SearchFilter,) + search_fields = ('username','client_name') + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + + client_name = self.request.query_params.get('client_name', None) + + if client_name is not None: + queryset = queryset.filter(id__in=Client.objects.get(name=client_name).team) + + username = self.request.query_params.get('username', None) + + if username is not None: + queryset = queryset.filter(username=username) + + return queryset diff --git a/requirements.txt b/requirements.txt index 2658f56..d862dc9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ Django~=4.2.0 +djangorestframework~=3.14.0 django-storages[boto3,google]>=1.12,<1.13 kaplan>=0.16,<0.17 psycopg2-binary>=2.9,<2.10 From 4ce6324c8cbe9f44052b3acd649c244223a056a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Sun, 25 Jun 2023 18:28:06 +0300 Subject: [PATCH 03/11] :recycle: Set project directory inside model.save --- kaplancloudapp/models.py | 6 ++++++ kaplancloudapp/views.py | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/kaplancloudapp/models.py b/kaplancloudapp/models.py index ca065ab..8055b24 100644 --- a/kaplancloudapp/models.py +++ b/kaplancloudapp/models.py @@ -211,6 +211,12 @@ def get_status(self): def save(self, *args, **kwargs): super().save(*args, **kwargs) + if self.status == 0 and self.directory.strip() == '': + project_directory = Path(settings.PROJECTS_DIR, str(self.uuid)) + self.directory = str(project_directory) + + self.save() + if self.status == 1 and self._are_all_files_submitted: ProjectFileModel = apps.get_model('kaplancloudapp', 'ProjectFile') project_files = ProjectFileModel.objects.filter(project=self) diff --git a/kaplancloudapp/views.py b/kaplancloudapp/views.py index f687c17..395fa86 100644 --- a/kaplancloudapp/views.py +++ b/kaplancloudapp/views.py @@ -53,10 +53,6 @@ def newproject(request): for tm in form.cleaned_data['translation_memories']: new_project.translationmemories.add(tm) - new_project.directory = str(Path(settings.PROJECTS_DIR, - str(new_project.uuid))) - new_project.save() - for file in form.files.getlist('project_files'): new_file = ProjectFile() new_file.name = file.name[3:] From d2488084910af996ebb1739c5a2c65d812a130b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Sun, 25 Jun 2023 18:30:54 +0300 Subject: [PATCH 04/11] :arrow_up: Upgrade Docker python container to 3.11 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7135a5a..2d179ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8 +FROM python:3.11 ENV PYTHONUNBUFFERED=1 From 103ec42cdc5cb47fa082415202e898aecef89891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Sun, 25 Jun 2023 18:49:01 +0300 Subject: [PATCH 05/11] :goal_net: Catch project deletion error Where the directory cannot be removed --- kaplancloudapp/models.py | 44 ++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/kaplancloudapp/models.py b/kaplancloudapp/models.py index 8055b24..b4db1ff 100644 --- a/kaplancloudapp/models.py +++ b/kaplancloudapp/models.py @@ -6,6 +6,7 @@ from kaplan import open_bilingualfile from pathlib import Path +import logging import shutil import tempfile import uuid @@ -160,36 +161,39 @@ def __str__(self): return str(self.id) + '-' + self.name def delete(self, *args, **kwargs): - if settings.DEFAULT_FILE_STORAGE == 'django.core.files.storage.FileSystemStorage': - shutil.rmtree(self.directory) - elif settings.DEFAULT_FILE_STORAGE == 'storages.backends.s3boto3.S3Boto3Storage': - import boto3 + try: + if settings.DEFAULT_FILE_STORAGE == 'django.core.files.storage.FileSystemStorage': + shutil.rmtree(self.directory) + elif settings.DEFAULT_FILE_STORAGE == 'storages.backends.s3boto3.S3Boto3Storage': + import boto3 - session = boto3.Session(aws_access_key_id=settings.AWS_S3_ACCESS_KEY_ID, - aws_secret_access_key=settings.AWS_S3_SECRET_ACCESS_KEY, - region_name=settings.AWS_S3_REGION_NAME) + session = boto3.Session(aws_access_key_id=settings.AWS_S3_ACCESS_KEY_ID, + aws_secret_access_key=settings.AWS_S3_SECRET_ACCESS_KEY, + region_name=settings.AWS_S3_REGION_NAME) - s3 = session.resource('s3', - endpoint_url=settings.AWS_S3_ENDPOINT_URL) + s3 = session.resource('s3', + endpoint_url=settings.AWS_S3_ENDPOINT_URL) - bucket = s3.Bucket(settings.S3_PRIVATE_BUCKET_NAME) + bucket = s3.Bucket(settings.S3_PRIVATE_BUCKET_NAME) - response = bucket.objects.filter(Prefix=str(Path(settings.S3_PRIVATE_BUCKET_LOCATION, self.directory))).delete() + bucket.objects.filter(Prefix=str(Path(settings.S3_PRIVATE_BUCKET_LOCATION, self.directory))).delete() - elif settings.DEFAULT_FILE_STORAGE == 'storages.backends.gcloud.GoogleCloudStorage': - from google.cloud import storage + elif settings.DEFAULT_FILE_STORAGE == 'storages.backends.gcloud.GoogleCloudStorage': + from google.cloud import storage - client = storage.Client() + client = storage.Client() - bucket = storage.Bucket(client, settings.GS_PRIVATE_BUCKET_NAME) + bucket = storage.Bucket(client, settings.GS_PRIVATE_BUCKET_NAME) - blobs = client.list_blobs(bucket, - prefix=str(Path(settings.GS_PRIVATE_BUCKET_LOCATION, self.directory)) - ) + blobs = client.list_blobs(bucket, + prefix=str(Path(settings.GS_PRIVATE_BUCKET_LOCATION, self.directory)) + ) - bucket.delete_blobs(list(blobs), client=client) + bucket.delete_blobs(list(blobs), client=client) - super().delete(*args, **kwargs) + super().delete(*args, **kwargs) + except Exception as e: + logging.error(e) def get_absolute_url(self): from django.urls import reverse From 6edcbfcdf96c9422e26b97b5d465d934b17afd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Mon, 26 Jun 2023 23:56:25 +0300 Subject: [PATCH 06/11] :sparkles: Expand UserSerializer --- kaplancloudapi/serializers.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/kaplancloudapi/serializers.py b/kaplancloudapi/serializers.py index 8524e26..1eba188 100644 --- a/kaplancloudapi/serializers.py +++ b/kaplancloudapi/serializers.py @@ -1,4 +1,6 @@ from django.contrib.auth.models import Group, User +from django.contrib.auth.hashers import make_password + from rest_framework import serializers from kaplancloudapi.models import ProjectWebHook, ProjectFileWebHook @@ -86,6 +88,14 @@ def create(self, validated_data): class UserSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True, required=False, style={'input_type': 'password'}) + is_active = serializers.BooleanField(initial=True) + class Meta: model = User - fields = ('id', 'username', 'email', 'groups') + fields = ('id', 'username', 'password', 'email', 'is_active', 'groups') + + def create(self, validated_data): + validated_data['password'] = make_password(validated_data.get('password')) + + return super().create(validated_data) From 1fc0766b8f1e388ffc634785a068d1ed1701f8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Mon, 26 Jun 2023 23:59:26 +0300 Subject: [PATCH 07/11] :bug: Fix file storage setting reference --- kaplancloudapp/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kaplancloudapp/models.py b/kaplancloudapp/models.py index b4db1ff..973d65d 100644 --- a/kaplancloudapp/models.py +++ b/kaplancloudapp/models.py @@ -162,9 +162,9 @@ def __str__(self): def delete(self, *args, **kwargs): try: - if settings.DEFAULT_FILE_STORAGE == 'django.core.files.storage.FileSystemStorage': + if settings.STORAGES['default']['BACKEND'] == 'django.core.files.storage.FileSystemStorage': shutil.rmtree(self.directory) - elif settings.DEFAULT_FILE_STORAGE == 'storages.backends.s3boto3.S3Boto3Storage': + elif settings.STORAGES['default']['BACKEND'] == 'storages.backends.s3boto3.S3Boto3Storage': import boto3 session = boto3.Session(aws_access_key_id=settings.AWS_S3_ACCESS_KEY_ID, @@ -178,7 +178,7 @@ def delete(self, *args, **kwargs): bucket.objects.filter(Prefix=str(Path(settings.S3_PRIVATE_BUCKET_LOCATION, self.directory))).delete() - elif settings.DEFAULT_FILE_STORAGE == 'storages.backends.gcloud.GoogleCloudStorage': + elif settings.STORAGES['default']['BACKEND'] == 'storages.backends.gcloud.GoogleCloudStorage': from google.cloud import storage client = storage.Client() From ed979552016d662cd3a4ef7f6dddf2a5bb81c7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Tue, 27 Jun 2023 00:15:30 +0300 Subject: [PATCH 08/11] Set initial project and project file status --- kaplancloudapi/serializers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kaplancloudapi/serializers.py b/kaplancloudapi/serializers.py index 1eba188..111fbb9 100644 --- a/kaplancloudapi/serializers.py +++ b/kaplancloudapi/serializers.py @@ -7,7 +7,7 @@ from kaplancloudapp.models import ( Client, LanguageProfile, Project, ProjectFile, - ProjectReferenceFile, TranslationMemory + ProjectReferenceFile, TranslationMemory, file_statuses, project_statuses ) @@ -39,6 +39,8 @@ def create(self, validated_data): class ProjectSerializer(serializers.ModelSerializer): + status = serializers.ChoiceField(choices=project_statuses, initial=0) + class Meta: model = Project fields = ( @@ -59,6 +61,8 @@ class Meta: class ProjectFileSerializer(serializers.ModelSerializer): + status = serializers.ChoiceField(choices=file_statuses, initial=0) + class Meta: model = ProjectFile exclude = ('bilingual_file', 'source_language', 'target_language') From f866b913c1a3340648f0bbc53e98cf79b03ffce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Tue, 27 Jun 2023 11:14:27 +0300 Subject: [PATCH 09/11] Set default status for project and project file --- kaplancloudapi/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kaplancloudapi/serializers.py b/kaplancloudapi/serializers.py index 111fbb9..e1495aa 100644 --- a/kaplancloudapi/serializers.py +++ b/kaplancloudapi/serializers.py @@ -39,7 +39,7 @@ def create(self, validated_data): class ProjectSerializer(serializers.ModelSerializer): - status = serializers.ChoiceField(choices=project_statuses, initial=0) + status = serializers.ChoiceField(choices=project_statuses, default=0, initial=0) class Meta: model = Project @@ -61,7 +61,7 @@ class Meta: class ProjectFileSerializer(serializers.ModelSerializer): - status = serializers.ChoiceField(choices=file_statuses, initial=0) + status = serializers.ChoiceField(choices=file_statuses, default=0, initial=0) class Meta: model = ProjectFile From 499fb3aab585b2a56888cb271c089154da8a8551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Tue, 27 Jun 2023 11:14:36 +0300 Subject: [PATCH 10/11] Update urls --- kaplancloudapi/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kaplancloudapi/urls.py b/kaplancloudapi/urls.py index bf8824f..b8c08d4 100644 --- a/kaplancloudapi/urls.py +++ b/kaplancloudapi/urls.py @@ -8,9 +8,9 @@ router.register(r'groups', views.GroupViewSet) router.register(r'language-profiles', views.LanguageProfileViewSet) router.register(r'projects', views.ProjectViewSet) -router.register(r'project-webhook', views.ProjectWebHookViewSet) +router.register(r'project-webhooks', views.ProjectWebHookViewSet) router.register(r'project-files', views.ProjectFileViewSet) -router.register(r'project-file-webhook', views.ProjectFileWebHookViewSet) +router.register(r'project-file-webhooks', views.ProjectFileWebHookViewSet) router.register(r'project-reference-files', views.ProjectReferenceFileViewSet) router.register(r'translation-memories', views.TranslationMemoryViewSet) router.register(r'users', views.UserViewSet) From 8bf22b42102e2ef1672e79382ed881c9099b7a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fatay=20Onur=20=C5=9Eeng=C3=B6r?= Date: Tue, 27 Jun 2023 11:15:47 +0300 Subject: [PATCH 11/11] :bookmark: version 0.5.0 --- docs/source/conf.py | 2 +- kaplancloud/__init__.py | 2 +- kaplancloudaccounts/templates/accounts/change-password.html | 2 +- kaplancloudaccounts/templates/accounts/login.html | 2 +- kaplancloudaccounts/templates/accounts/register.html | 2 +- kaplancloudapp/templates/index.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 4fb9f20..3d82055 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ author = 'Kaplan' # The full version, including alpha/beta/rc tags -release = '0.4.0' +release = '0.5.0' # -- General configuration --------------------------------------------------- diff --git a/kaplancloud/__init__.py b/kaplancloud/__init__.py index abeeedb..2b8877c 100644 --- a/kaplancloud/__init__.py +++ b/kaplancloud/__init__.py @@ -1 +1 @@ -__version__ = '0.4.0' +__version__ = '0.5.0' diff --git a/kaplancloudaccounts/templates/accounts/change-password.html b/kaplancloudaccounts/templates/accounts/change-password.html index fa1fd45..33d36df 100644 --- a/kaplancloudaccounts/templates/accounts/change-password.html +++ b/kaplancloudaccounts/templates/accounts/change-password.html @@ -23,7 +23,7 @@
-

v0.4.0

+

v0.5.0

{% endblock %} diff --git a/kaplancloudaccounts/templates/accounts/login.html b/kaplancloudaccounts/templates/accounts/login.html index c78e2f5..67c24d6 100644 --- a/kaplancloudaccounts/templates/accounts/login.html +++ b/kaplancloudaccounts/templates/accounts/login.html @@ -40,7 +40,7 @@
-

v0.4.0

+

v0.5.0

{% endblock %} diff --git a/kaplancloudaccounts/templates/accounts/register.html b/kaplancloudaccounts/templates/accounts/register.html index 5a73246..17622eb 100644 --- a/kaplancloudaccounts/templates/accounts/register.html +++ b/kaplancloudaccounts/templates/accounts/register.html @@ -23,7 +23,7 @@
-

v0.4.0

+

v0.5.0

{% endblock %} diff --git a/kaplancloudapp/templates/index.html b/kaplancloudapp/templates/index.html index 57cadb4..7855c51 100644 --- a/kaplancloudapp/templates/index.html +++ b/kaplancloudapp/templates/index.html @@ -13,7 +13,7 @@ Clients--->
-

v0.4.0

+

v0.5.0

{% block div %}