def api_versions(self): from readthedocs.builds.models import Version ret = [] for version_data in api.version.get(project=self.pk, active=True)['objects']: version = make_api_version(version_data) ret.append(version) return sort_version_aware(ret)
def ordered_active_versions(self, **kwargs): """ Get all active versions, sorted. :param kwargs: All kwargs are passed down to the `Version.internal.public` queryset. """ from readthedocs.builds.models import Version kwargs.update({ 'project': self, 'only_active': True, }, ) versions = (Version.internal.public(**kwargs).select_related( 'project', 'project__main_language_project', ).prefetch_related( Prefetch( 'project__superprojects', ProjectRelationship.objects.all().select_related('parent'), to_attr='_superprojects', ), Prefetch( 'project__domains', Domain.objects.filter(canonical=True), to_attr='_canonical_domains', ), )) return sort_version_aware(versions)
def test_sort_git_master_and_latest(self): """ The branch named master should havea a higher priority than latest, ideally users should only have one of the two activated. """ identifiers = ['latest', 'master', '1.0', '2.0', '1.1', '1.9', '1.10'] self.project.repo_type = REPO_TYPE_GIT self.project.save() self.project.versions.get(slug=LATEST).delete() for identifier in identifiers: get( Version, project=self.project, type=BRANCH, identifier=identifier, verbose_name=identifier, slug=identifier, ) versions = list(Version.objects.filter(project=self.project)) self.assertEqual( ['master', 'latest', '2.0', '1.10', '1.9', '1.1', '1.0'], [v.slug for v in sort_version_aware(versions)], )
def api_versions(self): from readthedocs.builds.models import APIVersion ret = [] for version_data in api.project(self.pk).active_versions.get()['versions']: version = APIVersion(**version_data) ret.append(version) return sort_version_aware(ret)
def api_versions(self): ret = [] for version_data in api.version.get(project=self.pk, active=True)['objects']: version = make_api_version(version_data) ret.append(version) return sort_version_aware(ret)
def test_sort_bzr_latest(self): """ BZR doesn't have a name for "master", so here master gets sorted by its ascii value. """ identifiers = ['master', '1.0', '2.0', '1.1', '1.9', '1.10'] self.project.repo_type = REPO_TYPE_BZR self.project.save() self.project.versions.get(slug=LATEST).delete() for identifier in identifiers: get( Version, project=self.project, type=BRANCH, identifier=identifier, verbose_name=identifier, slug=identifier, ) versions = list(Version.objects.filter(project=self.project)) self.assertEqual( ['2.0', '1.10', '1.9', '1.1', '1.0', 'master'], [v.slug for v in sort_version_aware(versions)], )
def ordered_active_versions(self, user=None): from readthedocs.builds.models import Version kwargs = {"project": self, "only_active": True} if user: kwargs["user"] = user versions = Version.objects.public(**kwargs) return sort_version_aware(versions)
def ordered_active_versions(self, user=None): from readthedocs.builds.models import Version kwargs = { 'project': self, 'only_active': True, } if user: kwargs['user'] = user versions = Version.objects.public(**kwargs) return sort_version_aware(versions)
def build_versions_form(project): """Versions form with a list of versions and version privacy levels.""" attrs = { 'project': project, } versions_qs = project.versions.all() # Admin page, so show all versions active = versions_qs.filter(active=True) if active.exists(): active = sort_version_aware(active) choices = [(version.slug, version.verbose_name) for version in active] attrs['default-version'] = forms.ChoiceField( label=_('Default Version'), choices=choices, initial=project.get_default_version(), ) versions_qs = sort_version_aware(versions_qs) for version in versions_qs: field_name = 'version-{}'.format(version.slug) privacy_name = 'privacy-{}'.format(version.slug) if version.type == TAG: label = '{} ({})'.format( version.verbose_name, version.identifier[:8], ) else: label = version.verbose_name attrs[field_name] = forms.BooleanField( label=label, widget=DualCheckboxWidget(version), initial=version.active, required=False, ) attrs[privacy_name] = forms.ChoiceField( # This isn't a real label, but just a slug for the template label='privacy', choices=constants.PRIVACY_CHOICES, initial=version.privacy_level, ) return type(str('VersionsForm'), (BaseVersionsForm, ), attrs)
def get_all_active_versions(self): """ Returns all active versions. Returns a smartly sorted list of tuples. First item of each tuple is the version's slug, and the second item is version's verbose_name. """ version_qs = self.instance.all_active_versions() if version_qs.exists(): version_qs = sort_version_aware(version_qs) all_versions = [(version.slug, version.verbose_name) for version in version_qs] return all_versions return None
def test_sort_wildcard(self): identifiers = ['1.0.x', '2.0.x', '1.1.x', '1.9.x', '1.10.x'] for identifier in identifiers: get( Version, project=self.project, type=BRANCH, identifier=identifier, verbose_name=identifier, slug=identifier, ) versions = list(Version.objects.filter(project=self.project)) self.assertEqual( ['latest', '2.0.x', '1.10.x', '1.9.x', '1.1.x', '1.0.x'], [v.slug for v in sort_version_aware(versions)], )
def test_sort_alpha(self): identifiers = ['banana', 'apple', 'carrot'] for identifier in identifiers: get( Version, project=self.project, type=BRANCH, identifier=identifier, verbose_name=identifier, slug=identifier, ) versions = list(Version.objects.filter(project=self.project)) self.assertEqual( ['latest', 'carrot', 'banana', 'apple'], [v.slug for v in sort_version_aware(versions)], )
def test_sort_git_master(self): identifiers = ['master', '1.0', '2.0', '1.1', '1.9', '1.10'] self.project.repo_type = REPO_TYPE_GIT self.project.save() self.project.versions.get(slug=LATEST).delete() for identifier in identifiers: get( Version, project=self.project, type=BRANCH, identifier=identifier, verbose_name=identifier, slug=identifier, ) versions = list(Version.objects.filter(project=self.project)) self.assertEqual( ['master', '2.0', '1.10', '1.9', '1.1', '1.0'], [v.slug for v in sort_version_aware(versions)], )
def ordered_active_versions(self, user=None): from readthedocs.builds.models import Version kwargs = { 'project': self, 'only_active': True, } if user: kwargs['user'] = user versions = Version.objects.public(**kwargs).select_related( 'project', 'project__main_language_project', ).prefetch_related( Prefetch( 'project__superprojects', ProjectRelationship.objects.all().select_related('parent'), to_attr='_superprojects', ), Prefetch( 'project__domains', Domain.objects.filter(canonical=True), to_attr='_canonical_domains', ), ) return sort_version_aware(versions)
def project_downloads(request, project_slug): """A detail view for a project with various dataz.""" project = get_object_or_404( Project.objects.protected(request.user), slug=project_slug, ) versions = Version.objects.public(user=request.user, project=project) versions = sort_version_aware(versions) version_data = OrderedDict() for version in versions: data = version.get_downloads() # Don't show ones that have no downloads. if data: version_data[version] = data return render( request, 'projects/project_downloads.html', { 'project': project, 'version_data': version_data, 'versions': versions, }, )
def project_downloads(request, project_slug): """A detail view for a project with various downloads.""" project = get_object_or_404( Project.objects.protected(request.user), slug=project_slug, ) versions = Version.objects.public(user=request.user, project=project) versions = sort_version_aware(versions) version_data = OrderedDict() for version in versions: data = version.get_downloads() # Don't show ones that have no downloads. if data: version_data[version] = data return render( request, 'projects/project_downloads.html', { 'project': project, 'version_data': version_data, 'versions': versions, }, )
def api_versions(self): ret = [] for version_data in api.project(self.pk).active_versions.get()['versions']: version = make_api_version(version_data) ret.append(version) return sort_version_aware(ret)
def ordered_active_versions(self): return sort_version_aware(self.versions.filter(active=True))
def ordered_active_versions(self): from readthedocs.builds.models import Version versions = Version.objects.public(project=self, only_active=True) return sort_version_aware(versions)
def get(self, request, project): """ Generate and serve a ``sitemap.xml`` for a particular ``project``. The sitemap is generated from all the ``active`` and public versions of ``project``. These versions are sorted by using semantic versioning prepending ``latest`` and ``stable`` (if they are enabled) at the beginning. Following this order, the versions are assigned priorities and change frequency. Starting from 1 and decreasing by 0.1 for priorities and starting from daily, weekly to monthly for change frequency. If the project doesn't have any public version, the view raises ``Http404``. :param request: Django request object :param project: Project instance to generate the sitemap :returns: response with the ``sitemap.xml`` template rendered :rtype: django.http.HttpResponse """ # pylint: disable=too-many-locals def priorities_generator(): """ Generator returning ``priority`` needed by sitemap.xml. It generates values from 1 to 0.1 by decreasing in 0.1 on each iteration. After 0.1 is reached, it will keep returning 0.1. """ priorities = [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2] yield from itertools.chain(priorities, itertools.repeat(0.1)) def hreflang_formatter(lang): """ sitemap hreflang should follow correct format. Use hyphen instead of underscore in language and country value. ref: https://en.wikipedia.org/wiki/Hreflang#Common_Mistakes """ if '_' in lang: return lang.replace('_', '-') return lang def changefreqs_generator(): """ Generator returning ``changefreq`` needed by sitemap.xml. It returns ``weekly`` on first iteration, then ``daily`` and then it will return always ``monthly``. We are using ``monthly`` as last value because ``never`` is too aggressive. If the tag is removed and a branch is created with the same name, we will want bots to revisit this. """ changefreqs = ['weekly', 'daily'] yield from itertools.chain(changefreqs, itertools.repeat('monthly')) public_versions = Version.internal.public( project=project, only_active=True, ) if not public_versions.exists(): raise Http404 sorted_versions = sort_version_aware(public_versions) # This is a hack to swap the latest version with # stable version to get the stable version first in the sitemap. # We want stable with priority=1 and changefreq='weekly' and # latest with priority=0.9 and changefreq='daily' # More details on this: https://github.com/rtfd/readthedocs.org/issues/5447 if (len(sorted_versions) >= 2 and sorted_versions[0].slug == LATEST and sorted_versions[1].slug == STABLE): sorted_versions[0], sorted_versions[1] = sorted_versions[ 1], sorted_versions[0] versions = [] for version, priority, changefreq in zip( sorted_versions, priorities_generator(), changefreqs_generator(), ): element = { 'loc': version.get_subdomain_url(), 'priority': priority, 'changefreq': changefreq, 'languages': [], } # Version can be enabled, but not ``built`` yet. We want to show the # link without a ``lastmod`` attribute last_build = version.builds.order_by('-date').first() if last_build: element['lastmod'] = last_build.date.isoformat() if project.translations.exists(): for translation in project.translations.all(): translation_versions = (Version.internal.public( project=translation).values_list('slug', flat=True)) if version.slug in translation_versions: href = project.get_docs_url( version_slug=version.slug, lang_slug=translation.language, ) element['languages'].append({ 'hreflang': hreflang_formatter(translation.language), 'href': href, }) # Add itself also as protocol requires element['languages'].append({ 'hreflang': project.language, 'href': element['loc'], }) versions.append(element) context = { 'versions': versions, } return render( request, 'sitemap.xml', context, content_type='application/xml', )
def sitemap_xml(request, project): """ Generate and serve a ``sitemap.xml`` for a particular ``project``. The sitemap is generated from all the ``active`` and public versions of ``project``. These versions are sorted by using semantic versioning prepending ``latest`` and ``stable`` (if they are enabled) at the beginning. Following this order, the versions are assigned priorities and change frequency. Starting from 1 and decreasing by 0.1 for priorities and starting from daily, weekly to monthly for change frequency. If the project is private, the view raises ``Http404``. On the other hand, if the project is public but a version is private, this one is not included in the sitemap. :param request: Django request object :param project: Project instance to generate the sitemap :returns: response with the ``sitemap.xml`` template rendered :rtype: django.http.HttpResponse """ def priorities_generator(): """ Generator returning ``priority`` needed by sitemap.xml. It generates values from 1 to 0.1 by decreasing in 0.1 on each iteration. After 0.1 is reached, it will keep returning 0.1. """ priorities = [1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2] yield from itertools.chain(priorities, itertools.repeat(0.1)) def changefreqs_generator(): """ Generator returning ``changefreq`` needed by sitemap.xml. It returns ``daily`` on first iteration, then ``weekly`` and then it will return always ``monthly``. We are using ``monthly`` as last value because ``never`` is too aggressive. If the tag is removed and a branch is created with the same name, we will want bots to revisit this. """ changefreqs = ['daily', 'weekly'] yield from itertools.chain(changefreqs, itertools.repeat('monthly')) if project.privacy_level == constants.PRIVATE: raise Http404 sorted_versions = sort_version_aware( Version.objects.public( project=project, only_active=True, ), ) versions = [] for version, priority, changefreq in zip( sorted_versions, priorities_generator(), changefreqs_generator(), ): element = { 'loc': version.get_subdomain_url(), 'priority': priority, 'changefreq': changefreq, 'languages': [], } # Version can be enabled, but not ``built`` yet. We want to show the # link without a ``lastmod`` attribute last_build = version.builds.order_by('-date').first() if last_build: element['lastmod'] = last_build.date.isoformat() if project.translations.exists(): for translation in project.translations.all(): translation_versions = translation.versions.public( ).values_list('slug', flat=True) if version.slug in translation_versions: href = project.get_docs_url( version_slug=version.slug, lang_slug=translation.language, private=False, ) element['languages'].append({ 'hreflang': translation.language, 'href': href, }) # Add itself also as protocol requires element['languages'].append({ 'hreflang': project.language, 'href': element['loc'], }) versions.append(element) context = { 'versions': versions, } return render( request, 'sitemap.xml', context, content_type='application/xml', )