Skip to content

Commit cc18a75

Browse files
humitosagjohnson
authored andcommitted
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 3fe6cb3. * 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()
1 parent 1edd47a commit cc18a75

File tree

3 files changed

+222
-4
lines changed

3 files changed

+222
-4
lines changed

readthedocs/projects/models.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,26 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
326326
log.exception('failed to sync supported versions')
327327
try:
328328
if not first_save:
329-
broadcast(type='app', task=tasks.symlink_project,
330-
args=[self.pk],)
329+
log.info(
330+
'Re-symlinking project and subprojects: project=%s',
331+
self.slug,
332+
)
333+
broadcast(
334+
type='app',
335+
task=tasks.symlink_project,
336+
args=[self.pk],
337+
)
338+
log.info(
339+
'Re-symlinking superprojects: project=%s',
340+
self.slug,
341+
)
342+
for superproject in self.superprojects.all():
343+
broadcast(
344+
type='app',
345+
task=tasks.symlink_project,
346+
args=[superproject.pk],
347+
)
348+
331349
except Exception:
332350
log.exception('failed to symlink project')
333351
try:

readthedocs/rtd_tests/tests/test_project_symlinks.py

+201-2
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
import os
66
import shutil
77
import tempfile
8-
import collections
9-
from functools import wraps
108

119
import mock
1210
from django.conf import settings
11+
from django.core.urlresolvers import reverse
1312
from django.test import TestCase, override_settings
1413
from django_dynamic_fixture import get
1514

1615
from readthedocs.builds.models import Version
1716
from readthedocs.projects.models import Project, Domain
17+
from readthedocs.projects.tasks import symlink_project
1818
from readthedocs.core.symlink import PublicSymlink, PrivateSymlink
1919

2020

@@ -908,3 +908,202 @@ def test_symlink_no_error(self):
908908
self.symlink.run()
909909
except:
910910
self.fail('Symlink run raised an exception on unicode slug')
911+
912+
def test_symlink_broadcast_calls_on_project_save(self):
913+
"""
914+
Test calls to ``readthedocs.core.utils.broadcast`` on Project.save().
915+
916+
When a Project is saved, we need to check that we are calling
917+
``broadcast`` utility with the proper task and arguments to re-symlink
918+
them.
919+
"""
920+
with mock.patch('readthedocs.projects.models.broadcast') as broadcast:
921+
project = get(Project)
922+
# skipped on first save
923+
broadcast.assert_not_called()
924+
925+
broadcast.reset_mock()
926+
project.description = 'New description'
927+
project.save()
928+
# called once for this project itself
929+
broadcast.assert_any_calls(
930+
type='app',
931+
task=symlink_project,
932+
args=[project.pk],
933+
)
934+
935+
broadcast.reset_mock()
936+
subproject = get(Project)
937+
# skipped on first save
938+
broadcast.assert_not_called()
939+
940+
project.add_subproject(subproject)
941+
# subproject.save() is not called
942+
broadcast.assert_not_called()
943+
944+
subproject.description = 'New subproject description'
945+
subproject.save()
946+
# subproject symlinks
947+
broadcast.assert_any_calls(
948+
type='app',
949+
task=symlink_project,
950+
args=[subproject.pk],
951+
)
952+
# superproject symlinks
953+
broadcast.assert_any_calls(
954+
type='app',
955+
task=symlink_project,
956+
args=[project.pk],
957+
)
958+
959+
960+
@override_settings()
961+
class TestPublicPrivateSymlink(TempSiterootCase, TestCase):
962+
963+
def setUp(self):
964+
super(TestPublicPrivateSymlink, self).setUp()
965+
from django.contrib.auth.models import User
966+
967+
self.user = get(User)
968+
self.project = get(
969+
Project, name='project', slug='project', privacy_level='public',
970+
users=[self.user], main_language_project=None)
971+
self.project.versions.update(privacy_level='public')
972+
self.project.save()
973+
974+
self.subproject = get(
975+
Project, name='subproject', slug='subproject', privacy_level='public',
976+
users=[self.user], main_language_project=None)
977+
self.subproject.versions.update(privacy_level='public')
978+
self.subproject.save()
979+
980+
def test_change_subproject_privacy(self):
981+
"""
982+
Change subproject's ``privacy_level`` creates proper symlinks.
983+
984+
When the ``privacy_level`` changes in the subprojects, we need to
985+
re-symlink the superproject also to keep in sync its symlink under the
986+
private/public roots.
987+
"""
988+
filesystem_before = {
989+
'private_cname_project': {},
990+
'private_cname_root': {},
991+
'private_web_root': {
992+
'project': {
993+
'en': {},
994+
},
995+
'subproject': {
996+
'en': {},
997+
},
998+
},
999+
'public_cname_project': {},
1000+
'public_cname_root': {},
1001+
'public_web_root': {
1002+
'project': {
1003+
'en': {
1004+
'latest': {
1005+
'type': 'link',
1006+
'target': 'user_builds/project/rtd-builds/latest',
1007+
},
1008+
},
1009+
'projects': {
1010+
'subproject': {
1011+
'type': 'link',
1012+
'target': 'public_web_root/subproject',
1013+
},
1014+
},
1015+
},
1016+
'subproject': {
1017+
'en': {
1018+
'latest': {
1019+
'type': 'link',
1020+
'target': 'user_builds/subproject/rtd-builds/latest',
1021+
},
1022+
},
1023+
},
1024+
},
1025+
}
1026+
1027+
filesystem_after = {
1028+
'private_cname_project': {},
1029+
'private_cname_root': {},
1030+
'private_web_root': {
1031+
'project': {
1032+
'en': {},
1033+
'projects': {
1034+
'subproject': {
1035+
'type': 'link',
1036+
'target': 'private_web_root/subproject',
1037+
},
1038+
},
1039+
},
1040+
'subproject': {
1041+
'en': {
1042+
'latest': {
1043+
'type': 'link',
1044+
'target': 'user_builds/subproject/rtd-builds/latest',
1045+
},
1046+
},
1047+
},
1048+
},
1049+
'public_cname_project': {},
1050+
'public_cname_root': {},
1051+
'public_web_root': {
1052+
'project': {
1053+
'en': {
1054+
'latest': {
1055+
'type': 'link',
1056+
'target': 'user_builds/project/rtd-builds/latest',
1057+
},
1058+
},
1059+
'projects': {},
1060+
},
1061+
'subproject': {
1062+
'en': {},
1063+
},
1064+
},
1065+
}
1066+
1067+
self.assertEqual(self.project.subprojects.all().count(), 0)
1068+
self.assertEqual(self.subproject.superprojects.all().count(), 0)
1069+
self.project.add_subproject(self.subproject)
1070+
self.assertEqual(self.project.subprojects.all().count(), 1)
1071+
self.assertEqual(self.subproject.superprojects.all().count(), 1)
1072+
1073+
self.assertTrue(self.project.versions.first().active)
1074+
self.assertTrue(self.subproject.versions.first().active)
1075+
symlink_project(self.project.pk)
1076+
1077+
self.assertFilesystem(filesystem_before)
1078+
1079+
self.client.force_login(self.user)
1080+
self.client.post(
1081+
reverse('project_version_detail',
1082+
kwargs={
1083+
'project_slug': self.subproject.slug,
1084+
'version_slug': self.subproject.versions.first().slug,
1085+
}),
1086+
data={'privacy_level': 'private', 'active': True},
1087+
)
1088+
1089+
self.assertEqual(self.subproject.versions.first().privacy_level, 'private')
1090+
self.assertTrue(self.subproject.versions.first().active)
1091+
1092+
self.client.post(
1093+
reverse('projects_advanced',
1094+
kwargs={
1095+
'project_slug': self.subproject.slug,
1096+
}),
1097+
data={
1098+
# Required defaults
1099+
'python_interpreter': 'python',
1100+
'default_version': 'latest',
1101+
1102+
'privacy_level': 'private',
1103+
},
1104+
)
1105+
1106+
self.assertTrue(self.subproject.versions.first().active)
1107+
self.subproject.refresh_from_db()
1108+
self.assertEqual(self.subproject.privacy_level, 'private')
1109+
self.assertFilesystem(filesystem_after)

requirements/testing.txt

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Mercurial==4.4.2
99

1010
# local debugging tools
1111
pdbpp
12+
datadiff

0 commit comments

Comments
 (0)