prompt: 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()
  
  ```
  
golden_diff: 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:
  
verification_info: {"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"}]}