diff --git a/lockbox/common/utils.py b/lockbox/common/utils.py index 9fc54e4..ab040d3 100644 --- a/lockbox/common/utils.py +++ b/lockbox/common/utils.py @@ -26,7 +26,7 @@ def cast_to_native_type(key, value, native_type): value = value.split(",") if native_type == bool: - if value == "false": + if value.lower() == "false": return False return True diff --git a/lockbox/lockbox/settings.py b/lockbox/lockbox/settings.py index 461680d..f207626 100644 --- a/lockbox/lockbox/settings.py +++ b/lockbox/lockbox/settings.py @@ -126,8 +126,13 @@ validate_paths(MEDIA_ROOT) DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" AUTH_USER_MODEL = "user.LockboxUser" +REST_FRAMEWORK_RENDER_CLASSES = ["rest_framework.renderers.JSONRenderer",] + +if get_config("ENABLE_BROWSABLE_API"): + REST_FRAMEWORK_RENDER_CLASSES.append("rest_framework.renderers.BrowsableAPIRenderer") REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", - "PAGE_SIZE": 50, + "PAGE_SIZE": 25, + "DEFAULT_RENDERER_CLASSES": tuple(REST_FRAMEWORK_RENDER_CLASSES), } diff --git a/lockbox/lockbox/urls.py b/lockbox/lockbox/urls.py index 83eb463..87e1e19 100644 --- a/lockbox/lockbox/urls.py +++ b/lockbox/lockbox/urls.py @@ -12,6 +12,6 @@ urlpatterns = [ if get_config("ENABLE_BROWSABLE_API"): - urlpatterns.extend(path("api-auth/", include("rest_framework.urls"))) + urlpatterns.append(path("api-auth/", include("rest_framework.urls"))) urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)) diff --git a/lockbox/storage/models.py b/lockbox/storage/models.py index b5e9273..0437557 100644 --- a/lockbox/storage/models.py +++ b/lockbox/storage/models.py @@ -12,6 +12,11 @@ from django.db import models, transaction from django.utils import timezone from django.utils.translation import gettext_lazy as _ +BROKEN_STATUSES = [ + UPLOAD_STATUS_TYPES.ABANDONED, + UPLOAD_STATUS_TYPES.ERROR, +] + class UploadError(Exception): @@ -24,7 +29,21 @@ def _upload_to_fielpath(instance, filename): return Path(str(instance.lid)).joinpath(f"{instance.filename}{settings.INCOMPLETE_EXT}") +class FileQuerySet(models.QuerySet): + '''Regular bulkd delete method but it invokes obj.delete to actually clear the file. + ''' + def delete(self, *args, **kwargs): + keep_file = kwargs.pop("keep_file", False) + for obj in self: + obj.delete(keep_file=keep_file) + + return super().delete(*args, **kwargs) + + class File(LockboxBase): + + objects = FileQuerySet.as_manager() + mime_type = models.CharField( max_length=128, blank=True, @@ -183,6 +202,27 @@ class File(LockboxBase): return False return Path(self.file.path).is_file() + @classmethod + def cleanup(self, dry_run=True, skip=None): + # Probably skip some actual files (or record files) first make dry run report + # Then skip=[file_1, file_2] + # Should cleanup be automatic? probably not + # Find whats broken status + # Find what has a record but does not exist + # Find what does have a record but doesnt exist. + # Cleanup any directories that are not from a record (UUID and no uuid.) + # Cleanup any other files? + pass + # broken = File.objects.filter(status__in=BROKEN_STATUSES) + # strays = Path() + + @classmethod + def reconcile(self, dry_run=True, delete_strays=True): + # finds stuff that has no record and creates one, moves it to the right place. + # Probably good to call another OS location to copy files to. + # would be cool if it hard linked + pass + def append_chunk(self, chunk_file, chunk_data): """Append chunks to a file @@ -203,6 +243,7 @@ class File(LockboxBase): # Oh oh, we are uploading a n + 1 chunk but theres no file if chunk_data["start_bytes"] != 0: self.status = UPLOAD_STATUS_TYPES.ERROR + self.file.storage.delete() self.save() raise UploadError( "File for uploaded chunk no longer exists", @@ -247,6 +288,7 @@ class File(LockboxBase): result = self.verify() if not result: self.status = UPLOAD_STATUS_TYPES.ERROR + self.file.storage.delete() # tentative raise UploadError( "File verification failed", code=UPLOAD_ERROR_CODES.VERIFICATION_FAILED @@ -256,7 +298,7 @@ class File(LockboxBase): with transaction.atomic(): Path(self.file.path).rename(final_path) - self.file.name = self.filename + self.file.name = str(final_path) self.status = UPLOAD_STATUS_TYPES.COMPLETED self.datetime_completed = timezone.now() self.save() @@ -285,11 +327,11 @@ class File(LockboxBase): return super().save(*args, **kwargs) def delete(self, *args, **kwargs): + keep_file = kwargs.pop("keep_file", False) with transaction.atomic(): - if self.file: - if Path(self.file.path).is_file(): + if not keep_file: + if self.file and self.exists: self.file.storage.delete(self.file.path) - # Delete containing directory (UUID) self.file.storage.delete(Path(self.file.path).parent) result = super().delete(*args, **kwargs) return result diff --git a/scripts/drone/lint.sh b/scripts/drone/lint.sh index 66b98a4..a0a32d6 100755 --- a/scripts/drone/lint.sh +++ b/scripts/drone/lint.sh @@ -1,2 +1,3 @@ printf "\n\n|| Starting lint check ||\n\n" -flake8 \ No newline at end of file +flake8 +printf "\n\n|| Finished lint check ||\n\n" \ No newline at end of file diff --git a/scripts/drone/setup.sh b/scripts/drone/setup.sh index 68193d5..d12168b 100755 --- a/scripts/drone/setup.sh +++ b/scripts/drone/setup.sh @@ -1,3 +1,4 @@ cd lockbox printf "\n\n|| Starting setup ||\n\n" -python manage.py migrate \ No newline at end of file +python manage.py migrate +printf "\n\n|| Finished setup ||\n\n" \ No newline at end of file diff --git a/scripts/drone/test.sh b/scripts/drone/test.sh index b36b368..2893f63 100755 --- a/scripts/drone/test.sh +++ b/scripts/drone/test.sh @@ -1,3 +1,4 @@ cd lockbox printf "\n\n|| Starting pytest run ||\n\n" -pytest --cov=. --cov-report term-missing --reuse-db \ No newline at end of file +pytest --cov=. --cov-report term-missing --reuse-db +printf "\n\n|| Finished pytest run ||\n\n" \ No newline at end of file