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
parent
1edd47a0cd
commit
cc18a75de8
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -9,3 +9,4 @@ Mercurial==4.4.2
|
|||
|
||||
# local debugging tools
|
||||
pdbpp
|
||||
datadiff
|
||||
|
|
Loading…
Reference in New Issue