From 9fd95f2bd4e21cf7a2a5c5b2b392077abdf048f8 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Fri, 10 Aug 2018 15:54:57 -0700 Subject: [PATCH 1/4] Allow modifying version slugs --- readthedocs/builds/forms.py | 21 +++++++++++++++++-- readthedocs/projects/forms.py | 4 +++- readthedocs/projects/views/private.py | 11 ++++++++++ .../projects/project_version_detail.html | 9 +++++++- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index cfd392ea20a..fb18b321591 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -3,8 +3,12 @@ from __future__ import absolute_import from builtins import object from django import forms +from django.core.validators import RegexValidator +from django.utils.translation import ugettext_lazy as _ -from readthedocs.builds.models import VersionAlias, Version +from .constants import STABLE, LATEST +from .models import VersionAlias, Version +from .version_slug import VERSION_SLUG_REGEX from readthedocs.projects.models import Project from readthedocs.core.utils import trigger_build @@ -29,9 +33,22 @@ def __init__(self, instance=None, *args, **kwargs): # noqa class VersionForm(forms.ModelForm): + slug = forms.CharField( + max_length=255, + validators=[RegexValidator('^{pattern}$'.format(pattern=VERSION_SLUG_REGEX))], + help_text=_("Used in this version's URL"), + ) + class Meta(object): model = Version - fields = ['active', 'privacy_level', 'tags'] + fields = ['slug', 'active', 'privacy_level', 'tags'] + + def __init__(self, *args, **kwargs): + super(VersionForm, self).__init__(*args, **kwargs) + if self.instance and self.instance.slug in (LATEST, STABLE): + self.fields['slug'].disabled = True + self.fields['slug'].help_text += ' - it is read only for "{}"'.format( + self.instance.slug) def save(self, commit=True): obj = super(VersionForm, self).save(commit=commit) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 6b3201458f4..7a17c0c8bd3 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -394,7 +394,9 @@ def build_versions_form(project): version.identifier[:8], ) else: - label = version.verbose_name + label = version.slug + if version.slug not in version.identifier: + label += ' ({})'.format(version.identifier) attrs[field_name] = forms.BooleanField( label=label, widget=DualCheckboxWidget(version), diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 30731529d8a..510eea1ff3c 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -22,6 +22,8 @@ from django.views.generic import ListView, TemplateView, View from formtools.wizard.views import SessionWizardView from vanilla import CreateView, DeleteView, DetailView, GenericView, UpdateView + +from readthedocs.builds.constants import STABLE, LATEST from readthedocs.builds.forms import AliasForm, VersionForm from readthedocs.builds.models import Version, VersionAlias from readthedocs.core.mixins import ListViewWithForm, LoginRequiredMixin @@ -154,6 +156,15 @@ def project_version_detail(request, project_slug, version_slug): if request.method == 'POST' and form.is_valid(): version = form.save() if form.has_changed(): + if 'slug' in form.changed_data and version.slug not in (STABLE, LATEST): + # Rebuild symlinks to the new slug, remove the old slug's symlink + # Latest and Stable would appear here because they are disabled on the form + log.info('Rebuilding symlinks for %s. A version slug changed', version.project) + broadcast(type='app', task=tasks.symlink_project, args=[version.project.pk]) + + log.info('Triggering a build of the moved version') + trigger_build(version.project, version) + if 'active' in form.changed_data and version.active is False: log.info('Removing files for version %s', version.slug) broadcast( diff --git a/readthedocs/templates/projects/project_version_detail.html b/readthedocs/templates/projects/project_version_detail.html index 593e4475043..b0435aef662 100644 --- a/readthedocs/templates/projects/project_version_detail.html +++ b/readthedocs/templates/projects/project_version_detail.html @@ -14,9 +14,16 @@ {% block content-header %}

{% blocktrans with version.name as version_name %}{{ version_name }}{% endblocktrans %}

{% endblock %} {% block content %} -

Editing {{ version.slug }}

+

{% blocktrans with version_name=version.verbose_name %}Editing version {{ version_name }}{% endblocktrans %}

+
{% csrf_token %} + +

+ + {{ version.identifier }} +

+ {{ form.as_p }}

From 532de0b83f4bbb3e2010e77abe2e322ea786f1e8 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Mon, 13 Aug 2018 14:52:46 -0700 Subject: [PATCH 2/4] Updates based on feedback - Check for existing versions - Use a RegexField - Double resyncing of symlinks --- readthedocs/builds/forms.py | 21 ++++++++++++++++----- readthedocs/projects/views/private.py | 9 +++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index fb18b321591..b40cd52f364 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from builtins import object from django import forms -from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from .constants import STABLE, LATEST @@ -33,9 +32,9 @@ def __init__(self, instance=None, *args, **kwargs): # noqa class VersionForm(forms.ModelForm): - slug = forms.CharField( + slug = forms.RegexField( + '^{pattern}$'.format(pattern=VERSION_SLUG_REGEX), max_length=255, - validators=[RegexValidator('^{pattern}$'.format(pattern=VERSION_SLUG_REGEX))], help_text=_("Used in this version's URL"), ) @@ -47,8 +46,20 @@ def __init__(self, *args, **kwargs): super(VersionForm, self).__init__(*args, **kwargs) if self.instance and self.instance.slug in (LATEST, STABLE): self.fields['slug'].disabled = True - self.fields['slug'].help_text += ' - it is read only for "{}"'.format( - self.instance.slug) + self.fields['slug'].help_text += _(' - read only for special versions') + + def clean_slug(self): + slug = self.cleaned_data['slug'] + + version = self.instance + if version: + if Version.objects.filter(project=version.project, slug=slug).exclude( + pk=version.pk).count() > 0: + raise forms.ValidationError( + _('There is already a version for this project with that slug'), + ) + + return slug def save(self, commit=True): obj = super(VersionForm, self).save(commit=commit) diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 510eea1ff3c..4f5bb200f81 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -156,12 +156,9 @@ def project_version_detail(request, project_slug, version_slug): if request.method == 'POST' and form.is_valid(): version = form.save() if form.has_changed(): - if 'slug' in form.changed_data and version.slug not in (STABLE, LATEST): - # Rebuild symlinks to the new slug, remove the old slug's symlink - # Latest and Stable would appear here because they are disabled on the form - log.info('Rebuilding symlinks for %s. A version slug changed', version.project) - broadcast(type='app', task=tasks.symlink_project, args=[version.project.pk]) - + if (version.active and 'slug' in form.changed_data and + version.slug not in (STABLE, LATEST)): + # Latest and Stable appear "changed" because they are disabled on the form log.info('Triggering a build of the moved version') trigger_build(version.project, version) From b260814fd177ee8646a7e2014315c17274f0e9f4 Mon Sep 17 00:00:00 2001 From: David Fischer Date: Tue, 14 Aug 2018 13:27:26 -0700 Subject: [PATCH 3/4] Get the PR merge ready --- readthedocs/builds/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py index b40cd52f364..789214d0bd7 100644 --- a/readthedocs/builds/forms.py +++ b/readthedocs/builds/forms.py @@ -3,7 +3,7 @@ from __future__ import absolute_import from builtins import object from django import forms -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, ugettext from .constants import STABLE, LATEST from .models import VersionAlias, Version @@ -46,7 +46,7 @@ def __init__(self, *args, **kwargs): super(VersionForm, self).__init__(*args, **kwargs) if self.instance and self.instance.slug in (LATEST, STABLE): self.fields['slug'].disabled = True - self.fields['slug'].help_text += _(' - read only for special versions') + self.fields['slug'].help_text += ugettext(' - read only for special versions') def clean_slug(self): slug = self.cleaned_data['slug'] @@ -54,7 +54,7 @@ def clean_slug(self): version = self.instance if version: if Version.objects.filter(project=version.project, slug=slug).exclude( - pk=version.pk).count() > 0: + pk=version.pk).exists(): raise forms.ValidationError( _('There is already a version for this project with that slug'), ) From a010105edeae8ad6ef57f819ddadfc272c42733b Mon Sep 17 00:00:00 2001 From: David Fischer Date: Wed, 15 Aug 2018 10:20:55 -0700 Subject: [PATCH 4/4] Updates based on feedback - good catches! --- readthedocs/templates/projects/project_version_detail.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/templates/projects/project_version_detail.html b/readthedocs/templates/projects/project_version_detail.html index b0435aef662..a9f28d04aa4 100644 --- a/readthedocs/templates/projects/project_version_detail.html +++ b/readthedocs/templates/projects/project_version_detail.html @@ -20,9 +20,9 @@

{% blocktrans with version_name=version.verbose_name %}Editing version {{ ve {% csrf_token %}

- - {{ version.identifier }} -

+ + {{ version.identifier }} +

{{ form.as_p }}