예제 #1
0
def finish_build(version_pk, build_pk, hostname=None, html=False,
                 localmedia=False, search=False, pdf=False, epub=False):
    """Build Finished, do house keeping bits"""
    version = Version.objects.get(pk=version_pk)
    build = Build.objects.get(pk=build_pk)

    if html:
        version.active = True
        version.built = True
        version.save()

    if not pdf:
        clear_pdf_artifacts(version)
    if not epub:
        clear_epub_artifacts(version)

    move_files(
        version_pk=version_pk,
        hostname=hostname,
        html=html,
        localmedia=localmedia,
        search=search,
        pdf=pdf,
        epub=epub,
    )

    # Symlink project on every web
    broadcast(type='app', task=symlink_project, args=[version.project.pk])

    # Delayed tasks
    update_static_metadata.delay(version.project.pk)
    fileify.delay(version.pk, commit=build.commit)
    update_search.delay(version.pk, commit=build.commit)
예제 #2
0
def project_version_detail(request, project_slug, version_slug):
    """Project version detail page"""
    project = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
    version = get_object_or_404(
        Version.objects.public(user=request.user, project=project, only_active=False),
        slug=version_slug)

    form = VersionForm(request.POST or None, instance=version)

    if request.method == 'POST' and form.is_valid():
        version = form.save()
        if form.has_changed():
            if 'active' in form.changed_data and version.active is False:
                log.info('Removing files for version %s' % version.slug)
                broadcast(type='app', task=tasks.clear_artifacts, args=[version.pk])
                version.built = False
                version.save()
        url = reverse('project_version_list', args=[project.slug])
        return HttpResponseRedirect(url)

    return render_to_response(
        'projects/project_version_detail.html',
        {'form': form, 'project': project, 'version': version},
        context_instance=RequestContext(request)
    )
예제 #3
0
def broadcast_remove_orphan_symlinks():
    """
    Broadcast the task ``remove_orphan_symlinks`` to all our web servers.

    This task is executed by CELERY BEAT.
    """
    broadcast(type='web', task=remove_orphan_symlinks, args=[])
예제 #4
0
def project_subprojects_delete(request, project_slug, child_slug):
    parent = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
    child = get_object_or_404(Project.objects.all(), slug=child_slug)
    parent.remove_subproject(child)
    broadcast(type='app', task=tasks.symlink_subproject, args=[parent.pk])
    return HttpResponseRedirect(reverse('projects_subprojects',
                                        args=[parent.slug]))
예제 #5
0
 def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
     from readthedocs.projects import tasks
     log.info('Removing files for version %s', self.slug)
     broadcast(type='app', task=tasks.clear_artifacts, args=[self.pk])
     broadcast(
         type='app', task=tasks.symlink_project, args=[self.project.pk])
     super(Version, self).delete(*args, **kwargs)
예제 #6
0
def project_subprojects(request, project_slug):
    """Project subprojects view and form view"""
    project = get_object_or_404(Project.objects.for_admin_user(request.user),
                                slug=project_slug)

    form_kwargs = {
        'parent': project,
        'user': request.user,
    }
    if request.method == 'POST':
        form = SubprojectForm(request.POST, **form_kwargs)
        if form.is_valid():
            form.save()
            broadcast(type='app', task=tasks.symlink_subproject, args=[project.pk])
            project_dashboard = reverse(
                'projects_subprojects', args=[project.slug])
            return HttpResponseRedirect(project_dashboard)
    else:
        form = SubprojectForm(**form_kwargs)

    subprojects = project.subprojects.all()

    return render_to_response(
        'projects/project_subprojects.html',
        {'form': form, 'project': project, 'subprojects': subprojects},
        context_instance=RequestContext(request)
    )
예제 #7
0
 def form_valid(self, form):
     broadcast(
         type='app',
         task=tasks.symlink_subproject,
         args=[self.get_project().pk],
     )
     return super(ProjectRelationshipMixin, self).form_valid(form)
예제 #8
0
 def handle(self, *args, **options):
     queryset = Project.objects.all()
     for p in queryset:
         log.info("Generating metadata for %s", p)
         try:
             broadcast(type='app', task=tasks.update_static_metadata, args=[p.pk])
         except Exception:
             log.exception('Build failed for %s', p)
예제 #9
0
 def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
     from readthedocs.projects import tasks
     broadcast(
         type='app',
         task=tasks.symlink_domain,
         args=[self.project.pk, self.domain, True],
     )
     super().delete(*args, **kwargs)
예제 #10
0
 def save(self, *args, **kwargs):
     from readthedocs.projects import tasks
     parsed = urlparse(self.domain)
     if parsed.scheme or parsed.netloc:
         self.domain = parsed.netloc
     else:
         self.domain = parsed.path
     super(Domain, self).save(*args, **kwargs)
     broadcast(type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk])
예제 #11
0
    def delete_selected_and_artifacts(self, request, queryset):
        """Remove HTML/etc artifacts from application instances

        Prior to the query delete, broadcast tasks to delete HTML artifacts from
        application instances.
        """
        if request.POST.get('post'):
            for project in queryset:
                broadcast(type='app', task=remove_dir, args=[project.doc_path])
        return delete_selected(self, request, queryset)
예제 #12
0
 def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
     """Add permissions to the Version for all owners on save."""
     from readthedocs.projects import tasks
     obj = super(Version, self).save(*args, **kwargs)
     for owner in self.project.users.all():
         assign('view_version', owner, self)
     try:
         self.project.sync_supported_versions()
     except Exception:
         log.error('failed to sync supported versions', exc_info=True)
     broadcast(type='app', task=tasks.symlink_project, args=[self.project.pk])
     return obj
예제 #13
0
 def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
     """Add permissions to the Version for all owners on save."""
     from readthedocs.projects import tasks
     obj = super().save(*args, **kwargs)
     for owner in self.project.users.all():
         assign('view_version', owner, self)
     broadcast(
         type='app',
         task=tasks.symlink_project,
         args=[self.project.pk],
     )
     return obj
예제 #14
0
 def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
     """Add permissions to the Version for all owners on save."""
     from readthedocs.projects import tasks
     obj = super(Version, self).save(*args, **kwargs)
     for owner in self.project.users.all():
         assign('view_version', owner, self)
     try:
         self.project.sync_supported_versions()
     except Exception:
         log.exception('failed to sync supported versions')
     broadcast(
         type='app', task=tasks.symlink_project, args=[self.project.pk])
     return obj
예제 #15
0
 def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
     from readthedocs.projects import tasks
     parsed = urlparse(self.domain)
     if parsed.scheme or parsed.netloc:
         self.domain = parsed.netloc
     else:
         self.domain = parsed.path
     super().save(*args, **kwargs)
     broadcast(
         type='app',
         task=tasks.symlink_domain,
         args=[self.project.pk, self.domain],
     )
예제 #16
0
 def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
     from readthedocs.projects import tasks
     parsed = urlparse(self.domain)
     if parsed.scheme or parsed.netloc:
         self.domain = parsed.netloc
     else:
         self.domain = parsed.path
     super(Domain, self).save(*args, **kwargs)
     broadcast(
         type='app',
         task=tasks.symlink_domain,
         args=[self.project.pk, self.pk],
     )
예제 #17
0
def wipe_version_via_slugs(version_slug, project_slug):
    """Wipes the given version of a given project."""
    version = get_object_or_404(
        Version,
        slug=version_slug,
        project__slug=project_slug,
    )
    del_dirs = [
        os.path.join(version.project.doc_path, 'checkouts', version.slug),
        os.path.join(version.project.doc_path, 'envs', version.slug),
        os.path.join(version.project.doc_path, 'conda', version.slug),
    ]
    for del_dir in del_dirs:
        broadcast(type='build', task=remove_dirs, args=[(del_dir, )])
예제 #18
0
    def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks

        # Remove local FS build artifacts on the web servers
        broadcast(
            type='app',
            task=tasks.remove_dirs,
            args=[(self.doc_path, )],
        )

        # Remove extra resources
        tasks.clean_project_resources(self)

        super().delete(*args, **kwargs)
예제 #19
0
def wipe_version_via_slugs(version_slug, project_slug):
    """Wipes the given version of a given project."""
    version = get_object_or_404(
        Version,
        slug=version_slug,
        project__slug=project_slug,
    )
    del_dirs = [
        os.path.join(version.project.doc_path, 'checkouts', version.slug),
        os.path.join(version.project.doc_path, 'envs', version.slug),
        os.path.join(version.project.doc_path, 'conda', version.slug),
    ]
    for del_dir in del_dirs:
        broadcast(type='build', task=remove_dirs, args=[(del_dir,)])
예제 #20
0
파일: private.py 프로젝트: silentphp/read
 def post(self, request, *args, **kwargs):
     version = self.get_object()
     if not version.active:
         version.built = False
         version.save()
         broadcast(
             type='app',
             task=tasks.remove_dirs,
             args=[version.get_artifact_paths()],
         )
     else:
         return HttpResponseBadRequest(
             "Can't delete HTML for an active version.", )
     return HttpResponseRedirect(self.get_success_url())
예제 #21
0
    def update_app_instances(
        self,
        html=False,
        localmedia=False,
        search=False,
        pdf=False,
        epub=False,
    ):
        """
        Update application instances with build artifacts.

        This triggers updates across application instances for html, pdf, epub,
        downloads, and search. Tasks are broadcast to all web servers from here.
        """
        # Update version if we have successfully built HTML output
        try:
            if html:
                version = api_v2.version(self.version.pk)
                version.patch({
                    'built': True,
                })
        except HttpClientError:
            log.exception(
                'Updating version failed, skipping file sync: version=%s',
                self.version,
            )

        # Broadcast finalization steps to web application instances
        broadcast(
            type='app',
            task=sync_files,
            args=[
                self.project.pk,
                self.version.pk,
                self.config.doctype,
            ],
            kwargs=dict(
                hostname=socket.gethostname(),
                html=html,
                localmedia=localmedia,
                search=search,
                pdf=pdf,
                epub=epub,
            ),
            callback=sync_callback.s(
                version_pk=self.version.pk,
                commit=self.build['commit'],
                search=search,
            ),
        )
예제 #22
0
    def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks
        first_save = self.pk is None
        if not self.slug:
            # Subdomains can't have underscores in them.
            self.slug = slugify(self.name)
            if self.slug == '':
                raise Exception(_('Model must have slug'))
        super(Project, self).save(*args, **kwargs)
        for owner in self.users.all():
            assign('view_project', owner, self)
        try:
            if self.default_branch:
                latest = self.versions.get(slug=LATEST)
                if latest.identifier != self.default_branch:
                    latest.identifier = self.default_branch
                    latest.save()
        except Exception:
            log.exception('Failed to update latest identifier')

        # Add exceptions here for safety
        try:
            self.sync_supported_versions()
        except Exception:
            log.exception('failed to sync supported versions')
        try:
            if not first_save:
                broadcast(
                    type='app',
                    task=tasks.symlink_project,
                    args=[self.pk],
                )
        except Exception:
            log.exception('failed to symlink project')
        try:
            if not first_save:
                broadcast(
                    type='app',
                    task=tasks.update_static_metadata,
                    args=[self.pk],
                )
        except Exception:
            log.exception('failed to update static metadata')
        try:
            branch = self.default_branch or self.vcs_repo().fallback_branch
            if not self.versions.filter(slug=LATEST).exists():
                self.versions.create_latest(identifier=branch)
        except Exception:
            log.exception('Error creating default branches')
예제 #23
0
    def update_app_instances(self,
                             html=False,
                             localmedia=False,
                             search=False,
                             pdf=False,
                             epub=False):
        """Update application instances with build artifacts

        This triggers updates across application instances for html, pdf, epub,
        downloads, and search. Tasks are broadcast to all web servers from here.
        """
        # Update version if we have successfully built HTML output
        try:
            if html:
                version = api_v2.version(self.version.pk)
                version.patch({
                    'active': True,
                    'built': True,
                })
        except HttpClientError as e:
            log.error(
                'Updating version failed, skipping file sync: version=%s',
                self.version.pk,
                exc_info=True)
        else:
            # Broadcast finalization steps to web application instances
            broadcast(type='app',
                      task=sync_files,
                      args=[
                          self.project.pk,
                          self.version.pk,
                      ],
                      kwargs=dict(
                          hostname=socket.gethostname(),
                          html=html,
                          localmedia=localmedia,
                          search=search,
                          pdf=pdf,
                          epub=epub,
                      ))

            # Delayed tasks
            # TODO these should be chained on to the broadcast calls. The
            # broadcast calls could be lumped together into a promise, and on
            # task result, these next few tasks can be updated, also in a
            # chained fashion
            fileify.delay(self.version.pk, commit=self.build.get('commit'))
            update_search.delay(self.version.pk,
                                commit=self.build.get('commit'))
예제 #24
0
    def delete_selected_and_artifacts(self, request, queryset):
        """
        Remove HTML/etc artifacts from application instances.

        Prior to the query delete, broadcast tasks to delete HTML artifacts from
        application instances.
        """
        if request.POST.get('post'):
            for project in queryset:
                broadcast(
                    type='app',
                    task=remove_dirs,
                    args=[(project.doc_path, )],
                )
        return delete_selected(self, request, queryset)
예제 #25
0
 def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
     from readthedocs.projects import tasks
     log.info('Removing files for version %s', self.slug)
     broadcast(
         type='app',
         task=tasks.remove_dirs,
         args=[self.get_artifact_paths()],
     )
     project_pk = self.project.pk
     super().delete(*args, **kwargs)
     broadcast(
         type='app',
         task=tasks.symlink_project,
         args=[project_pk],
     )
예제 #26
0
 def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
     from readthedocs.projects import tasks
     log.info('Removing files for version %s', self.slug)
     broadcast(
         type='app',
         task=tasks.remove_dirs,
         args=[self.get_artifact_paths()],
     )
     project_pk = self.project.pk
     super(Version, self).delete(*args, **kwargs)
     broadcast(
         type='app',
         task=tasks.symlink_project,
         args=[project_pk],
     )
예제 #27
0
파일: private.py 프로젝트: silentphp/read
 def form_valid(self, form):
     version = form.save()
     if form.has_changed():
         if 'active' in form.changed_data and version.active is False:
             log.info('Removing files for version %s', version.slug)
             broadcast(
                 type='app',
                 task=tasks.remove_dirs,
                 args=[version.get_artifact_paths()],
             )
             tasks.clean_project_resources(
                 version.project,
                 version,
             )
             version.built = False
             version.save()
     return HttpResponseRedirect(self.get_success_url())
예제 #28
0
def wipe_version(request, project_slug, version_slug):
    version = get_object_or_404(Version, project__slug=project_slug,
                                slug=version_slug)
    if request.user not in version.project.users.all():
        raise Http404("You must own this project to wipe it.")

    if request.method == 'POST':
        del_dirs = [
            os.path.join(version.project.doc_path, 'checkouts', version.slug),
            os.path.join(version.project.doc_path, 'envs', version.slug),
            os.path.join(version.project.doc_path, 'conda', version.slug),
        ]
        for del_dir in del_dirs:
            broadcast(type='build', task=remove_dir, args=[del_dir])
        return redirect('project_version_list', project_slug)
    return render_to_response('wipe_version.html',
                              context_instance=RequestContext(request))
예제 #29
0
def finish_build(version_pk,
                 build_pk,
                 hostname=None,
                 html=False,
                 localmedia=False,
                 search=False,
                 pdf=False,
                 epub=False):
    """Build Finished, do house keeping bits"""
    version = Version.objects.get(pk=version_pk)
    build = Build.objects.get(pk=build_pk)

    if html:
        version.active = True
        version.built = True
        version.save()

    if not pdf:
        broadcast(type='app', task=clear_pdf_artifacts, args=[version.pk])
    if not epub:
        broadcast(type='app', task=clear_epub_artifacts, args=[version.pk])

    # Sync files to the web servers
    broadcast(type='app',
              task=move_files,
              args=[version_pk, hostname],
              kwargs=dict(
                  html=html,
                  localmedia=localmedia,
                  search=search,
                  pdf=pdf,
                  epub=epub,
              ))

    # Symlink project on every web
    broadcast(type='app', task=symlink_project, args=[version.project.pk])

    # Update metadata
    broadcast(type='app',
              task=update_static_metadata,
              args=[version.project.pk])

    # Delayed tasks
    fileify.delay(version.pk, commit=build.commit)
    update_search.delay(version.pk, commit=build.commit)
예제 #30
0
def project_delete(request, project_slug):
    """
    Project delete confirmation view.

    Make a project as deleted on POST, otherwise show a form asking for
    confirmation of delete.
    """
    project = get_object_or_404(
        Project.objects.for_admin_user(request.user), slug=project_slug)

    if request.method == 'POST':
        broadcast(type='app', task=tasks.remove_dir, args=[project.doc_path])
        project.delete()
        messages.success(request, _('Project deleted'))
        project_dashboard = reverse('projects_dashboard')
        return HttpResponseRedirect(project_dashboard)

    return render(request, 'projects/project_delete.html', {'project': project})
예제 #31
0
def wipe_version(request, project_slug, version_slug):
    version = get_object_or_404(Version, project__slug=project_slug,
                                slug=version_slug)
    if request.user not in version.project.users.all():
        raise Http404("You must own this project to wipe it.")

    if request.method == 'POST':
        del_dirs = [
            os.path.join(version.project.doc_path, 'checkouts', version.slug),
            os.path.join(version.project.doc_path, 'envs', version.slug),
            os.path.join(version.project.doc_path, 'conda', version.slug),
        ]
        for del_dir in del_dirs:
            broadcast(type='build', task=remove_dir, args=[del_dir])
        return redirect('project_version_list', project_slug)
    else:
        return render_to_response('wipe_version.html',
                                  context_instance=RequestContext(request))
예제 #32
0
def project_version_delete_html(request, project_slug, version_slug):
    """Project version 'delete' HTML

    This marks a version as not built
    """
    project = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
    version = get_object_or_404(
        Version.objects.public(user=request.user, project=project, only_active=False),
        slug=version_slug)

    if not version.active:
        version.built = False
        version.save()
        broadcast(type='app', task=tasks.clear_artifacts, args=[version.pk])
    else:
        return HttpResponseBadRequest("Can't delete HTML for an active version.")
    return HttpResponseRedirect(
        reverse('project_version_list', kwargs={'project_slug': project_slug}))
예제 #33
0
def project_version_delete_html(request, project_slug, version_slug):
    """Project version 'delete' HTML

    This marks a version as not built
    """
    project = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
    version = get_object_or_404(
        Version.objects.public(user=request.user, project=project, only_active=False),
        slug=version_slug)

    if not version.active:
        version.built = False
        version.save()
        broadcast(type='app', task=tasks.clear_artifacts, args=[version.pk])
    else:
        return HttpResponseBadRequest("Can't delete HTML for an active version.")
    return HttpResponseRedirect(
        reverse('project_version_list', kwargs={'project_slug': project_slug}))
예제 #34
0
def project_delete(request, project_slug):
    """
    Project delete confirmation view.

    Make a project as deleted on POST, otherwise show a form asking for
    confirmation of delete.
    """
    project = get_object_or_404(
        Project.objects.for_admin_user(request.user), slug=project_slug)

    if request.method == 'POST':
        broadcast(type='app', task=tasks.remove_dir, args=[project.doc_path])
        project.delete()
        messages.success(request, _('Project deleted'))
        project_dashboard = reverse('projects_dashboard')
        return HttpResponseRedirect(project_dashboard)

    return render(request, 'projects/project_delete.html', {'project': project})
예제 #35
0
def wipe_version_via_slugs(version_slug, project_slug):
    """Wipes the given version of a given project."""
    version = get_object_or_404(
        Version,
        slug=version_slug,
        project__slug=project_slug,
    )
    del_dirs = [
        os.path.join(version.project.doc_path, 'checkouts', version.slug),
        os.path.join(version.project.doc_path, 'envs', version.slug),
        os.path.join(version.project.doc_path, 'conda', version.slug),
        os.path.join(version.project.doc_path, '.cache'),
    ]
    for del_dir in del_dirs:
        broadcast(type='build', task=remove_dirs, args=[(del_dir, )])

    # Delete the cache environment from storage
    build_environment_storage.delete(
        version.get_storage_environment_cache_path())
예제 #36
0
    def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks
        log.info('Removing files for version %s', self.slug)
        broadcast(
            type='app',
            task=tasks.remove_dirs,
            args=[self.get_artifact_paths()],
        )

        # Remove resources if the version is not external
        if self.type != EXTERNAL:
            tasks.clean_project_resources(self.project, self)

        project_pk = self.project.pk
        super().delete(*args, **kwargs)
        broadcast(
            type='app',
            task=tasks.symlink_project,
            args=[project_pk],
        )
예제 #37
0
    def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks

        # Remove local FS build artifacts on the web servers
        broadcast(
            type='app',
            task=tasks.remove_dirs,
            args=[(self.doc_path, )],
        )

        # Remove build artifacts from storage
        storage_paths = []
        for type_ in MEDIA_TYPES:
            storage_paths.append('{}/{}'.format(
                type_,
                self.slug,
            ))
        tasks.remove_build_storage_paths.delay(storage_paths)

        super().delete(*args, **kwargs)
예제 #38
0
    def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks
        first_save = self.pk is None
        if not self.slug:
            # Subdomains can't have underscores in them.
            self.slug = slugify(self.name)
            if self.slug == '':
                raise Exception(_("Model must have slug"))
        super(Project, self).save(*args, **kwargs)
        for owner in self.users.all():
            assign('view_project', owner, self)
        try:
            if self.default_branch:
                latest = self.versions.get(slug=LATEST)
                if latest.identifier != self.default_branch:
                    latest.identifier = self.default_branch
                    latest.save()
        except Exception:
            log.exception('Failed to update latest identifier')

        # Add exceptions here for safety
        try:
            self.sync_supported_versions()
        except Exception:
            log.exception('failed to sync supported versions')
        try:
            if not first_save:
                broadcast(type='app', task=tasks.symlink_project, args=[self.pk])
        except Exception:
            log.exception('failed to symlink project')
        try:
            if not first_save:
                broadcast(type='app', task=tasks.update_static_metadata, args=[self.pk])
        except Exception:
            log.exception('failed to update static metadata')
        try:
            branch = self.default_branch or self.vcs_repo().fallback_branch
            if not self.versions.filter(slug=LATEST).exists():
                self.versions.create_latest(identifier=branch)
        except Exception:
            log.exception('Error creating default branches')
예제 #39
0
    def save(self, *args, **kwargs):
        from readthedocs.projects import tasks

        first_save = self.pk is None
        if not self.slug:
            # Subdomains can't have underscores in them.
            self.slug = slugify(self.name).replace("_", "-")
            if self.slug == "":
                raise Exception(_("Model must have slug"))
        super(Project, self).save(*args, **kwargs)
        for owner in self.users.all():
            assign("view_project", owner, self)
        try:
            if self.default_branch:
                latest = self.versions.get(slug=LATEST)
                if latest.identifier != self.default_branch:
                    latest.identifier = self.default_branch
                    latest.save()
        except Exception:
            log.error("Failed to update latest identifier", exc_info=True)

        # Add exceptions here for safety
        try:
            self.sync_supported_versions()
        except Exception:
            log.error("failed to sync supported versions", exc_info=True)
        try:
            if not first_save:
                broadcast(type="app", task=tasks.symlink_project, args=[self.pk])
        except Exception:
            log.error("failed to symlink project", exc_info=True)
        try:
            update_static_metadata(project_pk=self.pk)
        except Exception:
            log.error("failed to update static metadata", exc_info=True)
        try:
            branch = self.default_branch or self.vcs_repo().fallback_branch
            if not self.versions.filter(slug=LATEST).exists():
                self.versions.create_latest(identifier=branch)
        except Exception:
            log.error("Error creating default branches", exc_info=True)
예제 #40
0
def project_version_detail(request, project_slug, version_slug):
    """Project version detail page."""
    project = get_object_or_404(
        Project.objects.for_admin_user(request.user),
        slug=project_slug,
    )
    version = get_object_or_404(
        Version.objects.public(
            user=request.user,
            project=project,
            only_active=False,
        ),
        slug=version_slug,
    )

    form = VersionForm(request.POST or None, instance=version)

    if request.method == 'POST' and form.is_valid():
        version = form.save()
        if form.has_changed():
            if 'active' in form.changed_data and version.active is False:
                log.info('Removing files for version %s', version.slug)
                broadcast(
                    type='app',
                    task=tasks.remove_dirs,
                    args=[version.get_artifact_paths()],
                )
                version.built = False
                version.save()
        url = reverse('project_version_list', args=[project.slug])
        return HttpResponseRedirect(url)

    return render(
        request,
        'projects/project_version_detail.html',
        {
            'form': form,
            'project': project,
            'version': version
        },
    )
예제 #41
0
    def update_app_instances(self, html=False, localmedia=False, search=False,
                             pdf=False, epub=False):
        """
        Update application instances with build artifacts.

        This triggers updates across application instances for html, pdf, epub,
        downloads, and search. Tasks are broadcast to all web servers from here.
        """
        # Update version if we have successfully built HTML output
        try:
            if html:
                version = api_v2.version(self.version.pk)
                version.patch({
                    'active': True,
                    'built': True,
                })
        except HttpClientError:
            log.exception(
                'Updating version failed, skipping file sync: version=%s',
                self.version,
            )

        # Broadcast finalization steps to web application instances
        broadcast(
            type='app',
            task=sync_files,
            args=[
                self.project.pk,
                self.version.pk,
            ],
            kwargs=dict(
                hostname=socket.gethostname(),
                html=html,
                localmedia=localmedia,
                search=search,
                pdf=pdf,
                epub=epub,
            ),
            callback=sync_callback.s(version_pk=self.version.pk, commit=self.build['commit']),
        )
예제 #42
0
    def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks
        first_save = self.pk is None
        if not self.slug:
            # Subdomains can't have underscores in them.
            self.slug = slugify(self.name)
            if not self.slug:
                raise Exception(_('Model must have slug'))
        super().save(*args, **kwargs)
        for owner in self.users.all():
            assign('view_project', owner, self)
        try:
            latest = self.versions.filter(slug=LATEST).first()
            default_branch = self.get_default_branch()
            if latest and latest.identifier != default_branch:
                latest.identifier = default_branch
                latest.save()
        except Exception:
            log.exception('Failed to update latest identifier')

        try:
            if not first_save:
                log.info(
                    'Re-symlinking project and subprojects: project=%s',
                    self.slug,
                )
                broadcast(
                    type='app',
                    task=tasks.symlink_project,
                    args=[self.pk],
                )
                log.info(
                    'Re-symlinking superprojects: project=%s',
                    self.slug,
                )
                for relationship in self.superprojects.all():
                    broadcast(
                        type='app',
                        task=tasks.symlink_project,
                        args=[relationship.parent.pk],
                    )

        except Exception:
            log.exception('failed to symlink project')
        try:
            if not first_save:
                broadcast(
                    type='app',
                    task=tasks.update_static_metadata,
                    args=[self.pk],
                )
        except Exception:
            log.exception('failed to update static metadata')
        try:
            branch = self.default_branch or self.vcs_repo().fallback_branch
            if not self.versions.filter(slug=LATEST).exists():
                self.versions.create_latest(identifier=branch)
        except Exception:
            log.exception('Error creating default branches')
예제 #43
0
def wipe_version(request, project_slug, version_slug):
    version = get_object_or_404(
        Version,
        project__slug=project_slug,
        slug=version_slug,
    )
    if request.user not in version.project.users.all():
        raise Http404('You must own this project to wipe it.')

    if request.method == 'POST':
        del_dirs = [
            os.path.join(version.project.doc_path, 'checkouts', version.slug),
            os.path.join(version.project.doc_path, 'envs', version.slug),
            os.path.join(version.project.doc_path, 'conda', version.slug),
        ]
        for del_dir in del_dirs:
            broadcast(type='build', task=remove_dir, args=[del_dir])
        return redirect('project_version_list', project_slug)
    return render(request, 'wipe_version.html', {
        'version': version,
        'project': version.project
    })
예제 #44
0
    def build_docs_html(self):
        """Build HTML docs."""
        html_builder = get_builder_class(self.project.documentation_type)(
            build_env=self.build_env,
            python_env=self.python_env,
        )
        if self.build_force:
            html_builder.force()
        html_builder.append_conf()
        success = html_builder.build()
        if success:
            html_builder.move()

        # Gracefully attempt to move files via task on web workers.
        try:
            broadcast(type='app', task=move_files,
                      args=[self.version.pk, socket.gethostname()],
                      kwargs=dict(html=True)
                      )
        except socket.error:
            log.exception('move_files task has failed on socket error.')

        return success
예제 #45
0
def wipe_version(request, project_slug, version_slug):
    version = get_object_or_404(
        Version,
        project__slug=project_slug,
        slug=version_slug,
    )
    # We need to check by ``for_admin_user`` here to allow members of the
    # ``Admin`` team (which doesn't own the project) under the corporate site.
    if version.project not in Project.objects.for_admin_user(user=request.user):
        raise Http404('You must own this project to wipe it.')

    if request.method == 'POST':
        del_dirs = [
            os.path.join(version.project.doc_path, 'checkouts', version.slug),
            os.path.join(version.project.doc_path, 'envs', version.slug),
            os.path.join(version.project.doc_path, 'conda', version.slug),
        ]
        for del_dir in del_dirs:
            broadcast(type='build', task=remove_dir, args=[del_dir])
        return redirect('project_version_list', project_slug)
    return render(
        request, 'wipe_version.html',
        {'version': version, 'project': version.project})
예제 #46
0
def wipe_version(request, project_slug, version_slug):
    version = get_object_or_404(
        Version,
        project__slug=project_slug,
        slug=version_slug,
    )
    # We need to check by ``for_admin_user`` here to allow members of the
    # ``Admin`` team (which doesn't own the project) under the corporate site.
    if version.project not in Project.objects.for_admin_user(user=request.user):
        raise Http404('You must own this project to wipe it.')

    if request.method == 'POST':
        del_dirs = [
            os.path.join(version.project.doc_path, 'checkouts', version.slug),
            os.path.join(version.project.doc_path, 'envs', version.slug),
            os.path.join(version.project.doc_path, 'conda', version.slug),
        ]
        for del_dir in del_dirs:
            broadcast(type='build', task=remove_dir, args=[del_dir])
        return redirect('project_version_list', project_slug)
    return render(
        request, 'wipe_version.html',
        {'version': version, 'project': version.project})
예제 #47
0
    def build_docs_html(self):
        """Build HTML docs."""
        html_builder = get_builder_class(self.project.documentation_type)(
            build_env=self.build_env,
            python_env=self.python_env,
        )
        if self.build_force:
            html_builder.force()
        html_builder.append_conf()
        success = html_builder.build()
        if success:
            html_builder.move()

        # Gracefully attempt to move files via task on web workers.
        try:
            broadcast(type='app', task=move_files,
                      args=[self.version.pk, socket.gethostname()],
                      kwargs=dict(html=True)
                      )
        except socket.error:
            log.exception('move_files task has failed on socket error.')

        return success
예제 #48
0
 def delete(self, *args, **kwargs):
     from readthedocs.projects import tasks
     broadcast(type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk, True])
     super(Domain, self).delete(*args, **kwargs)
예제 #49
0
 def delete(self, *args, **kwargs):
     from readthedocs.projects import tasks
     log.info('Removing files for version %s' % self.slug)
     tasks.clear_artifacts.delay(version_pk=self.pk)
     broadcast(type='app', task=tasks.symlink_project, args=[self.project.pk])
     super(Version, self).delete(*args, **kwargs)
예제 #50
0
 def delete(self, *args, **kwargs):  # pylint: disable=arguments-differ
     from readthedocs.projects import tasks
     broadcast(type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk, True])
     super(Domain, self).delete(*args, **kwargs)
예제 #51
0
    def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
        from readthedocs.projects import tasks
        first_save = self.pk is None
        if not self.slug:
            # Subdomains can't have underscores in them.
            self.slug = slugify(self.name)
            if not self.slug:
                raise Exception(_('Model must have slug'))
        if self.documentation_type == 'auto':
            # This used to determine the type and automatically set the
            # documentation type to Sphinx for rST and Mkdocs for markdown.
            # It now just forces Sphinx, due to markdown support.
            self.documentation_type = 'sphinx'
        super(Project, self).save(*args, **kwargs)
        for owner in self.users.all():
            assign('view_project', owner, self)
        try:
            latest = self.versions.filter(slug=LATEST).first()
            default_branch = self.get_default_branch()
            if latest and latest.identifier != default_branch:
                latest.identifier = default_branch
                latest.save()
        except Exception:
            log.exception('Failed to update latest identifier')

        # Add exceptions here for safety
        try:
            self.sync_supported_versions()
        except Exception:
            log.exception('failed to sync supported versions')
        try:
            if not first_save:
                log.info(
                    'Re-symlinking project and subprojects: project=%s',
                    self.slug,
                )
                broadcast(
                    type='app',
                    task=tasks.symlink_project,
                    args=[self.pk],
                )
                log.info(
                    'Re-symlinking superprojects: project=%s',
                    self.slug,
                )
                for relationship in self.superprojects.all():
                    broadcast(
                        type='app',
                        task=tasks.symlink_project,
                        args=[relationship.parent.pk],
                    )

        except Exception:
            log.exception('failed to symlink project')
        try:
            if not first_save:
                broadcast(
                    type='app',
                    task=tasks.update_static_metadata,
                    args=[self.pk],
                )
        except Exception:
            log.exception('failed to update static metadata')
        try:
            branch = self.default_branch or self.vcs_repo().fallback_branch
            if not self.versions.filter(slug=LATEST).exists():
                self.versions.create_latest(identifier=branch)
        except Exception:
            log.exception('Error creating default branches')
예제 #52
0
 def form_valid(self, form):
     broadcast(type='app',
               task=tasks.symlink_subproject,
               args=[self.get_project().pk])
     return super(ProjectRelationshipMixin, self).form_valid(form)