[damned-lies: 18/18] merge: from 'develop'




commit 35770e0c3ddd8e0d1ba04a7586cbf99c98eb2216
Merge: deeb1429 99fcc47f
Author: Guillaume Bernard <associations guillaume-bernard fr>
Date:   Wed Oct 5 17:00:28 2022 +0200

    merge: from 'develop'

 .gitlab-ci.yml                                     |    4 +-
 .pre-commit-config.yaml                            |   16 +
 CONTRIBUTING.md                                    |   13 +-
 README.md                                          |   55 +-
 api/tests.py                                       |  225 ++--
 api/urls.py                                        |   42 +-
 api/views.py                                       |  228 ++--
 common/backends.py                                 |    4 +-
 common/context_processors.py                       |    2 +-
 common/fields.py                                   |    5 +-
 common/middleware.py                               |    4 +-
 common/templatetags/list_to_columns.py             |    4 +-
 common/tests.py                                    |  107 +-
 common/utils.py                                    |   61 +-
 common/views.py                                    |   95 +-
 containers/build_buildah_runtime.sh                |    1 -
 .../configuration/local_settings.py.jinja2         |    6 +
 .../production/render_configuration_templates.sh   |    5 +
 damnedlies/context_processors.py                   |   12 +
 damnedlies/settings.py                             |  214 +--
 damnedlies/settings_tests.py                       |    8 +-
 damnedlies/urls.py                                 |  156 +--
 docs/source/conf.py                                |   46 +-
 feeds/urls.py                                      |    6 +-
 languages/admin.py                                 |    3 +-
 languages/management/commands/load-plurals.py      |  218 ++--
 languages/migrations/0001_initial.py               |   23 +-
 languages/models.py                                |   35 +-
 languages/tests.py                                 |   24 +-
 languages/urls.py                                  |   32 +-
 languages/views.py                                 |  139 +-
 people/admin.py                                    |    7 +-
 people/forms.py                                    |  107 +-
 people/migrations/0001_initial.py                  |   62 +-
 .../0002_set_use_gravatar_verbose_name.py          |   10 +-
 people/migrations/0003_person_avatar_service.py    |   13 +-
 people/migrations/0004_migrate_use_gravatar.py     |    5 +-
 .../migrations/0005_remove_person_use_gravatar.py  |    6 +-
 .../0006_remove_person_bugzilla_account.py         |    6 +-
 people/migrations/0007_person_auth_token.py        |    8 +-
 people/models.py                                   |   83 +-
 people/templatetags/people.py                      |   15 +-
 people/tests.py                                    |  227 ++--
 people/urls.py                                     |   36 +-
 people/views.py                                    |   84 +-
 pyproject.toml                                     |    8 +-
 requirements-dev.txt                               |    4 +
 requirements.txt                                   |    3 +-
 setup.py                                           |    5 +-
 stats/admin.py                                     |  151 ++-
 stats/doap.py                                      |   40 +-
 stats/forms.py                                     |   55 +-
 stats/management/commands/archive-release.py       |   28 +-
 stats/management/commands/compile-trans.py         |   25 +-
 stats/management/commands/run-maintenance.py       |    2 +-
 stats/management/commands/update-from-doap.py      |    7 +-
 stats/management/commands/update-stats.py          |   68 +-
 stats/management/commands/update-trans.py          |    7 +-
 stats/migrations/0001_initial.py                   |  368 ++++--
 stats/migrations/0002_add_category_name.py         |   20 +-
 stats/migrations/0003_migrate_category_names.py    |   40 +-
 stats/migrations/0004_remove_old_cat_name.py       |   20 +-
 stats/migrations/0005_update_module_name_field.py  |   21 +-
 stats/migrations/0006_add_domain_branch_from_to.py |   31 +-
 stats/migrations/0007_extend_bugs_base.py          |    6 +-
 stats/migrations/0008_domain_extra_its_dirs.py     |   10 +-
 .../migrations/0009_remove_null_on_text_fields.py  |   96 +-
 stats/migrations/0010_pot_method.py                |   36 +-
 stats/migrations/0011_migrate_pot_method.py        |   22 +-
 .../0012_remove_domain_pot_method_old.py           |    6 +-
 stats/migrations/0013_domain_layout.py             |   12 +-
 stats/migrations/0014_migrate_dir_to_layout.py     |   10 +-
 stats/migrations/0015_remove_domain_directory.py   |    6 +-
 stats/migrations/0016_removed_bugs_fields.py       |   10 +-
 stats/migrations/0017_pofile_path_relative.py      |   14 +-
 stats/migrations/0018_new_jsonfields.py            |   22 +-
 stats/migrations/0019_migrate_old_custom_files.py  |    4 +-
 stats/migrations/0020_remove_pofile_figures_old.py |   10 +-
 stats/migrations/0021_release_name_unique.py       |    6 +-
 stats/models.py                                    | 1375 ++++++++++----------
 stats/potdiff.py                                   |    9 +-
 stats/repos.py                                     |   78 +-
 stats/templatetags/stats_extras.py                 |  152 ++-
 stats/tests/fixture_factory.py                     |  143 +-
 stats/tests/tests.py                               |  845 ++++++------
 stats/tests/utils.py                               |   20 +-
 stats/utils.py                                     |  345 +++--
 stats/views.py                                     |  225 ++--
 teams/admin.py                                     |   18 +-
 teams/forms.py                                     |   64 +-
 teams/migrations/0001_initial.py                   |   73 +-
 teams/models.py                                    |   93 +-
 teams/tests.py                                     |  175 ++-
 teams/urls.py                                      |   13 +-
 teams/views.py                                     |  108 +-
 templates/about.html                               |   10 +-
 templates/base.html                                |    4 +
 vertimus/admin.py                                  |   18 +-
 vertimus/feeds.py                                  |   71 +-
 vertimus/forms.py                                  |   83 +-
 vertimus/migrations/0001_initial.py                |  278 ++--
 vertimus/migrations/0002_state_person_blank.py     |    8 +-
 vertimus/migrations/0003_add_action_sent_to_ml.py  |   10 +-
 vertimus/migrations/0004_tables_to_utf8mb4.py      |    7 +-
 vertimus/migrations/0005_action_proxy_pofile.py    |   31 +-
 vertimus/migrations/0006_pofile_path_relative.py   |    5 +-
 .../migrations/0008_actions_on_delete_setnull.py   |   16 +-
 vertimus/models.py                                 |  362 +++---
 vertimus/templatetags/vertimus.py                  |   32 +-
 vertimus/tests/tests.py                            |  627 ++++-----
 vertimus/urls.py                                   |   53 +-
 vertimus/views.py                                  |  324 ++---
 112 files changed, 4768 insertions(+), 4542 deletions(-)
---
diff --cc CONTRIBUTING.md
index 48fa6b70,33c77dd5..2afe3533
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@@ -93,11 -93,11 +93,11 @@@ You can install them on your operating 
  
  * On **Debian** based systems:
    ```bash
-   apt install gettext intltool itstool libmariadbclient-dev libicu-dev libxml2-dev python3-dev yelp-tools 
build-essential translate-toolkit
 -  apt install gettext intltool itstool libmariadbclient-dev libicu-dev libxml2-dev python3-dev yelp-tools 
build-essential python3-pillow pyicu
++  apt install gettext intltool itstool libmariadbclient-dev libicu-dev libxml2-dev python3-dev yelp-tools 
build-essential python3-pillow pyicu translate-toolkit
    ```
  * On **Fedora** based systems:
    ```
-   dnf install gettext intltool itstool mariadb-devel libicu-devel libxml2-devel python-devel yelp-tools 
@development-tools translate-toolkit
 -  dnf install gettext intltool itstool mariadb-devel libicu-devel libxml2-devel python-devel yelp-tools 
@development-tools python3-pillow python3-pyicu
++  dnf install gettext intltool itstool mariadb-devel libicu-devel libxml2-devel python-devel yelp-tools 
@development-tools python3-pillow python3-pyicu translate-toolkit
    ```
  
  ## Python environment
diff --cc api/tests.py
index e9cc976b,0ef7abf8..ee71b61f
--- a/api/tests.py
+++ b/api/tests.py
@@@ -151,90 -158,50 +159,81 @@@ class APITests(TestCase)
          response = self.client.post(url, data={})
          self.assertEqual(response.status_code, 403)
  
 -    def test_upload_translation(self):
 -        translator = Person.objects.create(
++
 +class APIUploadTests(TestCase):
-     fixtures = ['sample_data.json']
++    fixtures = ["sample_data.json"]
 +
 +    @classmethod
 +    def setUpTestData(cls):
 +        super().setUpTestData()
 +        cls.translator = Person.objects.create(
-             first_name='John', last_name='Translator',
-             email='jt devnull com', username='translator'
+             first_name="John", last_name="Translator", email="jt devnull com", username="translator"
          )
-         cls.team = Team.objects.get(name='fr')
 -        somebody = Person.objects.create(email="some devnull com", username="somebody")
 -        team = Team.objects.get(name="fr")
 -        Role.objects.create(team=team, person=translator)
 -        Role.objects.create(team=team, person=somebody)
++        cls.team = Team.objects.get(name="fr")
 +        Role.objects.create(team=cls.team, person=cls.translator)
-         cls.url = reverse('api-upload', args=['gnome-hello', 'master', 'po', 'fr'])
++        cls.url = reverse("api-upload", args=["gnome-hello", "master", "po", "fr"])
 +        cls.test_po = Path(__file__).parent.parent / "stats" / "tests" / "test.po"
  
 +    def _get_module_state(self):
 +        """Create basic data ready for testing file upload."""
          _, _, state = get_vertimus_state(
-             Branch.objects.get(module__name='gnome-hello'),
-             Domain.objects.get(module__name='gnome-hello', name='po'),
-             Language.objects.get(locale='fr')
+             Branch.objects.get(module__name="gnome-hello"),
+             Domain.objects.get(module__name="gnome-hello", name="po"),
+             Language.objects.get(locale="fr"),
          )
 -        url = reverse("api-upload", args=["gnome-hello", "master", "po", "fr"])
 -        test_po = Path(__file__).parent.parent / "stats" / "tests" / "test.po"
 -        # Test anonymous cannot post
 -        with test_po.open("rb") as fh:
 -            response = self.client.post(url, data={"file": File(fh)})
 -            self.assertRedirects(response, reverse("login") + f"?next={url}")
 +        return state
  
 -        state.change_state(StateTranslating, person=translator)
 +    def test_upload_translation_anonymously(self):
 +        """Non-logged in user cannot submit file."""
-         somebody = Person.objects.create(
-             email='some devnull com', username='somebody'
-         )
++        somebody = Person.objects.create(email="some devnull com", username="somebody")
 +        Role.objects.create(team=self.team, person=somebody)
  
 -        # somebody cannot post translation if reserved by translator.
 +        # Anonymous cannot post
-         with self.test_po.open('rb') as fh:
-             response = self.client.post(self.url, data={'file': File(fh)})
-             self.assertRedirects(response, reverse('login') + f'?next={self.url}')
++        with self.test_po.open("rb") as fh:
++            response = self.client.post(self.url, data={"file": File(fh)})
++            self.assertRedirects(response, reverse("login") + f"?next={self.url}")
 +
 +    def test_upload_translation_translating(self):
 +        """If module is already reserved by another translator, uploading will be refused."""
 +        state = self._get_module_state()
 +        state.change_state(StateTranslating, person=self.translator)
 +
-         somebody = Person.objects.create(
-             email='some devnull com', username='somebody'
-         )
++        somebody = Person.objects.create(email="some devnull com", username="somebody")
 +        Role.objects.create(team=self.team, person=somebody)
          self.client.force_login(somebody)
-         with self.test_po.open('rb') as fh:
-             response = self.client.post(self.url, data={'file': File(fh)})
 -        with test_po.open("rb") as fh:
 -            response = self.client.post(url, data={"file": File(fh)})
++
++        with self.test_po.open("rb") as fh:
++            response = self.client.post(self.url, data={"file": File(fh)})
              self.assertEqual(response.status_code, 403)
  
 -        self.client.force_login(translator)
 -        with test_po.open("rb") as fh:
 -            response = self.client.post(url, data={"file": File(fh)})
 +    def test_upload_invalid_file(self):
 +        state = self._get_module_state()
 +        state.change_state(StateTranslating, person=self.translator)
 +        self.client.force_login(self.translator)
-         response = self.client.post(self.url, data={
-             'file': SimpleUploadedFile('filename.po', b'No valid po file content')
-         })
-         err = '.po file does not pass “msgfmt -vc”. Please correct the file and try again.'
-         self.assertEqual(
-             response.json(),
-             {
-                 'result': 'Error',
-                 'error': {'file': [err]}
-             }
++        response = self.client.post(
++            self.url, data={"file": SimpleUploadedFile("filename.po", b"No valid po file content")}
 +        )
++        err = ".po file does not pass “msgfmt -vc”. Please correct the file and try again."
++        self.assertEqual(response.json(), {"result": "Error", "error": {"file": [err]}})
 +
 +    def test_upload_translation(self):
 +        state = self._get_module_state()
 +        state.change_state(StateTranslating, person=self.translator)
 +        self.client.force_login(self.translator)
-         with self.test_po.open('rb') as fh:
-             response = self.client.post(self.url, data={'file': File(fh)})
-         self.assertEqual(response.json(), {'result': 'OK'})
++        with self.test_po.open("rb") as fh:
++            response = self.client.post(self.url, data={"file": File(fh)})
+         self.assertEqual(response.json(), {"result": "OK"})
          self.assertEqual(len(mail.outbox), 1)
 -        self.assertEqual(mail.outbox[0].recipients(), [team.mailing_list])
 +        self.assertEqual(mail.outbox[0].recipients(), [self.team.mailing_list])
  
          # Test upload with comment
 -        state.change_state(StateTranslating, person=translator)
 -        with test_po.open("rb") as fh:
 +        state.change_state(StateTranslating, person=self.translator)
-         with self.test_po.open('rb') as fh:
++        with self.test_po.open("rb") as fh:
              data = {
-                 'file': File(fh),
-                 'comment': 'The comment',
+                 "file": File(fh),
+                 "comment": "The comment",
              }
 -            response = self.client.post(url, data=data)
 +            response = self.client.post(self.url, data=data)
-         self.assertEqual(response.json(), {'result': 'OK'})
+         self.assertEqual(response.json(), {"result": "OK"})
          action = Action.objects.last()
          self.assertEqual(action.comment, "The comment")
diff --cc api/views.py
index c2d07f1f,9988ac09..34aaa322
--- a/api/views.py
+++ b/api/views.py
@@@ -256,24 -253,14 +254,26 @@@ class ReserveTranslationView(LoginRequi
  class UploadTranslationView(LoginRequiredMixin, VertimusPageMixin, View):
      def post(self, request, *args, **kwargs):
          stats, state = self.get_state_from_kwargs()
 -        actions = [x.name for x in state.get_available_actions(request.user.person)]
 -        if "UT" not in actions:
 +        actions = state.get_available_actions(request.user.person)
-         if 'UT' not in [x.name for x in actions]:
-             return HttpResponseForbidden('You cannot upload a translation to this module')
++        if "UT" not in [x.name for x in actions]:
+             return HttpResponseForbidden("You cannot upload a translation to this module")
  
 -        action = ActionUT(person=request.user.person, file=request.FILES.get("file"))
 -        comment = request.POST.get("comment", "")
 -        action.apply_on(state, {"comment": comment, "send_to_ml": True})
 -        return JsonResponse({"result": "OK"})
 +        action_form = ActionForm(
-             request.user, state, actions,
++            request.user,
++            state,
++            actions,
 +            data={
-                 'action': 'UT',
-                 'comment': request.POST.get('comment', ''),
++                "action": "UT",
++                "comment": request.POST.get("comment", ""),
 +            },
 +            files=request.FILES,
 +        )
 +        if action_form.is_valid():
-             action = ActionUT(person=request.user.person, file=action_form.cleaned_data['file'])
-             action.apply_on(state, {'comment': action_form.cleaned_data['comment'], 'send_to_ml': True})
-             return JsonResponse({'result': 'OK'})
++            action = ActionUT(person=request.user.person, file=action_form.cleaned_data["file"])
++            action.apply_on(state, {"comment": action_form.cleaned_data["comment"], "send_to_ml": True})
++            return JsonResponse({"result": "OK"})
 +        else:
-             return JsonResponse({'result': 'Error', 'error': action_form.errors})
++            return JsonResponse({"result": "Error", "error": action_form.errors})
  
  
  # CSRF skipped, verification using a secret token.
diff --cc vertimus/forms.py
index aec1b9ab,278df385..f3e20ae5
--- a/vertimus/forms.py
+++ b/vertimus/forms.py
@@@ -51,46 -49,42 +49,43 @@@ class ActionForm(forms.Form)
      )
      author = AuthorChoiceField(label=_("Commit author"), queryset=Person.objects.none(), required=False)
      sync_master = forms.BooleanField(required=False)
-     file = forms.FileField(label=_("File"), required=False,
-                            help_text=_("Upload a .po, .gz, .bz2, .xz or .png file"))
+     file = forms.FileField(label=_("File"), required=False, help_text=_("Upload a .po, .gz, .bz2, .xz or 
.png file"))
      send_to_ml = forms.BooleanField(label=_("Send message to the team mailing list"), required=False)
  
 -    def __init__(self, current_user, state, actions, has_mailing_list, *args, **kwargs):
 +    def __init__(self, current_user, state, actions, *args, **kwargs):
          super().__init__(*args, **kwargs)
          self.actions = actions
          self.current_user = current_user
-         self.fields['action'].choices = [(
-             act.name,
-             DisabledLabel(act.description) if isinstance(act, ActionSeparator) else act.description
-         ) for act in actions]
-         self.fields['action'].help_link = reverse('help', args=['vertimus_workflow', 1])
+         self.fields["action"].choices = [
+             (act.name, DisabledLabel(act.description) if isinstance(act, ActionSeparator) else 
act.description)
+             for act in actions
+         ]
+         self.fields["action"].help_link = reverse("help", args=["vertimus_workflow", 1])
          if state and ActionCI in map(type, self.actions):
-             self.fields['author'].queryset = state.involved_persons(
-                 extra_user=current_user
-             ).order_by('last_name', 'username')
-             self.fields['author'].initial = state.get_latest_po_file_action().person
-         has_mailing_list = (
-             state and state.language and state.language.team and bool(state.language.team.mailing_list)
-         )
+             self.fields["author"].queryset = state.involved_persons(extra_user=current_user).order_by(
+                 "last_name", "username"
+             )
+             self.fields["author"].initial = state.get_latest_po_file_action().person
++        has_mailing_list = state and state.language and state.language.team and 
bool(state.language.team.mailing_list)
          if not has_mailing_list:
-             del self.fields['send_to_ml']
+             del self.fields["send_to_ml"]
          if state and state.branch.is_head():
-             del self.fields['sync_master']
+             del self.fields["sync_master"]
          elif state:
              main_branch = state.branch.module.get_head_branch()
-             self.fields['sync_master'].label = _("Sync with %(name)s") % {'name': main_branch.name}
-             self.fields['sync_master'].help_text = (
-                 _("Try to cherry-pick the commit to the %(name)s branch") % {'name': main_branch.name}
-             )
+             self.fields["sync_master"].label = _("Sync with %(name)s") % {"name": main_branch.name}
+             self.fields["sync_master"].help_text = _("Try to cherry-pick the commit to the %(name)s 
branch") % {
+                 "name": main_branch.name
+             }
  
      def clean_file(self):
-         data = self.cleaned_data['file']
+         data = self.cleaned_data["file"]
          if data:
              ext = os.path.splitext(data.name)[1]
-             if ext not in ('.po', '.gz', '.bz2', '.xz', '.png'):
+             if ext not in (".po", ".gz", ".bz2", ".xz", ".png"):
                  raise ValidationError(_("Only files with extension .po, .gz, .bz2, .xz or .png are 
admitted."))
              # If this is a .po file, check validity (msgfmt)
-             if ext == '.po':
+             if ext == ".po":
                  if check_po_conformity(data):
                      raise ValidationError(
                          _(".po file does not pass “msgfmt -vc”. Please correct the file and try again.")
diff --cc vertimus/tests/tests.py
index d98e071d,512921bf..2d933c51
--- a/vertimus/tests/tests.py
+++ b/vertimus/tests/tests.py
@@@ -226,18 -243,18 +243,18 @@@ class VertimusTest(TeamsAndRolesMixin, 
  
      def test_action_menu(self):
          state = StateNone(branch=self.b, domain=self.d, language=self.language)
 -        form = ActionForm(self.pt, state, state.get_available_actions(self.pt), False)
 +        form = ActionForm(self.pt, state, state.get_available_actions(self.pt))
          self.assertHTMLEqual(
-             str(form['action']),
+             str(form["action"]),
              '<select id="id_action" name="action">'
              '<option value="RT">Reserve for translation</option>'
              '<option value="UT">Upload the new translation</option>'
              '<option value="WC">Write a comment</option>'
-             '</select>'
+             "</select>",
          )
 -        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), False)
 +        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo))
          self.assertHTMLEqual(
-             str(form['action']),
+             str(form["action"]),
              '<select id="id_action" name="action">'
              '<option value="RT">Reserve for translation</option>'
              '<option value="UT">Upload the new translation</option>'
@@@ -264,7 -281,7 +281,7 @@@
          self.assertEqual(len(mail.outbox), 2)
          self.assertEqual(mail.outbox[1].recipients(), [self.pt.email])
          # Test that submitting a comment without text generates a validation error
-         form = ActionForm(self.pt, state, [ActionWC()], data=QueryDict('action=WC&comment='))
 -        form = ActionForm(self.pt, state, [ActionWC()], True, QueryDict("action=WC&comment="))
++        form = ActionForm(self.pt, state, [ActionWC()], data=QueryDict("action=WC&comment="))
          self.assertTrue("A comment is needed" in str(form.errors))
          self.assertNotEqual(state.updated, prev_updated)
  
@@@ -446,13 -464,13 +464,13 @@@
  
          self.assertIn(ActionCI, map(type, state.get_available_actions(self.pcoo)))
  
 -        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True)
 +        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo))
-         self.assertEqual(len(form.fields['author'].choices), 6)
-         self.assertEqual(form.fields['author'].initial, self.pr)
-         self.assertIn('sync_master', form.fields)
-         self.assertEqual(form.fields['sync_master'].label, "Sync with master")
+         self.assertEqual(len(form.fields["author"].choices), 6)
+         self.assertEqual(form.fields["author"].initial, self.pr)
+         self.assertIn("sync_master", form.fields)
+         self.assertEqual(form.fields["sync_master"].label, "Sync with master")
          self.assertHTMLEqual(
-             str(form['author']),
+             str(form["author"]),
              '<select id="id_author" name="author">'
              '<option value="">---------</option>'
              '<option disabled value="%d">ûsername (full name missing)</option>'
@@@ -478,34 -501,26 +501,26 @@@
          state = StateProofreading(branch=self.b, domain=self.d, language=self.language, person=pers)
          state.save()
          # Adding two comments from the commit author, as this might trigger a form error
-         action = Action.new_by_name('WC', person=self.pcoo)
-         action.apply_on(state, {'send_to_ml': False, 'comment': "Looks good"})
-         action = Action.new_by_name('WC', person=self.pcoo)
-         action.apply_on(state, {'send_to_ml': False, 'comment': "Looks good too"})
- 
-         self.upload_file(state, 'UP', pers=pers)
-         post_data = {
-             'action': 'CI',
-             'author': '',
-             'comment': '',
-             'send_to_ml': True
-         }
+         action = Action.new_by_name("WC", person=self.pcoo)
+         action.apply_on(state, {"send_to_ml": False, "comment": "Looks good"})
+         action = Action.new_by_name("WC", person=self.pcoo)
+         action.apply_on(state, {"send_to_ml": False, "comment": "Looks good too"})
+ 
+         self.upload_file(state, "UP", pers=pers)
+         post_data = {"action": "CI", "author": "", "comment": "", "send_to_ml": True}
 -        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True, post_data)
 +        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), data=post_data)
          # Missing author
          self.assertFalse(form.is_valid())
-         post_data['author'] = str(self.pcoo.pk)
+         post_data["author"] = str(self.pcoo.pk)
 -        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), True, post_data)
 +        form = ActionForm(self.pcoo, state, state.get_available_actions(self.pcoo), data=post_data)
          self.assertTrue(form.is_valid())
          # path needed when copying file to commit
-         (self.b.co_path / 'po').mkdir(parents=True, exist_ok=True)
-         with PatchShellCommand(only=['git ']) as cmds:
-             action = Action.new_by_name('CI', person=self.pcoo)
+         (self.b.co_path / "po").mkdir(parents=True, exist_ok=True)
+         with PatchShellCommand(only=["git "]) as cmds:
+             action = Action.new_by_name("CI", person=self.pcoo)
              msg = action.apply_on(state, form.cleaned_data)
-         self.assertIn(
-             'git commit -m Update French translation --author John Coordinator <jcoo imthebigboss fr>',
-             cmds
-         )
-         self.assertEqual(msg, 'The file has been successfully committed to the repository.')
+         self.assertIn("git commit -m Update French translation --author John Coordinator <jcoo imthebigboss 
fr>", cmds)
+         self.assertEqual(msg, "The file has been successfully committed to the repository.")
          state.refresh_from_db()
          # All actions should have been archived
          self.assertEqual(state.action_set.count(), 0)
@@@ -718,18 -740,18 +740,18 @@@
  
      def test_uploaded_file_validation(self):
          # Test a non valid po file
-         post_content = QueryDict('action=WC&comment=Test1')
-         post_file = MultiValueDict({'file': [SimpleUploadedFile('filename.po', b'Not valid po file 
content')]})
+         post_content = QueryDict("action=WC&comment=Test1")
+         post_file = MultiValueDict({"file": [SimpleUploadedFile("filename.po", b"Not valid po file 
content")]})
 -        form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
 +        form = ActionForm(self.pt, None, [ActionWC()], data=post_content, files=post_file)
-         self.assertTrue('file' in form.errors)
-         post_file = MultiValueDict({'file': [SimpleUploadedFile('filename.po', 'Niña'.encode('latin-1'))]})
+         self.assertTrue("file" in form.errors)
+         post_file = MultiValueDict({"file": [SimpleUploadedFile("filename.po", "Niña".encode("latin-1"))]})
 -        form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
 +        form = ActionForm(self.pt, None, [ActionWC()], data=post_content, files=post_file)
-         self.assertTrue('file' in form.errors)
+         self.assertTrue("file" in form.errors)
  
          # Test a valid po file
-         with (Path(__file__).parent / "valid_po.po").open('rb') as fh:
-             post_file = MultiValueDict({'file': [File(fh)]})
+         with (Path(__file__).parent / "valid_po.po").open("rb") as fh:
+             post_file = MultiValueDict({"file": [File(fh)]})
 -            form = ActionForm(self.pt, None, [ActionWC()], True, post_content, post_file)
 +            form = ActionForm(self.pt, None, [ActionWC()], data=post_content, files=post_file)
              self.assertTrue(form.is_valid())
  
          # Test form without file
diff --cc vertimus/views.py
index 79209f04,afceaa3f..44c61998
--- a/vertimus/views.py
+++ b/vertimus/views.py
@@@ -89,10 -102,9 +102,8 @@@ def vertimus(request, branch, domain, l
          # Only authenticated user can act on the translation and it's not
          # possible to edit an archived workflow
          available_actions = state.get_available_actions(person)
-         if request.method == 'POST':
-             action_form = ActionForm(
-                 request.user, state, available_actions, request.POST, request.FILES
-             )
 -        has_ml = bool(language.team.mailing_list) if language.team else False
+         if request.method == "POST":
 -            action_form = ActionForm(request.user, state, available_actions, has_ml, request.POST, 
request.FILES)
++            action_form = ActionForm(request.user, state, available_actions, request.POST, request.FILES)
  
              if action_form.is_valid():
                  # Process the data in form.cleaned_data
@@@ -118,37 -122,34 +121,34 @@@
                          messages.success(request, msg)
  
                  return HttpResponseRedirect(
-                     reverse(
-                         'vertimus_by_names',
-                         args=(branch.module.name, branch.name, domain.name, language.locale)
-                     )
+                     reverse("vertimus_by_names", args=(branch.module.name, branch.name, domain.name, 
language.locale))
                  )
          elif available_actions:
 -            action_form = ActionForm(request.user, state, available_actions, has_ml)
 +            action_form = ActionForm(request.user, state, available_actions)
  
      context = {
-         'pageSection': 'module',
-         'stats': stats,
-         'pot_stats': pot_stats,
-         'po_url': stats.po_url(),
-         'po_url_reduced': stats.has_reducedstat() and stats.po_url(reduced=True) or '',
-         'branch': branch,
-         'other_states': other_branch_states,
-         'domain': domain,
-         'language': language,
-         'module': branch.module,
-         'non_standard_repo_msg': _(settings.VCS_HOME_WARNING),
-         'state': state,
-         'is_team_member': person and language.team and person.is_translator(language.team),
-         'action_history': action_history,
-         'action_form': action_form,
-         'level': level,
-         'grandparent_level': grandparent_level,
+         "pageSection": "module",
+         "stats": stats,
+         "pot_stats": pot_stats,
+         "po_url": stats.po_url(),
+         "po_url_reduced": stats.has_reducedstat() and stats.po_url(reduced=True) or "",
+         "branch": branch,
+         "other_states": other_branch_states,
+         "domain": domain,
+         "language": language,
+         "module": branch.module,
+         "non_standard_repo_msg": _(settings.VCS_HOME_WARNING),
+         "state": state,
+         "is_team_member": person and language.team and person.is_translator(language.team),
+         "action_history": action_history,
+         "action_form": action_form,
+         "level": level,
+         "grandparent_level": grandparent_level,
      }
      if stats.has_figures():
-         context['fig_stats'] = stats.fig_stats()
-         del context['fig_stats']['prc']
-     return render(request, 'vertimus/vertimus_detail.html', context)
+         context["fig_stats"] = stats.fig_stats()
+         del context["fig_stats"]["prc"]
+     return render(request, "vertimus/vertimus_detail.html", context)
  
  
  def get_vertimus_state(branch, domain, language, stats=None):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]