Skip to content

Commit 0cf8f1a

Browse files
committed
Merge remote-tracking branch 'origin/master' into remove-doctype-from-search
2 parents 4b870f1 + 92563df commit 0cf8f1a

File tree

17 files changed

+228
-45
lines changed

17 files changed

+228
-45
lines changed

docs/conf.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import sys
77

88
import sphinx_rtd_theme
9-
from recommonmark.parser import CommonMarkParser
109

1110
sys.path.insert(0, os.path.abspath('..'))
1211
sys.path.append(os.path.dirname(__file__))
@@ -29,13 +28,11 @@
2928
'doc_extensions',
3029
'sphinx_tabs.tabs',
3130
'sphinx-prompt',
31+
'recommonmark',
3232
]
3333
templates_path = ['_templates']
3434

3535
source_suffix = ['.rst', '.md']
36-
source_parsers = {
37-
'.md': CommonMarkParser,
38-
}
3936

4037
master_doc = 'index'
4138
project = u'Read the Docs'

docs/faq.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ We deploy readthedocs.org from the `rel` branch in our GitHub repository. You ca
233233

234234

235235
How can I avoid search results having a deprecated version of my docs?
236-
---------------------------------------------------------------------
236+
----------------------------------------------------------------------
237237

238238
If readers search something related to your docs in Google, it will probably return the most relevant version of your documentation.
239239
It may happen that this version is already deprecated and you want to stop Google indexing it as a result,

readthedocs/builds/views.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""Views for builds app."""
44

55
import logging
6+
import textwrap
67

78
from django.contrib import messages
89
from django.contrib.auth.decorators import login_required
@@ -15,7 +16,10 @@
1516
from django.urls import reverse
1617
from django.utils.decorators import method_decorator
1718
from django.views.generic import DetailView, ListView
19+
from requests.utils import quote
20+
from urllib.parse import urlparse
1821

22+
from readthedocs.doc_builder.exceptions import BuildEnvironmentError
1923
from readthedocs.builds.models import Build, Version
2024
from readthedocs.core.permissions import AdminPermission
2125
from readthedocs.core.utils import trigger_build
@@ -104,6 +108,49 @@ class BuildDetail(BuildBase, DetailView):
104108
def get_context_data(self, **kwargs):
105109
context = super().get_context_data(**kwargs)
106110
context['project'] = self.project
111+
112+
build = self.get_object()
113+
if build.error != BuildEnvironmentError.GENERIC_WITH_BUILD_ID.format(build_id=build.pk):
114+
# Do not suggest to open an issue if the error is not generic
115+
return context
116+
117+
scheme = (
118+
'https://github.com/rtfd/readthedocs.org/issues/new'
119+
'?title={title}{build_id}'
120+
'&body={body}'
121+
)
122+
123+
# TODO: we could use ``.github/ISSUE_TEMPLATE.md`` here, but we would
124+
# need to add some variables to it which could impact in the UX when
125+
# filling an issue from the web
126+
body = """
127+
## Details:
128+
129+
* Project URL: https://readthedocs.org/projects/{project_slug}/
130+
* Build URL(if applicable): https://readthedocs.org{build_path}
131+
* Read the Docs username(if applicable): {username}
132+
133+
## Expected Result
134+
135+
*A description of what you wanted to happen*
136+
137+
## Actual Result
138+
139+
*A description of what actually happened*""".format(
140+
project_slug=self.project,
141+
build_path=self.request.path,
142+
username=self.request.user,
143+
)
144+
145+
scheme_dict = {
146+
'title': quote('Build error with build id #'),
147+
'build_id': context['build'].id,
148+
'body': quote(textwrap.dedent(body)),
149+
}
150+
151+
issue_url = scheme.format(**scheme_dict)
152+
issue_url = urlparse(issue_url).geturl()
153+
context['issue_url'] = issue_url
107154
return context
108155

109156

readthedocs/core/symlink.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ def symlink_cnames(self, domain=None):
152152
if domain:
153153
domains = [domain]
154154
else:
155-
domains = Domain.objects.filter(project=self.project)
155+
domains = Domain.objects.filter(project=self.project).values_list('domain', flat=True)
156156
for dom in domains:
157157
log_msg = 'Symlinking CNAME: {} -> {}'.format(
158-
dom.domain,
158+
dom,
159159
self.project.slug,
160160
)
161161
log.info(
@@ -167,13 +167,13 @@ def symlink_cnames(self, domain=None):
167167
)
168168

169169
# CNAME to doc root
170-
symlink = os.path.join(self.CNAME_ROOT, dom.domain)
170+
symlink = os.path.join(self.CNAME_ROOT, dom)
171171
self.environment.run('ln', '-nsf', self.project_root, symlink)
172172

173173
# Project symlink
174174
project_cname_symlink = os.path.join(
175175
self.PROJECT_CNAME_ROOT,
176-
dom.domain,
176+
dom,
177177
)
178178
self.environment.run(
179179
'ln',
@@ -183,16 +183,21 @@ def symlink_cnames(self, domain=None):
183183
)
184184

185185
def remove_symlink_cname(self, domain):
186-
"""Remove CNAME symlink."""
187-
log_msg = 'Removing symlink for CNAME {}'.format(domain.domain)
186+
"""
187+
Remove CNAME symlink.
188+
189+
:param domain: domain for which symlink is to be removed
190+
:type domain: str
191+
"""
192+
log_msg = 'Removing symlink for CNAME {}'.format(domain)
188193
log.info(
189194
constants.LOG_TEMPLATE.format(
190195
project=self.project.slug,
191196
version='',
192-
msg=log_msg,
197+
msg=log_msg
193198
),
194199
)
195-
symlink = os.path.join(self.CNAME_ROOT, domain.domain)
200+
symlink = os.path.join(self.CNAME_ROOT, domain)
196201
safe_unlink(symlink)
197202

198203
def symlink_subprojects(self):

readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
1414
#
1515

16-
from __future__ import absolute_import, division, print_function, unicode_literals
1716

1817
import importlib
1918
import sys
@@ -82,9 +81,9 @@ context = {
8281
("{{ slug }}", "{{ url }}"),{% endfor %}
8382
],
8483
'slug': '{{ project.slug }}',
85-
'name': '{{ project.name }}',
86-
'rtd_language': '{{ project.language }}',
87-
'programming_language': '{{ project.programming_language }}',
84+
'name': u'{{ project.name }}',
85+
'rtd_language': u'{{ project.language }}',
86+
'programming_language': u'{{ project.programming_language }}',
8887
'canonical_url': '{{ project.get_canonical_url }}',
8988
'analytics_code': '{{ project.analytics_code }}',
9089
'single_version': {{ project.single_version }},

readthedocs/projects/forms.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,14 @@ def save(self, commit=True):
566566
self.project.webhook_notifications.add(self.webhook)
567567
return self.project
568568

569+
def clean_url(self):
570+
url = self.cleaned_data.get('url')
571+
if not url:
572+
raise forms.ValidationError(
573+
_('This field is required.')
574+
)
575+
return url
576+
569577
class Meta:
570578
model = WebHook
571579
fields = ['url']

readthedocs/projects/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,15 +1199,15 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
11991199
broadcast(
12001200
type='app',
12011201
task=tasks.symlink_domain,
1202-
args=[self.project.pk, self.pk],
1202+
args=[self.project.pk, self.domain],
12031203
)
12041204

12051205
def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
12061206
from readthedocs.projects import tasks
12071207
broadcast(
12081208
type='app',
12091209
task=tasks.symlink_domain,
1210-
args=[self.project.pk, self.pk, True],
1210+
args=[self.project.pk, self.domain, True],
12111211
)
12121212
super().delete(*args, **kwargs)
12131213

readthedocs/projects/notifications.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
"""Project notifications."""
44

5+
from django.urls import reverse
56
from django.http import HttpRequest
7+
from django.utils.translation import ugettext_lazy as _
8+
from messages_extends.constants import ERROR_PERSISTENT
69

7-
from readthedocs.notifications import Notification
10+
from readthedocs.notifications import Notification, SiteNotification
811
from readthedocs.notifications.constants import REQUIREMENT
912

1013

@@ -16,6 +19,20 @@ class ResourceUsageNotification(Notification):
1619
level = REQUIREMENT
1720

1821

22+
class EmailConfirmNotification(SiteNotification):
23+
24+
failure_level = ERROR_PERSISTENT
25+
failure_message = _(
26+
'Your primary email address is not verified. '
27+
'Please <a href="{{account_email_url}}">verify it here</a>.',
28+
)
29+
30+
def get_context_data(self):
31+
context = super(EmailConfirmNotification, self).get_context_data()
32+
context.update({'account_email_url': reverse('account_email')})
33+
return context
34+
35+
1936
class DeprecatedViewNotification(Notification):
2037

2138
"""Notification to alert user of a view that is going away."""

readthedocs/projects/tasks.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,16 @@ def symlink_project(project_pk):
10681068

10691069

10701070
@app.task(queue='web', throws=(BuildEnvironmentWarning,))
1071-
def symlink_domain(project_pk, domain_pk, delete=False):
1071+
def symlink_domain(project_pk, domain, delete=False):
1072+
"""
1073+
Symlink domain.
1074+
1075+
:param project_pk: project's pk
1076+
:type project_pk: int
1077+
:param domain: domain for the symlink
1078+
:type domain: str
1079+
"""
10721080
project = Project.objects.get(pk=project_pk)
1073-
domain = Domain.objects.get(pk=domain_pk)
10741081
for symlink in [PublicSymlink, PrivateSymlink]:
10751082
sym = symlink(project=project)
10761083
if delete:

readthedocs/projects/views/private.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
)
6161
from readthedocs.projects.signals import project_import
6262
from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin
63+
from readthedocs.projects.notifications import EmailConfirmNotification
6364

6465
from ..tasks import retry_domain_verification
6566

@@ -78,9 +79,27 @@ class ProjectDashboard(PrivateViewMixin, ListView):
7879
model = Project
7980
template_name = 'projects/project_dashboard.html'
8081

82+
def validate_primary_email(self, user):
83+
"""
84+
Sends a persistent error notification.
85+
86+
Checks if the user has a primary email or if the primary email
87+
is verified or not. Sends a persistent error notification if
88+
either of the condition is False.
89+
"""
90+
email_qs = user.emailaddress_set.filter(primary=True)
91+
email = email_qs.first()
92+
if not email or not email.verified:
93+
notification = EmailConfirmNotification(user=user, success=False)
94+
notification.send()
95+
8196
def get_queryset(self):
8297
return Project.objects.dashboard(self.request.user)
8398

99+
def get(self, request, *args, **kwargs):
100+
self.validate_primary_email(request.user)
101+
return super(ProjectDashboard, self).get(self, request, *args, **kwargs)
102+
84103
def get_context_data(self, **kwargs):
85104
context = super().get_context_data(**kwargs)
86105

@@ -529,19 +548,18 @@ def project_notifications(request, project_slug):
529548
slug=project_slug,
530549
)
531550

532-
email_form = EmailHookForm(data=request.POST or None, project=project)
533-
webhook_form = WebHookForm(data=request.POST or None, project=project)
551+
email_form = EmailHookForm(data=None, project=project)
552+
webhook_form = WebHookForm(data=None, project=project)
534553

535554
if request.method == 'POST':
536-
if email_form.is_valid():
537-
email_form.save()
538-
if webhook_form.is_valid():
539-
webhook_form.save()
540-
project_dashboard = reverse(
541-
'projects_notifications',
542-
args=[project.slug],
543-
)
544-
return HttpResponseRedirect(project_dashboard)
555+
if 'email' in request.POST.keys():
556+
email_form = EmailHookForm(data=request.POST, project=project)
557+
if email_form.is_valid():
558+
email_form.save()
559+
elif 'url' in request.POST.keys():
560+
webhook_form = WebHookForm(data=request.POST, project=project)
561+
if webhook_form.is_valid():
562+
webhook_form.save()
545563

546564
emails = project.emailhook_notifications.all()
547565
urls = project.webhook_notifications.all()

readthedocs/rtd_tests/files/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
'.md': CommonMarkParser,
1414
}
1515
master_doc = 'index'
16-
project = 'Pip'
16+
project = u'Pip'
1717
copyright = str(datetime.now().year)
1818
version = '0.8.1'
1919
release = '0.8.1'
@@ -23,6 +23,6 @@
2323
html_theme = 'sphinx_rtd_theme'
2424
file_insertion_enabled = False
2525
latex_documents = [
26-
('index', 'pip.tex', 'Pip Documentation',
27-
'', 'manual'),
26+
('index', 'pip.tex', u'Pip Documentation',
27+
u'', 'manual'),
2828
]

readthedocs/rtd_tests/tests/test_notifications.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.test import TestCase
1010
from django.test.utils import override_settings
1111
from messages_extends.models import Message as PersistentMessage
12+
from allauth.account.models import EmailAddress
1213

1314
from readthedocs.builds.models import Build
1415
from readthedocs.notifications import Notification, SiteNotification
@@ -166,6 +167,9 @@ class TestNotification(SiteNotification):
166167
success_level = INFO_NON_PERSISTENT
167168

168169
user = fixture.get(User)
170+
# Setting the primary and verified email address of the user
171+
email = fixture.get(EmailAddress, user=user, primary=True, verified=True)
172+
169173
n = TestNotification(user, True)
170174
backend = SiteBackend(request=None)
171175

0 commit comments

Comments
 (0)