{"action":"created","comment":{"url":"https://api.github.com/repos/ideascube/ideascube/pulls/comments/98243749","pull_request_review_id":18882098,"id":98243749,"diff_hunk":"@@ -0,0 +1,141 @@\n+import os, stat\n+import pytest\n+\n+from django.core.management import call_command\n+\n+from ideascube.mediacenter.models import Document\n+from .factories import DocumentFactory\n+\n+pytestmark = pytest.mark.django_db\n+\n+DATA_PATH = os.path.join(os.path.dirname(__file__), 'data')\n+\n+\n+def test_clean_leftover_should_not_fail_if_no_media_or_file():\n+    call_command('clean', 'leftover-files')\n+\n+\n+def test_clean_leftover_should_not_remove_files_associated_with_documents(settings):\n+    document = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                               preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))\n+    original_path = document.original.path\n+    preview_path = document.preview.path\n+    assert os.path.exists(original_path)\n+    assert os.path.exists(preview_path)\n+    call_command('clean', 'leftover-files')\n+    assert os.path.exists(original_path)\n+    assert os.path.exists(preview_path)\n+\n+\n+def test_clean_leftover_should_not_remove_files_associated_with_documents_without_preview(settings):\n+    document = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                               preview=None)\n+    original_path = document.original.path\n+    assert os.path.exists(original_path)\n+    call_command('clean', 'leftover-files')\n+    assert os.path.exists(original_path)\n+\n+\n+def test_clean_leftover_should_correctly_remove_ophan_files(settings):\n+    document = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                               preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))\n+    original_path = document.original.path\n+    preview_path = document.preview.path\n+    Document.objects.all().delete()\n+    assert os.path.exists(original_path)\n+    assert os.path.exists(preview_path)\n+    call_command('clean', 'leftover-files')\n+    assert not os.path.exists(original_path)\n+    assert not os.path.exists(preview_path)\n+\n+\n+def test_clean_leftover_should_fail_nicely_if_cannot_remove(settings, capsys):\n+    document = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                               preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))\n+    original_path = document.original.path\n+    preview_path = document.preview.path\n+    document.delete()\n+    assert os.path.exists(original_path)\n+    assert os.path.exists(preview_path)\n+    os.chmod(os.path.join(settings.MEDIA_ROOT, 'mediacenter/document'), stat.S_IRUSR | stat.S_IXUSR)\n+    call_command('clean', 'leftover-files')\n+    assert os.path.exists(original_path)\n+    assert not os.path.exists(preview_path)\n+    _, err = capsys.readouterr()\n+    assert \"ERROR while deleting {}\".format(original_path) in err\n+\n+\n+def test_clean_leftover_should_not_remove_ophan_files_if_dry_run(settings, capsys):\n+    document = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                               preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))\n+    original_path = document.original.path\n+    preview_path = document.preview.path\n+    Document.objects.all().delete()\n+    assert os.path.exists(original_path)\n+    assert os.path.exists(preview_path)\n+    call_command('clean', 'leftover-files', dry_run=True)\n+    assert os.path.exists(original_path)\n+    assert os.path.exists(preview_path)\n+    out, _ = capsys.readouterr()\n+    assert original_path in out\n+    assert preview_path in out\n+\n+\n+def test_clean_leftover_should_correctly_clean_after_document_update(settings):\n+    from django.core.files import File\n+    document = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                               preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))\n+    old_original_path = document.original.path\n+    preview_path = document.preview.path\n+    with open(os.path.join(DATA_PATH, 'a-pdf.pdf'), 'rb') as f:\n+        document.original = File(f, name='a-pdf.pdf')\n+        document.save()\n+    new_original_path = document.original.path\n+    assert os.path.exists(old_original_path)\n+    assert os.path.exists(new_original_path)\n+    assert os.path.exists(preview_path)\n+    call_command('clean', 'leftover-files')\n+    assert not os.path.exists(old_original_path)\n+    assert os.path.exists(new_original_path)\n+    assert os.path.exists(preview_path)\n+\n+\n+def test_clean_leftover_should_correctly_clean_after_complex_situation(settings):\n+    from django.core.files import File\n+    document0 = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                                preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))\n+    document1 = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                                preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))\n+    document2 = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                                preview=None)\n+    document3 = DocumentFactory(original__from_path=os.path.join(DATA_PATH, 'a-video.mp4'),\n+                                preview__from_path=os.path.join(DATA_PATH, 'an-image.jpg'))","path":"ideascube/mediacenter/tests/test_clean.py","position":112,"original_position":112,"commit_id":"64ba73de667164508fb4d0e723791f1575b26646","original_commit_id":"64ba73de667164508fb4d0e723791f1575b26646","user":{"login":"mgautierfr","id":86161,"avatar_url":"https://avatars.githubusercontent.com/u/86161?v=3","gravatar_id":"","url":"https://api.github.com/users/mgautierfr","html_url":"https://github.com/mgautierfr","followers_url":"https://api.github.com/users/mgautierfr/followers","following_url":"https://api.github.com/users/mgautierfr/following{/other_user}","gists_url":"https://api.github.com/users/mgautierfr/gists{/gist_id}","starred_url":"https://api.github.com/users/mgautierfr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mgautierfr/subscriptions","organizations_url":"https://api.github.com/users/mgautierfr/orgs","repos_url":"https://api.github.com/users/mgautierfr/repos","events_url":"https://api.github.com/users/mgautierfr/events{/privacy}","received_events_url":"https://api.github.com/users/mgautierfr/received_events","type":"User","site_admin":false},"body":"It's the ORM/forms who create the files in MEDIA_ROOT (and ensure that two documents cannot share the same file even if they share the same **original** path)","created_at":"2017-01-27T16:54:40Z","updated_at":"2017-01-27T16:54:40Z","html_url":"https://github.com/ideascube/ideascube/pull/704#discussion_r98243749","pull_request_url":"https://api.github.com/repos/ideascube/ideascube/pulls/704","_links":{"self":{"href":"https://api.github.com/repos/ideascube/ideascube/pulls/comments/98243749"},"html":{"href":"https://github.com/ideascube/ideascube/pull/704#discussion_r98243749"},"pull_request":{"href":"https://api.github.com/repos/ideascube/ideascube/pulls/704"}}},"pull_request":{"url":"https://api.github.com/repos/ideascube/ideascube/pulls/704","id":103338161,"html_url":"https://github.com/ideascube/ideascube/pull/704","diff_url":"https://github.com/ideascube/ideascube/pull/704.diff","patch_url":"https://github.com/ideascube/ideascube/pull/704.patch","issue_url":"https://api.github.com/repos/ideascube/ideascube/issues/704","number":704,"state":"open","locked":false,"title":"Add a command to remove orphan files from the mediacenter.","user":{"login":"mgautierfr","id":86161,"avatar_url":"https://avatars.githubusercontent.com/u/86161?v=3","gravatar_id":"","url":"https://api.github.com/users/mgautierfr","html_url":"https://github.com/mgautierfr","followers_url":"https://api.github.com/users/mgautierfr/followers","following_url":"https://api.github.com/users/mgautierfr/following{/other_user}","gists_url":"https://api.github.com/users/mgautierfr/gists{/gist_id}","starred_url":"https://api.github.com/users/mgautierfr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mgautierfr/subscriptions","organizations_url":"https://api.github.com/users/mgautierfr/orgs","repos_url":"https://api.github.com/users/mgautierfr/repos","events_url":"https://api.github.com/users/mgautierfr/events{/privacy}","received_events_url":"https://api.github.com/users/mgautierfr/received_events","type":"User","site_admin":false},"body":"This PR add a command to remove orphan files from the mediacenter.\r\n\r\nAfter discussion on PR #686, we agree that a command to remove orphan would be a better idea than\r\nadd a listener on the `post_delete` signal.\r\nWith a command we handle the delete case (#684) and the update case (#457)\r\n\r\nFixes #684 and Fixes #457","created_at":"2017-01-26T16:19:58Z","updated_at":"2017-01-27T16:54:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c37a4ee31794b5e0dca5cab5556774f26cb0408b","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/ideascube/ideascube/pulls/704/commits","review_comments_url":"https://api.github.com/repos/ideascube/ideascube/pulls/704/comments","review_comment_url":"https://api.github.com/repos/ideascube/ideascube/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ideascube/ideascube/issues/704/comments","statuses_url":"https://api.github.com/repos/ideascube/ideascube/statuses/64ba73de667164508fb4d0e723791f1575b26646","head":{"label":"ideascube:remove_orphan_files","ref":"remove_orphan_files","sha":"64ba73de667164508fb4d0e723791f1575b26646","user":{"login":"ideascube","id":9719862,"avatar_url":"https://avatars.githubusercontent.com/u/9719862?v=3","gravatar_id":"","url":"https://api.github.com/users/ideascube","html_url":"https://github.com/ideascube","followers_url":"https://api.github.com/users/ideascube/followers","following_url":"https://api.github.com/users/ideascube/following{/other_user}","gists_url":"https://api.github.com/users/ideascube/gists{/gist_id}","starred_url":"https://api.github.com/users/ideascube/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ideascube/subscriptions","organizations_url":"https://api.github.com/users/ideascube/orgs","repos_url":"https://api.github.com/users/ideascube/repos","events_url":"https://api.github.com/users/ideascube/events{/privacy}","received_events_url":"https://api.github.com/users/ideascube/received_events","type":"Organization","site_admin":false},"repo":{"id":26708471,"name":"ideascube","full_name":"ideascube/ideascube","owner":{"login":"ideascube","id":9719862,"avatar_url":"https://avatars.githubusercontent.com/u/9719862?v=3","gravatar_id":"","url":"https://api.github.com/users/ideascube","html_url":"https://github.com/ideascube","followers_url":"https://api.github.com/users/ideascube/followers","following_url":"https://api.github.com/users/ideascube/following{/other_user}","gists_url":"https://api.github.com/users/ideascube/gists{/gist_id}","starred_url":"https://api.github.com/users/ideascube/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ideascube/subscriptions","organizations_url":"https://api.github.com/users/ideascube/orgs","repos_url":"https://api.github.com/users/ideascube/repos","events_url":"https://api.github.com/users/ideascube/events{/privacy}","received_events_url":"https://api.github.com/users/ideascube/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ideascube/ideascube","description":"Ideas Box server core application","fork":false,"url":"https://api.github.com/repos/ideascube/ideascube","forks_url":"https://api.github.com/repos/ideascube/ideascube/forks","keys_url":"https://api.github.com/repos/ideascube/ideascube/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ideascube/ideascube/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ideascube/ideascube/teams","hooks_url":"https://api.github.com/repos/ideascube/ideascube/hooks","issue_events_url":"https://api.github.com/repos/ideascube/ideascube/issues/events{/number}","events_url":"https://api.github.com/repos/ideascube/ideascube/events","assignees_url":"https://api.github.com/repos/ideascube/ideascube/assignees{/user}","branches_url":"https://api.github.com/repos/ideascube/ideascube/branches{/branch}","tags_url":"https://api.github.com/repos/ideascube/ideascube/tags","blobs_url":"https://api.github.com/repos/ideascube/ideascube/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ideascube/ideascube/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ideascube/ideascube/git/refs{/sha}","trees_url":"https://api.github.com/repos/ideascube/ideascube/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ideascube/ideascube/statuses/{sha}","languages_url":"https://api.github.com/repos/ideascube/ideascube/languages","stargazers_url":"https://api.github.com/repos/ideascube/ideascube/stargazers","contributors_url":"https://api.github.com/repos/ideascube/ideascube/contributors","subscribers_url":"https://api.github.com/repos/ideascube/ideascube/subscribers","subscription_url":"https://api.github.com/repos/ideascube/ideascube/subscription","commits_url":"https://api.github.com/repos/ideascube/ideascube/commits{/sha}","git_commits_url":"https://api.github.com/repos/ideascube/ideascube/git/commits{/sha}","comments_url":"https://api.github.com/repos/ideascube/ideascube/comments{/number}","issue_comment_url":"https://api.github.com/repos/ideascube/ideascube/issues/comments{/number}","contents_url":"https://api.github.com/repos/ideascube/ideascube/contents/{+path}","compare_url":"https://api.github.com/repos/ideascube/ideascube/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ideascube/ideascube/merges","archive_url":"https://api.github.com/repos/ideascube/ideascube/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ideascube/ideascube/downloads","issues_url":"https://api.github.com/repos/ideascube/ideascube/issues{/number}","pulls_url":"https://api.github.com/repos/ideascube/ideascube/pulls{/number}","milestones_url":"https://api.github.com/repos/ideascube/ideascube/milestones{/number}","notifications_url":"https://api.github.com/repos/ideascube/ideascube/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ideascube/ideascube/labels{/name}","releases_url":"https://api.github.com/repos/ideascube/ideascube/releases{/id}","deployments_url":"https://api.github.com/repos/ideascube/ideascube/deployments","created_at":"2014-11-16T08:39:32Z","updated_at":"2017-01-13T11:10:10Z","pushed_at":"2017-01-27T16:44:33Z","git_url":"git://github.com/ideascube/ideascube.git","ssh_url":"git@github.com:ideascube/ideascube.git","clone_url":"https://github.com/ideascube/ideascube.git","svn_url":"https://github.com/ideascube/ideascube","homepage":"http://ideascube.readthedocs.org/","size":11181,"stargazers_count":25,"watchers_count":25,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":27,"mirror_url":null,"open_issues_count":181,"forks":27,"open_issues":181,"watchers":25,"default_branch":"master"}},"base":{"label":"ideascube:master","ref":"master","sha":"cc3fa32739e88ea0eefde798011cd0f6317f98f3","user":{"login":"ideascube","id":9719862,"avatar_url":"https://avatars.githubusercontent.com/u/9719862?v=3","gravatar_id":"","url":"https://api.github.com/users/ideascube","html_url":"https://github.com/ideascube","followers_url":"https://api.github.com/users/ideascube/followers","following_url":"https://api.github.com/users/ideascube/following{/other_user}","gists_url":"https://api.github.com/users/ideascube/gists{/gist_id}","starred_url":"https://api.github.com/users/ideascube/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ideascube/subscriptions","organizations_url":"https://api.github.com/users/ideascube/orgs","repos_url":"https://api.github.com/users/ideascube/repos","events_url":"https://api.github.com/users/ideascube/events{/privacy}","received_events_url":"https://api.github.com/users/ideascube/received_events","type":"Organization","site_admin":false},"repo":{"id":26708471,"name":"ideascube","full_name":"ideascube/ideascube","owner":{"login":"ideascube","id":9719862,"avatar_url":"https://avatars.githubusercontent.com/u/9719862?v=3","gravatar_id":"","url":"https://api.github.com/users/ideascube","html_url":"https://github.com/ideascube","followers_url":"https://api.github.com/users/ideascube/followers","following_url":"https://api.github.com/users/ideascube/following{/other_user}","gists_url":"https://api.github.com/users/ideascube/gists{/gist_id}","starred_url":"https://api.github.com/users/ideascube/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ideascube/subscriptions","organizations_url":"https://api.github.com/users/ideascube/orgs","repos_url":"https://api.github.com/users/ideascube/repos","events_url":"https://api.github.com/users/ideascube/events{/privacy}","received_events_url":"https://api.github.com/users/ideascube/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ideascube/ideascube","description":"Ideas Box server core application","fork":false,"url":"https://api.github.com/repos/ideascube/ideascube","forks_url":"https://api.github.com/repos/ideascube/ideascube/forks","keys_url":"https://api.github.com/repos/ideascube/ideascube/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ideascube/ideascube/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ideascube/ideascube/teams","hooks_url":"https://api.github.com/repos/ideascube/ideascube/hooks","issue_events_url":"https://api.github.com/repos/ideascube/ideascube/issues/events{/number}","events_url":"https://api.github.com/repos/ideascube/ideascube/events","assignees_url":"https://api.github.com/repos/ideascube/ideascube/assignees{/user}","branches_url":"https://api.github.com/repos/ideascube/ideascube/branches{/branch}","tags_url":"https://api.github.com/repos/ideascube/ideascube/tags","blobs_url":"https://api.github.com/repos/ideascube/ideascube/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ideascube/ideascube/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ideascube/ideascube/git/refs{/sha}","trees_url":"https://api.github.com/repos/ideascube/ideascube/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ideascube/ideascube/statuses/{sha}","languages_url":"https://api.github.com/repos/ideascube/ideascube/languages","stargazers_url":"https://api.github.com/repos/ideascube/ideascube/stargazers","contributors_url":"https://api.github.com/repos/ideascube/ideascube/contributors","subscribers_url":"https://api.github.com/repos/ideascube/ideascube/subscribers","subscription_url":"https://api.github.com/repos/ideascube/ideascube/subscription","commits_url":"https://api.github.com/repos/ideascube/ideascube/commits{/sha}","git_commits_url":"https://api.github.com/repos/ideascube/ideascube/git/commits{/sha}","comments_url":"https://api.github.com/repos/ideascube/ideascube/comments{/number}","issue_comment_url":"https://api.github.com/repos/ideascube/ideascube/issues/comments{/number}","contents_url":"https://api.github.com/repos/ideascube/ideascube/contents/{+path}","compare_url":"https://api.github.com/repos/ideascube/ideascube/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ideascube/ideascube/merges","archive_url":"https://api.github.com/repos/ideascube/ideascube/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ideascube/ideascube/downloads","issues_url":"https://api.github.com/repos/ideascube/ideascube/issues{/number}","pulls_url":"https://api.github.com/repos/ideascube/ideascube/pulls{/number}","milestones_url":"https://api.github.com/repos/ideascube/ideascube/milestones{/number}","notifications_url":"https://api.github.com/repos/ideascube/ideascube/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ideascube/ideascube/labels{/name}","releases_url":"https://api.github.com/repos/ideascube/ideascube/releases{/id}","deployments_url":"https://api.github.com/repos/ideascube/ideascube/deployments","created_at":"2014-11-16T08:39:32Z","updated_at":"2017-01-13T11:10:10Z","pushed_at":"2017-01-27T16:44:33Z","git_url":"git://github.com/ideascube/ideascube.git","ssh_url":"git@github.com:ideascube/ideascube.git","clone_url":"https://github.com/ideascube/ideascube.git","svn_url":"https://github.com/ideascube/ideascube","homepage":"http://ideascube.readthedocs.org/","size":11181,"stargazers_count":25,"watchers_count":25,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":27,"mirror_url":null,"open_issues_count":181,"forks":27,"open_issues":181,"watchers":25,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ideascube/ideascube/pulls/704"},"html":{"href":"https://github.com/ideascube/ideascube/pull/704"},"issue":{"href":"https://api.github.com/repos/ideascube/ideascube/issues/704"},"comments":{"href":"https://api.github.com/repos/ideascube/ideascube/issues/704/comments"},"review_comments":{"href":"https://api.github.com/repos/ideascube/ideascube/pulls/704/comments"},"review_comment":{"href":"https://api.github.com/repos/ideascube/ideascube/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ideascube/ideascube/pulls/704/commits"},"statuses":{"href":"https://api.github.com/repos/ideascube/ideascube/statuses/64ba73de667164508fb4d0e723791f1575b26646"}}}}
{"actor":{"display_login":"mgautierfr"}}