Project updated when subproject modified (#3649)

* Test for changing subproject privacy level

* Proper test for privacy level on subprojects

* Trigger re-symlink for superproject when project changes

Re-symlink when:

* a subproject is deleted
* a subproject privacy level is changed
* a subproject version privacy level is changed

* Update test case for current implementation

* Revert "Trigger re-symlink for superproject when project changes"

This reverts commit 3fe6cb3f3dfddc87d8c1e658cb7f3ebad4f6f476.

* Move logic from Form to Model

Instead of trigger the re-symlink task on each of the Form actions, we
trigger it once on ``Project.save()`` or ``Project.delete()`` method.

* Test for calls to broadcast utility on Project.save()
humitos/django/compatibility
Manuel Kaufmann 2018-03-09 14:42:58 -05:00 committed by Anthony
parent 1edd47a0cd
commit cc18a75de8
3 changed files with 222 additions and 4 deletions

View File

@ -326,8 +326,26 @@ class Project(models.Model):
log.exception('failed to sync supported versions')
try:
if not first_save:
broadcast(type='app', task=tasks.symlink_project,
args=[self.pk],)
log.info(
'Re-symlinking project and subprojects: project=%s',
self.slug,
)
broadcast(
type='app',
task=tasks.symlink_project,
args=[self.pk],
)
log.info(
'Re-symlinking superprojects: project=%s',
self.slug,
)
for superproject in self.superprojects.all():
broadcast(
type='app',
task=tasks.symlink_project,
args=[superproject.pk],
)
except Exception:
log.exception('failed to symlink project')
try:

View File

@ -5,16 +5,16 @@ from builtins import object
import os
import shutil
import tempfile
import collections
from functools import wraps
import mock
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase, override_settings
from django_dynamic_fixture import get
from readthedocs.builds.models import Version
from readthedocs.projects.models import Project, Domain
from readthedocs.projects.tasks import symlink_project
from readthedocs.core.symlink import PublicSymlink, PrivateSymlink
@ -908,3 +908,202 @@ class TestPublicSymlinkUnicode(TempSiterootCase, TestCase):
self.symlink.run()
except:
self.fail('Symlink run raised an exception on unicode slug')
def test_symlink_broadcast_calls_on_project_save(self):
"""
Test calls to ``readthedocs.core.utils.broadcast`` on Project.save().
When a Project is saved, we need to check that we are calling
``broadcast`` utility with the proper task and arguments to re-symlink
them.
"""
with mock.patch('readthedocs.projects.models.broadcast') as broadcast:
project = get(Project)
# skipped on first save
broadcast.assert_not_called()
broadcast.reset_mock()
project.description = 'New description'
project.save()
# called once for this project itself
broadcast.assert_any_calls(
type='app',
task=symlink_project,
args=[project.pk],
)
broadcast.reset_mock()
subproject = get(Project)
# skipped on first save
broadcast.assert_not_called()
project.add_subproject(subproject)
# subproject.save() is not called
broadcast.assert_not_called()
subproject.description = 'New subproject description'
subproject.save()
# subproject symlinks
broadcast.assert_any_calls(
type='app',
task=symlink_project,
args=[subproject.pk],
)
# superproject symlinks
broadcast.assert_any_calls(
type='app',
task=symlink_project,
args=[project.pk],
)
@override_settings()
class TestPublicPrivateSymlink(TempSiterootCase, TestCase):
def setUp(self):
super(TestPublicPrivateSymlink, self).setUp()
from django.contrib.auth.models import User
self.user = get(User)
self.project = get(
Project, name='project', slug='project', privacy_level='public',
users=[self.user], main_language_project=None)
self.project.versions.update(privacy_level='public')
self.project.save()
self.subproject = get(
Project, name='subproject', slug='subproject', privacy_level='public',
users=[self.user], main_language_project=None)
self.subproject.versions.update(privacy_level='public')
self.subproject.save()
def test_change_subproject_privacy(self):
"""
Change subproject's ``privacy_level`` creates proper symlinks.
When the ``privacy_level`` changes in the subprojects, we need to
re-symlink the superproject also to keep in sync its symlink under the
private/public roots.
"""
filesystem_before = {
'private_cname_project': {},
'private_cname_root': {},
'private_web_root': {
'project': {
'en': {},
},
'subproject': {
'en': {},
},
},
'public_cname_project': {},
'public_cname_root': {},
'public_web_root': {
'project': {
'en': {
'latest': {
'type': 'link',
'target': 'user_builds/project/rtd-builds/latest',
},
},
'projects': {
'subproject': {
'type': 'link',
'target': 'public_web_root/subproject',
},
},
},
'subproject': {
'en': {
'latest': {
'type': 'link',
'target': 'user_builds/subproject/rtd-builds/latest',
},
},
},
},
}
filesystem_after = {
'private_cname_project': {},
'private_cname_root': {},
'private_web_root': {
'project': {
'en': {},
'projects': {
'subproject': {
'type': 'link',
'target': 'private_web_root/subproject',
},
},
},
'subproject': {
'en': {
'latest': {
'type': 'link',
'target': 'user_builds/subproject/rtd-builds/latest',
},
},
},
},
'public_cname_project': {},
'public_cname_root': {},
'public_web_root': {
'project': {
'en': {
'latest': {
'type': 'link',
'target': 'user_builds/project/rtd-builds/latest',
},
},
'projects': {},
},
'subproject': {
'en': {},
},
},
}
self.assertEqual(self.project.subprojects.all().count(), 0)
self.assertEqual(self.subproject.superprojects.all().count(), 0)
self.project.add_subproject(self.subproject)
self.assertEqual(self.project.subprojects.all().count(), 1)
self.assertEqual(self.subproject.superprojects.all().count(), 1)
self.assertTrue(self.project.versions.first().active)
self.assertTrue(self.subproject.versions.first().active)
symlink_project(self.project.pk)
self.assertFilesystem(filesystem_before)
self.client.force_login(self.user)
self.client.post(
reverse('project_version_detail',
kwargs={
'project_slug': self.subproject.slug,
'version_slug': self.subproject.versions.first().slug,
}),
data={'privacy_level': 'private', 'active': True},
)
self.assertEqual(self.subproject.versions.first().privacy_level, 'private')
self.assertTrue(self.subproject.versions.first().active)
self.client.post(
reverse('projects_advanced',
kwargs={
'project_slug': self.subproject.slug,
}),
data={
# Required defaults
'python_interpreter': 'python',
'default_version': 'latest',
'privacy_level': 'private',
},
)
self.assertTrue(self.subproject.versions.first().active)
self.subproject.refresh_from_db()
self.assertEqual(self.subproject.privacy_level, 'private')
self.assertFilesystem(filesystem_after)

View File

@ -9,3 +9,4 @@ Mercurial==4.4.2
# local debugging tools
pdbpp
datadiff