Skip to content

Allow changing of slug #5154

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions readthedocs/projects/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
)
from readthedocs.redirects.models import Redirect

from .tasks import change_project_slug


class ProjectForm(forms.ModelForm):

Expand Down Expand Up @@ -267,6 +269,7 @@ class Meta:
'name',
'repo',
'repo_type',
'slug',
# Extra
'description',
'documentation_type',
Expand Down Expand Up @@ -306,6 +309,22 @@ def clean_language(self):
)
return language

def clean_slug(self):
new_slug = self.cleaned_data.get('slug')
old_slug = self.instance.slug

if new_slug != old_slug:
if not Project.objects.filter(slug=new_slug).exists():
change_project_slug(
project=self.instance,
new_slug=new_slug
)
else:
raise forms.ValidationError(
_('This slug is currently not available. Please try a different one.')
)
return new_slug


class ProjectRelationshipBaseForm(forms.ModelForm):

Expand Down
20 changes: 20 additions & 0 deletions readthedocs/projects/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1466,3 +1466,23 @@ def retry_domain_verification(domain_pk):
sender=domain.__class__,
domain=domain,
)


def change_project_slug(project, new_slug):
"""
Change the slug of the given project.

:param project: project whose slug is to be changed
:type project: projects.models.Project
:param new_slug: new slug of the project
:param new_slug: str
"""
old_doc_path = project.doc_path
new_doc_path = os.path.join(
os.path.dirname(project.doc_path),
new_slug
)
broadcast(
type='web', task=rename_project_dir,
args=[old_doc_path, new_doc_path]
)
1 change: 1 addition & 0 deletions readthedocs/rtd_tests/tests/test_privacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def _create_kong(
'python_interpreter': 'python',
'description': 'OOHHH AH AH AH KONG SMASH',
'documentation_type': 'sphinx',
'slug': 'django-kong'
},
user=User.objects.get(username='eric'),
)
Expand Down
109 changes: 109 additions & 0 deletions readthedocs/rtd_tests/tests/test_project_forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-

import os
import mock
from django.contrib.auth.models import User
from django.test import TestCase
Expand Down Expand Up @@ -29,6 +30,7 @@
EmailHookForm
)
from readthedocs.projects.models import EnvironmentVariable, Project
from readthedocs.projects.tasks import rename_project_dir
Copy link
Member Author

@dojutsu-user dojutsu-user Jan 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have implemented this task in PR #4995 (code) which is yet to be merged. It is better to merge this PR after #4995 for the reusability of the function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, Travis will fail for now.



class TestProjectForms(TestCase):
Expand Down Expand Up @@ -491,6 +493,7 @@ def test_can_change_language_to_self_lang(self):
'name': self.project_a_es.name,
'documentation_type': 'sphinx',
'language': 'es',
'slug': self.project_a_es.slug,
},
instance=self.project_a_es,
)
Expand All @@ -504,11 +507,117 @@ def test_can_change_language_to_self_lang(self):
'name': self.project_b_en.name,
'documentation_type': 'sphinx',
'language': 'en',
'slug': self.project_b_en.slug,
},
instance=self.project_b_en,
)
self.assertTrue(form.is_valid())

@mock.patch('readthedocs.projects.tasks.broadcast')
def test_can_change_slug(self, mock_broadcast):
self.assertFalse(Project.objects.filter(slug='new-slug').exists())
old_doc_path = self.project_b_en.doc_path
form = UpdateProjectForm(
{
'repo': 'https://github.com/test/test',
'repo_type': self.project_b_en.repo_type,
'name': self.project_b_en.name,
'documentation_type': 'sphinx',
'language': 'es',
'slug': 'new-slug',
},
instance=self.project_b_en,
)
self.assertTrue(form.is_valid())
self.assertEqual(self.project_b_en.slug, 'new-slug')

expected_new_doc_path = os.path.join(
os.path.dirname(self.project_b_en.doc_path),
'new-slug',
)

mock_broadcast.assert_called_once_with(
type='web',
task=rename_project_dir,
args=[old_doc_path, expected_new_doc_path],
)

@mock.patch('readthedocs.projects.tasks.broadcast')
def test_changing_slug_with_already_existed_slug(self, mock_broadcast):
self.assertTrue(Project.objects.filter(slug=self.project_a_es.slug).exists())
form = UpdateProjectForm(
{
'repo': 'https://github.com/test/test',
'repo_type': self.project_b_en.repo_type,
'name': self.project_b_en.name,
'documentation_type': 'sphinx',
'language': 'es',
'slug': self.project_a_es.slug,
},
instance=self.project_b_en,
)
self.assertFalse(form.is_valid())
self.assertDictEqual(
form.errors,
{'slug': ['This slug is currently not available. Please try a different one.']}
)
mock_broadcast.assert_not_called()

@mock.patch('readthedocs.projects.tasks.broadcast')
def test_changing_slug_with_same_slug(self, mock_broadcast):
form = UpdateProjectForm(
{
'repo': 'https://github.com/test/test',
'repo_type': self.project_b_en.repo_type,
'name': self.project_b_en.name,
'documentation_type': 'sphinx',
'language': 'es',
'slug': self.project_b_en.slug,
},
instance=self.project_b_en,
)
self.assertTrue(form.is_valid())
mock_broadcast.assert_not_called()

@mock.patch('readthedocs.projects.tasks.broadcast')
def test_changing_slug_with_wrong_format(self, mock_broadcast):
form = UpdateProjectForm(
{
'repo': 'https://github.com/test/test',
'repo_type': self.project_b_en.repo_type,
'name': self.project_b_en.name,
'documentation_type': 'sphinx',
'language': 'es',
'slug': 'wrong-ooh!!',
},
instance=self.project_b_en,
)
self.assertFalse(form.is_valid())
self.assertDictEqual(
form.errors,
{'slug': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]}
)
mock_broadcast.assert_not_called()

@mock.patch('readthedocs.projects.tasks.broadcast')
def test_no_slug_provided(self, mock_broadcast):
form = UpdateProjectForm(
{
'repo': 'https://github.com/test/test',
'repo_type': self.project_b_en.repo_type,
'name': self.project_b_en.name,
'documentation_type': 'sphinx',
'language': 'es',
},
instance=self.project_b_en,
)
self.assertFalse(form.is_valid())
self.assertDictEqual(
form.errors,
{'slug': ['This field is required.']}
)
mock_broadcast.assert_not_called()


class TestNotificationForm(TestCase):

Expand Down
1 change: 1 addition & 0 deletions readthedocs/rtd_tests/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def test_imported_docs(self):
'python_interpreter': 'python',
'documentation_type': 'sphinx',
'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be',
'slug': 'django-kong',
},
user=user,
)
Expand Down