We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Expire project cache when language mappings change
# Steps to reproduce:

- change language mappings for a project

# Results (Expected/Actual):

- expected is that on disk files that are now mapped should be immediately detected

# Environment (i.e. 'pootle --version', DB, OS, Browser):

2.9+


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pootle/apps/pootle_project/forms.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) Pootle contributors.
4 #
5 # This file is a part of the Pootle project. It is distributed under the GPL3
6 # or later license. See the LICENSE file for a copy of the license and the
7 # AUTHORS file for copyright and authorship information.
8 
9 from django import forms
10 from django.db import connection
11 from django.forms.models import BaseModelFormSet
12 
13 from django_rq.queues import get_queue
14 
15 from pootle.core.utils.db import useable_connection
16 from pootle.i18n.gettext import ugettext as _
17 from pootle_config.utils import ObjectConfig
18 from pootle_language.models import Language
19 from pootle_misc.forms import LiberalModelChoiceField
20 from pootle_project.models import Project
21 from pootle_translationproject.models import TranslationProject
22 from pootle_translationproject.signals import (tp_init_failed_async,
23                                                tp_inited_async)
24 
25 
26 def update_translation_project(tp, response_url):
27     """Wraps translation project initializing to allow it to be running
28     as RQ job.
29     """
30     try:
31         with useable_connection():
32             tp.init_from_templates()
33     except Exception as e:
34         tp_init_failed_async.send(sender=tp.__class__, instance=tp)
35         raise e
36     tp_inited_async.send(sender=tp.__class__,
37                          instance=tp, response_url=response_url)
38 
39 
40 class TranslationProjectFormSet(BaseModelFormSet):
41 
42     def __init__(self, *args, **kwargs):
43         self.response_url = kwargs.pop("response_url")
44         super(TranslationProjectFormSet, self).__init__(*args, **kwargs)
45         self.queryset = self.queryset.select_related("language", "project")
46 
47     def save_new(self, form, commit=True):
48         return form.save(
49             response_url=self.response_url,
50             commit=commit)
51 
52     def delete_existing(self, tp, commit=True):
53         config = ObjectConfig(tp.project)
54         mapping = config.get("pootle.core.lang_mapping", {})
55         if tp.language.code in mapping:
56             del mapping[tp.language.code]
57             config["pootle.core.lang_mapping"] = mapping
58         super(TranslationProjectFormSet, self).delete_existing(
59             tp, commit=commit)
60 
61 
62 class TranslationProjectForm(forms.ModelForm):
63 
64     language = LiberalModelChoiceField(
65         label=_("Language"),
66         queryset=Language.objects.all(),
67         widget=forms.Select(
68             attrs={
69                 'class': 'js-select2 select2-language'}))
70     project = forms.ModelChoiceField(
71         queryset=Project.objects.all(),
72         widget=forms.HiddenInput())
73 
74     fs_code = forms.CharField(
75         label=_("Filesystem language code"),
76         required=False)
77 
78     class Meta(object):
79         prefix = "existing_language"
80         model = TranslationProject
81         fields = ('language', 'project')
82 
83     def __init__(self, *args, **kwargs):
84         """If this form is not bound, it must be called with an initial value
85         for Project.
86         """
87         super(TranslationProjectForm, self).__init__(*args, **kwargs)
88         if kwargs.get("instance"):
89             project_id = kwargs["instance"].project.pk
90             project = kwargs["instance"].project
91             language = kwargs["instance"].language
92             mappings = project.config.get("pootle.core.lang_mapping", {})
93             mappings = dict((v, k) for k, v in mappings.iteritems())
94             mapped = mappings.get(language.code)
95             self.fields["fs_code"].initial = mapped
96         else:
97             project_id = kwargs["initial"]["project"]
98             self.fields["language"].queryset = (
99                 self.fields["language"].queryset.exclude(
100                     translationproject__project_id=project_id))
101         self.fields["project"].queryset = self.fields[
102             "project"].queryset.filter(pk=project_id)
103 
104     def clean(self):
105         project = self.cleaned_data.get("project")
106         language = self.cleaned_data.get("language")
107         if project and language:
108             mapped_code = self.cleaned_data["fs_code"]
109             mapping = project.config.get("pootle.core.lang_mapping", {})
110             if mapped_code:
111                 tps = project.translationproject_set.all()
112                 lang_codes = tps.values_list("language__code", flat=True)
113                 bad_fs_code = (
114                     (mapped_code in mapping.keys()
115                      and not mapping.get(mapped_code) == language.code)
116                     or mapped_code in lang_codes)
117                 if bad_fs_code:
118                     self.errors["fs_code"] = self.error_class(
119                         [_("Unable to add mapped code '%(mapped_code)s' for "
120                            "language '%(code)s'. Mapped filesystem codes must "
121                            "be unique and cannot be in use with an existing "
122                            "Translation Project")
123                          % dict(mapped_code=mapped_code, code=language.code)])
124             if language.code in mapping.keys():
125                 self.errors["language"] = self.error_class(
126                     [_("Unable to add language '%s'. "
127                        "Another language is already mapped to this code")
128                      % language.code])
129 
130     def save(self, response_url=None, commit=True):
131         tp = self.instance
132         initialize_from_templates = False
133         if tp.id is None:
134             initialize_from_templates = tp.can_be_inited_from_templates()
135         tp = super(TranslationProjectForm, self).save(commit)
136         project = tp.project
137         config = ObjectConfig(project)
138         mappings = config.get("pootle.core.lang_mapping", {})
139         mappings = dict((v, k) for k, v in mappings.iteritems())
140         if not self.cleaned_data["fs_code"]:
141             if tp.language.code in mappings:
142                 del mappings[tp.language.code]
143         else:
144             mappings[tp.language.code] = self.cleaned_data["fs_code"]
145         config["pootle.core.lang_mapping"] = dict(
146             (v, k) for k, v in mappings.iteritems())
147         if initialize_from_templates:
148             def _enqueue_job():
149                 queue = get_queue('default')
150                 queue.enqueue(
151                     update_translation_project,
152                     tp,
153                     response_url)
154             connection.on_commit(_enqueue_job)
155         return tp
156
```
--- 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/pootle/apps/pootle_project/forms.py b/pootle/apps/pootle_project/forms.py
--- a/pootle/apps/pootle_project/forms.py
+++ b/pootle/apps/pootle_project/forms.py
@@ -12,6 +12,7 @@
 
 from django_rq.queues import get_queue
 
+from pootle.core.signals import update_revisions
 from pootle.core.utils.db import useable_connection
 from pootle.i18n.gettext import ugettext as _
 from pootle_config.utils import ObjectConfig
@@ -140,8 +141,18 @@
         if not self.cleaned_data["fs_code"]:
             if tp.language.code in mappings:
                 del mappings[tp.language.code]
+                context = self.instance.project.directory
+                update_revisions.send(
+                    context.__class__,
+                    instance=context,
+                    keys=["stats"])
         else:
             mappings[tp.language.code] = self.cleaned_data["fs_code"]
+            context = self.instance.project.directory
+            update_revisions.send(
+                context.__class__,
+                instance=context,
+                keys=["stats"])
         config["pootle.core.lang_mapping"] = dict(
             (v, k) for k, v in mappings.iteritems())
         if initialize_from_templates:

{"golden_diff": "diff --git a/pootle/apps/pootle_project/forms.py b/pootle/apps/pootle_project/forms.py\n--- a/pootle/apps/pootle_project/forms.py\n+++ b/pootle/apps/pootle_project/forms.py\n@@ -12,6 +12,7 @@\n \n from django_rq.queues import get_queue\n \n+from pootle.core.signals import update_revisions\n from pootle.core.utils.db import useable_connection\n from pootle.i18n.gettext import ugettext as _\n from pootle_config.utils import ObjectConfig\n@@ -140,8 +141,18 @@\n         if not self.cleaned_data[\"fs_code\"]:\n             if tp.language.code in mappings:\n                 del mappings[tp.language.code]\n+                context = self.instance.project.directory\n+                update_revisions.send(\n+                    context.__class__,\n+                    instance=context,\n+                    keys=[\"stats\"])\n         else:\n             mappings[tp.language.code] = self.cleaned_data[\"fs_code\"]\n+            context = self.instance.project.directory\n+            update_revisions.send(\n+                context.__class__,\n+                instance=context,\n+                keys=[\"stats\"])\n         config[\"pootle.core.lang_mapping\"] = dict(\n             (v, k) for k, v in mappings.iteritems())\n         if initialize_from_templates:\n", "issue": "Expire project cache when language mappings change\n# Steps to reproduce:\r\n\r\n- change language mappings for a project\r\n\r\n# Results (Expected/Actual):\r\n\r\n- expected is that on disk files that are now mapped should be immediately detected\r\n\r\n# Environment (i.e. 'pootle --version', DB, OS, Browser):\r\n\r\n2.9+\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright (C) Pootle contributors.\n#\n# This file is a part of the Pootle project. It is distributed under the GPL3\n# or later license. See the LICENSE file for a copy of the license and the\n# AUTHORS file for copyright and authorship information.\n\nfrom django import forms\nfrom django.db import connection\nfrom django.forms.models import BaseModelFormSet\n\nfrom django_rq.queues import get_queue\n\nfrom pootle.core.utils.db import useable_connection\nfrom pootle.i18n.gettext import ugettext as _\nfrom pootle_config.utils import ObjectConfig\nfrom pootle_language.models import Language\nfrom pootle_misc.forms import LiberalModelChoiceField\nfrom pootle_project.models import Project\nfrom pootle_translationproject.models import TranslationProject\nfrom pootle_translationproject.signals import (tp_init_failed_async,\n                                               tp_inited_async)\n\n\ndef update_translation_project(tp, response_url):\n    \"\"\"Wraps translation project initializing to allow it to be running\n    as RQ job.\n    \"\"\"\n    try:\n        with useable_connection():\n            tp.init_from_templates()\n    except Exception as e:\n        tp_init_failed_async.send(sender=tp.__class__, instance=tp)\n        raise e\n    tp_inited_async.send(sender=tp.__class__,\n                         instance=tp, response_url=response_url)\n\n\nclass TranslationProjectFormSet(BaseModelFormSet):\n\n    def __init__(self, *args, **kwargs):\n        self.response_url = kwargs.pop(\"response_url\")\n        super(TranslationProjectFormSet, self).__init__(*args, **kwargs)\n        self.queryset = self.queryset.select_related(\"language\", \"project\")\n\n    def save_new(self, form, commit=True):\n        return form.save(\n            response_url=self.response_url,\n            commit=commit)\n\n    def delete_existing(self, tp, commit=True):\n        config = ObjectConfig(tp.project)\n        mapping = config.get(\"pootle.core.lang_mapping\", {})\n        if tp.language.code in mapping:\n            del mapping[tp.language.code]\n            config[\"pootle.core.lang_mapping\"] = mapping\n        super(TranslationProjectFormSet, self).delete_existing(\n            tp, commit=commit)\n\n\nclass TranslationProjectForm(forms.ModelForm):\n\n    language = LiberalModelChoiceField(\n        label=_(\"Language\"),\n        queryset=Language.objects.all(),\n        widget=forms.Select(\n            attrs={\n                'class': 'js-select2 select2-language'}))\n    project = forms.ModelChoiceField(\n        queryset=Project.objects.all(),\n        widget=forms.HiddenInput())\n\n    fs_code = forms.CharField(\n        label=_(\"Filesystem language code\"),\n        required=False)\n\n    class Meta(object):\n        prefix = \"existing_language\"\n        model = TranslationProject\n        fields = ('language', 'project')\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"If this form is not bound, it must be called with an initial value\n        for Project.\n        \"\"\"\n        super(TranslationProjectForm, self).__init__(*args, **kwargs)\n        if kwargs.get(\"instance\"):\n            project_id = kwargs[\"instance\"].project.pk\n            project = kwargs[\"instance\"].project\n            language = kwargs[\"instance\"].language\n            mappings = project.config.get(\"pootle.core.lang_mapping\", {})\n            mappings = dict((v, k) for k, v in mappings.iteritems())\n            mapped = mappings.get(language.code)\n            self.fields[\"fs_code\"].initial = mapped\n        else:\n            project_id = kwargs[\"initial\"][\"project\"]\n            self.fields[\"language\"].queryset = (\n                self.fields[\"language\"].queryset.exclude(\n                    translationproject__project_id=project_id))\n        self.fields[\"project\"].queryset = self.fields[\n            \"project\"].queryset.filter(pk=project_id)\n\n    def clean(self):\n        project = self.cleaned_data.get(\"project\")\n        language = self.cleaned_data.get(\"language\")\n        if project and language:\n            mapped_code = self.cleaned_data[\"fs_code\"]\n            mapping = project.config.get(\"pootle.core.lang_mapping\", {})\n            if mapped_code:\n                tps = project.translationproject_set.all()\n                lang_codes = tps.values_list(\"language__code\", flat=True)\n                bad_fs_code = (\n                    (mapped_code in mapping.keys()\n                     and not mapping.get(mapped_code) == language.code)\n                    or mapped_code in lang_codes)\n                if bad_fs_code:\n                    self.errors[\"fs_code\"] = self.error_class(\n                        [_(\"Unable to add mapped code '%(mapped_code)s' for \"\n                           \"language '%(code)s'. Mapped filesystem codes must \"\n                           \"be unique and cannot be in use with an existing \"\n                           \"Translation Project\")\n                         % dict(mapped_code=mapped_code, code=language.code)])\n            if language.code in mapping.keys():\n                self.errors[\"language\"] = self.error_class(\n                    [_(\"Unable to add language '%s'. \"\n                       \"Another language is already mapped to this code\")\n                     % language.code])\n\n    def save(self, response_url=None, commit=True):\n        tp = self.instance\n        initialize_from_templates = False\n        if tp.id is None:\n            initialize_from_templates = tp.can_be_inited_from_templates()\n        tp = super(TranslationProjectForm, self).save(commit)\n        project = tp.project\n        config = ObjectConfig(project)\n        mappings = config.get(\"pootle.core.lang_mapping\", {})\n        mappings = dict((v, k) for k, v in mappings.iteritems())\n        if not self.cleaned_data[\"fs_code\"]:\n            if tp.language.code in mappings:\n                del mappings[tp.language.code]\n        else:\n            mappings[tp.language.code] = self.cleaned_data[\"fs_code\"]\n        config[\"pootle.core.lang_mapping\"] = dict(\n            (v, k) for k, v in mappings.iteritems())\n        if initialize_from_templates:\n            def _enqueue_job():\n                queue = get_queue('default')\n                queue.enqueue(\n                    update_translation_project,\n                    tp,\n                    response_url)\n            connection.on_commit(_enqueue_job)\n        return tp\n", "path": "pootle/apps/pootle_project/forms.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# Copyright (C) Pootle contributors.\n#\n# This file is a part of the Pootle project. It is distributed under the GPL3\n# or later license. See the LICENSE file for a copy of the license and the\n# AUTHORS file for copyright and authorship information.\n\nfrom django import forms\nfrom django.db import connection\nfrom django.forms.models import BaseModelFormSet\n\nfrom django_rq.queues import get_queue\n\nfrom pootle.core.signals import update_revisions\nfrom pootle.core.utils.db import useable_connection\nfrom pootle.i18n.gettext import ugettext as _\nfrom pootle_config.utils import ObjectConfig\nfrom pootle_language.models import Language\nfrom pootle_misc.forms import LiberalModelChoiceField\nfrom pootle_project.models import Project\nfrom pootle_translationproject.models import TranslationProject\nfrom pootle_translationproject.signals import (tp_init_failed_async,\n                                               tp_inited_async)\n\n\ndef update_translation_project(tp, response_url):\n    \"\"\"Wraps translation project initializing to allow it to be running\n    as RQ job.\n    \"\"\"\n    try:\n        with useable_connection():\n            tp.init_from_templates()\n    except Exception as e:\n        tp_init_failed_async.send(sender=tp.__class__, instance=tp)\n        raise e\n    tp_inited_async.send(sender=tp.__class__,\n                         instance=tp, response_url=response_url)\n\n\nclass TranslationProjectFormSet(BaseModelFormSet):\n\n    def __init__(self, *args, **kwargs):\n        self.response_url = kwargs.pop(\"response_url\")\n        super(TranslationProjectFormSet, self).__init__(*args, **kwargs)\n        self.queryset = self.queryset.select_related(\"language\", \"project\")\n\n    def save_new(self, form, commit=True):\n        return form.save(\n            response_url=self.response_url,\n            commit=commit)\n\n    def delete_existing(self, tp, commit=True):\n        config = ObjectConfig(tp.project)\n        mapping = config.get(\"pootle.core.lang_mapping\", {})\n        if tp.language.code in mapping:\n            del mapping[tp.language.code]\n            config[\"pootle.core.lang_mapping\"] = mapping\n        super(TranslationProjectFormSet, self).delete_existing(\n            tp, commit=commit)\n\n\nclass TranslationProjectForm(forms.ModelForm):\n\n    language = LiberalModelChoiceField(\n        label=_(\"Language\"),\n        queryset=Language.objects.all(),\n        widget=forms.Select(\n            attrs={\n                'class': 'js-select2 select2-language'}))\n    project = forms.ModelChoiceField(\n        queryset=Project.objects.all(),\n        widget=forms.HiddenInput())\n\n    fs_code = forms.CharField(\n        label=_(\"Filesystem language code\"),\n        required=False)\n\n    class Meta(object):\n        prefix = \"existing_language\"\n        model = TranslationProject\n        fields = ('language', 'project')\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"If this form is not bound, it must be called with an initial value\n        for Project.\n        \"\"\"\n        super(TranslationProjectForm, self).__init__(*args, **kwargs)\n        if kwargs.get(\"instance\"):\n            project_id = kwargs[\"instance\"].project.pk\n            project = kwargs[\"instance\"].project\n            language = kwargs[\"instance\"].language\n            mappings = project.config.get(\"pootle.core.lang_mapping\", {})\n            mappings = dict((v, k) for k, v in mappings.iteritems())\n            mapped = mappings.get(language.code)\n            self.fields[\"fs_code\"].initial = mapped\n        else:\n            project_id = kwargs[\"initial\"][\"project\"]\n            self.fields[\"language\"].queryset = (\n                self.fields[\"language\"].queryset.exclude(\n                    translationproject__project_id=project_id))\n        self.fields[\"project\"].queryset = self.fields[\n            \"project\"].queryset.filter(pk=project_id)\n\n    def clean(self):\n        project = self.cleaned_data.get(\"project\")\n        language = self.cleaned_data.get(\"language\")\n        if project and language:\n            mapped_code = self.cleaned_data[\"fs_code\"]\n            mapping = project.config.get(\"pootle.core.lang_mapping\", {})\n            if mapped_code:\n                tps = project.translationproject_set.all()\n                lang_codes = tps.values_list(\"language__code\", flat=True)\n                bad_fs_code = (\n                    (mapped_code in mapping.keys()\n                     and not mapping.get(mapped_code) == language.code)\n                    or mapped_code in lang_codes)\n                if bad_fs_code:\n                    self.errors[\"fs_code\"] = self.error_class(\n                        [_(\"Unable to add mapped code '%(mapped_code)s' for \"\n                           \"language '%(code)s'. Mapped filesystem codes must \"\n                           \"be unique and cannot be in use with an existing \"\n                           \"Translation Project\")\n                         % dict(mapped_code=mapped_code, code=language.code)])\n            if language.code in mapping.keys():\n                self.errors[\"language\"] = self.error_class(\n                    [_(\"Unable to add language '%s'. \"\n                       \"Another language is already mapped to this code\")\n                     % language.code])\n\n    def save(self, response_url=None, commit=True):\n        tp = self.instance\n        initialize_from_templates = False\n        if tp.id is None:\n            initialize_from_templates = tp.can_be_inited_from_templates()\n        tp = super(TranslationProjectForm, self).save(commit)\n        project = tp.project\n        config = ObjectConfig(project)\n        mappings = config.get(\"pootle.core.lang_mapping\", {})\n        mappings = dict((v, k) for k, v in mappings.iteritems())\n        if not self.cleaned_data[\"fs_code\"]:\n            if tp.language.code in mappings:\n                del mappings[tp.language.code]\n                context = self.instance.project.directory\n                update_revisions.send(\n                    context.__class__,\n                    instance=context,\n                    keys=[\"stats\"])\n        else:\n            mappings[tp.language.code] = self.cleaned_data[\"fs_code\"]\n            context = self.instance.project.directory\n            update_revisions.send(\n                context.__class__,\n                instance=context,\n                keys=[\"stats\"])\n        config[\"pootle.core.lang_mapping\"] = dict(\n            (v, k) for k, v in mappings.iteritems())\n        if initialize_from_templates:\n            def _enqueue_job():\n                queue = get_queue('default')\n                queue.enqueue(\n                    update_translation_project,\n                    tp,\n                    response_url)\n            connection.on_commit(_enqueue_job)\n        return tp\n", "path": "pootle/apps/pootle_project/forms.py"}]}