diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100755 new mode 100644 diff --git a/api/migrations/0066_photo_moved_to_trash_on.py b/api/migrations/0066_photo_moved_to_trash_on.py new file mode 100644 index 0000000000..df6ffc0216 --- /dev/null +++ b/api/migrations/0066_photo_moved_to_trash_on.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2024-04-24 21:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0065_apply_default_photo_count"), + ] + + operations = [ + migrations.AddField( + model_name="photo", + name="moved_to_trash_on", + field=models.DateTimeField(db_index=True, default=None), + preserve_default=False, + ), + ] diff --git a/api/models/photo.py b/api/models/photo.py index 3414716948..b997d4923b 100644 --- a/api/models/photo.py +++ b/api/models/photo.py @@ -60,6 +60,7 @@ class Photo(models.Model): aspect_ratio = models.FloatField(blank=True, null=True) + moved_to_trash_on = models.DateTimeField(null=False, blank=False, db_index=True) added_on = models.DateTimeField(null=False, blank=False, db_index=True) exif_gps_lat = models.FloatField(blank=True, null=True) diff --git a/api/tests/test_delete_month.py b/api/tests/test_delete_month.py new file mode 100644 index 0000000000..c420dbe784 --- /dev/null +++ b/api/tests/test_delete_month.py @@ -0,0 +1,42 @@ +from datetime import datetime, timedelta + +from django.test import TestCase +from rest_framework.test import APIClient + +from api.tests.utils import create_test_photos, create_test_user + + +class DeleteMonthOldPhotosTest(TestCase): + def setUp(self): + self.client = APIClient() + self.user1 = create_test_user() + self.client.force_authenticate(user=self.user1) + + def test_delete_month_old_photos(self): + twoMonthOldDate = datetime.now() - timedelta(days=60) + tenDaysAgoDate = datetime.now() - timedelta(days=10) + photos_to_delete = create_test_photos( + number_of_photos=2, + owner=self.user1, + deleted=True, + moved_to_trash_on=twoMonthOldDate, + ) + photos_not_to_delete = create_test_photos( + number_of_photos=2, + owner=self.user1, + deleted=True, + moved_to_trash_on=tenDaysAgoDate, + ) + image_hashes_not_deleted = [p.image_hash for p in photos_not_to_delete] + images_to_be_deleted = [p.image_hash for p in photos_to_delete] + payload = {"image_hashes": image_hashes_not_deleted + images_to_be_deleted} + headers = {"Content-Type": "application/json"} + response = self.client.delete( + "/api/deletephotosaftermonth/", + format="json", + data=payload, + headers=headers, + ) + data = response.json() + self.assertEqual(2, len(data["deleted"])) + self.assertEqual(2, len(data["not_deleted"])) diff --git a/api/views/photos.py b/api/views/photos.py index 7c932f7176..063daa9c1d 100755 --- a/api/views/photos.py +++ b/api/views/photos.py @@ -1,3 +1,5 @@ +from datetime import datetime + from django.db.models import Prefetch, Q from drf_spectacular.utils import OpenApiParameter, OpenApiTypes, extend_schema from rest_framework import filters, status, viewsets @@ -23,6 +25,38 @@ ) +class DeletePhotosOlderThanOneMonth(APIView): + def delete(self, request): + data = dict(request.data) + photos = Photo.objects.in_bulk(data["image_hashes"]) + current_date = datetime.now() + + deleted = [] + not_deleted = [] + for photo in photos.values(): + if ( + photo.owner == request.user + and photo.deleted + and photo.moved_to_trash_on + and photo.moved_to_trash_on.year <= current_date.year + and photo.moved_to_trash_on.month < current_date.month + and photo.moved_to_trash_on.day <= current_date.day + ): + deleted.append(photo.image_hash) + photo.manual_delete() + else: + not_deleted.append(photo.image_hash) + + return Response( + { + "status": True, + "results": deleted, + "not_deleted": not_deleted, + "deleted": deleted, + } + ) + + class RecentlyAddedPhotoListViewSet(ListViewSet): serializer_class = PhotoSummarySerializer pagination_class = HugeResultsSetPagination diff --git a/librephotos/urls.py b/librephotos/urls.py index 0e5d856fc9..cdb4b682e4 100644 --- a/librephotos/urls.py +++ b/librephotos/urls.py @@ -184,6 +184,9 @@ def post(self, request, *args, **kwargs): re_path( r"^api/photosedit/duplicate/delete", photos.DeleteDuplicatePhotos.as_view() ), + re_path( + r"^api/deletephotosaftermonth", photos.DeletePhotosOlderThanOneMonth.as_view() + ), re_path(r"^api/photosedit/setdeleted", photos.SetPhotosDeleted.as_view()), re_path(r"^api/photosedit/favorite", photos.SetPhotosFavorite.as_view()), re_path(r"^api/photosedit/hide", photos.SetPhotosHidden.as_view()),