Skip to content

Commit ad4d81e

Browse files
authored
Merge pull request #5601 from ozer550/fix-perseus-storage-calculation
Remove perseus files from storage calculations
2 parents c553c57 + 4e8b3a0 commit ad4d81e

File tree

2 files changed

+169
-20
lines changed

2 files changed

+169
-20
lines changed

contentcuration/contentcuration/models.py

Lines changed: 114 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -353,25 +353,77 @@ def check_feature_flag(self, flag_name):
353353

354354
def check_channel_space(self, channel):
355355
tree_cte = With(self.get_user_active_trees().distinct(), name="trees")
356-
files_cte = With(
357-
tree_cte.join(
358-
self.files.get_queryset(), contentnode__tree_id=tree_cte.col.tree_id
359-
)
360-
.values("checksum")
361-
.distinct(),
362-
name="files",
356+
357+
user_files_cte = With(
358+
self.files.get_queryset().values(
359+
"id",
360+
"checksum",
361+
"contentnode_id",
362+
"file_format_id",
363+
"file_size",
364+
"preset_id",
365+
),
366+
name="user_files",
363367
)
364368

365-
staging_tree_files = (
366-
self.files.filter(contentnode__tree_id=channel.staging_tree.tree_id)
369+
editable_files_qs = (
370+
user_files_cte.queryset()
367371
.with_cte(tree_cte)
368-
.with_cte(files_cte)
369-
.exclude(Exists(files_cte.queryset().filter(checksum=OuterRef("checksum"))))
370-
.values("checksum")
371-
.distinct()
372+
.with_cte(user_files_cte)
373+
.filter(
374+
Exists(
375+
tree_cte.join(
376+
ContentNode.objects.all(),
377+
tree_id=tree_cte.col.tree_id,
378+
)
379+
.with_cte(tree_cte)
380+
.filter(id=OuterRef("contentnode_id"))
381+
)
382+
)
383+
)
384+
385+
existing_checksums_cte = With(
386+
editable_files_qs.values("checksum").distinct(),
387+
name="existing_checksums",
388+
)
389+
390+
staging_files_qs = (
391+
user_files_cte.queryset()
392+
.with_cte(user_files_cte)
393+
.filter(
394+
Exists(
395+
ContentNode.objects.filter(
396+
tree_id=channel.staging_tree.tree_id,
397+
id=OuterRef("contentnode_id"),
398+
)
399+
)
400+
)
401+
)
402+
403+
new_staging_files_qs = (
404+
staging_files_qs.with_cte(tree_cte)
405+
.with_cte(existing_checksums_cte)
406+
.exclude(
407+
Exists(
408+
existing_checksums_cte.queryset().filter(
409+
checksum=OuterRef("checksum"),
410+
)
411+
)
412+
)
413+
)
414+
415+
new_staging_files_qs = self._filter_storage_billable_files(new_staging_files_qs)
416+
417+
unique_staging_ids = (
418+
new_staging_files_qs.order_by("checksum", "id")
419+
.distinct("checksum")
420+
.values("id")
372421
)
373422
staged_size = float(
374-
staging_tree_files.aggregate(used=Sum("file_size"))["used"] or 0
423+
new_staging_files_qs.filter(id__in=Subquery(unique_staging_ids)).aggregate(
424+
used=Sum("file_size")
425+
)["used"]
426+
or 0
375427
)
376428

377429
if self.get_available_space() < staged_size:
@@ -414,13 +466,55 @@ def get_user_active_trees(self):
414466
)
415467

416468
def get_user_active_files(self):
417-
cte = With(self.get_user_active_trees().distinct())
418469

419-
return (
420-
cte.join(self.files.get_queryset(), contentnode__tree_id=cte.col.tree_id)
421-
.with_cte(cte)
422-
.values("checksum")
423-
.distinct()
470+
tree_cte = With(self.get_user_active_trees().distinct(), name="trees")
471+
472+
user_files_cte = With(
473+
self.files.get_queryset().only(
474+
"id",
475+
"checksum",
476+
"contentnode_id",
477+
"file_format_id",
478+
"file_size",
479+
"preset_id",
480+
),
481+
name="user_files",
482+
)
483+
484+
base_files_qs = (
485+
user_files_cte.queryset()
486+
.with_cte(tree_cte)
487+
.with_cte(user_files_cte)
488+
.filter(
489+
Exists(
490+
tree_cte.join(
491+
ContentNode.objects.only("id", "tree_id"),
492+
tree_id=tree_cte.col.tree_id,
493+
)
494+
.with_cte(tree_cte)
495+
.filter(id=OuterRef("contentnode_id"))
496+
)
497+
)
498+
)
499+
500+
base_files_qs = self._filter_storage_billable_files(base_files_qs)
501+
502+
unique_file_ids = (
503+
base_files_qs.order_by("checksum", "id").distinct("checksum").values("id")
504+
)
505+
506+
files_qs = base_files_qs.filter(id__in=Subquery(unique_file_ids))
507+
508+
return files_qs
509+
510+
def _filter_storage_billable_files(self, queryset):
511+
"""
512+
Perseus exports would not be included in storage calculations.
513+
"""
514+
if queryset is None:
515+
return queryset
516+
return queryset.exclude(file_format_id__isnull=True).exclude(
517+
file_format_id=file_formats.PERSEUS
424518
)
425519

426520
def get_space_used(self, active_files=None):

contentcuration/contentcuration/tests/test_files.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.db.models import Exists
1010
from django.db.models import OuterRef
1111
from le_utils.constants import content_kinds
12+
from le_utils.constants import file_formats
1213
from mock import patch
1314

1415
from .base import BaseAPITestCase
@@ -232,3 +233,57 @@ def test_check_staged_space__exists(self):
232233
) as get_available_staged_space:
233234
get_available_staged_space.return_value = 0
234235
self.assertTrue(self.user.check_staged_space(100, f.checksum))
236+
237+
def test_check_channel_space_ignores_perseus_exports(self):
238+
with mock.patch("contentcuration.utils.user.calculate_user_storage"):
239+
self.node_file.file_format_id = file_formats.PERSEUS
240+
self.node_file.file_size = self.user.disk_space + 1
241+
self.node_file.checksum = uuid4().hex
242+
self.node_file.uploaded_by = self.user
243+
self.node_file.save(set_by_file_on_disk=False)
244+
245+
try:
246+
self.user.check_channel_space(self.staged_channel)
247+
except PermissionDenied:
248+
self.fail("Perseus exports should not count against staging space")
249+
250+
251+
class UserStorageUsageTestCase(StudioTestCase):
252+
def setUp(self):
253+
super().setUpBase()
254+
self.contentnode = (
255+
self.channel.main_tree.get_descendants(include_self=True)
256+
.filter(files__isnull=False)
257+
.first()
258+
)
259+
self.assertIsNotNone(self.contentnode)
260+
self.base_file = self.contentnode.files.first()
261+
self.assertIsNotNone(self.base_file)
262+
263+
def _create_file(self, *, file_format, size):
264+
file_record = File(
265+
contentnode=self.contentnode,
266+
checksum=uuid4().hex,
267+
file_format_id=file_format,
268+
file_size=size,
269+
uploaded_by=self.user,
270+
)
271+
file_record.save(set_by_file_on_disk=False)
272+
return file_record
273+
274+
def test_get_space_used_excludes_perseus_exports(self):
275+
baseline_usage = self.user.get_space_used()
276+
277+
perseus_size = 125
278+
with mock.patch("contentcuration.utils.user.calculate_user_storage"):
279+
self._create_file(file_format=file_formats.PERSEUS, size=perseus_size)
280+
self.assertEqual(self.user.get_space_used(), baseline_usage)
281+
282+
non_perseus_size = 275
283+
with mock.patch("contentcuration.utils.user.calculate_user_storage"):
284+
self._create_file(
285+
file_format=self.base_file.file_format_id, size=non_perseus_size
286+
)
287+
288+
expected_usage = baseline_usage + non_perseus_size
289+
self.assertEqual(self.user.get_space_used(), expected_usage)

0 commit comments

Comments
 (0)