We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Trigger build on default branch when saving a project
Currently when we save a project form (admin panel),
it triggers a build to latest

https://github.com/rtfd/readthedocs.org/blob/9874b866fea9696fa8495d7b3699f1bf1a3f923d/readthedocs/projects/forms.py#L69-L74

https://github.com/rtfd/readthedocs.org/blob/9874b866fea9696fa8495d7b3699f1bf1a3f923d/readthedocs/core/utils/__init__.py#L97-L98

Even if latest is deactivated, we should trigger a build to the default branch instead.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `readthedocs/core/utils/__init__.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 """Common utilty functions."""
4 
5 from __future__ import absolute_import
6 
7 import errno
8 import getpass
9 import logging
10 import os
11 import re
12 
13 from django.conf import settings
14 from django.utils.functional import keep_lazy
15 from django.utils.safestring import SafeText, mark_safe
16 from django.utils.text import slugify as slugify_base
17 from celery import group, chord
18 
19 from readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED
20 from readthedocs.doc_builder.constants import DOCKER_LIMITS
21 
22 log = logging.getLogger(__name__)
23 
24 SYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser())
25 
26 
27 def broadcast(type, task, args, kwargs=None, callback=None):  # pylint: disable=redefined-builtin
28     """
29     Run a broadcast across our servers.
30 
31     Returns a task group that can be checked for results.
32 
33     `callback` should be a task signature that will be run once,
34     after all of the broadcast tasks have finished running.
35     """
36     assert type in ['web', 'app', 'build']
37     if kwargs is None:
38         kwargs = {}
39     default_queue = getattr(settings, 'CELERY_DEFAULT_QUEUE', 'celery')
40     if type in ['web', 'app']:
41         servers = getattr(settings, 'MULTIPLE_APP_SERVERS', [default_queue])
42     elif type in ['build']:
43         servers = getattr(settings, 'MULTIPLE_BUILD_SERVERS', [default_queue])
44 
45     tasks = []
46     for server in servers:
47         task_sig = task.s(*args, **kwargs).set(queue=server)
48         tasks.append(task_sig)
49     if callback:
50         task_promise = chord(tasks, callback).apply_async()
51     else:
52         # Celery's Group class does some special handling when an iterable with
53         # len() == 1 is passed in. This will be hit if there is only one server
54         # defined in the above queue lists
55         if len(tasks) > 1:
56             task_promise = group(*tasks).apply_async()
57         else:
58             task_promise = group(tasks).apply_async()
59     return task_promise
60 
61 
62 def prepare_build(
63         project,
64         version=None,
65         record=True,
66         force=False,
67         immutable=True,
68 ):
69     """
70     Prepare a build in a Celery task for project and version.
71 
72     If project has a ``build_queue``, execute the task on this build queue. If
73     project has ``skip=True``, the build is not triggered.
74 
75     :param project: project's documentation to be built
76     :param version: version of the project to be built. Default: ``latest``
77     :param record: whether or not record the build in a new Build object
78     :param force: build the HTML documentation even if the files haven't changed
79     :param immutable: whether or not create an immutable Celery signature
80     :returns: Celery signature of update_docs_task and Build instance
81     :rtype: tuple
82     """
83     # Avoid circular import
84     from readthedocs.builds.models import Build
85     from readthedocs.projects.models import Project
86     from readthedocs.projects.tasks import update_docs_task
87 
88     build = None
89 
90     if not Project.objects.is_active(project):
91         log.warning(
92             'Build not triggered because Project is not active: project=%s',
93             project.slug,
94         )
95         return (None, None)
96 
97     if not version:
98         version = project.versions.get(slug=LATEST)
99 
100     kwargs = {
101         'version_pk': version.pk,
102         'record': record,
103         'force': force,
104     }
105 
106     if record:
107         build = Build.objects.create(
108             project=project,
109             version=version,
110             type='html',
111             state=BUILD_STATE_TRIGGERED,
112             success=True,
113         )
114         kwargs['build_pk'] = build.pk
115 
116     options = {}
117     if project.build_queue:
118         options['queue'] = project.build_queue
119 
120     # Set per-task time limit
121     time_limit = DOCKER_LIMITS['time']
122     try:
123         if project.container_time_limit:
124             time_limit = int(project.container_time_limit)
125     except ValueError:
126         log.warning('Invalid time_limit for project: %s', project.slug)
127 
128     # Add 20% overhead to task, to ensure the build can timeout and the task
129     # will cleanly finish.
130     options['soft_time_limit'] = time_limit
131     options['time_limit'] = int(time_limit * 1.2)
132 
133     return (
134         update_docs_task.signature(
135             args=(project.pk,),
136             kwargs=kwargs,
137             options=options,
138             immutable=True,
139         ),
140         build,
141     )
142 
143 
144 def trigger_build(project, version=None, record=True, force=False):
145     """
146     Trigger a Build.
147 
148     Helper that calls ``prepare_build`` and just effectively trigger the Celery
149     task to be executed by a worker.
150 
151     :param project: project's documentation to be built
152     :param version: version of the project to be built. Default: ``latest``
153     :param record: whether or not record the build in a new Build object
154     :param force: build the HTML documentation even if the files haven't changed
155     :returns: Celery AsyncResult promise and Build instance
156     :rtype: tuple
157     """
158     update_docs_task, build = prepare_build(
159         project,
160         version,
161         record,
162         force,
163         immutable=True,
164     )
165 
166     if (update_docs_task, build) == (None, None):
167         # Build was skipped
168         return (None, None)
169 
170     return (update_docs_task.apply_async(), build)
171 
172 
173 def send_email(
174         recipient, subject, template, template_html, context=None, request=None,
175         from_email=None, **kwargs
176 ):  # pylint: disable=unused-argument
177     """
178     Alter context passed in and call email send task.
179 
180     .. seealso::
181 
182         Task :py:func:`readthedocs.core.tasks.send_email_task`
183             Task that handles templating and sending email message
184     """
185     from ..tasks import send_email_task
186 
187     if context is None:
188         context = {}
189     context['uri'] = '{scheme}://{host}'.format(
190         scheme='https',
191         host=settings.PRODUCTION_DOMAIN,
192     )
193     send_email_task.delay(
194         recipient=recipient, subject=subject, template=template,
195         template_html=template_html, context=context, from_email=from_email,
196         **kwargs
197     )
198 
199 
200 @keep_lazy(str, SafeText)
201 def slugify(value, *args, **kwargs):
202     """
203     Add a DNS safe option to slugify.
204 
205     :param dns_safe: Remove underscores from slug as well
206     """
207     dns_safe = kwargs.pop('dns_safe', True)
208     value = slugify_base(value, *args, **kwargs)
209     if dns_safe:
210         value = mark_safe(re.sub('[-_]+', '-', value))
211     return value
212 
213 
214 def safe_makedirs(directory_name):
215     """
216     Safely create a directory.
217 
218     Makedirs has an issue where it has a race condition around checking for a
219     directory and then creating it. This catches the exception in the case where
220     the dir already exists.
221     """
222     try:
223         os.makedirs(directory_name)
224     except OSError as e:
225         if e.errno != errno.EEXIST:  # 17, FileExistsError
226             raise
227 
228 
229 def safe_unlink(path):
230     """
231     Unlink ``path`` symlink using ``os.unlink``.
232 
233     This helper handles the exception ``FileNotFoundError`` to avoid logging in
234     cases where the symlink does not exist already and there is nothing to
235     unlink.
236 
237     :param path: symlink path to unlink
238     :type path: str
239     """
240     try:
241         os.unlink(path)
242     except FileNotFoundError:
243         log.warning('Unlink failed. Path %s does not exists', path)
244
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py
--- a/readthedocs/core/utils/__init__.py
+++ b/readthedocs/core/utils/__init__.py
@@ -16,7 +16,7 @@
 from django.utils.text import slugify as slugify_base
 from celery import group, chord
 
-from readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED
+from readthedocs.builds.constants import BUILD_STATE_TRIGGERED
 from readthedocs.doc_builder.constants import DOCKER_LIMITS
 
 log = logging.getLogger(__name__)
@@ -73,7 +73,7 @@
     project has ``skip=True``, the build is not triggered.
 
     :param project: project's documentation to be built
-    :param version: version of the project to be built. Default: ``latest``
+    :param version: version of the project to be built. Default: ``project.get_default_version()``
     :param record: whether or not record the build in a new Build object
     :param force: build the HTML documentation even if the files haven't changed
     :param immutable: whether or not create an immutable Celery signature
@@ -95,7 +95,8 @@
         return (None, None)
 
     if not version:
-        version = project.versions.get(slug=LATEST)
+        default_version = project.get_default_version()
+        version = project.versions.get(slug=default_version)
 
     kwargs = {
         'version_pk': version.pk,

{"golden_diff": "diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py\n--- a/readthedocs/core/utils/__init__.py\n+++ b/readthedocs/core/utils/__init__.py\n@@ -16,7 +16,7 @@\n from django.utils.text import slugify as slugify_base\n from celery import group, chord\n \n-from readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED\n+from readthedocs.builds.constants import BUILD_STATE_TRIGGERED\n from readthedocs.doc_builder.constants import DOCKER_LIMITS\n \n log = logging.getLogger(__name__)\n@@ -73,7 +73,7 @@\n     project has ``skip=True``, the build is not triggered.\n \n     :param project: project's documentation to be built\n-    :param version: version of the project to be built. Default: ``latest``\n+    :param version: version of the project to be built. Default: ``project.get_default_version()``\n     :param record: whether or not record the build in a new Build object\n     :param force: build the HTML documentation even if the files haven't changed\n     :param immutable: whether or not create an immutable Celery signature\n@@ -95,7 +95,8 @@\n         return (None, None)\n \n     if not version:\n-        version = project.versions.get(slug=LATEST)\n+        default_version = project.get_default_version()\n+        version = project.versions.get(slug=default_version)\n \n     kwargs = {\n         'version_pk': version.pk,\n", "issue": "Trigger build on default branch when saving a project\nCurrently when we save a project form (admin panel),\r\nit triggers a build to latest\r\n\r\nhttps://github.com/rtfd/readthedocs.org/blob/9874b866fea9696fa8495d7b3699f1bf1a3f923d/readthedocs/projects/forms.py#L69-L74\r\n\r\nhttps://github.com/rtfd/readthedocs.org/blob/9874b866fea9696fa8495d7b3699f1bf1a3f923d/readthedocs/core/utils/__init__.py#L97-L98\r\n\r\nEven if latest is deactivated, we should trigger a build to the default branch instead.\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"Common utilty functions.\"\"\"\n\nfrom __future__ import absolute_import\n\nimport errno\nimport getpass\nimport logging\nimport os\nimport re\n\nfrom django.conf import settings\nfrom django.utils.functional import keep_lazy\nfrom django.utils.safestring import SafeText, mark_safe\nfrom django.utils.text import slugify as slugify_base\nfrom celery import group, chord\n\nfrom readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED\nfrom readthedocs.doc_builder.constants import DOCKER_LIMITS\n\nlog = logging.getLogger(__name__)\n\nSYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser())\n\n\ndef broadcast(type, task, args, kwargs=None, callback=None):  # pylint: disable=redefined-builtin\n    \"\"\"\n    Run a broadcast across our servers.\n\n    Returns a task group that can be checked for results.\n\n    `callback` should be a task signature that will be run once,\n    after all of the broadcast tasks have finished running.\n    \"\"\"\n    assert type in ['web', 'app', 'build']\n    if kwargs is None:\n        kwargs = {}\n    default_queue = getattr(settings, 'CELERY_DEFAULT_QUEUE', 'celery')\n    if type in ['web', 'app']:\n        servers = getattr(settings, 'MULTIPLE_APP_SERVERS', [default_queue])\n    elif type in ['build']:\n        servers = getattr(settings, 'MULTIPLE_BUILD_SERVERS', [default_queue])\n\n    tasks = []\n    for server in servers:\n        task_sig = task.s(*args, **kwargs).set(queue=server)\n        tasks.append(task_sig)\n    if callback:\n        task_promise = chord(tasks, callback).apply_async()\n    else:\n        # Celery's Group class does some special handling when an iterable with\n        # len() == 1 is passed in. This will be hit if there is only one server\n        # defined in the above queue lists\n        if len(tasks) > 1:\n            task_promise = group(*tasks).apply_async()\n        else:\n            task_promise = group(tasks).apply_async()\n    return task_promise\n\n\ndef prepare_build(\n        project,\n        version=None,\n        record=True,\n        force=False,\n        immutable=True,\n):\n    \"\"\"\n    Prepare a build in a Celery task for project and version.\n\n    If project has a ``build_queue``, execute the task on this build queue. If\n    project has ``skip=True``, the build is not triggered.\n\n    :param project: project's documentation to be built\n    :param version: version of the project to be built. Default: ``latest``\n    :param record: whether or not record the build in a new Build object\n    :param force: build the HTML documentation even if the files haven't changed\n    :param immutable: whether or not create an immutable Celery signature\n    :returns: Celery signature of update_docs_task and Build instance\n    :rtype: tuple\n    \"\"\"\n    # Avoid circular import\n    from readthedocs.builds.models import Build\n    from readthedocs.projects.models import Project\n    from readthedocs.projects.tasks import update_docs_task\n\n    build = None\n\n    if not Project.objects.is_active(project):\n        log.warning(\n            'Build not triggered because Project is not active: project=%s',\n            project.slug,\n        )\n        return (None, None)\n\n    if not version:\n        version = project.versions.get(slug=LATEST)\n\n    kwargs = {\n        'version_pk': version.pk,\n        'record': record,\n        'force': force,\n    }\n\n    if record:\n        build = Build.objects.create(\n            project=project,\n            version=version,\n            type='html',\n            state=BUILD_STATE_TRIGGERED,\n            success=True,\n        )\n        kwargs['build_pk'] = build.pk\n\n    options = {}\n    if project.build_queue:\n        options['queue'] = project.build_queue\n\n    # Set per-task time limit\n    time_limit = DOCKER_LIMITS['time']\n    try:\n        if project.container_time_limit:\n            time_limit = int(project.container_time_limit)\n    except ValueError:\n        log.warning('Invalid time_limit for project: %s', project.slug)\n\n    # Add 20% overhead to task, to ensure the build can timeout and the task\n    # will cleanly finish.\n    options['soft_time_limit'] = time_limit\n    options['time_limit'] = int(time_limit * 1.2)\n\n    return (\n        update_docs_task.signature(\n            args=(project.pk,),\n            kwargs=kwargs,\n            options=options,\n            immutable=True,\n        ),\n        build,\n    )\n\n\ndef trigger_build(project, version=None, record=True, force=False):\n    \"\"\"\n    Trigger a Build.\n\n    Helper that calls ``prepare_build`` and just effectively trigger the Celery\n    task to be executed by a worker.\n\n    :param project: project's documentation to be built\n    :param version: version of the project to be built. Default: ``latest``\n    :param record: whether or not record the build in a new Build object\n    :param force: build the HTML documentation even if the files haven't changed\n    :returns: Celery AsyncResult promise and Build instance\n    :rtype: tuple\n    \"\"\"\n    update_docs_task, build = prepare_build(\n        project,\n        version,\n        record,\n        force,\n        immutable=True,\n    )\n\n    if (update_docs_task, build) == (None, None):\n        # Build was skipped\n        return (None, None)\n\n    return (update_docs_task.apply_async(), build)\n\n\ndef send_email(\n        recipient, subject, template, template_html, context=None, request=None,\n        from_email=None, **kwargs\n):  # pylint: disable=unused-argument\n    \"\"\"\n    Alter context passed in and call email send task.\n\n    .. seealso::\n\n        Task :py:func:`readthedocs.core.tasks.send_email_task`\n            Task that handles templating and sending email message\n    \"\"\"\n    from ..tasks import send_email_task\n\n    if context is None:\n        context = {}\n    context['uri'] = '{scheme}://{host}'.format(\n        scheme='https',\n        host=settings.PRODUCTION_DOMAIN,\n    )\n    send_email_task.delay(\n        recipient=recipient, subject=subject, template=template,\n        template_html=template_html, context=context, from_email=from_email,\n        **kwargs\n    )\n\n\n@keep_lazy(str, SafeText)\ndef slugify(value, *args, **kwargs):\n    \"\"\"\n    Add a DNS safe option to slugify.\n\n    :param dns_safe: Remove underscores from slug as well\n    \"\"\"\n    dns_safe = kwargs.pop('dns_safe', True)\n    value = slugify_base(value, *args, **kwargs)\n    if dns_safe:\n        value = mark_safe(re.sub('[-_]+', '-', value))\n    return value\n\n\ndef safe_makedirs(directory_name):\n    \"\"\"\n    Safely create a directory.\n\n    Makedirs has an issue where it has a race condition around checking for a\n    directory and then creating it. This catches the exception in the case where\n    the dir already exists.\n    \"\"\"\n    try:\n        os.makedirs(directory_name)\n    except OSError as e:\n        if e.errno != errno.EEXIST:  # 17, FileExistsError\n            raise\n\n\ndef safe_unlink(path):\n    \"\"\"\n    Unlink ``path`` symlink using ``os.unlink``.\n\n    This helper handles the exception ``FileNotFoundError`` to avoid logging in\n    cases where the symlink does not exist already and there is nothing to\n    unlink.\n\n    :param path: symlink path to unlink\n    :type path: str\n    \"\"\"\n    try:\n        os.unlink(path)\n    except FileNotFoundError:\n        log.warning('Unlink failed. Path %s does not exists', path)\n", "path": "readthedocs/core/utils/__init__.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"Common utilty functions.\"\"\"\n\nfrom __future__ import absolute_import\n\nimport errno\nimport getpass\nimport logging\nimport os\nimport re\n\nfrom django.conf import settings\nfrom django.utils.functional import keep_lazy\nfrom django.utils.safestring import SafeText, mark_safe\nfrom django.utils.text import slugify as slugify_base\nfrom celery import group, chord\n\nfrom readthedocs.builds.constants import BUILD_STATE_TRIGGERED\nfrom readthedocs.doc_builder.constants import DOCKER_LIMITS\n\nlog = logging.getLogger(__name__)\n\nSYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser())\n\n\ndef broadcast(type, task, args, kwargs=None, callback=None):  # pylint: disable=redefined-builtin\n    \"\"\"\n    Run a broadcast across our servers.\n\n    Returns a task group that can be checked for results.\n\n    `callback` should be a task signature that will be run once,\n    after all of the broadcast tasks have finished running.\n    \"\"\"\n    assert type in ['web', 'app', 'build']\n    if kwargs is None:\n        kwargs = {}\n    default_queue = getattr(settings, 'CELERY_DEFAULT_QUEUE', 'celery')\n    if type in ['web', 'app']:\n        servers = getattr(settings, 'MULTIPLE_APP_SERVERS', [default_queue])\n    elif type in ['build']:\n        servers = getattr(settings, 'MULTIPLE_BUILD_SERVERS', [default_queue])\n\n    tasks = []\n    for server in servers:\n        task_sig = task.s(*args, **kwargs).set(queue=server)\n        tasks.append(task_sig)\n    if callback:\n        task_promise = chord(tasks, callback).apply_async()\n    else:\n        # Celery's Group class does some special handling when an iterable with\n        # len() == 1 is passed in. This will be hit if there is only one server\n        # defined in the above queue lists\n        if len(tasks) > 1:\n            task_promise = group(*tasks).apply_async()\n        else:\n            task_promise = group(tasks).apply_async()\n    return task_promise\n\n\ndef prepare_build(\n        project,\n        version=None,\n        record=True,\n        force=False,\n        immutable=True,\n):\n    \"\"\"\n    Prepare a build in a Celery task for project and version.\n\n    If project has a ``build_queue``, execute the task on this build queue. If\n    project has ``skip=True``, the build is not triggered.\n\n    :param project: project's documentation to be built\n    :param version: version of the project to be built. Default: ``project.get_default_version()``\n    :param record: whether or not record the build in a new Build object\n    :param force: build the HTML documentation even if the files haven't changed\n    :param immutable: whether or not create an immutable Celery signature\n    :returns: Celery signature of update_docs_task and Build instance\n    :rtype: tuple\n    \"\"\"\n    # Avoid circular import\n    from readthedocs.builds.models import Build\n    from readthedocs.projects.models import Project\n    from readthedocs.projects.tasks import update_docs_task\n\n    build = None\n\n    if not Project.objects.is_active(project):\n        log.warning(\n            'Build not triggered because Project is not active: project=%s',\n            project.slug,\n        )\n        return (None, None)\n\n    if not version:\n        default_version = project.get_default_version()\n        version = project.versions.get(slug=default_version)\n\n    kwargs = {\n        'version_pk': version.pk,\n        'record': record,\n        'force': force,\n    }\n\n    if record:\n        build = Build.objects.create(\n            project=project,\n            version=version,\n            type='html',\n            state=BUILD_STATE_TRIGGERED,\n            success=True,\n        )\n        kwargs['build_pk'] = build.pk\n\n    options = {}\n    if project.build_queue:\n        options['queue'] = project.build_queue\n\n    # Set per-task time limit\n    time_limit = DOCKER_LIMITS['time']\n    try:\n        if project.container_time_limit:\n            time_limit = int(project.container_time_limit)\n    except ValueError:\n        log.warning('Invalid time_limit for project: %s', project.slug)\n\n    # Add 20% overhead to task, to ensure the build can timeout and the task\n    # will cleanly finish.\n    options['soft_time_limit'] = time_limit\n    options['time_limit'] = int(time_limit * 1.2)\n\n    return (\n        update_docs_task.signature(\n            args=(project.pk,),\n            kwargs=kwargs,\n            options=options,\n            immutable=True,\n        ),\n        build,\n    )\n\n\ndef trigger_build(project, version=None, record=True, force=False):\n    \"\"\"\n    Trigger a Build.\n\n    Helper that calls ``prepare_build`` and just effectively trigger the Celery\n    task to be executed by a worker.\n\n    :param project: project's documentation to be built\n    :param version: version of the project to be built. Default: ``latest``\n    :param record: whether or not record the build in a new Build object\n    :param force: build the HTML documentation even if the files haven't changed\n    :returns: Celery AsyncResult promise and Build instance\n    :rtype: tuple\n    \"\"\"\n    update_docs_task, build = prepare_build(\n        project,\n        version,\n        record,\n        force,\n        immutable=True,\n    )\n\n    if (update_docs_task, build) == (None, None):\n        # Build was skipped\n        return (None, None)\n\n    return (update_docs_task.apply_async(), build)\n\n\ndef send_email(\n        recipient, subject, template, template_html, context=None, request=None,\n        from_email=None, **kwargs\n):  # pylint: disable=unused-argument\n    \"\"\"\n    Alter context passed in and call email send task.\n\n    .. seealso::\n\n        Task :py:func:`readthedocs.core.tasks.send_email_task`\n            Task that handles templating and sending email message\n    \"\"\"\n    from ..tasks import send_email_task\n\n    if context is None:\n        context = {}\n    context['uri'] = '{scheme}://{host}'.format(\n        scheme='https',\n        host=settings.PRODUCTION_DOMAIN,\n    )\n    send_email_task.delay(\n        recipient=recipient, subject=subject, template=template,\n        template_html=template_html, context=context, from_email=from_email,\n        **kwargs\n    )\n\n\n@keep_lazy(str, SafeText)\ndef slugify(value, *args, **kwargs):\n    \"\"\"\n    Add a DNS safe option to slugify.\n\n    :param dns_safe: Remove underscores from slug as well\n    \"\"\"\n    dns_safe = kwargs.pop('dns_safe', True)\n    value = slugify_base(value, *args, **kwargs)\n    if dns_safe:\n        value = mark_safe(re.sub('[-_]+', '-', value))\n    return value\n\n\ndef safe_makedirs(directory_name):\n    \"\"\"\n    Safely create a directory.\n\n    Makedirs has an issue where it has a race condition around checking for a\n    directory and then creating it. This catches the exception in the case where\n    the dir already exists.\n    \"\"\"\n    try:\n        os.makedirs(directory_name)\n    except OSError as e:\n        if e.errno != errno.EEXIST:  # 17, FileExistsError\n            raise\n\n\ndef safe_unlink(path):\n    \"\"\"\n    Unlink ``path`` symlink using ``os.unlink``.\n\n    This helper handles the exception ``FileNotFoundError`` to avoid logging in\n    cases where the symlink does not exist already and there is nothing to\n    unlink.\n\n    :param path: symlink path to unlink\n    :type path: str\n    \"\"\"\n    try:\n        os.unlink(path)\n    except FileNotFoundError:\n        log.warning('Unlink failed. Path %s does not exists', path)\n", "path": "readthedocs/core/utils/__init__.py"}]}