From 151582f34ae22a05ad7ab2bde421d1d420d0daa3 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 4 Dec 2024 18:30:36 +0100 Subject: [PATCH] Add project: use an instance variable to avoid 500 (#11795) Closes https://github.com/readthedocs/ext-theme/issues/477 --- readthedocs/projects/views/private.py | 132 +++++++++++++++----------- 1 file changed, 76 insertions(+), 56 deletions(-) diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index 16a43710ed1..470378d0191 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -1,5 +1,6 @@ """Project views for authenticated users.""" + import structlog from allauth.socialaccount.models import SocialAccount from django.conf import settings @@ -314,6 +315,79 @@ def post(self, request, *args, **kwargs): return HttpResponseRedirect(self.get_success_url()) +def show_config_step(wizard): + """ + Decide whether or not show the config step on "Add project" wizard. + + If the `.readthedocs.yaml` file already exist in the default branch, we + don't show this step. + """ + + # try to get the cleaned data of step 1 + cleaned_data = wizard.get_cleaned_data_for_step("basics") or {} + repo = cleaned_data.get("repo") + remote_repository = cleaned_data.get("remote_repository") + default_branch = cleaned_data.get("default_branch") + + if ( + repo + and default_branch + and remote_repository + and remote_repository.vcs_provider == GITHUB + ): + # I don't know why `show_config_step` is called multiple times (at least 4). + # This is a problem for us because we perform external calls here and add messages to the request. + # Due to that, we are adding this instance variable to prevent this function to run multiple times. + # Maybe related to https://github.com/jazzband/django-formtools/issues/134 + if hasattr(wizard, "_show_config_step_executed"): + return False + + remote_repository_relations = ( + remote_repository.remote_repository_relations.filter( + user=wizard.request.user, + account__isnull=False, + ) + .select_related("account", "user") + .only("user", "account") + ) + for relation in remote_repository_relations: + service = GitHubService(relation.user, relation.account) + session = service.get_session() + + for yaml in [ + ".readthedocs.yaml", + ".readthedocs.yml", + "readthedocs.yaml", + "readthedocs.yml", + ]: + try: + querystrings = f"?ref={default_branch}" if default_branch else "" + response = session.head( + f"https://api.github.com/repos/{remote_repository.full_name}/contents/{yaml}{querystrings}", + timeout=1, + ) + if response.ok: + log.info( + "Read the Docs YAML file found for this repository.", + filename=yaml, + ) + messages.success( + wizard.request, + _( + "We detected a configuration file in your repository and started your project's first build." + ), + ) + wizard._show_config_step_executed = True + return False + except Exception: + log.warning( + "Failed when hitting GitHub API to check for .readthedocs.yaml file.", + filename=yaml, + ) + continue + return True + + class ImportWizardView(ProjectImportMixin, PrivateViewMixin, SessionWizardView): """ @@ -323,13 +397,13 @@ class ImportWizardView(ProjectImportMixin, PrivateViewMixin, SessionWizardView): per session (since it's per class). """ + initial_dict_key = "initial-data" + condition_dict = {"config": show_config_step} form_list = [ ("basics", ProjectBasicsForm), ("config", ProjectConfigForm), ] - initial_dict_key = "initial-data" - def get(self, *args, **kwargs): # The method from the parent should run first, # as the storage is initialized there. @@ -366,60 +440,6 @@ def get_template_names(self): """Return template names based on step name.""" return f"projects/import_{self.steps.current}.html" - def process_step(self, form): - # pylint: disable=too-many-nested-blocks - if isinstance(form, ProjectBasicsForm): - remote_repository = form.cleaned_data.get("remote_repository") - default_branch = form.cleaned_data.get("default_branch") - if remote_repository and remote_repository.vcs_provider == GITHUB: - remote_repository_relations = ( - remote_repository.remote_repository_relations.filter( - user=self.request.user, - account__isnull=False, - ) - .select_related("account", "user") - .only("user", "account") - ) - for relation in remote_repository_relations: - service = GitHubService(relation.user, relation.account) - session = service.get_session() - - for yaml in [ - ".readthedocs.yaml", - ".readthedocs.yml", - "readthedocs.yaml", - "readthedocs.yml", - ]: - try: - querystrings = ( - f"?ref={default_branch}" if default_branch else "" - ) - response = session.head( - f"https://api.github.com/repos/{remote_repository.full_name}/contents/{yaml}{querystrings}", - timeout=1, - ) - if response.ok: - log.info( - "Read the Docs YAML file found for this repository.", - filename=yaml, - ) - messages.success( - self.request, - _( - "We detected a configuration file in your repository and started your project's first build." - ), - ) - self.form_list.pop("config") - break - except Exception: - log.warning( - "Failed when hitting GitHub API to check for .readthedocs.yaml file.", - filename=yaml, - ) - continue - - return super().process_step(form) - def done(self, form_list, **kwargs): """ Save form data as object instance.