@@ -373,6 +373,26 @@
Loading
373 373
        except (cls.DoesNotExist, LookupError):
374 374
            return cls.get_default()
375 375
376 +
    @transaction.atomic
377 +
    def delete(self, *args, **kwargs):
378 +
        # if we're deleting the locale used on the root page node, reassign that to a new locale first
379 +
        root_page_with_this_locale = Page.objects.filter(depth=1, locale=self)
380 +
        if root_page_with_this_locale.exists():
381 +
            # Select the default locale, if one exists and isn't the one being deleted
382 +
            try:
383 +
                new_locale = Locale.get_default()
384 +
                default_locale_is_ok = (new_locale != self)
385 +
            except (Locale.DoesNotExist, LookupError):
386 +
                default_locale_is_ok = False
387 +
388 +
            if not default_locale_is_ok:
389 +
                # fall back on any remaining locale
390 +
                new_locale = Locale.all_objects.exclude(pk=self.pk).first()
391 +
392 +
            root_page_with_this_locale.update(locale=new_locale)
393 +
394 +
        return super().delete(*args, **kwargs)
395 +
376 396
    def language_code_is_valid(self):
377 397
        return self.language_code in get_content_languages()
378 398

@@ -1,8 +1,8 @@
Loading
1 1
from django.contrib.messages import get_messages
2 -
from django.test import TestCase
2 +
from django.test import TestCase, override_settings
3 3
from django.urls import reverse
4 4
5 -
from wagtail.core.models import Locale
5 +
from wagtail.core.models import Locale, Page
6 6
from wagtail.tests.utils import WagtailTestUtils
7 7
8 8
@@ -175,6 +175,10 @@
Loading
175 175
        self.assertFalse(Locale.objects.filter(language_code='fr').exists())
176 176
177 177
    def test_cannot_delete_locales_with_pages(self):
178 +
        # create a French locale so that the deletion is not rejected on grounds of being the only
179 +
        # existing locale
180 +
        Locale.objects.create(language_code='fr')
181 +
178 182
        response = self.post()
179 183
180 184
        self.assertEqual(response.status_code, 200)
@@ -186,3 +190,76 @@
Loading
186 190
187 191
        # Check that the locale was not deleted
188 192
        self.assertTrue(Locale.objects.filter(language_code='en').exists())
193 +
194 +
    @override_settings(
195 +
        LANGUAGE_CODE='de-at',
196 +
        WAGTAIL_CONTENT_LANGUAGES=[
197 +
            ('en', 'English'),
198 +
            ('fr', 'French'),
199 +
            ('de', 'German'),
200 +
            ('pl', 'Polish'),
201 +
            ('ja', 'Japanese')
202 +
        ]
203 +
    )
204 +
    def test_can_delete_default_locale(self):
205 +
        # The presence of the locale on the root page node (if that's the only thing using the
206 +
        # locale) should not prevent deleting it
207 +
208 +
        for lang in ('fr', 'de', 'pl', 'ja'):
209 +
            Locale.objects.create(language_code=lang)
210 +
211 +
        self.assertTrue(Page.get_first_root_node().locale.language_code, 'en')
212 +
        Page.objects.filter(depth__gt=1).delete()
213 +
        response = self.post()
214 +
215 +
        # Should redirect back to index
216 +
        self.assertRedirects(response, reverse('wagtaillocales:index'))
217 +
218 +
        # Check that the locale was deleted
219 +
        self.assertFalse(Locale.objects.filter(language_code='en').exists())
220 +
221 +
        # root node's locale should now have been reassigned to the one matching the current
222 +
        # LANGUAGE_CODE
223 +
        self.assertTrue(Page.get_first_root_node().locale.language_code, 'de')
224 +
225 +
    @override_settings(
226 +
        LANGUAGE_CODE='de-at',
227 +
        WAGTAIL_CONTENT_LANGUAGES=[
228 +
            ('en', 'English'),
229 +
            ('fr', 'French'),
230 +
            ('de', 'German'),
231 +
            ('pl', 'Polish'),
232 +
            ('ja', 'Japanese')
233 +
        ]
234 +
    )
235 +
    def test_can_delete_default_locale_when_language_code_has_no_locale(self):
236 +
        Locale.objects.create(language_code='fr')
237 +
238 +
        self.assertTrue(Page.get_first_root_node().locale.language_code, 'en')
239 +
        Page.objects.filter(depth__gt=1).delete()
240 +
        response = self.post()
241 +
242 +
        # Should redirect back to index
243 +
        self.assertRedirects(response, reverse('wagtaillocales:index'))
244 +
245 +
        # Check that the locale was deleted
246 +
        self.assertFalse(Locale.objects.filter(language_code='en').exists())
247 +
248 +
        # root node's locale should now have been reassigned to 'fr' despite that not matching
249 +
        # LANGUAGE_CODE (because it's the only remaining Locale record)
250 +
        self.assertTrue(Page.get_first_root_node().locale.language_code, 'fr')
251 +
252 +
    def test_cannot_delete_last_remaining_locale(self):
253 +
        Page.objects.filter(depth__gt=1).delete()
254 +
255 +
        response = self.post()
256 +
257 +
        self.assertEqual(response.status_code, 200)
258 +
259 +
        # Check error message
260 +
        messages = list(get_messages(response.wsgi_request))
261 +
        self.assertEqual(messages[0].level_tag, 'error')
262 +
        self.assertEqual(messages[0].message, "This locale cannot be deleted because there are no other locales.\n\n\n\n\n")
263 +
264 +
        # Check that the locale was not deleted
265 +
        self.assertTrue(Locale.objects.filter(language_code='en').exists())

@@ -43,14 +43,25 @@
Loading
43 43
44 44
class DeleteView(generic.DeleteView):
45 45
    success_message = gettext_lazy("Locale '{0}' deleted.")
46 -
    cannot_delete_message = gettext_lazy("This locale cannot be deleted because there are pages and/or other objects using it.")
47 46
    page_title = gettext_lazy("Delete locale")
48 47
    confirmation_message = gettext_lazy("Are you sure you want to delete this locale?")
49 48
    template_name = 'wagtaillocales/confirm_delete.html'
50 49
    queryset = Locale.all_objects.all()
51 50
52 51
    def can_delete(self, locale):
53 -
        return get_locale_usage(locale) == (0, 0)
52 +
        if not self.queryset.exclude(pk=locale.pk).exists():
53 +
            self.cannot_delete_message = gettext_lazy(
54 +
                "This locale cannot be deleted because there are no other locales."
55 +
            )
56 +
            return False
57 +
58 +
        if get_locale_usage(locale) != (0, 0):
59 +
            self.cannot_delete_message = gettext_lazy(
60 +
                "This locale cannot be deleted because there are pages and/or other objects using it."
61 +
            )
62 +
            return False
63 +
64 +
        return True
54 65
55 66
    def get_context_data(self, object=None):
56 67
        context = context = super().get_context_data()
Files Coverage
client/src 90.67%
wagtail 44.34%
Project Totals (438 files) 46.23%
15868.1
TRAVIS_PYTHON_VERSION=3.6
TRAVIS_OS_NAME=linux
TOXENV=py36-dj22-sqlite-elasticsearch2
backend
1
codecov:
2
  notify:
3
    after_n_builds: 10
4
    require_ci_to_pass: no
5

6
coverage:
7
  status:
8
    project: off
9
    patch: off
10

11
comment: off
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading