Skip to content

Commit

Permalink
Add modeling for intersphinx data (#5289)
Browse files Browse the repository at this point in the history
* Remove old test references to intersphinx

* Initial commit of domains app

* Add initial domaindata modeling and API integration

* Add indexing of DomainData to the builds.

* Cleanup of some domain stuff

* Add search to Domains 🎉

* Add initial search implementation to DomainData

* Move project search to a subset of site search

This removes the second entry point to site search, and unifies all our searching to use fancy facets and be nice.

* Small cleanup around faceting

* Limit resuts by DomainData type

* Remove old domains app

* Remove search changes

* Missed a few

* Missed a bit more

* remove random bits that got merged in

* Undo url fix

* Fix linting issues

* Fix tests and linting

* Review feedback

* Fix lint

* Move sphinx to a runtime requirement.

* Use timestamped model for standardized class names

* Address review feedback

* Fix API name

* Fix test too

* Update readthedocs/projects/tasks.py

Co-Authored-By: ericholscher <25510+ericholscher@users.noreply.github.com>
  • Loading branch information
ericholscher committed Feb 27, 2019
1 parent 2c479ec commit 1fd489c
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 1 deletion.
57 changes: 57 additions & 0 deletions readthedocs/projects/tasks.py
Expand Up @@ -24,6 +24,8 @@
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from slumber.exceptions import HttpClientError
from sphinx.ext import intersphinx


from readthedocs.builds.constants import (
BUILD_STATE_BUILDING,
Expand Down Expand Up @@ -58,6 +60,7 @@
)
from readthedocs.doc_builder.loader import get_builder_class
from readthedocs.doc_builder.python_environments import Conda, Virtualenv
from readthedocs.sphinx_domains.models import SphinxDomain
from readthedocs.projects.models import APIProject
from readthedocs.restapi.client import api as api_v2
from readthedocs.vcs_support import utils as vcs_support_utils
Expand Down Expand Up @@ -1232,6 +1235,7 @@ def fileify(version_pk, commit):
),
)
_manage_imported_files(version, path, commit)
_update_intersphinx_data(version, path, commit)
else:
log.info(
LOG_TEMPLATE.format(
Expand All @@ -1242,6 +1246,59 @@ def fileify(version_pk, commit):
)


def _update_intersphinx_data(version, path, commit):
"""
Update intersphinx data for this version
:param version: Version instance
:param path: Path to search
:param commit: Commit that updated path
"""
object_file = os.path.join(path, 'objects.inv')

# These classes are copied from Sphinx
# https://git.io/fhFbI
class MockConfig:
intersphinx_timeout = None
tls_verify = False

class MockApp:
srcdir = ''
config = MockConfig()

def warn(self, msg):
log.warning('Sphinx MockApp: %s', msg)

invdata = intersphinx.fetch_inventory(MockApp(), '', object_file)
for key, value in sorted(invdata.items() or {}):
domain, _type = key.split(':')
for name, einfo in sorted(value.items()):
# project, version, url, display_name
# ('Sphinx', '1.7.9', 'faq.html#epub-faq', 'Epub info')
url = einfo[2]
if '#' in url:
doc_name, anchor = url.split('#')
else:
doc_name, anchor = url, ''
display_name = einfo[3]
obj, _ = SphinxDomain.objects.get_or_create(
project=version.project,
version=version,
domain=domain,
name=name,
display_name=display_name,
type=_type,
doc_name=doc_name,
anchor=anchor,
)
if obj.commit != commit:
obj.commit = commit
obj.save()
SphinxDomain.objects.filter(project=version.project,
version=version
).exclude(commit=commit).delete()


def _manage_imported_files(version, path, commit):
"""
Update imported files for version.
Expand Down
2 changes: 2 additions & 0 deletions readthedocs/restapi/urls.py
Expand Up @@ -25,6 +25,7 @@
SocialAccountViewSet,
VersionViewSet,
)
from readthedocs.sphinx_domains.api import SphinxDomainAPIView


router = routers.DefaultRouter()
Expand All @@ -34,6 +35,7 @@
router.register(r'project', ProjectViewSet, basename='project')
router.register(r'notification', NotificationViewSet, basename='emailhook')
router.register(r'domain', DomainViewSet, basename='domain')
router.register(r'sphinx_domains', SphinxDomainAPIView, base_name='sphinxdomain')
router.register(
r'remote/org',
RemoteOrganizationViewSet,
Expand Down
1 change: 1 addition & 0 deletions readthedocs/rtd_tests/tests/test_privacy_urls.py
Expand Up @@ -364,6 +364,7 @@ def setUp(self):
'api_webhook_gitlab': {'status_code': 405},
'api_webhook_bitbucket': {'status_code': 405},
'api_webhook_generic': {'status_code': 403},
'sphinxdomain-detail': {'status_code': 404},
'remoteorganization-detail': {'status_code': 404},
'remoterepository-detail': {'status_code': 404},
'remoteaccount-detail': {'status_code': 404},
Expand Down
1 change: 1 addition & 0 deletions readthedocs/settings/base.py
Expand Up @@ -103,6 +103,7 @@ def INSTALLED_APPS(self): # noqa
'readthedocs.notifications',
'readthedocs.integrations',
'readthedocs.analytics',
'readthedocs.sphinx_domains',
'readthedocs.search',


Expand Down
Empty file.
13 changes: 13 additions & 0 deletions readthedocs/sphinx_domains/admin.py
@@ -0,0 +1,13 @@
"""Domain Admin classes."""
from django.contrib import admin

from .models import SphinxDomain


class SphinxDomainAdmin(admin.ModelAdmin):
list_filter = ('type', 'project')
raw_id_fields = ('project', 'version')
search_fields = ('doc_name', 'name')


admin.site.register(SphinxDomain, SphinxDomainAdmin)
43 changes: 43 additions & 0 deletions readthedocs/sphinx_domains/api.py
@@ -0,0 +1,43 @@
"""Domain API classes."""

from rest_framework import serializers

from readthedocs.restapi.views.model_views import UserSelectViewSet

from .models import SphinxDomain


class SphinxDomainSerializer(serializers.ModelSerializer):
project = serializers.SlugRelatedField(slug_field='slug', read_only=True)
version = serializers.SlugRelatedField(slug_field='slug', read_only=True)

class Meta:
model = SphinxDomain
fields = (
'project',
'version',
'name',
'display_name',
'role_name',
'docs_url',
)


class SphinxDomainAdminSerializer(SphinxDomainSerializer):

class Meta(SphinxDomainSerializer.Meta):
fields = '__all__'


class SphinxDomainAPIView(UserSelectViewSet): # pylint: disable=too-many-ancestors
model = SphinxDomain
serializer_class = SphinxDomainSerializer
admin_serializer_class = SphinxDomainAdminSerializer
filter_fields = (
'project__slug',
'version__slug',
'domain',
'type',
'doc_name',
'name',
)
84 changes: 84 additions & 0 deletions readthedocs/sphinx_domains/models.py
@@ -0,0 +1,84 @@
"""
Sphinx Domain modeling.
http://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html
"""

from django.db import models
from django.utils.translation import ugettext_lazy as _

from django_extensions.db.models import TimeStampedModel

from readthedocs.builds.models import Version
from readthedocs.core.resolver import resolve
from readthedocs.projects.models import Project
from readthedocs.projects.querysets import RelatedProjectQuerySet


class SphinxDomain(TimeStampedModel):

"""
Information from a project about it's Sphinx domains.
This captures data about API objects that exist in that codebase.
"""

project = models.ForeignKey(
Project,
related_name='sphinx_domains',
)
version = models.ForeignKey(
Version,
verbose_name=_('Version'),
related_name='sphinx_domains',
)
commit = models.CharField(_('Commit'), max_length=255, null=True)

domain = models.CharField(
_('Domain'),
max_length=255,
)
name = models.CharField(
_('Name'),
max_length=255,
)
display_name = models.CharField(
_('Display Name'),
max_length=255,
)
type = models.CharField(
_('Type'),
max_length=255,
)
doc_name = models.CharField(
_('Doc Name'),
max_length=255,
)
anchor = models.CharField(
_('Anchor'),
max_length=255,
)
objects = RelatedProjectQuerySet.as_manager()

def __str__(self):
return f'''
SphinxDomain [{self.project.slug}:{self.version.slug}]
[{self.domain}:{self.type}] {self.name} ->
{self.doc_name}#{self.anchor}
'''

@property
def role_name(self):
return f'{self.domain}:{self.type}'

@property
def docs_url(self):
path = self.doc_name
if self.anchor:
path += f'#{self.anchor}'
full_url = resolve(
project=self.project,
version_slug=self.version.slug,
filename=path,
)
return full_url
1 change: 0 additions & 1 deletion requirements/local-docs-build.txt
Expand Up @@ -2,7 +2,6 @@

# Base packages
docutils==0.14
Sphinx==1.8.4
sphinx_rtd_theme==0.4.3
sphinx-tabs==1.1.10
# Required to avoid Transifex error with reserved slug
Expand Down
3 changes: 3 additions & 0 deletions requirements/pip.txt
Expand Up @@ -10,6 +10,9 @@ django-extensions==2.1.6

djangorestframework==3.9.1

# For intersphinx during builds
Sphinx==1.8.4

# Filtering for the REST API
django-filter==2.1.0

Expand Down

0 comments on commit 1fd489c

Please sign in to comment.