22
22
from readthedocs .core .resolver import resolver
23
23
from readthedocs .core .unresolver import UnresolverError , unresolver
24
24
from readthedocs .core .utils .extend import SettingsOverrideObject
25
- from readthedocs .projects .models import Feature
25
+ from readthedocs .projects .models import Feature , Project
26
26
27
27
log = structlog .get_logger (__name__ ) # noqa
28
28
@@ -47,10 +47,19 @@ class BaseReadTheDocsConfigJson(CDNCacheTagsMixin, APIView):
47
47
48
48
Attributes:
49
49
50
- url (required): absolute URL from where the request is performed
51
- (e.g. ``window.location.href``)
52
-
53
50
api-version (required): API JSON structure version (e.g. ``0``, ``1``, ``2``).
51
+
52
+ project-slug (required): slug of the project.
53
+ Optional if "url" is sent.
54
+
55
+ version-slug (required): slug of the version.
56
+ Optional if "url" is sent.
57
+
58
+ url (optional): absolute URL from where the request is performed.
59
+ When sending "url" attribute, "project-slug" and "version-slug" are ignored.
60
+ (e.g. ``window.location.href``).
61
+
62
+ client-version (optional): JavaScript client version (e.g. ``0.6.0``).
54
63
"""
55
64
56
65
http_method_names = ["get" ]
@@ -61,61 +70,73 @@ class BaseReadTheDocsConfigJson(CDNCacheTagsMixin, APIView):
61
70
@lru_cache (maxsize = 1 )
62
71
def _resolve_resources (self ):
63
72
url = self .request .GET .get ("url" )
64
- if not url :
65
- # TODO: not sure what to return here when it fails on the `has_permission`
66
- return None , None , None , None
73
+ project_slug = self .request .GET .get ("project-slug" )
74
+ version_slug = self .request .GET .get ("version-slug" )
75
+
76
+ project = None
77
+ version = None
78
+ build = None
79
+ filename = None
67
80
68
81
unresolved_domain = self .request .unresolved_domain
69
82
70
83
# Main project from the domain.
71
84
project = unresolved_domain .project
72
85
73
- try :
74
- unresolved_url = unresolver .unresolve_url (url )
75
- # Project from the URL: if it's a subproject it will differ from
76
- # the main project got from the domain.
77
- project = unresolved_url .project
78
- version = unresolved_url .version
79
- filename = unresolved_url .filename
80
- # This query should use a particular index:
81
- # ``builds_build_version_id_state_date_success_12dfb214_idx``.
82
- # Otherwise, if the index is not used, the query gets too slow.
83
- build = version .builds .filter (
84
- success = True ,
85
- state = BUILD_STATE_FINISHED ,
86
- ).last ()
87
-
88
- except UnresolverError as exc :
89
- # If an exception is raised and there is a ``project`` in the
90
- # exception, it's a partial match. This could be because of an
91
- # invalid URL path, but on a valid project domain. In this case, we
92
- # continue with the ``project``, but without a ``version``.
93
- # Otherwise, we return 404 NOT FOUND.
94
- project = getattr (exc , "project" , None )
95
- if not project :
96
- raise Http404 () from exc
97
-
98
- version = None
99
- filename = None
100
- build = None
86
+ if url :
87
+ try :
88
+ unresolved_url = unresolver .unresolve_url (url )
89
+ # Project from the URL: if it's a subproject it will differ from
90
+ # the main project got from the domain.
91
+ project = unresolved_url .project
92
+ version = unresolved_url .version
93
+ filename = unresolved_url .filename
94
+ # This query should use a particular index:
95
+ # ``builds_build_version_id_state_date_success_12dfb214_idx``.
96
+ # Otherwise, if the index is not used, the query gets too slow.
97
+ build = version .builds .filter (
98
+ success = True ,
99
+ state = BUILD_STATE_FINISHED ,
100
+ ).last ()
101
+
102
+ except UnresolverError as exc :
103
+ # If an exception is raised and there is a ``project`` in the
104
+ # exception, it's a partial match. This could be because of an
105
+ # invalid URL path, but on a valid project domain. In this case, we
106
+ # continue with the ``project``, but without a ``version``.
107
+ # Otherwise, we return 404 NOT FOUND.
108
+ project = getattr (exc , "project" , None )
109
+ if not project :
110
+ raise Http404 () from exc
111
+
112
+ else :
113
+ project = Project .objects .filter (slug = project_slug ).first ()
114
+ version = Version .objects .filter (slug = version_slug , project = project ).first ()
115
+ if version :
116
+ build = version .builds .last ()
101
117
102
118
return project , version , build , filename
103
119
104
120
def _get_project (self ):
105
- project , version , build , filename = self ._resolve_resources ()
121
+ project , _ , _ , _ = self ._resolve_resources ()
106
122
return project
107
123
108
124
def _get_version (self ):
109
- project , version , build , filename = self ._resolve_resources ()
125
+ _ , version , _ , _ = self ._resolve_resources ()
110
126
return version
111
127
112
128
def get (self , request , format = None ):
113
129
url = request .GET .get ("url" )
130
+ project_slug = request .GET .get ("project-slug" )
131
+ version_slug = request .GET .get ("version-slug" )
114
132
if not url :
115
- return JsonResponse (
116
- {"error" : "'url' GET attribute is required" },
117
- status = 400 ,
118
- )
133
+ if not project_slug or not version_slug :
134
+ return JsonResponse (
135
+ {
136
+ "error" : "'project-slug' and 'version-slug' GET attributes are required when not sending 'url'"
137
+ },
138
+ status = 400 ,
139
+ )
119
140
120
141
addons_version = request .GET .get ("api-version" )
121
142
if not addons_version :
@@ -148,6 +169,7 @@ def get(self, request, format=None):
148
169
version ,
149
170
build ,
150
171
filename ,
172
+ url ,
151
173
user = request .user ,
152
174
)
153
175
return JsonResponse (data , json_dumps_params = {"indent" : 4 , "sort_keys" : True })
@@ -199,6 +221,7 @@ def get(
199
221
version = None ,
200
222
build = None ,
201
223
filename = None ,
224
+ url = None ,
202
225
user = None ,
203
226
):
204
227
"""
@@ -208,12 +231,12 @@ def get(
208
231
best JSON structure for that particular version.
209
232
"""
210
233
if addons_version .major == 0 :
211
- return self ._v0 (project , version , build , filename , user )
234
+ return self ._v0 (project , version , build , filename , url , user )
212
235
213
236
if addons_version .major == 1 :
214
- return self ._v1 (project , version , build , filename , user )
237
+ return self ._v1 (project , version , build , filename , url , user )
215
238
216
- def _v0 (self , project , version , build , filename , user ):
239
+ def _v0 (self , project , version , build , filename , url , user ):
217
240
"""
218
241
Initial JSON data structure consumed by the JavaScript client.
219
242
@@ -308,27 +331,6 @@ def _v0(self, project, version, build, filename, user):
308
331
versions_active_built_not_hidden .values_list ("slug" , flat = True )
309
332
),
310
333
},
311
- "doc_diff" : {
312
- "enabled" : Feature .ADDONS_DOC_DIFF_DISABLED not in project_features ,
313
- # "http://test-builds-local.devthedocs.org/en/latest/index.html"
314
- "base_url" : resolver .resolve (
315
- project = project ,
316
- # NOTE: we are using LATEST version to compare against to for now.
317
- # Ideally, this should be configurable by the user.
318
- version_slug = LATEST ,
319
- language = project .language ,
320
- filename = filename ,
321
- )
322
- if filename
323
- else None ,
324
- "root_selector" : "[role=main]" ,
325
- "inject_styles" : True ,
326
- # NOTE: `base_host` and `base_page` are not required, since
327
- # we are constructing the `base_url` in the backend instead
328
- # of the frontend, as the doc-diff extension does.
329
- "base_host" : "" ,
330
- "base_page" : "" ,
331
- },
332
334
"flyout" : {
333
335
"enabled" : Feature .ADDONS_FLYOUT_DISABLED not in project_features ,
334
336
"translations" : [
@@ -347,22 +349,22 @@ def _v0(self, project, version, build, filename, user):
347
349
"versions" : [
348
350
{
349
351
# TODO: name this field "display_name"
350
- "slug" : version .slug ,
352
+ "slug" : version_ .slug ,
351
353
"url" : resolver .resolve (
352
354
project = project ,
353
- version_slug = version .slug ,
354
- external = version .type == EXTERNAL ,
355
+ version_slug = version_ .slug ,
356
+ external = version_ .type == EXTERNAL ,
355
357
),
356
358
}
357
- for version in versions_active_built_not_hidden
359
+ for version_ in versions_active_built_not_hidden
358
360
],
359
361
"downloads" : [
360
362
{
361
363
# TODO: name this field "display_name"
362
364
"name" : name ,
363
- "url" : url ,
365
+ "url" : url_ ,
364
366
}
365
- for name , url in version_downloads
367
+ for name , url_ in version_downloads
366
368
],
367
369
# TODO: find a way to get this data in a reliably way.
368
370
# We don't have a simple way to map a URL to a file in the repository.
@@ -417,6 +419,38 @@ def _v0(self, project, version, build, filename, user):
417
419
},
418
420
}
419
421
422
+ # DocDiff depends on `url=` GET attribute.
423
+ # This attribute allows us to know the exact filename where the request was made.
424
+ # If we don't know the filename, we cannot return the data required by DocDiff to work.
425
+ # In that case, we just don't include the `doc_diff` object in the response.
426
+ if url :
427
+ data ["addons" ].update (
428
+ {
429
+ "doc_diff" : {
430
+ "enabled" : Feature .ADDONS_DOC_DIFF_DISABLED
431
+ not in project_features ,
432
+ # "http://test-builds-local.devthedocs.org/en/latest/index.html"
433
+ "base_url" : resolver .resolve (
434
+ project = project ,
435
+ # NOTE: we are using LATEST version to compare against to for now.
436
+ # Ideally, this should be configurable by the user.
437
+ version_slug = LATEST ,
438
+ language = project .language ,
439
+ filename = filename ,
440
+ )
441
+ if filename
442
+ else None ,
443
+ "root_selector" : "[role=main]" ,
444
+ "inject_styles" : True ,
445
+ # NOTE: `base_host` and `base_page` are not required, since
446
+ # we are constructing the `base_url` in the backend instead
447
+ # of the frontend, as the doc-diff extension does.
448
+ "base_host" : "" ,
449
+ "base_page" : "" ,
450
+ },
451
+ }
452
+ )
453
+
420
454
# Update this data with the one generated at build time by the doctool
421
455
if version and version .build_data :
422
456
data .update (version .build_data )
@@ -450,7 +484,7 @@ def _v0(self, project, version, build, filename, user):
450
484
451
485
return data
452
486
453
- def _v1 (self , project , version , build , filename , user ):
487
+ def _v1 (self , project , version , build , filename , url , user ):
454
488
return {
455
489
"api_version" : "1" ,
456
490
"comment" : "Undefined yet. Use v0 for now" ,
0 commit comments