Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin functions for wiping a version #5140

Merged
merged 11 commits into from
Feb 26, 2019
18 changes: 17 additions & 1 deletion readthedocs/builds/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from readthedocs.builds.models import Build, BuildCommandResult, Version
from readthedocs.core.utils import trigger_build
from readthedocs.core.utils.general import wipe_version_via_slugs


class BuildCommandResultInline(admin.TabularInline):
Expand Down Expand Up @@ -57,7 +58,22 @@ class VersionAdmin(GuardedModelAdmin):
list_filter = ('type', 'privacy_level', 'active', 'built')
search_fields = ('slug', 'project__slug')
raw_id_fields = ('project',)
actions = ['build_version']
actions = ['wipe_selected_versions', 'build_version']

def wipe_selected_versions(self, request, queryset):
"""Wipes the selected versions."""
for version in queryset:
wipe_version_via_slugs(
version_slug=version.slug,
project_slug=version.project.slug
)
self.message_user(
request,
'Wiped {}.'.format(version.slug),
level=messages.SUCCESS
)

wipe_selected_versions.short_description = 'Wipe selected versions'

def build_version(self, request, queryset):
"""Trigger a build for the project version."""
Expand Down
25 changes: 25 additions & 0 deletions readthedocs/core/utils/general.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-

import os

from django.shortcuts import get_object_or_404

from readthedocs.core.utils import broadcast
from readthedocs.projects.tasks import remove_dirs
from readthedocs.builds.models import Version


def wipe_version_via_slugs(version_slug, project_slug):
"""Wipes the given version of a given project."""
version = get_object_or_404(
Version,
slug=version_slug,
project__slug=project_slug,
)
del_dirs = [
os.path.join(version.project.doc_path, 'checkouts', version.slug),
os.path.join(version.project.doc_path, 'envs', version.slug),
os.path.join(version.project.doc_path, 'conda', version.slug),
]
for del_dir in del_dirs:
broadcast(type='build', task=remove_dirs, args=[(del_dir,)])
14 changes: 5 additions & 9 deletions readthedocs/core/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
documentation and header rendering, and server errors.
"""

from __future__ import absolute_import
from __future__ import division
import os
import logging
from urllib.parse import urlparse
Expand All @@ -19,6 +17,7 @@


from readthedocs.builds.models import Version
from readthedocs.core.utils.general import wipe_version_via_slugs
from readthedocs.core.resolver import resolve_path
from readthedocs.core.symlink import PrivateSymlink, PublicSymlink
from readthedocs.core.utils import broadcast
Expand Down Expand Up @@ -89,13 +88,10 @@ def wipe_version(request, project_slug, version_slug):
raise Http404('You must own this project to wipe it.')

if request.method == 'POST':
del_dirs = [
os.path.join(version.project.doc_path, 'checkouts', version.slug),
os.path.join(version.project.doc_path, 'envs', version.slug),
os.path.join(version.project.doc_path, 'conda', version.slug),
]
for del_dir in del_dirs:
broadcast(type='build', task=remove_dirs, args=[(del_dir,)])
wipe_version_via_slugs(
version_slug=version_slug,
project_slug=project_slug
)
return redirect('project_version_list', project_slug)
return render(
request,
Expand Down
61 changes: 61 additions & 0 deletions readthedocs/rtd_tests/tests/test_core_utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# -*- coding: utf-8 -*-
"""Test core util functions."""

import os
import mock

from mock import call
from django.http import Http404
from django.test import TestCase
from django_dynamic_fixture import get

from readthedocs.builds.models import Version
from readthedocs.core.utils.general import wipe_version_via_slugs
from readthedocs.projects.tasks import remove_dirs
from readthedocs.core.utils import slugify, trigger_build
from readthedocs.projects.models import Project

Expand Down Expand Up @@ -153,3 +159,58 @@ def test_slugify(self):
slugify('A title_-_with separated parts', dns_safe=False),
'a-title_-_with-separated-parts',
)

@mock.patch('readthedocs.core.utils.general.broadcast')
def test_wipe_version_via_slug(self, mock_broadcast):
wipe_version_via_slugs(
version_slug=self.version.slug,
project_slug=self.version.project.slug
)
expected_del_dirs = [
os.path.join(self.version.project.doc_path, 'checkouts', self.version.slug),
os.path.join(self.version.project.doc_path, 'envs', self.version.slug),
os.path.join(self.version.project.doc_path, 'conda', self.version.slug),
]

mock_broadcast.assert_has_calls(
[
call(type='build', task=remove_dirs, args=[(expected_del_dirs[0],)]),
call(type='build', task=remove_dirs, args=[(expected_del_dirs[1],)]),
call(type='build', task=remove_dirs, args=[(expected_del_dirs[2],)]),
],
any_order=False
)

@mock.patch('readthedocs.core.utils.general.broadcast')
def test_wipe_version_via_slug_wrong_param(self, mock_broadcast):
self.assertFalse(Version.objects.filter(slug='wrong-slug').exists())
with self.assertRaises(Http404):
wipe_version_via_slugs(
version_slug='wrong-slug',
project_slug=self.version.project.slug
)
mock_broadcast.assert_not_called()

@mock.patch('readthedocs.core.utils.general.broadcast')
def test_wipe_version_via_slugs_same_version_slug_with_diff_proj(self, mock_broadcast):
project_2 = get(Project)
version_2 = get(Version, project=project_2, slug=self.version.slug)
wipe_version_via_slugs(
version_slug=version_2.slug,
project_slug=project_2.slug,
)

expected_del_dirs = [
os.path.join(version_2.project.doc_path, 'checkouts', version_2.slug),
os.path.join(version_2.project.doc_path, 'envs', version_2.slug),
os.path.join(version_2.project.doc_path, 'conda', version_2.slug),
]

mock_broadcast.assert_has_calls(
[
call(type='build', task=remove_dirs, args=[(expected_del_dirs[0],)]),
call(type='build', task=remove_dirs, args=[(expected_del_dirs[1],)]),
call(type='build', task=remove_dirs, args=[(expected_del_dirs[2],)]),
],
any_order=False
)
Empty file.
60 changes: 60 additions & 0 deletions readthedocs/rtd_tests/tests/versions/test_admin_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-

import os
import mock

from mock import call
import django_dynamic_fixture as fixture
from django.test import TestCase
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.auth.models import User
from django import urls

from readthedocs.builds.models import Version
from readthedocs.core.models import UserProfile
from readthedocs.projects.models import Project
from readthedocs.projects.tasks import remove_dirs


class VersionAdminActionsTest(TestCase):

@classmethod
def setUpTestData(cls):
cls.owner = fixture.get(User)
cls.profile = fixture.get(UserProfile, user=cls.owner, banned=False)
cls.admin = fixture.get(User, is_staff=True, is_superuser=True)
cls.project = fixture.get(
Project,
main_language_project=None,
users=[cls.owner],
)
cls.version = fixture.get(Version, project=cls.project)

def setUp(self):
self.client.force_login(self.admin)

@mock.patch('readthedocs.core.utils.general.broadcast')
def test_wipe_selected_version(self, mock_broadcast):
action_data = {
ACTION_CHECKBOX_NAME: [self.version.pk],
'action': 'wipe_selected_versions',
'post': 'yes',
}
resp = self.client.post(
urls.reverse('admin:builds_version_changelist'),
action_data
)
expected_del_dirs = [
os.path.join(self.version.project.doc_path, 'checkouts', self.version.slug),
os.path.join(self.version.project.doc_path, 'envs', self.version.slug),
os.path.join(self.version.project.doc_path, 'conda', self.version.slug),
]

mock_broadcast.assert_has_calls(
[
call(type='build', task=remove_dirs, args=[(expected_del_dirs[0],)]),
call(type='build', task=remove_dirs, args=[(expected_del_dirs[1],)]),
call(type='build', task=remove_dirs, args=[(expected_del_dirs[2],)]),
],
any_order=False
)